质控阈值选择策略:找到那条「刚刚好」的过滤线
我刚开始做单细胞分析时,最头疼的其实不是整合,而是设阈值。
设松了,一堆低质量细胞混进来。设严了,好的细胞也被干掉。说白了,你手里的过滤参数决定了后续所有结论的可信度。今天就聊聊,怎么科学地找到那条「刚刚好」的过滤线。
阈值选不对,后果是什么?
过滤不足和过度过滤,是两个极端。我见过一个项目,线粒体阈值设到 30%,结果聚类里全是「线粒体高表达」的假群。另一个项目,基因数砍到 200 以下,结果稀有细胞类型直接没了。
两条路都走不通。你需要一个「可重复、可解释」的阈值策略。我个人习惯,从来不用「我记得上次设了 500」这种拍脑袋方式。
策略一:基于分布曲线
这是最直观的方法。你画出每个 QC 指标的分布曲线,找到「拐点」或「尾部」。
- nFeature_RNA:看低端尾部。低于 200-500 的细胞,通常是空液滴或破裂细胞。
- percent.mt:看高端尾部。高于 10-20% 的细胞,可能已死亡。
- nCount_RNA:双端都要看。超高 UMI 可能是细胞团块,低 UMI 可能是空液滴。
具体做法很简单:
# 用中位数加减倍数法
median_genes <- median(sce$nFeature_RNA)
mad_genes <- mad(sce$nFeature_RNA)
lower_bound <- median_genes - 3 * mad_genes
upper_bound <- median_genes + 3 * mad_genes
核心逻辑:中位数稳健,受少数极端值影响小。MAD(中位数绝对偏差)比标准差更抗离群值。
我在项目里常用 3 倍 MAD。为什么?你想想看,对于正态分布的尾巴,3 倍标准差已经覆盖了 99.7% 的数据。单细胞数据虽然不完全正态,但这个尺度很实用。
如果数据量小(< 1000 细胞),我可能会放宽到 2.5 倍 MAD。数据量大时,4 倍 MAD 也没问题。
对了,别忘了做一件事情——可视化确认:
# 画个小提琴图+异常值标记
plotColData(sce, x = "Sample", y = "nFeature_RNA", colour_by = "is_outlier") +
geom_hline(yintercept = c(lower_bound, upper_bound), linetype = "dashed")
策略二:双峰建模
有时候,QC 特征会出现明显的双峰分布。我遇到过一张数据,nFeature_RNA 在 400 和 1500 各有一个峰。这说明样本里混了两种不同质量的细胞亚群。
双峰建模的核心,就是用混合模型把两个分布拆开:
library(mixtools)
# 对 log10 变换后的基因数建模
log_genes <- log10(sce$nFeature_RNA)
model <- normalmixEM(log_genes, k = 2)
# 提取两个分布的均值和标准差
mu1 <- model$mu[1]; mu2 <- model$mu[2]
sd1 <- model$sigma[1]; sd2 <- model$sigma[2]
怎么选阈值?
- 低质量峰:取均值 + 2SD 作为上限
- 高质量峰:取均值 - 2SD 作为下限
- 两个边界之间的细胞,可能就是你需要保留的
小技巧:如果两个峰重叠太多,说明细胞质量在连续过渡。这时用分布曲线法更合适,双峰建模反而不灵。
策略三:经验阈值——不是随便拍
有些文献会推荐固定值。比如 gene 数 > 500,线粒体比例 < 20%。这些值有用,但容易误导。
为什么不能直接套用?
- 10x v2 和 v3 的基因检出率不一样
- 不同组织类型的线粒体基线不同(肝细胞本身线粒体比例就高)
- 冷冻样本 vs 新鲜样本的 UMI 分布差异明显
我习惯把经验阈值当「安全网」,而不是「决定因素」。先看数据分布,再用经验值做个交叉验证。
| QC 特征 | 常见经验范围 | 适用场景 | 谨慎使用场景 |
|---|---|---|---|
| nFeature_RNA | 200 - 5000 | PBMC、组织解离样本 | 神经元、肝细胞(基因数天然高) |
| percent.mt | 5% - 20% | 普通培养细胞 | 肝细胞、心肌细胞(线粒体丰富) |
| percent.ribo | 5% - 40% | 大多数样本 | 代谢活跃的组织(如肠道) |
| nCount_RNA | 1000 - 50000 | 标准 10x 实验 | 超高通量 / 低深度实验 |
避坑指南:我曾经踩过的三个坑
第一个坑,我设了过分严格的基因数阈值。那次是分析心脏组织,心肌细胞的基因数天然比免疫细胞高很多。结果所有心肌细胞都被过滤掉了。
第二个坑,线粒体阈值设太宽松。我遇到一个样本,线粒体比例在 15%-18% 的细胞其实已经快死了,但我按经验值设到 20%。后面发现那一群全部是应激相关基因高表达。
第三个坑,忽略批次间的 QC 差异。同一个实验做了两批,第一批 median gene 数 800,第二批只有 400。我没分批次调阈值,结果第二批基本被全丢了。
重要提醒:永远不要用一个全局阈值处理所有样本。每个样本的 QC 特征都不同,分样本画分布、分样本设阈值,才是正解。
多指标联合过滤,事半功倍
单独看 nFeature_RNA 或 percent.mt 都可能误判。但把两个指标一结合,准确率就上来了。
举个例子:一个细胞基因数很高(>3000),但线粒体比例只有 5%。这很可能是双胞。
另一个细胞基因数很低(<300),但线粒体比例也不高。这可能是空液滴,也可能是低质量细胞。
我惯用的联合过滤策略:
# 定义高质量细胞
sce_high_quality <- sce[, sce$nFeature_RNA > 500 &
sce$nFeature_RNA < 5000 &
sce$percent.mt < 15 &
sce$percent.ribo < 40 &
sce$nCount_RNA < 30000]
当然,这些值我不会一次性定死。我会先画出一个四维的 QC 分布图(用 plotColData + 分面),找到数据本身的自然边界,再微调。
别忘了差异基因过滤
还有一个比较容易忽略的点:线粒体基因、核糖体基因、血红蛋白基因这些,如果你在整合前没去掉,它们会在聚类时形成伪群。
我一般会在 QC 阶段就把它们挑出来过滤:
# 去掉线粒体、核糖体、血红蛋白基因
mito_genes <- grep("^MT-", rownames(sce), value = TRUE)
ribo_genes <- grep("^RP[SL]", rownames(sce), value = TRUE)
hb_genes <- grep("^HB[^(P)]", rownames(sce), value = TRUE)
genes_remove <- unique(c(mito_genes, ribo_genes, hb_genes))
sce <- sce[!rownames(sce) %in% genes_remove, ]
我的习惯:线粒体基因和核糖体基因不是不保留,而是不参与标准化和聚类。它们在 QC 阶段用来评估细胞质量就够了。保留它们会在后续整合时引入技术噪声。
一句话总结阈值选择的核心逻辑
没有万能阈值。但你有三个工具:
- 分布曲线法:最快、最直观,适合大多数场景
- 双峰建模:适合 QC 特征明显分裂的情况
- 经验阈值:做安全垫,别当铁律
三者结合,再配合可视化确认,基本不会踩大坑。过滤不是目的,保留真正的细胞才是。你做得越仔细,后面的整合就越省心。