数据格式入门:h5ad、AnnData 与 Visium 数据读取
空间转录组的数据,说白了就是一锅「基因表达矩阵 + 空间坐标 + 组织图像」的大杂烩。怎么把这锅东西装进 Python 里高效处理,就是咱们这节要聊的核心。
我个人最早接触空间转录组数据时,也被一大堆文件搞得头大:`filtered_feature_bc_matrix.h5`、`spatial/tissue_positions_list.csv`、`spatial/tissue_hires_image.png`……每个文件都只记录了数据的一个侧面。直到我遇到了 h5ad 格式和 AnnData 对象,才感觉终于找到了「统一容器」。
为什么 h5ad 是空间转录组的「通用语言」?
h5ad 的全称是 Hierarchical Data Format 5 for AnnData。它基于 HDF5,这是一种被工业界广泛使用的二进制数据格式。好处很明显:读取快、压缩率高、自带分层结构。
我在项目中经常碰到同事直接拿 CSV 存 10 万细胞的数据集,结果文件十几个 G,读一次要几分钟。换成 h5ad 后,同样的数据压缩到 2-3 G,读取不到 10 秒。嗯,这就是二进制格式的魅力。
从文件到对象:读取 h5ad 的核心方法
在 Python 里操作 h5ad,主角是 scanpy 库中的 read_h5ad 函数。用法简单到不像话:
import scanpy as sc
adata = sc.read_h5ad('spatial_data.h5ad')
print(adata)
就这么两行,数据就进来了。输出结果大概长这样:
AnnData object with n_obs × n_vars = 4000 × 18000
obs: 'batch', 'cluster'
var: 'highly_variable', 'means', 'dispersions'
obsm: 'X_pca', 'X_umap', 'spatial'
uns: 'spatial_neighbors', 'image_thumbnail'
这里每个字段都有含义。我个人建议,拿到一个新数据后,第一件事就是打印这个摘要信息,快速摸清数据到底有哪些「零件」。
read_h5ad。你需要先用 sc.read_10x_h5() 转换一下。
h5py库的读取方法套上去。数据内部的层级结构是 AnnData 特有的,直接用 h5py 读取可能会得到一团乱麻。
解剖 AnnData:从五个核心属性入手
AnnData 对象看起来复杂,本质上就五块内容。我习惯把它想象成一个「五斗柜」,每个抽屉里放一类数据:
| 属性 | 维度 | 装什么 |
|---|---|---|
.X |
细胞 × 基因 | 基因表达矩阵(通常是稀疏矩阵,节省内存) |
.obs |
细胞 × 属性 | 细胞级别的元数据:批次、聚类标签、UMI 总数等 |
.var |
基因 × 属性 | 基因级别的元数据:是否高变基因、平均表达量等 |
.obsm |
细胞 × 特征(可扩展) | 细胞的多维矩阵:降维坐标(UMAP/PCA)、空间坐标(spatial) |
.uns |
无固定维度 | 非结构化数据:组织学图像缩略图、聚类所用参数、绘图配色 |
光看表可能不够直观。我曾经在项目里踩过一个坑:把空间坐标写进了 .obs 里,结果后续用 sc.pl.spatial 绘图时死活找不到坐标。后来才发现,空间坐标应该放在 .obsm['spatial'] 里,这是 scanpy 的约定。
.X 存表达值,.obs 存细胞信息,.var 存基因信息,.obsm 存坐标和降维结果,.uns 存杂项但很重要的东西。
实战:读取 10x Visium 数据
理论讲了这么多,我们来点实际的。10x Visium 平台的数据,官方通常会给两种格式:.h5 (原始 counts) + 一个 spatial 目录(包含坐标和图像)。
我习惯的步骤是——先用 scanpy 自带的函数一口气读进来:
adata = sc.read_visium(
path='path/to/outs/', # 包含 spatial 目录的上级文件夹
count_file='filtered_feature_bc_matrix.h5',
load_images=True # 把组织图像也读进来
)
这一步完成后,咱们来看看新对象里多了什么:
print(adata.obsm['spatial'].shape) # 看看有几个 spot(捕获点)
print(adata.uns['spatial']) # 看看图像数据是否加载成功
obsm['spatial'] 是一个 ndarray,每一行对应一个 spot 在图像上的像素坐标。列的顺序是 [y, x],我刚开始读数据时就先入为主地以为是 [x, y],导致后面画图叠不上。嗯,这里要注意,10x 的习惯是行在前、列在后。
load_images=True 后打印 .uns['spatial'] 是空的,多半是因为 spatial 目录下缺少 tissue_hires_image.png 或 scalefactors_json.json。我曾经在一次批量下载中漏掉了图像文件,结果卡了整整一下午。检查文件完整性是最简单但最容易被忽略的一步。
读取完之后,你可以快速检查一下数据是否正常:
# 检查表达矩阵的数据类型
print(adata.X.dtype)
# 看看前几个 spot 的表达量
print(adata.X[:3, :5].toarray())
如果是 float32 或 uint32,说明数据解析正确。如果是 object 或全是 NaN,那大概率是读取路径搞错了。
sc.pl.highest_expr_genes(adata, n_top=20)。这一方面能验证数据是否有生物学意义,另一方面也能快速暴露质量问题——如果排第一的基因是线粒体基因,说明细胞可能已经破裂了。
小结:这一节我们干了什么?
说白了,就是两件事:
- 认识了 h5ad 格式——一个把多源数据打包成单一文件的二进制容器。
- 解剖了 AnnData 对象——理解了它的五斗柜结构,每个属性装什么数据。
- 实操了 10x Visium 数据读取——用
sc.read_visium一行代码搞定,并快速做了数据完整性的初步验证。
这些东西看着基础,但往后做整合分析、空间聚类、配准比对,全都要依赖这个核心数据结构。你现在把 AnnData 玩熟了,后面 20 多个章节就相当于在搭积木,而不是从头造轮子。嗯,打好基础比什么都重要。