4、数据下载与格式了解:获取公共数据集、H5与MEX格式、元数据文件解析

好,这一章我们来实战一下——先把数据搞到手,再搞明白它长什么样。我个人觉得,很多同学分析半天结果不对,往往不是算法的问题,而是数据格式就搞错了。嗯,咱们从源头开始。

4.1 公共Visium数据集去哪下

目前最主流的来源有三个,我按推荐顺序列一下:

  • 10x Genomics 官方数据集页面:最标准、最干净。每个数据集都附带完整的H5和MEX格式,还有图片和元数据。
  • GEO (Gene Expression Omnibus):很多公开发表的论文会把原始数据存这里。但要注意,GEO里经常只给MEX格式,H5可能得自己转。
  • SpatialData / spatialdata.scverse.org:社区整理好的参考数据集,适合练习。我个人的习惯是先用官方数据跑通流程,再碰GEO的“野生”数据。

举个具体的例子,你可以在10x官网搜索“Human Breast Cancer Visium”或“Mouse Brain Visium”,下载页面会给一个压缩包,里面包含raw_feature_bc_matrix.h5和filtered_feature_bc_matrix.h5。我去年做项目时遇到过下载页面上同时列了两种格式,结果团队里有人只下了MEX没下H5,后面解析元数据时还得重新下。

我的小技巧:不管后续用哪种格式分析,先把H5和MEX都下载到本地。H5打开速度快,MEX方便手动检查,两个互补。

4.2 H5格式的结构与读取

H5全名叫HDF5,说白了就是一个分层容器,里面能塞矩阵、字符串、属性。Visium生成的H5文件打开后,你会看到类似下面的结构:

/
├── matrix
│   ├── barcodes       # 细胞条形码,shape (n_spots,)
│   ├── data            # 非零元素的count值,shape (n_nonzero,)
│   ├── indices         # 非零元素的行索引(稀疏矩阵的列索引)
│   ├── indptr          # 稀疏矩阵的列指针
│   └── shape           # 矩阵形状 [n_features, n_spots]
└── features
    ├── _all_tag        # 类型标记
    ├── feature_type    # 特征类型(Gene Expression, Antibody Capture等)
    ├── genome          # 基因组
    ├── id              # 基因ID(ENSEMBL)
    ├── name            # 基因名称
    └── interval        # 坐标信息

用Python读取其实非常直接。我习惯用scanpy.read_10x_h5(),一行搞定:

import scanpy as sc
adata = sc.read_10x_h5("filtered_feature_bc_matrix.h5")
print(adata.shape)        # (36601 个基因, 4689 个spot)
print(adata.obs_names)    # barcode 列表
print(adata.var_names)    # gene 名称

如果你坚持用h5py库手撸,也能拿到原始数组,但我不推荐——因为你还要自己手动把CSR矩阵拼起来,容易出错。我曾经在一次内部培训中演示手动读取,结果indices和indptr的维度搞反了,多花半小时debug。所以,能用ScanPy就别自己造轮子。

4.3 MEX格式的三大核心文件

MEX是Market Exchange格式的缩写,Visium用三个文件来描述同一个表达矩阵:

文件名内容行数
matrix.mtx非零表达的坐标和count值n_nonzero + 3行头
barcodes.tsv每个spot的条形码n_spots
features.tsv基因ID、基因名称、类型n_features

这三个文件必须放在同一个目录下,名字不能改。ScanPy读取MEX的方法也很简单:

import scanpy as sc
adata = sc.read_10x_mtx("filtered_feature_bc_matrix/")
# 注意这里是目录路径,不是文件路径
print(adata)

你想想看,为什么10x要提供两种格式?我个人的理解是:H5把所有数据锁在一个文件里,方便程序化加载;MEX是文本+稀疏矩阵的经典组合,方便你用命令行grep、awk查一下基因名对应的barcode,或者给其他工具用。我在跨平台迁移数据时,就经常在MEX目录下用head -n 5 barcodes.tsv快速验证spot数量。

踩过的坑:features.tsv的列数在不同版本里不一样!老的Visium试剂盒输出两列(id + name),新的增加了第三列feature_type(比如"Gene Expression")。如果你写死用两列解析,一定会报错。我曾经在生产流水线上因为这个bug,导致500多个样本重新读取。解决方案:始终用scanpy.read_10x_mtx,它自动处理列数变化。

4.4 元数据文件里有什么

除了表达矩阵,每次Visium实验还会生成一个spatial/目录,里面包含元数据。

  • tissue_hires_image.png – 高分辨率组织切片图片
  • tissue_lowres_image.png – 低分辨率预览图
  • scalefactors_json.json – 缩放因子、spot直径等物理参数
  • tissue_positions_list.csv – 每个spot的像素坐标和组织区域判断(in_tissue=1表示在组织上)

来看一个典型的scalefactors_json.json:

{
  "tissue_hires_scalef": 0.17012685,
  "tissue_lowres_scalef": 0.05103706,
  "fiducial_diameter_fullres": 206.73,
  "spot_diameter_fullres": 110.34,
  "regist_target_img_scalef": 0.17012685
}

这些参数对后续空间坐标映射至关重要。比如,你想把低分辨率图上的像素坐标转回高分辨率,就得乘以tissue_lowres_scalef的倒数。我记得刚做空间转录组时,直接拿低分辨率图的像素去匹配H5里的barcode坐标,结果偏了半个视野。后来一查,原来是少乘了一个scalefactor。嗯,这个坑你别再踩了。

tissue_positions_list.csv长这样:

barcode,in_tissue,array_row,array_col,pxl_row_in_fullres,pxl_col_in_fullres
AAACAAGTATCTCCCT-1,1,0,0,334,353
AAACAAACATTCGTTT-1,1,0,1,537,353
...

其中in_tissue=1表示该spot位于组织切片上,=0表示在背景区。后面的array_row/col是捕获阵列的格子坐标,而pxl_row/col是H&E全分辨率图像上的像素坐标。

一个快速验证是否加载正确的方法:用ScanPy读入后,打印adata.obs.head()看是不是有in_tissue列。如果没有,说明你漏了spatial目录或者没正确合并元数据。
🔗 Visium数据从获取到可用实例的完整路线 公共数据源 10x官网 · GEO · SpatialData 压缩包下载 .tar.gz / .zip 解压检查 ls -lh 确认文件完整 选择数据格式进行加载 H5 格式 raw_feature_bc_matrix.h5 单文件,分层结构 ScanPy 读取 sc.read_10x_h5() MEX 格式 matrix.mtx + .tsv 三个文件缺一不可 ScanPy 读取 sc.read_10x_mtx() Anndata 对象 → 准备联合分析 注意 features.tsv 列数版本 差异

看完这个流程图你就明白了。从数据源下载后,不管选H5还是MEX,最终喂给ScanPy的都是一个Anndata对象。再往后,你就可以做标准化、降维、去批次等一系列操作了。

最后再啰嗦一句:元数据文件里的spatial/scalefactors_json.json和tissue_positions_list.csv,很多人一开始会忽略。但如果你要做空间可变基因分析(SPARK、Seurat等),这两个文件是必需的。别不信,我曾经就吃过亏——跑完流程才发现没有合并空间坐标,只好回头再读一次。

一句话总结本章:下载数据认准10x官方源,拿到H5或MEX后用ScanPy一键读取,别忘了把spatial目录里的元数据一并归拢好。数据格式清楚了,后续的分析才能稳稳落地。