4. 数据读取
拿到Visium的原始数据之后,第一件事就是把它们拉进Python环境。
我个人习惯用scanpy,它封装好了Visium数据的专用读取器,一行代码就能把空间转录组的三个核心文件——组织切片图像、斑点坐标、基因表达矩阵——打包成一个对象。这个对象叫AnnData,是单细胞和空间转录组数据的事实标准。
嗯,AnnData长得有点像Pandas DataFrame套了个壳,但多了很多专用槽位。今天我们就把它拆开看个明白。
4.1 从原始目录读取Visium数据
Space Ranger输出的是一个标准文件夹,包含三个文件:
filtered_feature_bc_matrix.h5(基因表达矩阵)spatial/tissue_positions_list.csv(斑点坐标)spatial/tissue_hires_image.png(高分辨率组织图像)
Scanpy的read_visium函数能自动识别并加载它们。
<code>import scanpy as sc
adata = sc.read_visium("path/to/Visium_output/outs/")
print(adata)
输出类似这样:
<code>AnnData object with n_obs × n_var = 4992 × 36601
obs: 'in_tissue', 'array_row', 'array_col'
var: 'gene_ids', 'feature_types', 'genome'
uns: 'spatial', 'hires_image', 'lowres_image'
obsm: 'spatial'
看到 obs、var、uns、obsm 这些字段没有?它们就是 AnnData 的核心骨架。
4.2 AnnData对象结构:一张图讲清楚
AnnData 就像一个有五个抽屉的柜子。每个抽屉放不同类型的数据。下图是我画的简化结构:
4.3 逐个拆解AnnData的“抽屉”
下面我用一个表格帮你快速对照每个字段的用途:
| 字段 | 中文名 | 存储内容 | 形状/类型 | 典型例子 |
|---|---|---|---|---|
adata.obs | 观测元数据 | 每个斑点的属性 | n_obs × m(DataFrame) | in_tissue, total_counts, Pct_mito |
adata.var | 特征(基因)元数据 | 每个基因的属性 | n_var × k(DataFrame) | gene_ids, mt(是否线粒体) |
adata.X | 表达矩阵 | 归一化或原始UMI计数 | n_obs × n_var(稀疏矩阵) | 多为scipy.sparse.csr_matrix |
adata.obsm | 观测级多维矩阵 | 每个观测对应一个向量(降维/坐标) | n_obs × d(数组/DataFrame) | 'spatial'(XY坐标)、'X_pca'(PCA嵌入) |
adata.varm | 特征级多维矩阵 | 每个基因对应一个向量 | n_var × d(数组) | 用于存储基因贡献度等 |
adata.uns | 非结构化数据 | 任意Python对象(字典) | dict | 组织图像数组、聚类颜色、差异分析结果 |
adata.layers | 额外表达层 | 同一形状的不同变换版本 | dict of sparse matrices | log1p层、scaled层 |
你看,.obs 和 .var 用的是 Pandas DataFrame,可以像操作表格一样筛选、添加列。我一个项目里,需要在 .obs 里加一列“区域编号”,直接写 adata.obs['region'] = ['A1', 'A2', ...] 就搞定。
4.4 实际操作:读取后检查内容
假设你已经用 sc.read_visium 载入了数据,接下来我习惯先看三样东西:
<code># 1. 看obs的前几行
print(adata.obs.head())
# 2. 看var的前几行(一般只显示基因名)
print(adata.var.head())
# 3. 看uns的keys
print(adata.uns.keys())
输出类似:
<code> in_tissue array_row array_col
barcode_1 True 0 0
barcode_2 True 0 1
...
gene_ids feature_types genome
gene_A ENSG... Gene Expression GRCh38
...
dict_keys(['spatial', 'hires_image', 'lowres_image', 'spatial_neighbors'])
看到 uns['spatial'] 里存的就是图像缩放信息,obsm['spatial'] 里是每个斑点的像素坐标(Y轴已经翻转好,可以直接用)。
read_visium已经帮你翻转了。adata.obsm['spatial'][:, 1] 就是图像Y坐标,无需额外处理。4.5 我个人避坑经验
有一次我帮同事调试,他读完数据后adata里面obs是空的,怎么看都少列。排查半天发现是他拷贝数据时把spatial/子目录漏了。read_visium依赖这个子目录里的CSV文件。缺了这个,obs里就只有默认索引。
另外,如果内存不太宽裕,建议读取后马上检查adata.X的稀疏矩阵类型。默认是csr_matrix,占内存小。但如果你误用了.toarray(),分分钟爆内存。我遇到过一个52G内存的服务器,toarray()后直接OOM。
obs(斑点标签)、obsm(坐标)、uns(图像)这三个,你就可以开始做空间可视化。剩下的随着分析过程会自然填满。