第3章 p值校正常见方法概述
聊到多重假设检验,我第一个想到的场景就是RNA-seq差异表达分析。
你一张芯片上测了两万个基因,每个基因都要做个假设检验,问它到底在实验组和对照组之间有没有差异。
那问题就来了:如果你用p < 0.05作为阈值,每个基因犯假阳性(Type I Error)的概率是5%。
好,两万个基因各测一次,光靠概率算一下,就有1000个基因是“假阳性”——白高兴一场。
所以我们要做p值校正。
说白了,就是把原始的p值“打个补丁”,让整体出错的概率降回可控范围。
这个行当里主流的方法,我归纳下来就四个:Bonferroni、Holm、Benjamini-Hochberg 以及 Storey's q-value。
后面两个是控制FDR的,前面两个控制FWER。今天我把它们挨个拆开讲清楚。
3.1 Bonferroni 校正
Bonferroni 是最老派的方法,也是我最开始做生信时用的第一个校正方法。
它的逻辑简单粗暴:
- 假设你做了 m 个检验,原始显著水平为 α。
- 那就把每个检验的阈值设为 α/m。
- 只有 p ≤ α/m 的才算显著。
举个例子,m = 20,000 个基因,α = 0.05。
那 Bonferroni 校正后的阈值 = 0.05 / 20000 = 2.5 × 10⁻⁶。
满足这个条件的基因很少,非常少。我当年做癌基因组差异分析时,Bonferroni 校正后只筛出4个基因——我一度以为数据做错了。
缺点:极度保守,容易漏掉真实差异基因(高假阴性)。
后来我建议他们换 BH 校正,立刻找到了关键通路。选方法前一定要评估情况。
3.2 Holm(Holm-Bonferroni)校正
Holm 方法比 Bonferroni 温和一点,它是 Bonferroni 的“逐步增强版”。
我个人习惯把它叫做“逐步向下法”。
先对所有原始 p 值从小到大排序。然后从最小的那个开始,依次判断:
- 最小的 p 值 ≤ α / m?是 → 拒绝(显著)。
- 第二小的 p 值 ≤ α / (m-1)?是 → 拒绝。
- 第三小的 p 值 ≤ α / (m-2)?…
- 一旦遇到不满足的,后面所有都不显著。
你看,阈值在逐步放宽,所以它比 Bonferroni 有更多机会拒绝假说。但依然是控制 FWER 的,还是比较严。
我在一个低表达基因的 pilot 实验中用过它,保持了严格性,又没筛得太惨。
R 里实现也很简单:
p.adjust(p_values, method = "holm")
一行搞定,R 内置的。
3.3 Benjamini-Hochberg (BH) 校正
BH 是我在 RNA-seq 差异分析中最常用的校正方法,没有之一。
它的目标不是控制“至少有一个假阳性的概率”,而是控制假发现率(FDR)。
FDR 是什么?你筛出了 100 个差异基因,里面允许有一定比例的假阳性。比如 5% FDR,就是允许100个里最多5个是假的。
BH 的步骤:
- 把所有 p 值从小到大排序:p₁ ≤ p₂ ≤ … ≤ p(m)。
- 从最大的往回看:
- 找到最大的 i,使得 p(i) ≤ (i / m) × α。
- 拒绝所有排名 ≤ i 的假设。
比起 Bonferroni,BH 更“宽容”——允许有一些假阳性,但整体可控。尤其适用于差异基因数量多、信号不极端强的情况。
你通常会看到这个值被写成 padj,很多 RNA-seq 软件(DESeq2、edgeR)默认输出的就是这个。
# R 语言中
padj <- p.adjust(p_values, method = "BH")
de_genes <- which(padj < 0.05)
我自己的习惯:在做差异基因的初步筛选时,把 raw p 值和 BH 校正后的 padj 一起输出。这样方便判断有多少基因的显著性是真的能被保留下来。
3.4 Storey's q-value
q-value 是 BH 的一种“进阶版”。
它不要求你预设一个 α(比如 0.05),而是从数据本身估计出:我若在接受这个基因时,它属于假阳性的“后验概率”有多高。
通俗地说:
- BH 是固定 FDR 阈值,然后说“padj < 0.05 的都显著”。
- q-value 是直接告诉你“这个 p 值的 q = 0.03,意味着它有 3% 的概率是假阳性”——你可以自己决定要不要收它。
Storey 还引入了 π₀ 的估计。π₀ 代表真正的零假设(无差异)在所有基因中所占的比例。
RNA-seq 数据中,大多数基因其实没差异,所以 π₀ 往往接近 1。利用这个信息,q-value 比 BH 更“锐利”,在某些场景下能多识别出一些真实差异基因。
# R 中 qvalue 包的使用
library(qvalue)
qobj <- qvalue(p_values)
q_values <- qobj$qvalues
# 查看 π₀ 的估计值
print(qobj$pi0)
我曾在只有 3 个生物学重复的数据上用过 q-value,结果 π₀ 估计为 0.6(明显偏小),导致很多 p 值一般大的基因被错误标记为显著。这种情况下,我建议你用 BH 更稳妥。
3.5 四种方法对比
为了方便你快速参考,我整理了一张对比表格:
| 方法 | 控制指标 | 严格程度 | 适合场景 |
|---|---|---|---|
| Bonferroni | FWER | 最严格 | 必须零假阳性的场景(如临床诊断) |
| Holm | FWER | 较严格 | 比 Bonferroni 稍宽松的 FWER 控制 |
| Benjamini-Hochberg (BH) | FDR | 适中 | RNA-seq 差异分析、高通量筛选 |
| Storey's q-value | FDR(含 π₀ 估计) | 最宽松 | 大样本、低信号噪声、探索性分析 |
3.6 方法选择决策流
好了,上面的方法看完,你可能会有点迷糊:到底选哪个?
我不喜欢给死板的“首先其次”,而是习惯用一个决策图来带大家走一遍。
如果你答“不能”,就走左边;答“可以有一定比例”,就走右边。很直接。
3.7 一个小建议
我个人习惯在实际项目中,至少跑两种校正方法做对比。比如:
- 先用 BH 做快速筛选,拿到初步结果。
- 再用 Storey's q-value 做一遍,看多了哪些新基因。
- 如果重点基因列表变化很大,再回原始数据看表达量分布和样本离群情况。
这比随便选一个方法然后埋头往下跑要靠谱得多。你想想看,校正方法选错了,后面富集分析、通路分析都是在“屎上雕花”,毫无意义。
嗯,这一章关于四种主流 p 值校正方法就讲到这里。
理解了它们的区别,你就能在面对不同项目时,做出更合适的选择,不会一上来就拿 Bonferroni 把全部差异基因都给杀了。