数据格式破译:h5、mtx、spatial 与矩阵家族
做空间转录组分析,第一步就是跟文件夹和文件格式打交道。说实话,我早期经常因为读错文件把自己坑惨——不是矩阵反了就是坐标对不上。所以这章咱们把最基础的三种数据格式彻底说透:h5、mtx 和 spatial 目录,以及它们背后的 barcode 矩阵和特征矩阵。
1. h5 格式:一个文件搞定一切
h5(HDF5)是 Hierarchical Data Format 的缩写。说白了就是一个能塞进无数数据集的超级容器。我个人很喜欢这种格式,因为它把表达矩阵、空间坐标、甚至图像信息全部打包在一个文件里,移植的时候不会丢三落四。
一个标准的 10x Visium h5 文件,内部结构大概长这样:
file.h5
├── /matrix
│ ├── barcodes (dataset)
│ ├── data (dataset)
│ ├── indices (dataset)
│ ├── indptr (dataset)
│ ├── shape (dataset)
│ └── features
│ ├── _all_tag_keys (dataset)
│ ├── feature_type (dataset)
│ ├── genome (dataset)
│ ├── id (dataset)
│ └── name (dataset)
├── /spatial
│ ├── scalefactors_json (dataset)
│ ├── tissue_hires_image (dataset)
│ ├── tissue_lowres_image (dataset)
│ └── tissue_positions_list (dataset)
看到那个 /matrix/barcodes 了吗?这就是我们后面要说的 barcode 矩阵的列名。而 /matrix/features 对应的就是特征矩阵的行信息。
h5py.File(file, 'r') 时加上 swmr=True,能绕开不少坑。
2. mtx 格式:稀疏矩阵三件套
mtx 全称 Market Exchange Format,是稀疏矩阵的标准存储方式。10x 把三个文件扔进一个文件夹:
filtered_feature_bc_matrix/
├── barcodes.tsv.gz
├── features.tsv.gz
└── matrix.mtx.gz
这三个文件必须配套使用。我来还原我当年踩过的坑:
Seurat::Read10X 读一个目录,结果矩阵的行列顺序完全反了。后来发现是 matrix.mtx 里的行(features)和列(barcodes)的索引顺序和 barcodes.tsv / features.tsv 不匹配——因为我手动解压修改了文件顺序。记住:永远不要私自调整这三个文件的顺序,要改就用专门的函数操作。
现在看一个标准的 matrix.mtx 文件头:
%%MatrixMarket matrix coordinate integer general
%
32285 36601 171920671
32285 1 1
32285 2 1
...
第一行表示这是一份坐标格式的整数矩阵。第二行:32285 个基因(行),36601 个 barcode(列),1.7 亿个非零值。后面每行一个非零坐标:行号、列号、计数。
3. spatial 目录:把基因放回组织里
有了表达矩阵还不够,你要知道每个 spot 长在哪个物理位置。spatial 目录就是干这个的:
spatial/
├── tissue_positions_list.csv
├── scalefactors_json.json
├── tissue_hires_image.png
└── tissue_lowres_image.png
tissue_positions_list.csv 每一行对应一个 barcode,包含:barcode(去掉后缀)、在低分辨率图像上的行/列坐标(px_col, px_row)、在组织切片上的高分辨率坐标(x, y)、以及是否有组织的标志(in_tissue=1/0)。注意坐标原点是左上角,和图像坐标系一致。
scalefactors_json.json 则保存了高分辨图、低分辨图、以及原图之间的缩放比例。读图像时要先除这个系数,否则坐标会偏移。
4. barcode 矩阵与特征矩阵详解
很多新手搞混这两个概念。我习惯这样区分:
| 矩阵类型 | 行 | 列 | 数值含义 | 典型文件 |
|---|---|---|---|---|
| barcode 矩阵 | 基因 / 特征 | spots / 捕获点 | 每个 spot 上每个基因的 count | barcodes.tsv(列名)matrix.mtx(计数) |
| 特征矩阵 | 基因 / 特征 | 属性列(id, name, type 等) | 特征的元信息 | features.tsv 或 h5 内 /matrix/features |
| 空间坐标矩阵 | spots | 坐标 + 标志位 | 每个 spot 的 x, y, in_tissue | tissue_positions_list.csv 或 h5 内 /spatial/tissue_positions_list |
你可以把 barcode 矩阵想象成一个巨大的稀疏表——行是 2 万个基因,列是几千个 spot,里面填的是 UMI count。而特征矩阵是这张表的「行注释」。空间坐标矩阵则是「列注释」的扩展(除了哪个 spot,还要告诉我它在哪)。
5. 一张图看懂数据流
下面这张 SVG 图把从原始文件到关键矩阵的转换关系画出来了。这也是我每次做新项目前必画的路线图,对着它调代码不容易走歪。
6. 实战:用代码把三种格式统一读进来
不管原始数据是 h5 还是 mtx+spatial,最终我们都要拿到两个矩阵和一个坐标表。以 R 语言为例,最常用的做法:
读取 h5(使用 Seurat)
library(Seurat)
# 直接从 h5 读入
data <- Read10X_h5("raw_feature_bc_matrix.h5")
# 返回一个稀疏矩阵,列是 barcode,行是 feature
spatial_coords <- ReadVizgen("spatial/") # 或者手动解析 h5 内 /spatial/tissue_positions_list
读取 mtx + spatial
library(Seurat)
# 读 mtx 三件套(需要保证目录下只有这三个文件)
mat <- Read10X("filtered_feature_bc_matrix/")
# 读空间坐标
spatial_data <- read.csv("spatial/tissue_positions_list.csv", header = FALSE)
# 根据 barcode 匹配矩阵列和坐标行
跨格式检查
无论哪种方式,最终你得到的 barcode 矩阵列数应等于 tissue_positions_list.csv 的行数(除去 header)。我当时做验证时写过一个简单脚本:
# 检查 barcode 是否完全一致
bc_mat <- colnames(mat)
bc_coord <- spatial_data$V1 # 假设第一列是 barcode
if (all(bc_mat == bc_coord)) {
print("✅ barcode 匹配,可以生成 Seurat 对象")
} else {
stop("❌ barcode 不匹配,检查文件是否对应同一份组织切片")
}
核心原则:barcode 是 spot 的唯一标识符,它串联了表达矩阵的列和空间坐标表的行。一旦出现错配,后面所有空间分析都是废的。务必在数据加载后立即做一致性校验。
数据格式搞清楚了,下一步才能放心地做 Spot 过滤。这章讲的这些文件结构你最好手头备一个实际数据集练一练,打开看看 h5 的 group 和 dataset,数数 mtx 的非零值,核对一下 spatial 里的坐标单位,这样以后遇到奇怪的格式变化也能一眼识破。