第3章 数据格式:h5ad、Space Ranger 输出及其他格式
做空间转录组分析,第一关就是跟数据格式打交道。我记得刚入行那会儿,不同平台的数据格式多得让人头大,光是搞清.h5、.h5ad、.mtx、.tiff这些后缀就花了几天。这一章我带你理清Scanpy最常用的几种数据格式,尤其是h5ad和Space Ranger的输出。
3.1 h5ad:Scanpy 的“亲儿子”
h5ad是Scanpy原生存储格式,基于HDF5。说白了,它就是一棵用HDF5组装的树,把表达矩阵、元数据、降维结果、图像全部塞到一个文件里。我个人习惯把h5ad看作“数据集装箱”:
adata.X # 表达矩阵(稀疏或稠密)
adata.obs # 细胞/点元数据(数据框)
adata.var # 基因元数据(数据框)
adata.obsm # 细胞多维数据(降维坐标等)
adata.obsp # 细胞间关系矩阵(邻接图等)
adata.varm # 基因多维数据
adata.uns # 非结构化数据(聚类颜色、参数等)
adata.layers # 多层表达数据(原始counts、归一化、对数化等)
关键点:空间转录组的h5ad比单细胞多一个adata.obsm['spatial'],里面存的是组织切片上每个spot的坐标(像素坐标)。另外adata.uns['spatial']会存放图像缩放因子、组织区域等空间元信息。
我在一个Visium项目中遇到过,同事直接把空的adata.obsm塞进h5ad,结果下游找不到坐标报错。嗯,这里要注意:写入h5ad之前一定要确认空间信息是否完整。
技巧:用adata.write('my_spatial.h5ad')保存后,可以用adescribe()快速查看结构——它会把各组件的shape、数据类型都打印出来,排查问题很实用。
3.2 Space Ranger 输出:官方标准数据
10x Genomics的Space Ranger是Visium平台的标准上游流程。输出目录结构大概长这样:
outs/
├── filtered_feature_bc_matrix.h5 # 过滤后的spot x gene矩阵 (HDF5格式)
├── raw_feature_bc_matrix.h5 # 原始矩阵(含背景)
├── spatial/
│ ├── tissue_positions_list.csv # spot坐标 + tissue标号
│ ├── scalefactors_json.json # 图像缩放因子
│ ├── tissue_hires_image.png # 高分辨率组织图像
│ └── tissue_lowres_image.png # 低分辨率组织图像
├── analysis/ # 下游分析结果(非必须)
└── web_summary.html # 网页质控报告
我刚开始用Space Ranger时,图省事只读了filtered_feature_bc_matrix.h5,结果忘记加载坐标文件,后面画图全偏了。避坑指南来了——一定要把spatial文件夹完整复制到分析目录下。
Scanpy提供了专门函数读取Space Ranger输出,一行搞定:
import scanpy as sc
adata = sc.read_10x_h5('filtered_feature_bc_matrix.h5')
sc.pp.filter_cells(adata, min_genes=200) # 可选过滤
# 再手动加载空间坐标和图像
import pandas as pd
positions = pd.read_csv('spatial/tissue_positions_list.csv', header=None,
names=['barcode','in_tissue','row','col','pxl_row','pxl_col'])
# 然后合并到 adata.obs 和 adata.obsm['spatial']
其实现在Scanpy新版本有sc.read_visium()函数,可以直接把整个文件夹路径传进去,连坐标和图像都自动读好了。我建议你用这个:
adata = sc.read_visium('outs/', source_image='hires')
注意:source_image='hires'会读取高分辨率图像,内存占用较大。如果只做统计不下游成像分析,用lowres更轻量。另外tissue_positions_list.csv的列名在新版Space Ranger中可能变化,记得检查版本号。
3.3 其他常见格式
除了h5ad和Space Ranger原生输出,我们还经常遇到下面几种:
| 格式 | 说明 | 读取方式 | 个人经验 |
|---|---|---|---|
Visium HDF5 (.h5) | 旧版Space Ranger结果,纯HDF5格式 | sc.read_10x_h5() | 需要手动补充空间坐标 |
MTX (.mtx) + 元数据 | Matrix Market格式,常配合genes.tsv和barcodes.tsv | sc.read_10x_mtx() | 适合做多样本合并时使用 |
Seurat对象 (.rds) | R语言常用格式 | 先用SeuratDisk转成h5ad | 我在跨团队合作时经常需要R↔Python互转 |
Loom (.loom) | Stream & Hirschberg的旧格式 | sc.read_loom() | 现在逐渐被h5ad替代,但一些公共数据库仍提供 |
| AnnData JSON | h5ad的JSON序列化版本 | anndata.read_zarr() 或 anndata.read_h5ad()后导出JSON | 很少用,适合网页展示场景 |
转换工具我常用anndata.utils.convert_to_rds()和h5ad2rds,但说实话最稳妥的还是两个环境都装好,分别读写。你想想看,如果rds文件里的基因名是CD3E,而h5ad里是ENSG00000134571,转完直接去配对号……我以前就在这里栽过跟头。
3.4 实战:读取并检查空间数据
下面我带你走一遍常规流程,从原始Space Ranger输出到可分析的h5ad:
- 解压Space Ranger输出包,确认目录结构完整。
- 用
sc.read_visium()加载。 - 检查数据基本结构:
import scanpy as sc
adata = sc.read_visium('path/to/outs/')
print(adata)
print(adata.obsm['spatial'][:5,:]) # 前5个spot的坐标
print(adata.uns['spatial']['Visium_FFPE']['scalefactors'])
# 查看图像是否存在
if 'hires' in adata.uns['spatial']['Visium_FFPE']['images']:
print("高分辨率图像已加载")
- 如果从h5ad文件恢复,务必验证空间坐标和图像:
adata = sc.read_h5ad('my_spatial.h5ad')
assert 'spatial' in adata.obsm, "没有空间坐标!"
assert adata.obsm['spatial'].shape[1] == 2, "坐标应该是2列"
我个人习惯在读取后立即绘制一个sc.pl.spatial(adata)看看组织轮廓对不对,3秒就能发现问题。
格式对比与选择
为了让你一目了然,我画了一张思维导图——从原始数据到h5ad的转化路径:
说实话,格式处理占了我项目前期30%的时间——特别是当你有多个批次、不同平台的数据时。我的经验是:一步到位全部转成h5ad,后续分析只用sc.read_h5ad()和adata.write(),最大程度减少格式焦虑。
注意:h5ad底层是HDF5,大文件读写可能较慢。如果数据量超过50GB,考虑用adata.write_zarr('my_path.zarr')换成Zarr格式,支持分块读取和并行IO。我曾在512GB内存的机器上测试过,读写速度提升5倍以上。
好了,这一章的内容就到这。记住:掌握了h5ad结构和Space Ranger输出,你就拿下了空间转录组数据入口的钥匙。遇到奇怪格式别慌,先试着用anndata的read_*函数套一套——实在不行,用Python的h5py和pandas硬读也是家常便饭。下节课我们聊预处理,但先把数据格式功夫练扎实,后面的路会顺很多。