第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对象的全过程,我把它贴在工位旁边,每次读数据都参考一遍:

从原始文件到可质控的 AnnData 对象 filtered h5 表达矩阵 + 基因信息 tissue_positions_list.csv barcode + 像素坐标 使用Scanpy/Seurat读取 读入h5 → 提取表达矩阵 读入csv → 提取坐标 只保留 in_tissue == 1 barcode 统一 & 验证 AnnData对象 .X: 表达矩阵 .obs: 坐标 核心检查项: • h5的barcode是否与csv中的一致?——用 set 取交集验证 • 空间坐标是否都为正整数?——肉眼扫一眼 min 值即可 • 读取后基因名是否有重复?——打印 var_names 看一眼 避坑: 别用raw h5做质控,那里面大量空滴 会拉低整体指标,让你误判数据质量 技巧: 读取后立马写一行: adata.obs['total_counts'] = adata.X.sum(axis=1)

数据读进来了,下一步就能做真正的质控清洗了。别指望一次读对,多跑几遍,你自然能摸清h5和csv的门道。