第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.tsvsc.read_10x_mtx()适合做多样本合并时使用
Seurat对象 (.rds)R语言常用格式先用SeuratDisk转成h5ad我在跨团队合作时经常需要R↔Python互转
Loom (.loom)Stream & Hirschberg的旧格式sc.read_loom()现在逐渐被h5ad替代,但一些公共数据库仍提供
AnnData JSONh5ad的JSON序列化版本anndata.read_zarr() 或 anndata.read_h5ad()后导出JSON很少用,适合网页展示场景

转换工具我常用anndata.utils.convert_to_rds()和h5ad2rds,但说实话最稳妥的还是两个环境都装好,分别读写。你想想看,如果rds文件里的基因名是CD3E,而h5ad里是ENSG00000134571,转完直接去配对号……我以前就在这里栽过跟头。

3.4 实战:读取并检查空间数据

下面我带你走一遍常规流程,从原始Space Ranger输出到可分析的h5ad:

  1. 解压Space Ranger输出包,确认目录结构完整。
  2. 用sc.read_visium()加载。
  3. 检查数据基本结构:
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("高分辨率图像已加载")
  1. 如果从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的转化路径:

空间转录组数据格式与转化链路 Space Ranger outs/ 目录 其他格式 Visium H5 MTX / Loom Seurat RDS h5ad (AnnData) X / obs / var / obsm uns['spatial'] layers Scanpy分析 预处理 → 降维 → 聚类 关键转换建议 • 优先使用 sc.read_visium() 直接读取 Space Ranger 输出 • 所有格式最终统一转为 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硬读也是家常便饭。下节课我们聊预处理,但先把数据格式功夫练扎实,后面的路会顺很多。