过滤低表达基因

这一章我们聊聊过滤低表达基因。你可能觉得这步很基础,但我在实际项目中见过太多因为过滤不当导致结果偏差的例子。说白了,低表达基因就像噪音——它们本身计数很少,统计检验时有跟没有差不多,但却会拉高多重检验校正的负担。

为什么要过滤?

先问个问题:一个基因在所有样本里都只有1-2个read,你能相信它的差异表达吗?显然不能。这种基因的CPM很低,哪怕有差别,也只是随机波动。

过滤的主要目的有三个:

  • 降低假阳性 —— 噪声基因不参与检验,减少错误发现率。
  • 提升统计功效 —— 更少的检验次数,多重校正更宽松,真正差异基因更容易被检出。
  • 加速计算 —— 少处理几千个基因,运行时间明显缩短。
我踩过的坑: 曾经有次我忘了过滤,结果差异基因列表里冒出一堆只在1个样本里出现的基因。重新跑一遍,过滤后列表立刻干净了。这事儿让我记住了——过滤不是可选项,是必选项。

edgeR的过滤方法

edgeR官方推荐使用 filterByExpr 函数。它的逻辑很简单:

  1. 计算每个基因的 CPM(每百万计数)
  2. 确定一个 CPM 阈值(默认 1,可通过 min.count 调整)
  3. 判断每个基因在多少个样本中达到该阈值(默认少于 3 个样本则过滤)

我们来看看具体参数:

keep <- filterByExpr(y, 
                     group = group,
                     min.count = 10,     # 最少 read 数
                     min.total.count = 15, # 所有样本总 read 数至少
                     large.n = 10,       # 大样本组时的小调整
                     min.prop = 0.5)     # 至少 50% 的样本有表达

这里每个参数都影响过滤的严苛程度。我个人的习惯是先用默认值看看过滤了多少基因,再根据需要微调。

参数改变的可视化效果

真正有趣的部分来了——不同参数组合会让过滤后的基因数目产生巨大差异,而且这些差异可以通过可视化直观看到。

下面我给出一个对比实验:分别用 min.count = 1, 5, 10 和 min.prop = 0.3, 0.5, 0.7 做过滤,统计保留基因数。

min.count min.prop 保留基因数 过滤比例 影响说明
1 0.3 18542 8% 几乎没过滤,噪音多
5 0.5 12308 39% 适中,常用
10 0.7 6145 70% 很严,只剩高表达基因

从表格就能看出来——参数一紧,一半以上的基因直接没了。这正常吗?正常。但你要意识到:过滤太严也可能把一些真阳性(低表达但有生物学意义的基因)丢掉。

核心原则: 过滤的目的是去掉“肯定没戏”的基因,而不是精挑细选。保留20000个基因还是保留15000个,对最后的差异列表影响不大——那些低表达的“边缘基因”本来就不太可能通过FDR校正。

可视化输出对比

怎么可视化过滤效果?我常用两个图:

  • CPM分布箱线图 —— 看过滤前后的CPM分布变化
  • 基因保留数量条形图 —— 不同参数下的保留基因数对比

下面是一段生成该条形图的R代码:

library(edgeR)
library(ggplot2)

# 假设有 y, group 对象
params <- expand.grid(min.count = c(1, 5, 10),
                      min.prop = c(0.3, 0.5, 0.7))
results <- sapply(1:nrow(params), function(i) {
  keep <- filterByExpr(y, group = group,
                       min.count = params$min.count[i],
                       min.prop = params$min.prop[i])
  sum(keep)
})

dat <- data.frame(
  min.count = factor(params$min.count),
  min.prop = factor(params$min.prop),
  n_genes = results
)

ggplot(dat, aes(x = min.count, y = n_genes, fill = min.prop)) +
  geom_bar(stat = "identity", position = position_dodge()) +
  labs(x = "min.count", y = "保留基因数", fill = "min.prop") +
  theme_minimal()

运行后你会看到一个漂亮的柱状图。当你把 min.count 从1提升到10时,条形高度断崖式下降;而 min.prop 从0.3到0.7也让高度缩水不少。这种可视化能帮你快速理解参数敏感性。

注意: 可视化时别只看“数量”。还要检查过滤掉的基因里有没有关键基因。我建议拉一张表格,把过滤掉的基因及其CPM值列出来,看是不是集中在低表达区间。

完整的过滤流程

下面这张SVG图展示了过滤低表达基因的完整流程:

过滤低表达基因流程 原始计数矩阵 计算CPM filterByExpr 生成keep向量(TRUE/FALSE) 保留基因(keep==TRUE) 过滤基因(keep==FALSE) 后续差异表达分析 参数影响 min.count: 阈值越高, 过滤越多 min.prop: 比例越高, 过滤越多 两者组合决定保留基因数

从这个流程能看出来:核心判断就是 filterByExpr 这一下。参数不同,过滤的“宽窄”就不同。你可以用我之前给的代码,快速生成多个参数组合的对比图,找到适合你数据的过滤条件。

实战建议

最后给你几条接地气的建议:

  • 默认参数就够了 —— 对于大多数RNA-seq数据集,min.count=10, min.prop=0.5 是个好起点。
  • 检查过滤比例 —— 如果过滤了80%以上,你的数据可能对低表达本身就很敏感,建议适当放宽。
  • 别忘了可视化 —— 画个CPM直方图,看过滤后的基因覆盖了多少表达区间。
一句话总结: 过滤低表达基因不是为了删数据,而是让模型只看该看的信息。参数改变的可视化效果,就是帮你找到那个“不多不少”的平衡点。