多重检验校正:Bonferroni、FDR 怎么选
跟完这篇,您能弄清 Bonferroni 与 BH/FDR 到底控的是什么、组学分析该用哪个,并用一行 p.adjust 把成千上万个原始 p 值校正成 padj,再按合适阈值筛出一份经得起追问的显著结果表。
为什么几万次检验必须校正
单独看一次检验,p<0.05 的意思是:如果这个基因其实没变化,纯靠随机也只有 5% 的概率被误判成「显著」。听起来很稳。问题出在「一次」变成「几万次」。
假设您做一次转录组差异分析,一口气对 20000 个基因各做一次检验。就算这 20000 个基因全都没有真实变化,在 p<0.05 这条线下,您预期仍会有约 0.05 × 20000 = 1000 个基因被判成显著——这 1000 个全是假阳性。
这就是多重检验的核心麻烦:检验做得越多,纯靠运气蒙对的假阳性就越多。您筛出来的「差异基因列表」里,可能一大半是噪声。多重检验校正,就是在这么多次检验之后,把「显著」这条线抬高、把假阳性压回可控范围的一套办法。
一句话记住:做一次检验,看原始 p 值就够了;做成百上千次检验,必须校正后再下结论。
先分清两种错误率:FWER 与 FDR
不同校正方法控的不是同一件事。选方法前,先分清两个概念,后面就不会用错。
| 概念 | 全称 | 控的是什么 | 代表方法 |
|---|---|---|---|
| FWER | 整体错误率(family-wise error rate) | 至少出现一个假阳性的概率 | Bonferroni、Holm |
| FDR | 假发现率(false discovery rate) | 在您判为显著的那批结果里,假阳性所占的比例 | BH(Benjamini-Hochberg) |
差别在于严格程度和您能接受的代价:
- 控 FWER,是要求「几乎不容许犯一个错」。适合结论要非常保守、一个假阳性都代价很大的场景,代价是容易把真信号也一起漏掉。
- 控 FDR,是允许结果里混入一小比例假阳性,换取抓到更多真信号。组学里通常是先筛出一批候选基因再往下验证,能接受「这 200 个里大概 10 个是假的」,所以 FDR 更实用。
Bonferroni 与 BH 各控什么、谁更严
组学里真正要在这两种之间做选择,先看它们各自怎么算。
Bonferroni(控 FWER,最严):把阈值 α 除以检验次数 m,只有 p < α / m 才算显著。做 20000 次检验、想把整体 α 控在 0.05,单个基因门槛就是 0.05 / 20000 = 0.0000025。等价写法是把每个 p 值乘以 m(超过 1 截成 1)再和 0.05 比。优点是不管检验之间相不相关都成立、逻辑直白;缺点是检验一多门槛低到离谱,极容易连真差异一起漏掉,几万个基因往往只剩个位数过线。
BH / Benjamini-Hochberg(控 FDR,组学最常用):把原始 p 值从小到大排好,找到满足 p(k) ≤ (k / m) × α 的最大的 k,把它及之前的全判为显著。您不用手排,p.adjust(method = "BH") 会直接给出校正值(常被叫作 q 值 / padj)。它最直观的读法是:按 padj<0.05 筛出 200 个基因,这 200 个里预期约 5%(约 10 个)是假阳性。
用一个 5 个 p 值的小例子,一眼看清三种线的松紧(Bonferroni 乘 5 封顶 1,BH 由 p.adjust 给出):
| 排序 k | 原始 p | Bonferroni | BH(padj) |
|---|---|---|---|
| 1 | 0.001 | 0.005 | 0.005 |
| 2 | 0.008 | 0.040 | 0.020 |
| 3 | 0.020 | 0.100 | 0.033 |
| 4 | 0.040 | 0.200 | 0.050 |
| 5 | 0.300 | 1.000 | 0.300 |
在 padj<0.05 这条线下:原始 p 会放行 4 个,BH 放行 3 个,Bonferroni 只放行 2 个。第 4 个基因原始 p 是 0.04(本来过线),BH 把它抬到刚好 0.05 而落选——这正是校正在起作用。同一批数据,严格程度从松到严是:原始 p → BH → Bonferroni。
小结成一句选择:
- 几十次以内的关键检验、要结论很稳 → Bonferroni(或更常用的改进版
Holm,同控 FWER 但功效更高)。 - 成百上千次的组学筛选 → BH。DESeq2、edgeR、limma、Seurat 等主流工具输出的
padj/adj.P.Val列,默认就是 BH 校正过的,别当成原始 p 值再校正一遍。 - 担心检验之间存在任意方向的强相关,可换更保守的
BY(Benjamini-Yekutieli),代价是更严;多数常规组学用 BH 即可。
动手:一行 p.adjust 校正并筛显著
R 里做校正只需一个函数 p.adjust():喂进一批原始 p 值,拿回校正后的值。照下面四步走:
- 取出这一批检验的原始 p 值,凑成一个向量。
- 调
p.adjust()做校正,选好method。 - 按阈值筛出显著项。
- 存成表,方法和阈值一并记清楚。
# 第 1 步:pvals 是一批原始 p 值(比如几万个基因各做一次检验得到的向量)
pvals <- res$pvalue
# 第 2 步:一行校正
p_bonf <- p.adjust(pvals, method = "bonferroni") # 控整体错误率,最严
p_bh <- p.adjust(pvals, method = "BH") # 控假发现比例,写 "fdr" 完全等价
# 第 3 步:按 FDR < 0.05 筛出显著项(0.05 是常用惯例,按数据与阶段调,非铁律)
sig_idx <- which(p_bh < 0.05)
# 第 4 步:整理成结果表并落盘
out <- data.frame(gene = names(pvals), p = pvals, padj = p_bh)
out <- out[order(out$padj), ] # 按 padj 从小到大排
write.csv(out, "results_fdr.csv", row.names = FALSE)
method 常用取值:"bonferroni"、"holm"、"BH"(即 "fdr")、"BY"、"none"。
如果您用 DESeq2 跑差异分析,results() 返回的 padj 列已经是 BH 校正好的,跳过前两步直接筛即可:
res <- results(dds) # res$padj 已是 BH 校正后的值
# padj<0.05 且 |log2FC|>1 是常用惯例(非铁律,按数据与研究目的调)
sig <- subset(res, padj < 0.05 & abs(log2FoldChange) > 1)
一个高频坑:
p.adjust要把同一批检验的 p 值一次性全喂进去才对——它需要知道总检验数 m。逐个基因分别调用、或只把「看起来显著的那几个」拿去校正,都会算错。
校正前先看一眼 p 值直方图
在信任任何 FDR 结果之前,养成一个习惯:把原始 p 值画成直方图看一眼。BH 的假发现率保证,建立在 p 值分布行为正常的前提上;分布不对,padj 也不可信。
hist(pvals, breaks = 50, main = "raw p-value 分布", xlab = "p")
对照下面这张表读形状:
| 直方图形状 | 通常意味着 | 该怎么办 |
|---|---|---|
| 靠近 0 有尖峰、其余大致平坦 | 健康:有真信号 + 一堆无效假设 | 正常,可放心用 BH |
| 完全平坦、0 附近没有堆积 | 大概率没有真差异 | 别硬筛,回头看分组与效应量 |
| 靠近 1 一侧反而堆高 | 偏保守,检验或模型可能设错 | 检查模型设定、方差估计 |
| 两端都堆、中间空(双峰) | 常见于批次效应等系统偏差没处理干净 | 先做批次校正再重跑 |
期望看到的是第一行那种「0 处尖峰 + 其余平坦」。出现后面几种形状,先别急着解读 padj,回去查数据和模型。
padj 阈值怎么定
padj < 0.05 是行业惯例,不是铁律。0.05 没有任何生物学或数学上的必然性,只是长期沿用下来的默认线。按研究阶段和风险,可调松或调严:
| 场景 | 常用 padj 阈值 | 理由 |
|---|---|---|
| 探索 / 发现阶段,后续还会实验验证 | 0.1(有时更松) | 宁可多留候选,漏掉真信号代价更大 |
| 常规差异分析、投稿主图 | 0.05 | 通用惯例,审稿人默认接受 |
| 确证性结论、假阳性代价大 | 0.01 或更严 | 结论要更稳,容错更低 |
几条务实建议:
- 阈值要在看结果之前定好,别根据「哪个阈值能让我的目标基因过线」反过来挑,那会引入偏倚。
- 写论文时把方法和阈值写清楚:用了哪种校正(如 BH)、padj 卡在多少、差异倍数(如 |log2FC|>1)卡在多少。这几个数直接决定您的差异基因列表,必须可复现。
- 差异分析里,padj 阈值常和 log2FoldChange 阈值一起用:前者管「统计上是否可信」,后者管「变化幅度是否够大」,两个维度互补,别只卡一个。
什么时候可以不校正
校正不是越多越好,也不是任何情况都要上。以下情形通常不需要、或要谨慎处理:
- 只有一个预先设定的主假设:整个研究就围绕一个事先定好的假设做一次检验,看原始 p 值即可,没有「多重」问题。
- 纯描述、不下显著性结论:只是描述数据、画分布、报告效应量而不宣称「显著」时,校正意义不大。但一旦您开始从一堆结果里挑「显著的」来讲故事,多重检验的问题就回来了。
- 校正的范围(family)要讲清楚:校正针对「一族相关的检验」做。哪些检验算同一族,需按研究设计事先界定,而不是把八竿子打不着的检验硬凑在一起除以一个大 m。
诚实说,「一个主假设不必校正」在统计学界仍有争论,尤其当您实际上偷偷看了很多结果才挑出这一个来报告时(这叫 p-hacking)。最稳妥的做法:事先想清楚要检验哪些假设、同一族有多少个,再决定校正与否——而不是跑完看到 p 值再补决定。
在平台上跑一遍
不想写 p.adjust、也不想手动排 p 值,可以在「百沐一下」的智能统计里零代码完成:上传数据表,用一句话说清目的(比如「比较两组的差异表达,做多重检验校正,按 FDR<0.05 筛显著基因」),它会自动选好方法、跑出校正后的 padj、给出显著列表、画好 p 值直方图,并附上可复现的 R 代码。跑完还能追问「换成 Bonferroni 再看一遍」「阈值放宽到 0.1」,无需从头再来。
入口与用法见 智能统计使用指南,或直接开始:app.baimuyixia.com。