2. edgeR介绍:历史背景、核心算法与适用条件
聊差异表达分析,edgeR 是绕不开的坎儿。我记得 2010 年前后,RNA-seq 刚开始火起来,大家还在为「怎么处理那些几百万条的 reads 数」发愁。那时候有个叫 Mark Robinson 的哥们儿写了一篇论文——对,就是 2010 年那篇《edgeR: a Bioconductor package for differential expression analysis of digital gene expression data》。
从那时起,edgeR 就成了处理计数数据的标杆工具。说白了,它最核心的思路就四个字:小样本、多重复。你想想看,生物学重复就那么 2-3 个,传统统计方法根本不敢这么玩。edgeR 却用了一个巧妙的办法——借力打力,借用所有基因的信息来稳定单个基因的方差估计。
2.1 历史背景:为什么会有 edgeR?
在 edgeR 诞生之前,做差异表达主要靠 Microarray 的方法,比如 limma。但 RNA-seq 的数据是离散的整数计数,不是连续的荧光信号强度。强行用 limma 去处理,结果往往很惨——假阳性满天飞。
我刚开始接触生物信息学的时候,导师扔给我一个 RNA-seq 数据集说:「你跑个差异表达看看」。我拿 limma 一跑,出来几百个显著基因。高兴了没两天,一验证,全是假的。那时候我才意识到:计数数据的分布假设完全不一样。
edgeR 的核心贡献在于:它把「负二项分布」引入了这个领域。这背后有个特别直观的物理意义——你想想,同一个基因在同一个条件下测三次,每次的计数不可能一模一样。这种波动既有「技术重复」带来的,也有「生物学重复」带来的。泊松分布只能描述前者,而负二项分布多了一个分散参数,刚好能把生物学变异也包进来。
2.2 核心算法:edgeR 到底干了什么?
你以为 edgeR 只是跑个负二项检验就完事了?不是的,它的算法管线其实相当有层次感。我个人习惯把它分成三个关键步骤:
- 计算归一化因子(TMM 归一化)
- 估计分散参数(经验贝叶斯 + 条件似然)
- 精确检验 / 似然比检验(判断基因是否差异表达)
下面这张图我把它们串起来了,你看完就明白整体的脉络:
2.2.1 精确检验与经验贝叶斯
edgeR 最初的版本用的是「精确检验」。这名字听着吓人,其实原理很简单——既然我们有负二项分布的假设,那就直接算这个基因在两组间的「计数差异」是随机出现的概率。
为什么需要经验贝叶斯?
因为样本数太少。我举个例子:假设一个基因在对照组测了 3 个样本,在实验组测了 3 个样本。只用这 6 个数据点去估计分散参数,结果会很离谱。edgeR 的做法是:用所有基因的计数信息去「挤压(shrink)」单个基因的分散估计——这就是经验贝叶斯的精髓。
2.2.2 基于条件似然的分散估计
到了 edgeR 的后期版本(3.x 以后),加入了 estimateDisp() 函数。这里用了一个叫「条件似然」的技术。你想想看,每个基因的计数总和是已知的,在这个总和的约束下,分散参数的估计可以被大大稳定化。
嗯,这里要注意一个细节:edgeR 实际上是针对基因的「均值-方差关系」做了半参数建模。它假设分散参数随着基因表达水平的变化而变化——低表达的基因本身波动就大,高表达的基因波动就小。这个假设在实践中是非常合理的。
# edgeR 核心估计步骤
library(edgeR)
y <- DGEList(counts = count_matrix, group = group_vector)
y <- calcNormFactors(y) # TMM 归一化
y <- estimateDisp(y) # 分散估计(经验贝叶斯)
# 检验差异
et <- exactTest(y)
topTags(et, n = 10)
2.2.3 TMM 归一化
为什么要单独拎出 TMM 来说?因为在 RNA-seq 分析中,归一化是决定成败的第一步。传统的 RPM/CPM 归一化忽略了「组成效应」——如果一个样本中很多高表达基因都被上调了,那低表达基因的 RPM 会被错误地压低。
TMM 的思路其实很朴素:在所有基因中找一个「参考样本」,然后对每个样本计算一个缩放因子。这个缩放因子不是简单的总 reads 比值,而是去掉高表达和低表达的基因后,用中间的基因来估算。
我曾经帮一个临床合作者排查差异表达结果不一致的问题。他拿了两批数据,一批用 RPKM 归一化,一批用 TMM 归一化,结果 top 差异基因几乎没有重叠。我建议他统一用 edgeR 的 TMM 归一化再跑一次,这次结果就对上了。
——归一化的选择真的不能随便。
2.3 适用条件:什么场景选 edgeR?
没有万能的工具。edgeR 有自己明确的能力边界。我用一张表把它的适宜条件和雷区分清楚:
| 维度 | edgeR 擅长 | edgeR 不太擅长 |
|---|---|---|
| 样本量 | 每组 2-10 个样本 | 每组大于 50 个样本(有更高效的模型) |
| 数据特性 | 离散计数(RNA-seq、CAGE、ChIP-seq 等) | 连续数据(Microarray、蛋白质组学强度值) |
| 实验设计 | 两组比较、简单因子设计 | 复杂多因子交互效应(此时用 edgeR 的 GLM 也可) |
| 基因筛选 | 筛选少量高置信差异基因 | 需要排序所有基因做 GSEA 时(DESeq2 的效应量估计更稳定) |
| 数据量 | 万级基因轻松跑 | 基因组级别(太大时内存消耗需要留意) |
从我个人的项目经验来看,我特别喜欢在以下场景用 edgeR:
- 早期探索分析: 想快速跑一遍,看看哪些基因有信号。edgeR 的 exactTest 跑一次也就几秒钟。
- 样本质量参差不齐: 有些样本的测序深度差很多。TMM 归一化能很好地处理这种深度不一致。
- 只关注 top 差异基因: 如果需要 p 值最显著的前几十个基因做 PCR 验证,edgeR 的假阳性控制是比较严格的。
2.4 一个避坑指南
我曾经接手过一个项目,上游给的 count 矩阵里有很多基因只有 1-2 个总计数。直接丢进 edgeR 跑,那些基因全变成了显著差异——因为极端低表达的基因,一分之一的波动都会被放大。后来我在预处理阶段加了一个简单过滤:必须至少在 min.count > 10 的样本中才能保留。从此假阳性就下来了。
# 我常用的过滤标准
keep <- rowSums(cpm(y) > 1) >= 2
y <- y[keep, , keep.lib.sizes = FALSE]
另外,别忽略 prior.count 参数。edgeR 默认加了 0.125 的 prior count,就是为了防止零计数导致的对数倍数变化炸裂。这个参数我一般不动,但在处理某些超稀疏数据时,稍微加大到 0.5 反而更稳定。