单细胞RNA-seq数据入门:表达矩阵与元数据格式归纳总结
单细胞数据分析的第一步,就是搞清楚你手里拿到的数据到底是什么样的。
很多新手一上来就急着做降维聚类,结果发现画出来的图乱七八糟。为什么?因为连基础的表达矩阵结构和元数据都没搞明白。我刚开始接触单细胞时也犯过这个错——拿到一个rds文件就直接跑,后来才发现里面根本沒有基因名,只有一堆EnsemblID,搞得我只能在cluster里猜功能,相当尴尬。
所以,这一章我们把最底层的东西说透。说白了就是两样东西:表达矩阵 和 元数据。你掌握好它们,后面的质控、归一化、整合才能顺风顺水。
表达矩阵:每个格子的意义
单细胞表达矩阵本质上就是个二维表格:
- 行 = 基因(或feature,包括蛋白、CRISPR等)
- 列 = 细胞(每个barcode一行)
- 值 = 该基因在该细胞中的表达量
但这里的“表达量”有好几种,你得区分开:
| 类型 | 说明 | 典型值范围 | 我用过的场景 |
|---|---|---|---|
| UMI counts | 基于唯一分子标识的真实分子计数,整数 | 0 ~ 几千 | 10X、Drop-seq常见,我个人习惯用这个做质控 |
| Read counts | 比对到该基因的测序片段数,可能重复 | 0 ~ 上万 | Smart-seq2等全长方法,注意PCR重复需要去重 |
| CPM/TPM | 文库大小归一化后的值,浮点数 | 0 ~ 几十 | 比较不同样本时可用,但质控阶段不推荐 |
| Log-normalized | 对数归一化后的值,常为ln(CPM+1) | 0 ~ 10+ | 下游聚类可视化常用,但scran质控工具要求原始counts |
注意:scran的质控函数如
addPerCellQC()、quickPerCellQC(),它们都只接受原始整数计数矩阵。如果你传入的是对数归一化后的数据,每个细胞的总UMI/基因数计算就会完全错误。我曾经见过有人直接拿Seurat的NormalizeData结果做质控,结果过滤掉了90%的好细胞——血泪教训。
元数据:给数据贴标签
表达矩阵只告诉你哪个基因在哪个细胞里有多少。但你还想知道:
- 这个细胞来自哪个样本?哪个批次?
- 这个细胞是活细胞还是空液滴?
- 这个细胞经过了哪些质控指标?
- 这个基因是哪个物种的?是蛋白编码还是lncRNA?
这些信息就叫元数据。元数据分为两类:
- 细胞元数据(cell-level metadata):每个细胞一条记录,列包括barcode、样本ID、批次、FACS门控、质控指标(总UMI、基因数、线粒体比例等)。
- 基因元数据(gene-level metadata):每个基因一条记录,列包括gene symbol、Ensembl ID、基因类型、染色体位置等。
在实际项目中,我建议你养成一个习惯:每做一步处理,都在元数据里加上对应的时间戳和参数。比如“这一步用了min_genes=200过滤”,直接写到元数据的一个列里。这样几个月后你看回代码,还能知道当时怎么想的。
常见存储格式对比
单细胞数据格式五花八门,你至少得认识下面这几种:
| 格式 | 典型文件 | 包含内容 | 谁在用 | 我的评价 |
|---|---|---|---|---|
| 10X自带格式 | matrix.mtx, barcodes.tsv, features.tsv |
稀疏表达矩阵 + 细胞barcode + 基因信息 | Cell Ranger输出 | 最原始,推荐作为存档格式 |
| HDF5 (h5) | filtered_feature_bc_matrix.h5 |
同一个内容但打包成一个文件 | 10X官方、HDF5爱好 | 占用空间小,但需要额外库读取 |
| SingleCellExperiment | .rds 或内存对象 |
表达矩阵 + 细胞元数据 + 基因元数据 + 降维结果 | scran/scater用户 | scran全家桶的天然容器,我所有项目都用它 |
| Seurat对象 | .rds |
类似SCE,但数据结构不同 | Seurat用户 | 可用as.SingleCellExperiment()转换 |
| H5AD (AnnData) | .h5ad |
Python版单细胞对象 | Scanpy用户 | 与R互转需要zellkonverter或sceasy |
我的建议:只要项目允许,统一用SingleCellExperiment对象做内部存储。它结构清晰,能同时装下counts矩阵、logcounts矩阵、元数据、降维坐标、甚至差异表达结果。scran的质控、归一化函数也直接操作这个对象。如果你从10X或Seurat开始,记得先转成SCE再跑scran的流程。
从文件到SCE对象:快速上手代码
假设你刚跑完Cell Ranger,拿到了filtered_feature_bc_matrix目录。用下面的代码就能读进来:
library(DropletUtils)
library(SingleCellExperiment)
# 从三个文件读入
sce <- read10xCounts("filtered_feature_bc_matrix/")
# 返回一个SingleCellExperiment对象
# 其中 counts(sce) 就是原始UMI矩阵
# colData(sce) 包含barcode
# rowData(sce) 包含基因名、ID、类型
# 你也可以从HDF5读
sce_h5 <- read10xCounts("filtered_feature_bc_matrix.h5")
如果要转换Seurat对象:
library(Seurat)
library(scater)
seurat_obj <- readRDS("my_seurat.rds")
sce <- as.SingleCellExperiment(seurat_obj)
# 默认会把counts和normalized数据都带过来
关于稀疏矩阵:单细胞数据90%以上的数值是0,所以存储时都用稀疏格式(dgCMatrix)。scran内部处理也是基于稀疏矩阵的,不用担心内存。我自己处理过20万个细胞的表达矩阵,SCE对象在内存中也就几个GB,完全可行。
知识体系总览
下面这张图概括了单细胞原始数据到标准分析对象的链路:
记一个检查清单:每次拿到新的单细胞数据,先跑下面几行:
# 检查表达矩阵
dim(counts(sce)) # 基因数 × 细胞数
class(counts(sce)) # 应该是 dgCMatrix 或其他稀疏格式
head(counts(sce)[,1:5]) # 看一下第一行前5个细胞的值
# 检查元数据
colnames(colData(sce)) # 细胞元数据列名
rowData(sce)$Symbol # 记得确认基因名列是否存在