单细胞数据预处理:从原始计数到可信细胞群体
预处理这一步,说白了就是给原始数据「洗澡换衣服」。我见过不少项目,数据刚拿到手就急着跑CellChat,结果出来的通讯网络全是噪声。嗯,这一步省不得,省了后面全是坑。
我个人习惯把预处理拆成三个核心动作:细胞质控(QC)、doublet检测、过滤低质量细胞。下面一个一个说。
细胞质控(QC)—— 源头把关
QC的核心是找出那些「要死不活」的细胞。你想想看,一个细胞如果线粒体基因占比飙到20%以上,多半是膜破了,内容物泄露。这种细胞在通讯分析里会虚假地高表达某些配体受体,把结果带歪。
我一般用三个指标做初步筛选:
- nCount_RNA:每个细胞的UMI总数。太低说明细胞空载,太高可能是doublet。
- nFeature_RNA:检测到的基因数。太低可能是死细胞,太高也是doublet嫌疑。
- percent.mt:线粒体基因占比。通常 >10-15% 就要警惕。
这里贴一段常用的Seurat QC代码,我项目里几乎每回都用:
# 计算线粒体基因比例
sce[["percent.mt"]] <- PercentageFeatureSet(sce, pattern = "^MT-")
# 查看分布
VlnPlot(sce, features = c("nCount_RNA", "nFeature_RNA", "percent.mt"))
# 常用过滤阈值(可调整)
sce <- subset(sce,
nCount_RNA > 500 & nCount_RNA < 8000 &
nFeature_RNA > 200 & nFeature_RNA < 3000 &
percent.mt < 15)
我的小建议:阈值不要死搬硬套。不同组织、不同建库方案,分布差很多。先做小提琴图看看数据分布,再定阈值。我曾经在一个脑组织数据里,正常神经元的中位UMI只有800,硬套通用阈值直接干掉了一大半细胞。
| 指标 | 典型下限 | 典型上限 | 备注 |
|---|---|---|---|
| nCount_RNA | 500 | 8000 | 根据文库深度调整 |
| nFeature_RNA | 200 | 3000 | 低基因数可能是空液滴 |
| percent.mt | — | 15% | 组织差异大,肌肉组织可放宽到20% |
doublet检测 —— 揪出「双胞胎」
doublet就是两个细胞被同一个barcode捕获了。在10X数据里,doublet率大概1%每1000个细胞,如果你捕获了8000个细胞,预计有8%左右的doublet。CellChat对细胞身份敏感,doublet会硬塞出假通讯。
我现在主要用两种方法:
- DoubletFinder:基于人工再混入模拟doublet做分类,准确率不错,就是慢。
- scDblFinder:集成在SingleR包链里,快,对大规模数据友好。
坑,一定要填:doublet检测必须在QC过滤之后才能做。为啥?因为死细胞的异常特征会干扰分类器,模型会把死细胞也判成doublet。我曾有一次先用原始数据跑DoubletFinder,结果1/3的细胞被标为doublet……后来发现是大量空液滴的干扰。
下面是用scDblFinder的示例:
library(scDblFinder)
sce <- scDblFinder(sce, samples = "sample_id")
# 查看doublet比例
table(sce$scDblFinder.class)
# 过滤掉 doublet
sce <- sce[, sce$scDblFinder.class == "singlet"]
过滤低质量细胞 —— 清除干扰
QC和doublet检测做完了,还不能急着收工。我习惯再加一道「手动过滤」:把那些明明存活率正常、但基因表达谱极其稀疏的细胞也去掉(比如nFeature_RNA极低但UMI却很高的异常样本)。说白了,就是做一次最后的质检。
另外,如果你的数据来自多个批次,强烈建议在合并之前先单独对每个样本做QC和doublet检测。否则批次效应会骗过doublet检测器,把批次差异当doublet。
# 按样本循环处理
for (s in unique(sce$sample_id)) {
sub <- sce[, sce$sample_id == s]
sub <- scDblFinder(sub)
sub$scDblFinder.class[is.na(sub$scDblFinder.class)] <- "singlet"
sce$scDblFinder.class[sce$sample_id == s] <- sub$scDblFinder.class
}
# 最后一步:过滤掉 QC flag 和 doublet
sce <- sce[, sce$scDblFinder.class == "singlet" &
sce$nFeature_RNA > 200 &
sce$nCount_RNA > 500 &
sce$percent.mt < 15]
核心原则:CellChat的输入是干净的高质量细胞矩阵。宁可严格一点流失少量细胞,也要把假通讯扼杀在摇篮里。
整个预处理流程可以用下面这张图串起来:
预处理做扎实了,后面CellChat的批次效应校正才能更有底气。别嫌麻烦——这一步花的时间,将来都会在通讯网络的准确性上补回来。