2、空间转录组数据基础:AnnData数据结构、读取10X Visium数据

空间转录组学里,AnnData 这个数据结构你绕不开。说白了,它就是为单细胞和空间组学量身定做的“数据容器”。我刚开始接触时也被它的嵌套结构绕晕过,但用顺手后会发现——它把所有关联数据打包得清清楚楚,比你自己用 dict 拼一个稳定多了。

2.1 AnnData 核心结构

AnnData 的核心就四个字:表、行、列、附属。你可以把它想象成一个 Excel 表格,但比 Excel 聪明得多。每个 AnnData 对象里藏了这么几个关键属性:

属性名中文叫法通俗解释
.X主要数据矩阵基因×细胞的表达矩阵,通常用 numpy 的稀疏矩阵存
.obs观测(细胞/点)元数据每一行是一个 spot 或 cell,列是各种注释(样本、聚类、QC指标)
.var变量(基因)元数据每一行是一个基因,列是基因名、染色体位置等
.obsm观测的多维数组存放降维结果(PCA、UMAP)或空间坐标(spatial)
.varm变量的多维数组基因载荷、差异基因等
.uns非结构化数据可以放任何东西——图像、聚类颜色、分析参数
.layers多层矩阵例如原始count、标准化count并存

你可能觉得记不住,嗯,我有个小技巧:把 .obs 当成“哪桌客人”,.var 当成“菜单上的菜”,.X 就是“每桌点了哪些菜”。理解了这层关系,后面所有 API 调用都顺了。

我个人习惯在读取数据后第一件事就是打印 adata 对象的摘要。AnnData 的 repr 会直接告诉你形状、稀疏程度和关键字段,一眼就能确认数据加载是否正确。

2.2 读取 10X Visium 数据

10X 的 Visium 数据通常由几个文件组成:

  • filtered_feature_bc_matrix.h5 或 raw_feature_bc_matrix.h5(HDF5 格式的 count 矩阵)
  • spatial/ 目录下的 tissue_positions_list.csv(每个 spot 的坐标)
  • spatial/ 目录下的 scalefactors_json.json(组织图像缩放参数)
  • spatial/ 目录下的 tissue_hires_image.png 或 tissue_lowres_image.png

最省事的读取方式是直接用 scanpy.read_visium():

import scanpy as sc

adata = sc.read_visium(
    path = "./data/Visium_Mouse_Olfactory_Bulb/",
    count_file = "filtered_feature_bc_matrix.h5",
    library_id = "sample_1"
)

print(adata)

这段代码做了什么?它自动解析 H5 文件,把 count 矩阵塞进 adata.X;把 spot 的条形码、组织分类放进 adata.obs;把空间坐标(pxl_col, pxl_row)放进 adata.obsm["spatial"];还把图像信息存到 adata.uns["spatial"] 这块复杂的嵌套 dict 里。

我曾经遇到过一个坑:path 参数要求指向包含 filtered_feature_bc_matrix.h5 和 spatial 子目录的根目录。如果你只给了 H5 的完整路径,反而会报错。另外,文件路径里不要有中文或空格,否则底层 h5py 库可能抛奇怪异常。

如果你拿到的是 10X 旧的 MEX 格式(三个文件:barcodes.tsv.gz、features.tsv.gz、matrix.mtx.gz),也可以用 sc.read_10x_mtx() 自己拼,但得手动补空间坐标。我建议在项目中统一用 H5 版本,它的元数据更完整。

2.3 数据基本探索

读取成功后,先看看里面有什么:

# 查看 obs 的前几行
print(adata.obs.head())

# 查看空间坐标
print(adata.obsm["spatial"][:5])

# 查看 var 的列名
print(adata.var.columns)

# 查看 umi 和 gene 的统计
sc.pp.calculate_qc_metrics(adata, percent_top=None, log1p=False, inplace=True)
print(adata.obs[["n_genes_by_counts", "total_counts"]].head())

观察几个关键指标:

  • n_genes_by_counts:每个 spot 能检测到多少基因。一般 Visium 的捕获效率下,一个 spot 有 500~3000 个基因是正常的。
  • total_counts:总 UMI 数,通常在几千到几万不等。过低意味着组织折叠或捕获失败,过高可能有组织重叠。
  • tissue 列(如果存在):标识该 spot 是否在组织区域内。只有组织内的 spot 才应该用于后续分析。

空间坐标的存储格式:adata.obsm["spatial"] 是一个二维数组,第 0 列是 x(像素列),第 1 列是 y(像素行)。注意 y 轴方向与图像坐标一致,从左上角向下递增。当你要用 matplotlib 显示时,记得 flip 一下 y 轴才能和原图匹配。

最后,我们用一张图来总结整个数据读取与结构的关系,方便你快速回顾。

AnnData 与 Visium 读取流程 AnnData 数据结构与 Visium 读取流程 10X Visium 文件 .h5 / mtx spatial/ 目录 sc.read_visium() 解析 H5 + 坐标 自动构建 AnnData AnnData 对象 .X: 表达矩阵 .obs: 细胞/spot 元数据 .obsm['spatial']: 坐标 .obs 示例字段 barcode in_tissue / n_genes total_counts .obsm 常见键 spatial (坐标) X_pca / X_umap neighbors (邻接图) .uns 常用内容 spatial (图像信息) neighbors (参数) clusters (配色) 💡 关键点 • sc.read_visium 自动填充 X、obs、obsm['spatial']、uns['spatial'] • 空间坐标(像素)存储在 obsm['spatial'],[:,0]=x, [:,1]=y • 后续 Squidpy 的邻域分析会用到 obsm['spatial'] 和 uns['spatial']