单细胞RNA-seq数据基础:读入原始数据格式

做单细胞分析,第一步就是把手头的原始数据读进来。我刚开始接触这个领域时,面对一堆文件后缀完全摸不着头脑——.mtx、.barcodes.tsv、.features.tsv、.h5ad……到底哪个才是正经数据?

其实说白了,单细胞转录组数据的核心就一个东西:cell × gene 矩阵。行是细胞,列是基因,每个格子告诉你某个基因在这个细胞里有多少个UMI(或count)。后面所有的聚类、差异分析、批次校正,全是在这个矩阵上做文章。

嗯,咱们先看两种最常见的原始数据格式:10X 格式和 h5ad 格式。

10X 格式:三个文件一个也不能少

10X Genomics 平台产出的数据,通常会给你一个文件夹,里面至少三个文件:

文件名作用
matrix.mtx(或 matrix.mtx.gz)存储稀疏矩阵格式的计数数据,只记录非零元素
barcodes.tsv(或 barcodes.tsv.gz)每个细胞对应的barcode序列,一行一个
features.tsv(或 genes.tsv)基因标识符,通常包含两列:基因ID和基因名

注意:这三个文件的名字不能自己乱改,必须严格对应。我早期有个项目,团队把文件改了名,结果 scanpy.read_10x_mtx 死活报错,查了半天才发现文件名不匹配。避坑指南:保持原始命名,或者用软链接。

读入代码很简单:

import scanpy as sc

adata = sc.read_10x_mtx(
    './data/filtered_feature_bc_matrix/',  # 包含三个文件的目录
    var_names='gene_symbols',               # 用基因名作为变量名
    cache=True                              # 缓存加速多次读取
)

这里 var_names='gene_symbols' 的意思是,用 features.tsv 第二列的基因符号作为列名。如果选 gene_ids,就会用第一列的ENSEMBL ID。我个人习惯用基因符号,因为后续做可视化直接显示基因名,不用再映射。

h5ad 格式:一步到位

如果你拿到的是 .h5ad 文件,那就更省事了。h5ad 是 AnnData 对象的原生存储格式,里面已经包含了细胞×基因矩阵、细胞元数据、基因元数据,甚至降维结果。读入只有一行:

adata = sc.read_h5ad('./data/pbmc3k_processed.h5ad')

为什么会有两种格式?实践中,10X 格式是测序公司的标准产出版本,而 h5ad 更多是分析流程中保存中间结果用的。比如我用 scwrite(adata, 'my_data.h5ad') 存下来,下次直接读,免去重复预处理。

读入之后,先看看矩阵长什么样

无论哪种格式,读进来都是 AnnData 对象。里面的核心是 X 属性,就是那个 cell × gene 矩阵。我来展示一下怎么检查:

print(adata.shape)    # (细胞数, 基因数)
print(adata.X)        # 稀疏矩阵,默认是 csr_matrix

我见过不少新人把矩阵打印出来看,结果因为稀疏矩阵里全是0,吓一跳以为数据丢了。其实单细胞矩阵极度稀疏——通常 90% 以上的格子都是0。用稀疏格式存储,内存能省几十倍。

cell × gene 矩阵到底是什么?

这个概念我画了一张图,你一看就明白:

Cell × Gene 矩阵结构示意 细胞1 细胞2 细胞3 … 细胞N 细胞 (barcode) 基因A 基因B 基因C … 基因M 基因 (features) 0 3 1 2 5 1 0 说明 • 每个格子 = 基因在细胞中的 原始UMI count • 白色/未着色格子 = 0 • 绝大多数格子是0(稀疏) • 行数 = 细胞数,列数 = 基因数

关键理解:矩阵中的数值是原始的UMI计数,不是归一化后的表达量。后续我们会做标准化、对数化,但那是后面的章节。现在只需要确保你读进来的矩阵,能正确对应到细胞和基因。

实战:检查读入数据的完整性

我建议你每读入一个数据集,先跑一遍下面这个检查清单:

  1. adata.X 的类型是 csr_matrix 吗?(如果不是,考虑转换:adata.X = adata.X.tocsr())
  2. adata.obs_names 有没有重复?adata.var_names 有没有重复?
  3. 总count的分布是否合理?sc.pp.sum(adata, total='counts_per_cell') 看看
  4. 基因是否包含线粒体基因、核糖体基因(后续质控要用)

有一次我在一个公共数据集上遇到 adata.var_names 里出现了NA,一查发现 features.tsv 第三行是空基因名。我当时没检查,结果后面画图时基因名全是 NA 搞得一头雾水。所以现在每次读数据,我都会用 print(adata.var.head()) 扫一眼。

两种格式的对比

对比维度10X 三文件格式h5ad 格式
读入函数sc.read_10x_mtxsc.read_h5ad
文件数量至少3个(.mtx, .tsv ×2)单个文件
读取速度稍慢(需解析多个文件)较快,直接读取
是否包含元数据仅矩阵和标识可包含 obs、var、uns 等全部内容
适用场景测序公司直接产出,最原始流程中保存中间/最终结果

避坑指南:千万不要用 pandas.read_csv 去读 .mtx 文件!那是稀疏矩阵格式,不是文本表格。一定要用专门的读取函数。我曾经见过同事试图用Excel打开 matrix.mtx,结果计算机直接卡死——文件虽然小,但行数动辄几十万。

小结

这一章的核心就两件事:读入 和 验证。读入用 sc.read_10x_mtx 或 sc.read_h5ad,验证用 .shape 和 print(adata)。理解 cell × gene 矩阵是后续所有分析的基础,把这个概念印在脑子里,后面讲质控、归一化、降维你都不会跑偏。