1. 认识DESeq2:差异表达分析的核心思想与参数概览

差异表达分析,说白了就是要找出两组样本里哪些基因的表达量真的变了。RNA-seq 数据一出来就是几十个G的测序片段,但真正做分析时,我们面对的是一个计数矩阵——每个基因在每个样本里有多少个读数。你想想看,这个数字本身并不直接告诉我们哪个基因上调了、哪个下调了。它受很多因素影响:测序深度、基因长度、文库复杂度……甚至同一批样本里,同一个基因的计数也能差好几倍。

DESeq2 是这几年最主流的差异表达工具之一。我个人习惯用它来跑所有 RNA-seq 项目,因为它对低表达基因的处理特别稳。我记得最早我刚入行时,用过 edgeR 和 limma,但后来发现 DESeq2 的方差模型更贴合真实数据——它从根本上解决了“低表达基因为什么看起来差异那么大”这个问题。好,我们直接进正题。

1.1 核心思想:方差均值关系与负二项分布

为什么不用泊松分布?很多入门教程会告诉你“RNA-seq 计数符合泊松”,但实际项目中你会发现,基因表达不是那么乖的。泊松分布假设方差等于均值——可现实中低表达基因的方差往往远大于均值,这叫「过离散」(overdispersion)。

我曾在一次肿瘤 vs 正常组织的项目里,看到某个基因在正常组平均只有5个读数,但肿瘤组里有20个——你看着觉得很显著吧?结果 DESeq2 把它的 p 值调得很高。为什么?因为它发现这个基因在组内的波动太大了,5 和 20 不过是随机波动的错觉。这就是负二项分布出场的原因:它多了一个离散参数,允许方差比均值大。

DESeq2 的核心假设是:

  • 每个基因的计数服从 负二项分布(NB)
  • 均值由实验条件和测序深度决定
  • 方差 = 均值 + 离散参数 × 均值²

而那个离散参数,DESeq2 会借用所有基因的信息一起估计。这种做法在统计里叫「信息共享」——对于低表达基因,单靠它自己那几个计数根本算不準离散参数,但借了高表达基因的力量就能稳住。

一个实用建议:跑 DESeq2 前,最好先看看总文库大小是否差异过大。如果最大比最小差了 10 倍以上,DESeq2 的 sizeFactor 估计可能会不太准。我一般会用 estimateSizeFactors 提前检查一下,如果看到某个样本的 sizeFactor 超过 2,我就会回去看数据是否存在批次问题。

1.2 核心参数一:设计公式 (design formula)

设计公式是 DESeq2 里第一个必须认真对待的参数。它告诉模型“哪些因素会影响表达量”。最简单的写法:

dds <- DESeqDataSetFromMatrix(countData = counts,
                              colData = colData,
                              design = ~ condition)

其中 condition 是你要比较的分组变量。如果你有批次效应,就得加上:

design = ~ batch + condition

这里要注意顺序:DESeq2 会用设计公式最后一列做对比。所以如果你写 ~ condition + batch,默认比较的是 condition 的水平差异,batch 只是协变量。我见过有人把顺序搞反了,结果比较的是批次差异,哭笑不得。

另外,如果你的因素有多个水平,比如三种处理(Ctrl, TreatA, TreatB),你可以用 results 的 contrast 参数指定要比较哪两个。我不会把所有对比都设成公式里的交互项,那样参数太多,小样本下容易过拟合。

我曾经踩过的坑:有一次项目里五个样本,三个处理、两个对照,结果我把样本的批次 ID 也放进了 design formula——但批次和分组几乎完全共线。DESeq2 跑完后提示系数估计失败,有些基因的 p 值全是 NA。后来我删掉多余的批次变量,问题就解决了。设计公式的变量数量不能超过样本数,尤其要注意有交互项时。

1.3 核心参数二:差异检验方法

DESeq2 提供两种检验:Wald test 和 LRT(似然比检验)。Wald 是默认的,速度快,适合简单的两组比较。但如果你要比较多个条件,或者要评估某个因素的整体显著性(比如“批次这个因素到底有没有影响”),LRT 更合适。

# Wald test (默认)
dds <- DESeq(dds)
res <- results(dds)

# LRT:比较全模型 vs 简化模型
dds <- DESeq(dds, test = "LRT", reduced = ~ 1)
res <- results(dds)

除了检验方法,还有两个过滤参数我也经常调:

  • cooksCutoff:用来标记异常样本。默认是 TRUE,DESeq2 会自动计算 Cook's distance 并过滤掉那些单个样本影响力过大的基因。如果你样本数太少(比如每个组只有2个),自动过滤可能会把好基因也杀掉。我一般样本数少于4时会把 cooksCutoff 改成 FALSE,然后手动检查。
  • independentFiltering:默认开启,它会根据基因的平均表达量自动选择一个阈值,过滤掉那些表达太低、没法做差异检验的基因。这能减少多重检验负担。我个人觉得这个功能很好用,一般保持默认即可。

1.4 核心参数三:倍数变化收缩 (shrinkage)

这是 DESeq2 最让我喜欢的地方。原始的 log2 倍数变化(LFC)对于低表达基因常常高估得离谱。比如一个基因在对照组计数为2,处理组为10,直接算 LFC = log2(10/2) ≈ 2.32。但如果这个基因本身波动很大,这个 2.32 的效应很可能只是噪声。

DESeq2 通过 lfcShrink 函数做收缩(shrinkage),把那些方差大、证据弱的倍数变化往0缩。这个收缩不是随便减一个数,而是利用了先验分布——说白了就是“借力”,借所有基因的信息来告诉你,这个基因的效应不可靠,得缩回去。

res_shrink <- lfcShrink(dds, contrast = c("condition", "treated", "control"),
                        type = "apeglm")

常用的收缩方法有 apeglm 和 ashr。我个人更推荐 apeglm,它在大多数情况下性能稳定,而且收缩后的 LFC 仍然可以用标准阈值(比如 |LFC| > 1)来筛选。

关键点:收缩后的倍数变化不改变 p 值,但会影响 log2FoldChange 的数值。如果你要画火山图、热图展示差异基因,一定要用收缩后的结果,否则那些低表达基因会“假装”很显著,把图弄得很难看。我见过有文章直接用了 results 的 LFC 来画图,结果审稿人一眼就指出了异常。

1.5 核心流程与数据要求

整个 DESeq2 分析流程可以概括为三步:

  1. 构建对象:DESeqDataSetFromMatrix
  2. 差异分析:DESeq
  3. 提取结果:results 和 lfcShrink

输入数据也很明确:

名称 类型 描述
countData 整数矩阵 行是基因,列是样本,值为原始计数(非标准化)
colData 数据框 行是样本,列是实验变量(条件、批次等)
design 公式 指定模型的固定效应,如上所述

我通常从 featureCounts 或 HTSeq 得到计数矩阵,然后直接用 R 读入。注意计数矩阵必须全是整数,不要用 RPKM/FPKM 之类的值——DESeq2 的设计就是基于原始计数的,它自己会做归一化。

1.6 参数概览速查表

下表列出了本章涉及的核心参数,方便你快速回顾:

参数名 所属函数 功能 默认值
design DESeqDataSetFromMatrix 指定实验设计模型 无(必须用户提供)
test DESeq 差异检验方法(Wald / LRT) "Wald"
reduced DESeq (LRT) LRT 的简化模型 无
cooksCutoff results 过滤异常样本影响的基因 TRUE
independentFiltering results 自动过滤低表达基因 TRUE
lfcShrink type lfcShrink 倍数变化收缩方法 "apeglm"

这份表格可以贴在墙上参考,但实际使用时,还是要根据你的数据特点来做选择。比如样本量很小,就不要轻易用自动的 cooksCutoff;如果是为了做功能富集,收缩后的 LFC 比原始 LFC 更可靠。

DESeq2核心思想与参数概览 原始计数矩阵 基因 × 样本 负二项分布建模 方差 = 均值 + 离散×均值² 方差均值关系 基因间信息共享 设计公式 ~ condition + batch 差异检验 Wald / LRT 倍数变化收缩 lfcShrink (apeglm) 💡 需要用户指定的参数: • design formula • test method • lfcShrink type

现在你应该对 DESeq2 的「体」有了一个大致的认识:它用负二项分布来建模原始计数,通过信息共享来稳定低表达基因的方差估计,然后根据你指定的设计公式做差异检验,最后利用收缩手段得到更可靠的倍数变化。后面几章我会逐一展开每个参数的具体调优策略——包括那些文档里没写的坑。(真的,我花了好几个通宵才摸清楚。)