第4章:数据格式与读取——h5与Spaceranger输出
拿到原始数据,第一件事儿就是读懂它长什么样。空间转录组的数据格式,说白了就两大类:一个是h5背后的HDF5格式,另一个是10x的Spaceranger输出。今天我把这两块一次性讲透,保证你读完能直接上手。
4.1 理解h5——不只是个文件
我个人习惯把h5文件比作一个「文件袋」。你在袋子里可以塞矩阵、塞空间坐标、塞数据框,甚至塞图片。HDF5的核心理念就是分层与压缩,结构非常灵活。
我用Scanpy读一个10x Visium的h5文件时,代码就这么简单:
import scanpy as sc
adata = sc.read_10x_h5("filtered_feature_bc_matrix.h5")
扫一眼返回的adata是什么:
print(adata)
# AnnData object with n_obs × n_vars = 4233 × 19369
# var: 'gene_ids', 'feature_types', 'genome'
这里4233是spot数,19369是基因数。实际项目中,我发现很多人把原始h5和过滤后的h5搞混,直接用原始数据算质控——结果被大量空滴稀释了。
提示:10x有两个h5文件,一个叫raw_feature_bc_matrix.h5(包含所有barcode),一个叫filtered_feature_bc_matrix.h5(只保留组织区域的spot)。做质控一定要用filtered版本,否则后续工作全白费。
4.2 Spaceranger输出结构——你拿到的是什么?
Spaceranger跑完,会生成一堆文件夹。我第一次接触时被绕晕过,这里给你把关键目录列出来:
| 目录/文件 | 说明 | 我一般怎么用 |
|---|---|---|
| outs/filtered_feature_bc_matrix.h5 | 过滤后的表达矩阵(h5) | 直接给Scanpy读,最干净 |
| outs/spatial/tissue_positions_list.csv | 每个spot的像素坐标 | 构建空间邻接图时用 |
| outs/spatial/scalefactors_json.json | 缩放因子、spot直径等 | 可视化时对齐图像尺寸 |
| outs/spatial/tissue_hires_image.png | 高清组织切片图 | 叠加表达量热图 |
| outs/raw_feature_bc_matrix.h5 | 原始表达矩阵(含空滴) | 不用于质控,仅做背景评估 |
你想想看,这六个文件里,最核心的就两个:h5矩阵和tissue_positions_list.csv。一个管表达,一个管位置,搭配起来就能做空间分析了。
4.3 用Scanpy完整读取一个Visium数据
在实际项目中,我习惯一次读完所有信息,代码通常是这样写的:
import scanpy as sc
import pandas as pd
# 1. 读表达矩阵
adata = sc.read_10x_h5("outs/filtered_feature_bc_matrix.h5")
# 2. 读空间坐标
positions = pd.read_csv(
"outs/spatial/tissue_positions_list.csv",
header=None,
names=["barcode", "in_tissue", "row", "col", "pxl_row", "pxl_col"]
)
# 3. 只保留组织内的spot
positions_tissue = positions[positions["in_tissue"] == 1].copy()
positions_tissue.index = positions_tissue["barcode"].values
# 4. 将坐标加到adata.obs
adata.obs = adata.obs.join(positions_tissue[["pxl_row", "pxl_col"]], how="inner")
print(adata)
# AnnData object with n_obs × n_vars = 4032 × 19369
# obs: 'pxl_row', 'pxl_col'
# var: 'gene_ids', 'feature_types', 'genome'
注意:in_tissue = 1 表示该spot落在组织区域内。我曾经遇到过一批数据in_tissue全为0,最后发现是切片贴歪了——Spaceranger没对准。遇到这种问题,先回头检查组织图像,别盲目往下跑分析。
4.4 用Seurat的读法——给R用户
如果你习惯用R,Seurat读取方式也很直接。我虽然主用Python,但偶尔也会切到R看一眼:
library(Seurat)
library(DropletUtils)
# 用Read10X读Cell Ranger目录格式
counts <- Read10X_h5("outs/filtered_feature_bc_matrix.h5")
# 创建Seurat对象
visium <- CreateSeuratObject(counts, project = "Visium")
# 加空间坐标
positions <- read.csv("outs/spatial/tissue_positions_list.csv", header = FALSE)
positions <- positions[positions$V2 == 1, ]
row.names(positions) <- positions$V1
visium <- AddMetaData(visium, positions[, c("V5", "V6")], col.name = c("row", "col"))
其实逻辑都一样,就是先把矩阵读进来,再把坐标附上。核心永远是先验证barcode一致性。
4.5 数据读取常见踩坑
这里列几个我从项目里摸爬滚打出来的避坑指南:
- barcode命名冲突:不同的样本如果barcode头部相同,合并时容易撞车。我会在读取后加后缀,比如
adata.obs_names = [x + "-sample1" for x in adata.obs_names]。 - 坐标列顺序搞错:tissue_positions_list.csv的列顺序是固定的:barcode、in_tissue、array_row、array_col、pxl_row、pxl_col。不要想当然地把列名搞反。
- h5文件被截断:网络传输中断、硬盘空间不够都可能导致h5损伤。拿到数据后先试读,能正常输出shape和var就没问题。
经验总结:读数据是质控的第一步,这里出问题后面全白干。每次拿到新数据,我会先看一眼shape,再输出前5个barcode和基因名,确认格式没问题才进入正式分析。这一步花不到30秒,但能省下之后几小时排错的时间。
4.6 知识结构一览
下面这张图概括了从原始文件到可用于质控的AnnData对象的全过程,我把它贴在工位旁边,每次读数据都参考一遍:
数据读进来了,下一步就能做真正的质控清洗了。别指望一次读对,多跑几遍,你自然能摸清h5和csv的门道。