공간 전사체 분석은 종양 연구에서 매우 중요한데 종양 미세환경 내의 세포들(종양 세포, 면역세포, 혈관세포 등)과 그들의 상호작용을 이해하는 데 도움이 되기 때문입니다. 특히 공간에서 일어나는 유전자 발현 패턴이 특정 치료제나 면역 치료에 대한 반응성을 예측할 수 있다고 믿어지기 때문에 더욱 더 중요해지고 있습니다. 이번 글에서는 공간 전사체(이하 Visium)데이터와 scRNA-seq 데이터를 결합하는 방법을 위주로 살펴보겠습니다.
1 들어가며
이 튜토리얼은 Giovanni Palla 및 Carlos Talavera-López의 튜토리얼을 각색한 것입니다. Visium 데이터는 여러 면에서 scRNAseq 데이터와 비슷한 형태를 띄고 있습니다. 다만 5~20개 세포가 하나의 UMI 카운트를 의미해서 단일 세포 수준이 아니고 조직 내 공간적 위치에 대한 추가 정보가 있다는 차이가 있습니다. 여기서는 Visium 데이터 QC를 진행하고 필터링, 차원 축소, 통합 및 클러스터링을 수행해봅니다. 그런 다음 마우스 뇌의 scRNAseq 데이터를 레퍼런스로 사용해 세포 유형을 예측해봅니다.
여기서 사용한 데이터는 10xGenomics에 공개한 마우스 뇌의 Visium 데이터셋 입니다. 먼저, 필요한 라이브러리를 불러옵니다.
Note
예시 데이터셋은 이미 조직과 겹치지 않는 부분은 필터링이 완료된 상태입니다. 실제 데이터에서 진행한다면 추가 필터링 작업이 필요합니다.
import osimport pickleimport warningswarnings.filterwarnings("ignore", message=".*IProgress not found.*")import matplotlib.pyplot as pltimport numpy as npimport pandas as pdimport scanoramaimport scanpy as scfrom scvi.external import RNAStereoscope, SpatialStereoscope# 경고 무시하기warnings.filterwarnings("ignore")plt.style.use("classic")sc.settings.n_jobs =-1sc.settings.verbosity =0# 오류 (0), 경고 (1), 정보 (2), 힌트 (3)sc.settings.set_figure_params( dpi=60, frameon=False, figsize=(3, 3), facecolor="white", color_map="viridis_r")print(f"사용한 SCANPY 버전: {sc.__version__}")
사용한 SCANPY 버전: 1.10.1
2 데이터 불러오기
데이터를 불러오기 위해 datasets.visium_sge() 함수로 10xgenomics 데이터셋을 가져오겠습니다. 만약에 직접 수집한 Visium 데이터를 사용하는 경우 Scanpy의 read_visium() 함수를 사용해야 합니다. 그리고 pp.calculate_qc_metrics()함수를 사용해 표준 QC 평가 지표를 계산하겠습니다.
# 누락되거나 계산 시간이 오래 걸리는 경우 미리 계산된 데이터 다운로드fetch_data =True# url for source datapath_data ="https://export.uppmax.uu.se/naiss2023-23-3/workshops/workshop-scrnaseq"ifnot os.path.exists("./input/spatial/visium"): os.makedirs("./input/spatial/visium")adata_anterior = sc.datasets.visium_sge(sample_id="V1_Mouse_Brain_Sagittal_Anterior")adata_posterior = sc.datasets.visium_sge(sample_id="V1_Mouse_Brain_Sagittal_Posterior")adata_anterior.var_names_make_unique()adata_posterior.var_names_make_unique()# 하나의 데이터 집합으로 병합library_names = ["V1_Mouse_Brain_Sagittal_Anterior","V1_Mouse_Brain_Sagittal_Posterior",]adata = adata_anterior.concatenate( adata_posterior, batch_key="library_id", uns_merge="unique", batch_categories=library_names,)adata
섹션이 두 개이므로 batch_key="library_id"로 가변 유전자를 선택한 다음 추가 분석을 위해 가변 유전자의 조합을 가져옵니다. 이 방법은 분석에 특정 배치의 유전자가 편향적으로 포함되지 않게 하기 위함입니다.
# 나중을 위해 원시 데이터 카운트를 별도의 객체에 저장counts_adata = adata.copy()sc.pp.normalize_total(adata, inplace=True)sc.pp.log1p(adata)# 배치당 1500개의 가변 유전자를 가져온 다음 이들의 조합을 사용합니다.sc.pp.highly_variable_genes( adata, flavor="seurat", n_top_genes=1500, inplace=True, batch_key="library_id")adata.raw = adata# 가변 유전자에 대한 하위 집합adata = adata[:, adata.var.highly_variable_nbatches >0]# 데이터 스케일링sc.pp.scale(adata)
이제 개별 유전자의 유전자 발현을 플로팅할 수 있는데, Hpca 유전자는 해마 조직을 나타내는 마커 유전자이고 Ttr 유전자는 맥락막 신경총(Choroid plexus)의 마커 유전자입니다.
for library in library_names:print(library) sc.pl.spatial( adata[adata.obs.library_id == library, :], library_id=library, color=["Ttr", "Hpca"], ncols=2, )
V1_Mouse_Brain_Sagittal_Anterior
V1_Mouse_Brain_Sagittal_Posterior
5 비통합 클러스터링 (Non-integrated Clustering)
각 데이터셋을 독립적으로 분석한 후 클러스터링을 수행합니다. scRNA-seq 분석법과 동일한 방법으로 차원 축소 및 클러스터링을 진행합니다.
조직 섹션에 클러스터를 시각화하기 위해 데이터에서 클러스터 색상을 가져와 동일한 색상으로 지정해야 합니다.
clusters_colors =dict(zip( [str(i) for i inrange(len(adata.obs.clusters.cat.categories))], adata.uns["clusters_colors"], ))fig, axs = plt.subplots(1, 2, figsize=(8, 8))for i, library inenumerate(library_names): ad = adata[adata.obs.library_id == library, :].copy() sc.pl.spatial( ad, img_key="hires", library_id=library, color="clusters", size=1.5, palette=[v for k, v in clusters_colors.items() if k in ad.obs.clusters.unique().tolist()], legend_loc=None, show=False, ax=axs[i], ) axs[i].set_title(f"{library} cluster", fontsize=10)plt.tight_layout()
6 통합 클러스터링 (Integrated Clustering)
데이터셋을 하나로 통합한 후 클러스터링을 수행하는 방법입니다. 서로 다른 실험 조건이나 샘플에서 얻은 데이터를 함께 분석하여 전체적인 구조를 파악합니다. 서로 다른 조직 섹션 간에 강력한 배치 효과가 있는 경우가 많으므로 일반적으로 통합 클러스터링이 권장됩니다. 여기서는 통합을 위해 Scanorama를 사용해 진행하겠습니다.
# AnnData 객체 목록으로 변환adatas =list(adatas.values())# scanorama.integrate 실행scanorama.integrate_scanpy(adatas, dimred=50)# 모든 통합 행렬을 가져옵니다.scanorama_int = [ad.obsm["X_scanorama"] for ad in adatas]# 하나의 행렬로 만듭니다.all_s = np.concatenate(scanorama_int)# print(all_s.shape)# AnnData 객체에 추가합니다.adata.obsm["Scanorama"] = all_sadata
Found 2405 genes among all datasets
[[0. 0.47824413]
[0. 0. ]]
Processing datasets (0, 1)
clusters_colors =dict(zip( [str(i) for i inrange(len(adata.obs.clusters.cat.categories))], adata.uns["clusters_colors"], ))fig, axs = plt.subplots(1, 2, figsize=(8, 8))for i, library inenumerate(library_names): ad = adata[adata.obs.library_id == library, :].copy() sc.pl.spatial( ad, img_key="hires", library_id=library, color="clusters", size=1.5, palette=[v for k, v in clusters_colors.items() if k in ad.obs.clusters.unique().tolist()], legend_loc=None, show=False, ax=axs[i], ) axs[i].set_title(f"{library} cluster", fontsize=10)plt.tight_layout()
7 공간적 유전자 변화
조직 내 공간적 위치와 상관관계가 있는 유전자 발현을 식별하는 방법에는 크게 두 가지가 있습니다. 첫 번째는 공간적으로 구분되는 클러스터를 기반으로 DEG를 찾는 것이고, 두번째는 클러스터나 위치를 고려하지 않고 공간적 패턴을 찾는 것입니다.
# 공간적 위치에 시각화top_genes = sc.get.rank_genes_groups_df(adata, group="5", log2fc_min=0)["names"][:3]for library in library_names:print(library) sc.pl.spatial(adata[adata.obs.library_id == library, :], library_id=library, color=top_genes) ncols = (2,)
V1_Mouse_Brain_Sagittal_Anterior
V1_Mouse_Brain_Sagittal_Posterior
연구자들은 유전자 발현 추세가 공간에 따라 어떻게 달라지는지 조사하여 유전자 발현의 공간적 패턴을 파악할 수 있습니다. 공간에 대한 유전자 발현 패턴을 분석하는 도구로는 SpatailDE, SPARK, Trendsceek, HMRF, Splotch가 있습니다. 여기서는 가우스 프로세스 기반 통계 프레임워크인 SpatialDE을 사용해 공간적으로 가변적인 유전자를 식별해봅니다.
Note
SpatialDE를 사용하는 방법은 아래 코드를 참고하세요.
if not fetch_data:
import NaiveDE
import SpatialDE
counts = sc.get.obs_df(adata, keys=list(adata.var_names), use_raw=True)
total_counts = sc.get.obs_df(adata, keys=["total_counts"])
norm_expr = NaiveDE.stabilize(counts.T).T
resid_expr = NaiveDE.regress_out(
total_counts, norm_expr.T, "np.log(total_counts)").T
results = SpatialDE.run(adata.obsm["spatial"], resid_expr)
import pickle
with open('data/spatial/visium/scanpy_spatialde.pkl', 'wb') as file:
pickle.dump(results, file)
다만 위 코드는 계산량이 많아서 시간이 오래걸립니다. 그래서 미리 계산된 파일을 다음과 같이 제공하니 미리 계산된 파일을 다운로드받아 진행하세요.
# 결과를 변수 주석의 DataFrame인 `adata.var`와 연결합니다.results.index = results["g"]adata.var = pd.concat([adata.var, results.loc[adata.var.index.values, :]], axis=1)adata.write_h5ad("./data/spatial/visium/adata_processed_sc.h5ad")# 그런 다음 공간에 따라 달라지는 중요한 유전자를 검사하고 `sc.pl.spatial` 함수를 사용하여 시각화할 수 있습니다.results.sort_values("qval").head(10)
FSV
M
g
l
max_delta
max_ll
max_mu_hat
max_s2_t_hat
model
n
s2_FSV
s2_logdelta
time
BIC
max_ll_null
LLR
pval
qval
g
Pcp2
0.311857
4
Pcp2
1143.629056
1.942926
-5951.142118
2.926354
0.250801
SE
5749
0.000151
0.003614
0.004486
11936.911361
-6660.401054
709.258937
0.0
0.0
Arpp21
0.111727
4
Arpp21
544.733658
7.705871
-4564.518758
-5.508770
0.234582
SE
5749
0.000009
0.000811
0.004505
9163.664641
-4883.168776
318.650018
0.0
0.0
Fam107b
0.142934
4
Fam107b
544.733658
5.811807
-3153.361188
-0.138821
0.029021
SE
5749
0.000011
0.000676
0.004312
6341.349500
-3593.498319
440.137132
0.0
0.0
Ccdc3
0.089396
4
Ccdc3
544.733658
9.872922
-1973.573273
-1.011429
0.017939
SE
5749
0.000011
0.001366
0.004875
3981.773670
-2119.287504
145.714232
0.0
0.0
Spink8
0.386160
4
Spink8
544.733658
1.540709
-89.582212
-1.292471
0.048386
SE
5749
0.000023
0.000463
0.004225
213.791549
-878.843599
789.261387
0.0
0.0
Vim
0.152102
4
Vim
544.733658
5.403095
-5073.752713
0.314954
0.061191
SE
5749
0.000023
0.001294
0.004482
10182.132550
-5309.926117
236.173404
0.0
0.0
Cdhr4
0.058661
4
Cdhr4
544.733658
15.553480
5174.380569
-0.720568
0.003751
SE
5749
0.000007
0.001779
0.004288
-10314.134013
5055.927174
118.453395
0.0
0.0
Camkv
0.148880
4
Camkv
544.733658
5.540997
-4285.018227
-5.748463
0.267292
SE
5749
0.000011
0.000640
0.004441
8604.663579
-4661.390989
376.372762
0.0
0.0
Pip4k2a
0.057655
4
Pip4k2a
544.733658
15.841994
-4829.402616
0.638928
0.021795
SE
5749
0.000005
0.001414
0.004472
9693.432357
-4950.743568
121.340952
0.0
0.0
Enc1
0.459936
4
Enc1
1143.629056
1.033906
-4429.628950
-5.762820
0.330282
SE
5749
0.000324
0.006134
0.003355
8893.885026
-5240.320650
810.691700
0.0
0.0
8 단일 세포 데이터를 참조해 세포 유형 추정
Visium 데이터에서 해당 위치의 세포 유형을 알아내기 위해 scRNA-seq 데이터를 참조할 수 있습니다. 가능하다면 동일한 조직 시료에서 얻은 데이터를 사용하는 것이 좋습니다. 여기에서는 Allen Institute에서 공개한 마우스 대뇌 피질 데이터셋을 사용합니다. 이 데이터셋은 사전 처리된 데이터를 h5ad형식으로 제공됩니다. 아래 코드를 사용해 다운로드 합니다.
path_file ="./input/spatial/visium/allen_cortex.h5ad"ifnot os.path.exists(path_file): file_url = os.path.join(path_data, "spatial/visium/allen_cortex.h5ad") urllib.request.urlretrieve(file_url, path_file)adata_cortex = sc.read_h5ad("./input/spatial/visium/allen_cortex.h5ad")# 이 객체의 원시 행렬의 인덱스에는 유전자 이름이 없음으로 추가해줍니다.adata_cortex.raw.var.index = adata_cortex.raw.var._index# adata_cortex.raw.var.indexadata_cortex
디컨볼루션은 scRNA-seq 데이터를 사용해 벌크 RNA-seq 데이터셋에서 세포 유형의 비율를 추정하는 방법입니다. Visium 데이터도 일종의 작은 벌크 RNA-seq 데이터로 판단할 수 있기에 적용 할 수 있습니다. 디컨볼루션을 하는 방법에는 DWLS, cell2location, Tangram, Stereo-Seq, RCTD, SCDC 등이 알려져 있습니다. 여기서는 SCVI-tools 패키지에 구현된 Stereoscope을 사용합니다. 자세한 내용은 깃허브를 참조하세요.
8.2.1 디컨볼루션을 위한 유전자 선택하기
디컨볼루션 방법을 사용하기 위해서는 사전에 유전자 선택해야 하며 아래와 같은 여러 옵션이 있습니다.
이제 scRNA-seq 데이터를 사용해 모델을 훈련해보겠습니다. 모든 데이터가 카운트 단위여야 한다는 것에 유의하세요. 또한 anndata 객체에 카운트 데이터가 복사본으로 count 레이어로 저장되어 있어야 합니다.
# 카운트 데이터 복사하기sc_adata = adata_cortex.copy()sc_adata.X = adata_cortex.raw.X.copy()# 카운트 레이어 추가sc_adata.layers["counts"] = sc_adata.X.copy()# DEG에 속한 유전자로 하위 집합 만들기sc_adata = sc_adata[:, deg].copy()# stereoscope 만들기RNAStereoscope.setup_anndata(sc_adata, layer="counts", labels_key="subclass")# 모델은 파일에 저장되므로 실행 속도가 느린 경우 train = False를 설정해 로컬에 저장된 모델을 읽습니다.train =Trueif train: sc_model = RNAStereoscope(sc_adata) sc_model.train(max_epochs=300) sc_model.history["elbo_train"][10:].plot() sc_model.save("./data/spatial/visium/scanpy_scmodel", overwrite=True)else: sc_model = RNAStereoscope.load("./data/spatial/visium/scanpy_scmodel", sc_adata)print("Loaded RNA model from file!")
An NVIDIA GPU may be present on this machine, but a CUDA-enabled jaxlib is not installed. Falling back to cpu.
GPU available: True (cuda), used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
HPU available: False, using: 0 HPUs
You are using a CUDA device ('NVIDIA GeForce RTX 4090') that has Tensor Cores. To properly utilize them, you should set `torch.set_float32_matmul_precision('medium' | 'high')` which will trade-off precision for performance. For more details, read https://pytorch.org/docs/stable/generated/torch.set_float32_matmul_precision.html#torch.set_float32_matmul_precision
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]
디컨볼루션 결과는 예측 결과로 매개변수, 유전자 선택 등을 어떻게 조정하는지에 따라 다른 결과가 나올 수 있습니다. 그러니 항상 검증하는 단계가 필요합니다.
9 다른 구역 선택하기
대뇌피질이 아닌 다른 구역(여기서는 Brain Sagittal Posterior)을 선택하는 코드는 다음과 같습니다. Sagittal Posterior는 신경세포뿐만 아니라 비신경세포로 구성된 부분으로 감각 처리, 운동 조절, 기억 형성 등 다양한 뇌 기능에 중요한 역할을 합니다.
공간 전사체(spatial transcriptomics)는 세포 내 유전자 발현을 조직 내 특정 위치와 연계하여 분석하는 혁신적인 기술로 활발히 발전되고 있습니다. 공간 전사체 데이터가 있으면 조직의 공간적 맥락에서 유전자 활동을 정밀하게 파악할 수 있어 세포 간의 상호작용과 미세 환경의 영향을 포함한 다양한 생물학적 의미를 분석 가능합니다.따라서 앞으로 암 연구 및 신경과학등의 다양한 분야에서 활용될 것으로 보이며 특히 종양의 발병 기작을 이해하고 맞춤형 치료법 개발에 기여할 것으로 예측됩니다.
이것으로 오랫동안 미뤄왔던 Scanpy workshop 시리즈를 마치도록 하겠습니다. 글에 잘못된 부분이나 질문이 있으시다면 메일로 연락주시면 감사하겠습니다.