☰
有参转录组GO富集分析实战:从差异基因到候选靶点
2026/10/27 7:11:14 网站建设 项目流程

1. 拿到差异基因表之后,为什么第一件事是GO富集

做过有参转录组的人都有这个体会:前面差异表达分析跑完,拿到那张几万行的表格,心里其实是发虚的。上调和下调的基因动辄上千个,盯着基因名一行行看,除了少数几个熟悉的 marker,其余的全是陌生符号。这时候真正的活儿才刚刚开始——你得回答一个生物学问题:这些基因凑在一起,到底在干什么。

GO富集分析就是干这件事的。它把差异基因列表映射到基因本体(Gene Ontology)的三个分支上——生物过程(BP)、细胞组分(CC)、分子功能(MF),然后统计哪些条目显著地"扎堆"出现。说白了,就是给一堆看起来杂乱的基因贴上功能标签,再看哪些标签出现的频率远超随机预期,从而给你一个方向性的判断。

这个环节在整个有参转录组流程里属于"下游解读"的第一步,位置很关键。上游的比对、定量、差异分析产出的是一份纯数据,而GO富集是把数据翻译成生物学语言的第一个环节。它适合的人群其实挺广:做课题的研究生、需要快速判断实验表型的实验人员、以及要在一堆候选基因里挑后续验证靶点的从业者。哪怕你只会在命令行里敲几个脚本,这套流程也能跑通。

我自己踩过的坑是,一开始把GO富集当成"跑个软件出张气泡图"的机械操作,结果图是出来了,但根本说不清哪个条目值得深挖。后来才明白,富集分析本身不难,难的是输入怎么给、背景怎么设、结果怎么筛、图怎么讲。这四个环节任何一个处理不当,出来的图都可能是假阳性堆出来的漂亮废物。后面几节我就围绕这四个环节,把整个实战流程拆开讲。


2. 整体思路与输入数据的整理逻辑

2.1 为什么富集分析的成败八成取决于输入基因列表

很多人以为富集分析是软件的事,其实软件只是执行统计,真正决定结论质量的是你喂进去的那份基因列表。这里有个基本原则:输入列表必须和你的科学问题严格对应。

常见的错误做法是把所有差异基因一股脑全丢进去,不管上调下调、不管显著性阈值。这样做的后果是,上调和下调的基因在功能上可能是完全相反的(比如一个通路被激活、另一个被抑制),混在一起富集,信号会互相抵消,最后什么都富集不出来,或者富集出一堆模糊的大条目。

我实际操作中的习惯是分层处理:

  • 第一层:全部差异基因(|log2FC| ≥ 1,padj < 0.05),用来看整体功能倾向;
  • 第二层:仅上调基因,看被激活的过程;
  • 第三层:仅下调基因,看被抑制的过程;
  • 第四层(可选):对阈值更严格的核心基因集(|log2FC| ≥ 1.5 或 2),用来锁定高置信度的候选。

这四份列表分别独立跑富集,最后对比着看。这个思路的原因很直白:转录组反映的是表达量的变化方向,方向本身就携带信息,丢掉方向等于丢掉了半个结论。

2.2 基因ID的统一是所有坑里最烦的那个

有参转录组的物种通常有完善的注释,但基因ID的格式五花八门。你从定量结果里拿到的可能是 Ensembl ID(如 ENSXXXG00000XXXX),而GO注释文件里可能是 Entrez ID、Symbol 或者带版本号的 Ensembl ID。这中间的转换如果没做对,富集结果会出现大量"未匹配",最后只映射上几百个基因,统计效力大打折扣。

处理办法我一般是这样:先确认定量所用参考基因组的注释版本,从对应的注释 GTF/GFF 里提取gene_id和gene_name的对应关系,做成一张本地映射表。这样做的理由是,在线转换工具依赖网络且版本可能与你本地不一致,而本地映射表可以反复用、可追溯,出了问题也能查。

有个细节特别容易被忽略:Ensembl ID 后面的版本号后缀(.数字)。带版本号和不带版本号的ID在软件看来是两个完全不同的字符串。我见过不止一次,注释文件里是ENSXXXG00000123456,而表达矩阵里是ENSXXXG00000123456.5,结果匹配率只有个位数。批量去掉版本号这一步,务必在流程最开始就做掉。

2.3 背景基因集:被严重低估的参数

富集分析的统计本质是超几何检验或 Fisher 精确检验,它需要一个"背景"——也就是你这次实验里所有被检测到的基因,而不是全基因组所有基因。

为什么这点重要?因为 RNA-seq 只能检测到在特定组织、特定发育阶段有表达的基因。如果你用全基因组注释作为背景,会系统性地高估某些条目的显著性。举个通俗的例子:假设你从一筐水果里挑出了十个苹果,问"苹果是否被显著富集",分母应该是这筐水果的总数,而不是整个果园的水果总数。

实操中,背景集就用表达矩阵里经过低表达过滤后剩余的基因列表。我通常的过滤标准是:至少在半数样本中 count ≥ 1,或者用 CPM/TPM 阈值过滤。这个过滤要在差异分析之前完成,这样背景集和差异分析用的是同一套基因,逻辑才自洽。

2.4 工具选型:在线、R包还是命令行

工具这块我实际用过三类,各有适用场景:

工具类型代表方案优势局限适用场景
在线平台各类网页版富集工具零代码,出图快背景不可控、版本不透明、批量处理难快速摸底、临时汇报
R 语言包clusterProfiler 等参数完全可控、可脚本化、可重复需要 R 基础、注释包需匹配物种正式分析、批量样本
命令行工具本地化富集脚本易集成进流程、资源占用低可视化能力弱流水线化生产

我个人的主线方案是 R 的 clusterProfiler 配合本地注释。理由是它把富集检验、结果整理、可视化三件事串在了一起,而且支持setReadable把 Entrez ID 转回基因名,出图时不需要再手工改标签。在线工具我现在只在两种情况下用:一是刚拿到数据想快速看一眼有没有明显信号,二是给别人演示流程。

注意:不管用哪种工具,注释数据库的版本一定要记录下来写进方法部分,否则后续复现会非常痛苦。


3. 核心实操:从基因列表到富集结果的完整流程

3.1 环境准备与注释包的匹配

先确认物种。有参转录组意味着有参考基因组,所以物种是明确的。以常见的模式生物为例,R 里的注释包命名规则很固定:物种缩写加.db后缀。安装时注意 R 版本与 Bioconductor 版本的对应关系,版本错配是安装失败最常见的原因。

# 安装核心包(版本对应关系务必先查清) if (!requireNamespace("BiocManager", quietly = TRUE)) install.packages("BiocManager") BiocManager::install(c("clusterProfiler", "enrichplot", "ggplot2", "DOSE")) # 物种注释包:按实际物种替换 BiocManager::install("org.Xx.eg.db")

安装完成后,第一件事是检查注释包里到底有多少基因有GO注释。这一步很关键,如果注释覆盖率很低,后面所有结果都不可信。

library(org.Xx.eg.db) # 查看该注释包支持的key类型 keytypes(org.Xx.eg.db) # 统计有GO注释的基因数量 length(keys(org.Xx.eg.db, keytype = "ENTREZID"))

3.2 基因ID转换的正确姿势

假设你手上的差异基因表第一列是 Ensembl ID。转换分两步:先去掉版本号,再转 Entrez ID(clusterProfiler 的 enrichGO 默认用 Entrez,虽然新版也支持其他类型,但 Entrez 最稳)。

library(clusterProfiler) library(org.Xx.eg.db) deg <- read.table("deg_result.txt", header = TRUE, sep = "\t") # 关键一步:去掉 Ensembl ID 的版本号后缀 deg$ensembl <- gsub("\\..*$", "", deg$gene_id) # 转 Entrez ID gene_map <- bitr(deg$ensembl, fromType = "ENSEMBL", toType = "ENTREZID", OrgDb = org.Xx.eg.db) # 看转换率,这一步必须检查 cat("输入基因数:", nrow(deg), "\n") cat("成功转换:", nrow(gene_map), "\n") cat("转换率:", round(nrow(gene_map)/nrow(deg)*100, 2), "%\n")

转换率这个数字,我的经验阈值是低于 60% 就要停下来查原因。常见原因有三个:一是版本号没去干净;二是参考基因组注释版本与注释包版本差距过大;三是基因ID根本不是 Ensembl 而是其他体系(曾经遇到过定量流程输出的是转录本ID,被当成基因ID处理)。

3.3 运行 enrichGO 的参数取舍

参数设置这块我拆开讲,因为每个参数背后都对应一个判断。

ego_all <- enrichGO(gene = gene_map$ENTREZID, universe = background_entrez, # 背景基因集 OrgDb = org.Xx.eg.db, keyType = "ENTREZID", ont = "BP", # 先跑BP pAdjustMethod = "BH", # 多重检验校正 pvalueCutoff = 0.05, qvalueCutoff = 0.2, readable = TRUE) # 结果直接转基因名

ont参数的选择有讲究。三个分支里,BP 的信息量最大、解释力最强,通常先看 BP;CC 在分析细胞定位变化、分泌蛋白时更有意义;MF 适合关注酶活、结合能力的课题。我一般三个分支都跑,但汇报时以 BP 为主线,CC 和 MF 作为补充。

pAdjustMethod用 BH(Benjamini-Hochberg)是常规做法,控制 FDR。这里必须说明为什么一定要做校正:GO 条目有几万个,即使全是随机基因,按 p < 0.05 也会有上千个条目"显著"。不做校正的富集结果基本没有参考价值。

qvalueCutoff设 0.2 是我的习惯,比 pvalueCutoff 宽松一档。原因是 qvalue 的计算依赖 p 值分布估计,在基因数较少时波动较大,设得太严会把一些真实信号筛掉。

3.4 背景基因集怎么传给 universe

这一参数经常被忽略。universe不填的话,软件默认用注释包里所有有GO注释的基因作为背景,这就是前面说的"用整个果园当分母"的问题。

# 从表达矩阵构建背景:过滤低表达后的全部基因 expr <- read.table("expression_matrix.txt", header = TRUE, row.names = 1) keep <- rowSums(expr >= 1) >= ncol(expr) * 0.5 # 半数样本count>=1 bg_genes <- rownames(expr)[keep] bg_genes <- gsub("\\..*$", "", bg_genes) bg_map <- bitr(bg_genes, fromType = "ENSEMBL", toType = "ENTREZID", OrgDb = org.Xx.eg.db) background_entrez <- unique(bg_map$ENTREZID)

这个背景列表应该和你差异分析的输入矩阵完全一致。我建议把它存成一个文件,后面所有富集分析都复用,保证前后一致。

3.5 结果整理与去冗余

enrichGO 的原始输出有个问题:条目之间高度冗余。比如"免疫应答"和"先天免疫应答"可能同时显著,基因大量重叠,画在图上看不出差异。simplify函数用语义相似度做去冗余:

ego_simple <- simplify(ego_all, cutoff = 0.7, by = "p.adjust", select_fun = min)

cutoff = 0.7是常用的相似度阈值。这个值的含义是:两个条目语义相似度超过 0.7 时,保留 p 值更小的那个。调低到 0.5 会保留更多条目但冗余增加,调到 0.9 则几乎不去冗余。

导出的结果表我一般只保留几列:ONTOLOGY、Description、GeneRatio、BgRatio、pvalue、p.adjust、geneID、Count。其中GeneRatio 和 BgRatio 的对比是判断富集强度的核心,很多人只看 p 值,其实 GeneRatio/BgRatio 的倍数(fold enrichment)更能反映富集的富集程度。

3.6 富集图的三种画法与各自的表达重点

气泡图是最常用的。横轴 GeneRatio,纵轴条目,点大小代表 Count,颜色代表 p.adjust。它的优势是信息密度高,一张图能同时呈现四个维度。

dotplot(ego_simple, showCategory = 20, split = "ONTOLOGY") + facet_grid(ONTOLOGY ~ ., scales = "free")

条形图适合汇报场合,横轴 Count,一眼看出条目涉及多少基因,但丢失了显著性信息,所以我会在报告里配合表格一起给。

富集网络图(cnetplot)用来展示条目与基因的对应关系,特别适合挑候选基因。当你想知道某个显著条目里到底是哪几个基因在驱动时,这张图能直接告诉你,而且能看出基因是否被多个条目共享。

cnetplot(ego_simple, categorySize = "pvalue", foldChange = fc_vector, showCategory = 5)

foldChange参数传入一个以基因名为名字的向量,图上节点的颜色就能反映上调下调。这个功能在挑靶点时太有用了——同一个通路里,上调的基因比下调的更可能是被激活的驱动者。


4. 结果解读与常见问题排查

4.1 富集结果空空如也的五种原因

这是问得最多的问题。按我的排查顺序列出来:

现象可能原因排查方法解决
结果表 0 行基因ID完全没匹配上检查 bitr 输出行数去版本号、换keyType
只有零星几条背景集设置过大检查 universe 长度用表达过滤后的基因做背景
p 值全大于 0.05差异基因太少看输入基因数放宽差异阈值或合并分组
富集条目很泛输入基因混杂方向检查是否混了上下调分层跑富集
与预期完全不符注释包物种错误核对物种缩写换正确的 OrgDb

这个表我基本是照着挨个过的。其中"输入基因混杂方向"是最隐蔽的,因为结果看着正常,图也漂亮,但结论是错的。

4.2 转换率低怎么办

转换率低于 60% 时不要硬着头皮往下做,先把原因定位清楚。三个检查点:

第一,打印几条原始ID出来人眼看一下格式。有次我拿到的ID是gene-XXXXX这种自定义命名,根本不在任何标准体系里,只能去找原始的 GTF 文件重新提取标准ID。

第二,确认注释包的版本。如果参考基因组用的是较新的注释版本,而 OrgDb 包较旧,新基因就对不上。这时候可以考虑用 AnnotationHub 拉取对应版本的注释。

第三,考虑是否有大量非编码基因。lncRNA 很多没有 GO 注释,如果你的差异基因里 lncRNA 占比高,转换率天然会低。这种情况要把蛋白编码基因单独拎出来做富集。

4.3 p 值和 q 值到底该看哪个

实操建议:以 p.adjust(BH校正后的 p 值)为主,qvalue 作为辅助筛选项。原因在于 qvalue 的估计在基因数少时不稳,容易出现"p.adjust 显著但 qvalue 不显著"的情况。如果两者冲突,我倾向于相信 p.adjust。

另外提醒一点,p.adjust 是条目层面的显著性,不代表生物学重要性。一个 padj = 0.001 的条目,如果 GeneRatio 只有 3/500,实际参与基因极少,结论的分量要打折扣。所以我在筛选时是双条件:padj < 0.05 且 Count ≥ 3(有时 ≥ 5)。

4.4 三个容易翻车的细节

细节一:重复基因名的问题。readable = TRUE会把 Entrez 转成 Symbol,但存在多个 Entrez 对应同一个 Symbol 的情况,转完之后基因名重复,画图时会出错或者基因被合并。解决办法是保留一列 Entrez ID 作为唯一标识,Symbol 只作展示用。

细节二:不同分支的条目不要混在一张图上排序。BP、CC、MF 的条目性质和基因数量差异很大,混在一起按 p 值排序,往往 BP 条目全部占满前二十,CC 和 MF 完全看不到。分面(facet)或者分开出图是更稳妥的做法。

细节三:富集结果的基因名要回查原始表达量。富集只是统计关联,不保证生物学真实。我习惯挑出 top 条目里的关键基因,回到表达矩阵里看它在样本间的具体数值和变化倍数。有好几次,富集图上很漂亮的条目,回查发现是由一个变化极大的基因单独驱动的,其余基因变化都很微弱,这种条目的可信度就要打问号。


5. 结果落地:从富集表到可讲述的结论

5.1 分层富集结果的对比呈现

前面提到分四层跑富集,这里讲怎么把四份结果放在一起讲出故事。我的做法是做一个三线表,列出上调组和下调组各自最显著的 top 10 条目,然后看两组的条目是否有重叠。有重叠说明该功能整体受影响但方向不一致;无重叠且功能对立,说明是明确的激活/抑制模式。

这个对比往往比单独看全部差异基因的富集结果信息量大得多。有一次分析某个处理前后的数据,全基因集富集只出了一个很泛的"应激响应",但拆开看之后,上调组集中在某类合成代谢,下调组集中在某类分解代谢,方向性一下就清楚了。

5.2 富集条目与通路的交叉验证

GO 富集给出的是功能分类,比较粗。拿到显著条目后,我通常会再做一次 KEGG 通路富集,两边对照。GO 条目里指向某个生物学过程,KEGG 里能看到具体的通路节点,两者互相印证,结论才站得住。

需要说明的是,GO 和 KEGG 的结果不完全一致是正常的,因为分类体系不同。如果 GO 显示某类功能显著但 KEGG 没有对应通路,可能是这类功能分散在多个通路中,也可能只是注释粒度的差异,不必强行解释。

5.3 挑选候选基因的实操逻辑

富集分析的最终目的,多数情况下是为了缩小候选范围。我的筛选逻辑是三层过滤:

第一层,从显著条目里提取所有涉及基因;第二层,按 fold change 绝对值排序,取前若干;第三层,回查该基因在样本间的表达模式是否稳定(有无离群样本驱动)。三层过完,通常从上千个差异基因能收敛到十几个高优先级候选。

这个流程走下来,你会发现富集分析的价值不只是"出一张图交差",它是把统计结果转化为可验证假设的桥梁。每次做完这套,我对数据的理解都会比只看差异表时深一层。

提示:所有中间文件——基因映射表、背景基因列表、各分层的富集结果表——都建议保留并规范命名。转录组项目周期长,几个月后回头复现时,你会感谢当时留了这些文件。

5.4 我个人的几条经验

第一条,富集分析的结果一定要结合实验设计去读。同样的富集条目,在不同的处理条件下意义完全不同,脱离实验背景谈富集等于空谈。

第二条,不要迷信 top 条目。排第一的条目有时只是因为它的基因数量多、统计效力高,真正有生物学意义的可能是排在第七第八的条目。养成看完 top 20 再下判断的习惯。

第三条,出图前先想清楚这张图要讲什么。如果是要说明整体功能倾向,气泡图配 top 20 足够;如果是要突出某个通路,就用 cnetplot 把基因和条目一起展示。图的目的是沟通,不是炫技。

第四条,整个流程脚本化。把 ID 转换、背景构建、三层富集、结果导出、出图写成一条脚本,参数放在开头。这样换一批数据重跑只需要改路径,效率提升非常明显,也避免了手工操作引入的不一致。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询