参数 min.pct:最小表达比例——别让低表达基因浑水摸鱼

好,咱们接着聊参数。这一章我讲 min.pct,全名叫“最小表达比例”。听名字你就知道,它是用来卡基因表达广度的。说白了,就是一个基因至少要在多大比例的细胞里检测到,才让它参与后续的标记基因筛选。

我刚开始用 Seurat 的时候,其实没太在意这个参数。想着“反正我数据质量好,全跑一边呗”。结果跑出来的结果一堆稀奇古怪的基因——比如某个只在 0.3% 细胞里出现的线粒体假基因,居然被标记成了某个 cluster 的“特异基因”。你说这能信吗?后来才明白,min.pct 就是干这个的——拦住那些因为零星表达就被统计学吹上天的“虚假英雄”。

参数本质:两种比例都要算

你仔细看 Seurat 文档会发现,min.pct 不是一个简单的单边比例。它其实是两侧同时看:

  • 表达比例:在某个 cluster 中,检测到该基因的细胞数 ÷ 该 cluster 总细胞数。
  • 默认值 0.1,意味着两边 cluster 里,至少有一边的表达比例超过 10%,否则直接跳过这个基因。

等等,这里有个易错点——我见过不少同事以为“要两边都超过 10%”。不对!是任意一边超过 10% 就算通过。为什么这样设计?你想,如果一个基因在 cluster A 里 80% 都表达,在 cluster B 里只有 2% 表达,那它仍然是一个很好的标记候选(高特异度)。Seurat 不会因为 B 侧比例低就把它扔了,而是保留它让你自己去判断。

min.pct 取值 过滤强度 典型适用场景 常见后果
0(不过滤) 极低 全基因扫描、探索性分析 噪声基因多,结果表太长
0.05 低 稀有细胞群、低丰度基因 保留少量珍贵标记,但假阳性上升
0.1(默认) 中等 常规数据、10x 标准流程 平衡灵敏度与特异性
0.25 较高 高深度测序、细胞群大 遗漏稀有事件,结果简洁
0.5 高 强制高表达基因 只留下广谱高表达基因

什么情况下你需要调整 min.pct?

我个人的经验是,除非你对数据有特殊理解,否则不要轻易改默认值 0.1。但工作中肯定会遇到需要调的情况,我给你列三个最常见的:

  1. 稀有细胞亚群:如果你有一个 cluster 只占全数据的 0.5%,那它里面的大部分基因可能总表达细胞数就很少。此时 min.pct = 0.1 可能过滤掉太多真标记。我建议降到 0.05 甚至 0.02,同时配合 min.diff.pct 或者手动检查表达量。我在做 neuron 亚型分析时就遇到过——一个 GABA 能中间神经元亚群只有不到 100 个细胞,降了 min.pct 才找到 GAD2 这个真命天子。
  2. 高背景噪声数据:有些数据(比如 FFPE 样本)背景表达高,很多低质量基因到处都是。提高 min.pct 到 0.2 或 0.3 可以快速砍掉假阳性。我记得有一次处理小鼠肝脏数据,线粒体基因几乎每个细胞都有微弱信号,调高 min.pct 后标记结果干净了不止一倍。
  3. 超精细分辨率:当你把 cluster 分辨率设得特别高(比如 resolution = 2),接近技术噪声的分割。此时很多“差异基因”只是随机波动。适当提高 min.pct 到 0.15 甚至 0.2,能让结果更可信。

我曾经踩过的坑:有一次我用 min.pct = 0 跑了一个 10x 样本,出来 8000 多个标记基因。兴冲冲拿去验证,结果 qPCR 验证了 20 个,只有 2 个有差异。从那以后我明白了一个道理:min.pct 不是可选配菜,而是主菜。不设过滤就等于打开了垃圾箱。

实际代码怎么写?

你调用 FindAllMarkers 的时候,直接指定参数就行:

# 默认值 0.1
markers_default <- FindAllMarkers(pbmc, only.pos = TRUE, min.pct = 0.1)

# 降到 0.05 以捕获稀有信号
markers_rare <- FindAllMarkers(pbmc, only.pos = TRUE, min.pct = 0.05)

# 提高过滤,只留高表达基因
markers_highconf <- FindAllMarkers(pbmc, only.pos = TRUE, min.pct = 0.25)

# 极端:关闭过滤(不推荐)
markers_all <- FindAllMarkers(pbmc, only.pos = TRUE, min.pct = 0)

你想想看,这三个参数的变化,就能让结果列表从 200 行变成 2000 行。所以每次跑之前,先问问自己:我到底要找什么样的标记? 是广谱的、高可信的,还是不放过任何一个可能的线索?

小技巧:如果你不确定选哪个 min.pct,可以先用默认 0.1 跑一遍,然后观察结果中基因的 p_val_adj 分布。如果大部分基因 p 值很差(比如接近 1),或者都是低表达量基因,那考虑提高 min.pct。反之,如果找不到几个差异基因,就降低它。

核心逻辑:一张图看懂 min.pct 过滤流程

下面这个 SVG 流程图,对应的是 Seurat 在计算差异前做的基因预筛选。它不是唯一步骤,但确实是第一道关卡。你看一眼就明白:

min.pct 过滤基因流程 所有检测到的基因 该基因在目标 cluster 中 表达比例是否 >= min.pct? 过滤(丢弃) 保留候选 进入实际差异检验 (Wilcoxon / t-test / …) 输出候选标记基因 注意: 此过滤对两侧 cluster 分别独立进行, 满足一侧即保留。

你看,这个流程其实很朴素——先过滤,再计算。很多初学者以为是跑完统计检验再筛选,那样会产生大量不必要的运算,而且假阳性会污染后续的 heatmap 和富集分析。Seurat 在设计上很聪明,先把那些明显不满足表达广度的基因踢出去,节省时间也减少噪声。

配合 min.diff.pct 使用才是完整方案

嗯,这里还要补充一点。我经常看到有人只调 min.pct,却忽略了另一个兄弟参数 min.diff.pct。你可以这么理解:

  • min.pct:控制基因至少在一边有多大的表达比例。
  • min.diff.pct:控制两边表达比例的差值至少有多大。

假设 min.pct = 0.1,一个基因在 cluster A 里 12% 表达,在 cluster B 里 11% 表达。虽然两边都过了 10%,但两者只差 1%,这几乎不可能是标记基因。此时如果设置 min.diff.pct = 0.2(即两侧比例差 ≥ 20%),这种基因就会被砍掉。我习惯把两个参数一起调:

# 同时使用两个参数
markers_strict <- FindAllMarkers(pbmc, 
                                 min.pct = 0.1, 
                                 min.diff.pct = 0.2)

说实话,大部分情况下默认组合(min.pct = 0.1,不设 min.diff.pct)就够用。但如果你想要更干净的结果,加上 min.diff.pct 是个很实用的习惯。

个人经验总结:min.pct 是 Seurat 所有参数里最容易被新手忽视、同时最影响结果质量的一个。我每次做新的数据集,第一件事就是先跑一个 min.pct = 0.05~0.2 的快速对比,看看总标记数的变化趋势。如果从 0.05 到 0.1 时标记数量断崖式下降,说明有很多低表达噪声;如果变化很小,说明数据本身表达谱比较干净。这个小技巧能帮你快速感知数据的“脾气”。