4. 基本结果提取:results函数参数(contrast、alpha、lfcThreshold)

跑完 DESeq2 分析,拿到 dds 对象之后,最核心的一步就是提取差异表达结果。

说白了,results() 就是咱们的「数据筛子」。你希望从几万个基因里捞出哪些?这个函数的几个参数直接决定了你的输出长啥样。

我最早用 DESeq2 时,总觉得这步很简单——直接调用 results(dds) 就行。结果后来发现,同一个项目,同事跑出来的基因列表跟我完全不一样……嗯,那才意识到参数设置有多关键。


4.1 contrast 参数:指定你要比较哪两组

contrast 是 results() 里我使用最频繁的参数。

它解决了什么问题?你的实验设计可能有多个分组(比如:Control、Treatment_A、Treatment_B)。你总不能把所有组混在一起比。你需要明确告诉 DESeq2:我要看的是「哪两个组之间的差异」。

contrast 有三种写法:

# 写法一:指定因子名称和比较组(最常用)
results(dds, contrast = c("condition", "Treatment_A", "Control"))

# 写法二:直接给数字索引
results(dds, contrast = c(2, -1, 0, ...))

# 写法三:使用名称表达式(不推荐新手)
results(dds, name = "condition_Treatment_A_vs_Control")
写法 是否推荐 原因
c("因子名", "分子组", "分母组") 强烈推荐 可读性高,不容易搞混
数字索引 谨慎使用 一旦因子水平顺序改变,结果就错
name 表达式 了解即可 自动生成的名称有时很拗口
⚠️ 我曾经踩过的坑: 在 contrast 里写反了分子和分母顺序。结果是我以为上调的基因,实际上是下调的。尤其是 report 截图上,名字叫 "Treatment_vs_Control",log2FC 显示 +2,我以为是处理组上调……后来发现因为我写反了分母,其实它是对照组上调。从那以后,我每次都先跑个 summary() 看一眼方向。

4.2 alpha 参数:调显着性阈值

alpha 这个参数,我建议你理解成一个「截断点」。默认值是 0.1,但你的生物学背景或审稿人可能要求更严格的标准。

# 严格模式(大多数文章用这个)
res_strict <- results(dds, alpha = 0.05)

# 默认模式
res_default <- results(dds, alpha = 0.1)

# 宽松筛选(探索性分析可用)
res_explore <- results(dds, alpha = 0.2)

需要区分两个概念:

  • pvalue:原始统计检验的 p 值
  • padj:多重假设检验校正后的 q 值(BH 方法)

DESeq2 默认用 independent filtering 策略。你设定的 alpha 值会影响这个过滤强度。alpha 越小,过滤掉的基因越多,剩下来的「假阳性风险」越低。

我个人习惯: 在项目早期探索阶段,alpha 设 0.1。写 paper 或者做 downstream 验证时,alpha 降到 0.05。如果遇到样本量特别少(n=2),我甚至会把 alpha 放宽松到 0.2 去捕捉候选基因,但一定要在结论里标注清楚。

4.3 lfcThreshold 参数:不只是看 p 值

很多初学者只盯着 p 值和 padj,觉得 < 0.05 就是差异基因。但做生物信息学久了你就会发现:

统计显着 ≠ 生物学相关

一个基因 padj = 0.0001,但 log2FoldChange 只有 0.2。这种基因,你放在 RNA-seq 里大概率是测序误差,换个平台验证不出来。

lfcThreshold 就是为了解决这个问题而存在的。

# 默认情况:只看 fold change 是否为 0
results(dds, contrast = c("condition", "A", "B"))

# 设置一个 fold change 阈值:只有 |log2FC| > 1 才算有意义
results(dds, contrast = c("condition", "A", "B"),
        lfcThreshold = 1)

设置了 lfcThreshold 后,DESeq2 会执行一个 Wald test,零假设是「真实的 log2FC 的绝对值 ≤ 你设定的阈值」。而不是默认的「真实的 log2FC = 0」。

💡 技巧: 当你用 lfcThreshold 时,results 表里会出现一列叫 svalue(取代 padj)。svalue 表示「这个基因的 log2FC 落在阈值以内的后验概率」。我个人觉得 svalue 在解释时更直观。

4.4 三个参数协同工作

这三个参数不是孤立的。它们配合起来,能让你提取出更高质量的结果。

# 经典组合:严格筛选
res_final <- results(dds,
                     contrast = c("condition", "Treatment", "Control"),
                     alpha = 0.05,
                     lfcThreshold = 1)

这个命令做了三件事:

  1. contrast:指定 Treatment vs Control 的比较
  2. alpha:多重检验校正阈值设为 0.05
  3. lfcThreshold:要求 log2FC 至少大于 1 或小于 -1,才算「生物学上有意义」

为什么要用 lfcThreshold?我遇到过这种情况:某个基因 padj = 0.001,看起来很厉害。但 log2FC = -0.3。在 qPCR 验证时,死活做不出来。后来发现是测序深度不均导致的假象。从那以后,我对那些 fold change 很小的「显着基因」都保持警惕。

4.5 核心逻辑:从原始统计到可解释结果

下面这个图我经常在培训里用,它概括了 results() 函数的三个核心参数分别控制着差异表达分析的哪一层。

results() 参数作用逻辑 DESeq2 统计检验 (dds对象) contrast alpha lfcThreshold 指定比较的组合 控制假阳性率 (FDR) 设定生物学意义门槛 Wald检验 + 独立过滤 + 阈值判断 最终结果表:baseMean, log2FC, pvalue, padj, svalue

你看这个图就会明白:contrast 决定了「比什么」,alpha 决定了「多严格」,lfcThreshold 决定了「多大才值得报」。三个参数串起来,最终决定你报告里是哪张基因列表。

一个实用建议: 当你想比较不同阈值设置对结果数量的影响时,可以跑一个循环:

thresholds <- c(0, 0.5, 1, 1.5)
sapply(thresholds, function(t) {
  res <- results(dds, contrast = c("condition", "A", "B"), lfcThreshold = t)
  sum(res$padj < 0.05, na.rm = TRUE)
})

这样你就能直观看到:随着生物学意义门槛提高,还能剩下多少基因。

⚠️ 别忘了检查 results 对象的基本统计:
# 每次跑完 results,我都会习惯性跑一下这个
summary(res_final)

它能告诉你:总共有多少基因,多少上调,多少下调。如果数字异常(比如上调基因数离谱),赶紧回头查你的 contrast 有没有写反。

4.6 参数速查表

参数 默认值 作用 常见设置
contrast 无 指定比较的分组对 c("condition", "B", "A")
alpha 0.1 多重检验校正后的显着阈值 0.05(严格), 0.2(探索)
lfcThreshold 0 log2 fold change 的绝对阈值 1(两倍), 1.5(三倍)
altHypothesis "greaterAbs" 备择假设方向(与 lfcThreshold 配合) "greater", "less"
independentFiltering TRUE 是否启用独立过滤 一般不动,除非特殊需求

4.7 避坑指南

  • 忽视 contrast 的方向 —— 我同事曾经把分子和分母写反了,结果认为上调的基因其实是对照组上调。报告发出去后才发现,非常尴尬。
  • 只看 p 值不看 fold change —— 这是我自己的教训。某些低表达基因的 padj 很小,但 log2FC 连 0.5 都不到。这类基因在独立验证时存活率极低。
  • alpha 和 lfcThreshold 混用而不理解逻辑 —— 设置 lfcThreshold 后,DESeq2 的统计检验模型会变化。不能用默认的 pvalue 直接判断,要看 svalue。

好了,results() 的这三个参数,你理解透了吗?我建议你打开自己的 dds 对象,挨个试试不同的 alpha 和 lfcThreshold 组合,对比一下输出结果的变化。亲身跑一遍,比看十遍文档都管用。