2. 数据格式入门

做空间转录组分析,第一步就是跟各种数据格式打交道。我刚入行那会儿,光搞清楚文件后缀就花了两天。你知道的,空间转录组不像普通单细胞,它涉及的东西特别杂:基因表达矩阵、空间坐标、组织切片图像……每个供应商、每种工具都给你弄一套不同的格式。

这一讲我就带你快速过一遍常见的格式:HDF5、h5ad、mtx,还有那个让人头疼的 spatial 文件夹结构。搞懂了这些,后面跑质控就不会一头雾水。

1. HDF5:大容量数据的容器

HDF5 全称是 Hierarchical Data Format version 5。你可以把它理解成一个压缩包,里面能装多个数据集。空间转录组的原始计数矩阵、基因注释、细胞条形码,经常被打包在一个 .h5 文件里。

举个例子,10x Genomics 的空间 Visium 数据,官方给的过滤后的表达矩阵就是 filtered_feature_bc_matrix.h5。里面分三个组:matrix 存计数,features 存基因信息,barcodes 存细胞/点索引。

# 用 h5py 看一眼内部结构
import h5py

with h5py.File("filtered_feature_bc_matrix.h5", "r") as f:
    def print_attrs(name, obj):
        print(name)
    f.visititems(print_attrs)
# 输出大概长这样:
# matrix
# matrix/barcode_idx
# matrix/data
# matrix/indices
# matrix/indptr
# matrix/shape
# features
# features/feature_type
# features/genome
# features/id
# features/name
# barcodes

我个人习惯用 h5py 直接读,但日常分析更推荐用 scanpy.read_10x_h5(),它帮你解析好了。保存时也建议用 HDF5,压缩率高,读起来快。

为什么空间转录组偏爱 HDF5? 因为一张组织切片可能包含 5000 个 spots,每个 spot 测到几万个 UMI。普通 CSV 存不下,HDF5 的 BTree + 分块存储能让随机访问快两个数量级。

2. h5ad:单细胞分析的标准格式

h5ad 是 AnnData 的后缀。AnnData 是 scanpy 生态的核心对象,它用 HDF5 作为底层存储,但把数据结构暴露得更加友好。一个 h5ad 文件包含 X(表达矩阵)、obs(观测元数据,例如每个 spot 的总 UMIs、批次标签)、var(特征元数据,例如基因名、高变基因标志)、obsm(观测水平的嵌入,例如空间坐标 spatial)、uns(非结构化数据,例如图像路径)。

import anndata as ad

adata = ad.read_h5ad("sample.h5ad")
print(adata)
# AnnData object with n_obs × n_var = 4890 × 31053
#     obs: 'batch', 'n_counts', 'pct_mito'
#     var: 'gene_ids', 'n_cells'
#     obsm: 'spatial'  # 空间坐标,形状 (4890, 2)
#     uns: 'spatial'   # 包含组织图像路径和缩放因子

我在项目里遇到过同事直接把 mtx + 坐标分开存,结果后期整合数据时坐标和表达矩阵对不上。我建议从一开始就统一用 h5ad 格式,obs 里记批次、条件,obsm 里放坐标,一起写进一个文件。以后迁移、分享都省心。

小技巧:当你用 adata.write("output.h5ad") 保存时,如果数据比较大,可以加 compression='gzip' 参数,文件能小 40%。

3. mtx:市场交换格式,三件套

mtx 是 Matrix Market 格式,常见于 10x 早期版本和 CELESTA 等自定义 pipeline。它永远跟两个文件配套:barcodes.tsv.gz 和 features.tsv.gz,合称“三件套”。

结构很简单:

matrix.mtx.gz
%%MatrixMarket matrix coordinate real general
% metadata
4890 31053 14532288
0 5 1
0 8 2
1 3 1
...
# 每行:spot_index(0-based) gene_index(0-based) count

如果你拿到的是 mtx,我建议你立马用 scanpy.read_10x_mtx() 转成 h5ad。为啥?因为 mtx 存不了坐标和图像。你得额外再读 tissue_positions_list.csv,然后手动写到 obsm 里。多一步就多一个坑。

注意:有些供应商把 barcodes.tsv 和 features.tsv 排成单列,有些排成双列。读之前用 head 看一眼。我曾经就因为 features.tsv 里第三列多了个空值,导致基因名全是 NaN,排查了三个小时。

4. spatial 文件夹:图像与坐标的藏身处

这个文件夹是 10x Visium 数据的标配,名字通常叫 spatial/,放在 outs/ 下。里面长这样:

spatial/
├── tissue_hires_image.png      # 高分辨率组织图,通常 2000×2000
├── tissue_lowres_image.png     # 低分辨率缩略图,600×600
├── scalefactors_json.json      # 缩放比例、spot 直径等
├── tissue_positions_list.csv   # 所有 spot 坐标、是否在组织内
└── aligned_fiducials.jpg       # 对齐用的 fiducial 标记(不常用)

最核心的是 tissue_positions_list.csv,每行一个 spot:

barcode,in_tissue,array_row,array_col,pxl_row_in_fullres,pxl_col_in_fullres
AACCCAAGTCATTTCG-1,1,0,0,2003.5,2175.2
AACCCACCTAGCACAT-1,1,0,1,1981.6,2235.4
...

pxl_row_in_fullres 和 pxl_col_in_fullres 是原始像素坐标,基于 tissue_hires_image.png。如果你想在低分辨率图上画点,需要乘 scalefactors_json.json 里的 tissue_lowres_scalef 因子。

# scalefactors_json.json 内容示例
{
    "spot_diameter_fullres": 89.5,
    "tissue_hires_scalef": 0.5,
    "tissue_lowres_scalef": 0.25,
    "fiducial_diameter_fullres": 144.5
}
我的经验:读取 tissue_positions_list.csv 时,一定要检查 in_tissue 字段。只有值为 1 的 spot 才在组织区域里,那些值为 0 的点是背景或 fiducial 标记,通常要过滤掉。否则质控指标会漂。

5. 图像与坐标的关系

空间转录组的“空间”二字,核心就是坐标系统。一般有两种:

  • 像素坐标(pixel coordinates):对应原始图像,单位是像素。pxl_row 是 y 轴(从图像顶部往下),pxl_col 是 x 轴(从左到右)。
  • 阵列坐标(array coordinates):array_row 和 array_col,是探针阵列上的逻辑位置,跟图像无关。用于 spot 之间的几何关系分析,比如找邻域。

我把它们画成一张流程图,方便你理解:

空间转录组数据格式流转结构 原始测序数据 FASTQ / BCL spaceranger / stpipeline 对齐 → 计数 → 组织检测 outs/ 输出目录 filtered_feature_bc_matrix.h5 HDF5 (.h5) matrix / features / barcodes 一次读取,结构清晰 mtx 三件套 matrix.mtx / barcodes.tsv features.tsv (无坐标) h5ad X + obs + var + obsm 推荐最终格式 spatial/ 文件夹 图像 + JSON + 坐标 CSV 整合:h5ad + 坐标 + 图像 注意 h5ad 可以直接存 obsm['spatial'] 以及 uns['spatial']['images']

6. 实战建议:如何选择一个好格式

格式适用场景需配套文件推荐指数
HDF5 (.h5)10x 官方输出,后续要转 adata无额外坐标文件⭐⭐⭐⭐
mtx 三件套自定义 pipeline,但缺坐标tissue_positions_list.csv⭐⭐
h5ad分析全流程,整合批次无,自带全部信息⭐⭐⭐⭐⭐
spatial 文件夹图像可视化,坐标溯源配合 h5ad 使用⭐⭐⭐⭐
我个人工作流:拿到原始数据后,先用 scanpy.read_10x_h5() 读 HDF5,然后用 sc.read_json() 读 scalefactors,再读 tissue_positions_list.csv 构建 obsm['spatial'],最后统一存成 h5ad。全程不超过 10 行代码。