拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GO/KEGG富集分析:从差异基因列表到功能通路解读

GO/KEGG富集分析:从差异基因列表到功能通路解读

做RNA-seq转录组分析,前两步通常是拿fastq比对到参考基因组,得到基因表达矩阵,再用DESeq2或者edgeR做差异表达分析,筛出一批p值小于0.05、log2FC大于阈值的基因。到这一步,很多人会捧着一堆差异基因列表问:接下来怎么办?GO和KEGG富集分析,就是把这些基因映射到功能注释和通路数据库里,做一轮统计检验,回答"这些差异基因集中在哪些生物学过程、哪些信号通路中"。搞清楚这一步,你的结论才能真正从"这几个基因表达量变了"升级到"这几个通路被激活了"。这篇文章我把富集分析的原理、工具选型、完整代码、可视化技巧和实际踩过的坑一次讲清楚。

1. 富集分析到底在解决什么问题

1.1 从"哪些基因变了"到"哪些功能活了"

差异表达分析给你的是一串基因名和对应的log2FC、p值。单独盯着某一个基因看,你会很快迷失:500个差异基因里每个都升或降,单独看谁都像很重要,但你要怎么把它们组织成结论?审稿人问你"你的处理组到底影响了哪些生物学功能",你不能回答"影响了500个基因",你要回答"影响了细胞周期和DNA修复"。

富集分析干的就是这件事。它本质上是个统计学检验:假设你手里有200个显著性差异基因,其中30个注释到了"细胞周期"这个GO条目上,而整个基因组里总共1000个基因里面只有50个注释到"细胞周期"。那你就需要判断,30/200明显高于50/1000吗?高出的程度是不是随机就能碰到的?这个判断过程,就是富集检验。

做这个分析之前,你手里必须有三样东西:高质量的差异基因列表、对应的物种注释数据库、一套合理的统计阈值。缺哪一个都会让结果失真,后面我会逐个展开。

1.2 差异基因列表、背景基因与输入准备

先聊聊最容易被忽略的背景基因。所谓背景基因,就是你做富集检验时的"分母"。很多人图省事,直接把所有差异基因往里一丢,不设置背景,结果经常富集出一堆上学学过的经典通路,看起来特别漂亮,但仔细一想,几乎每个通路都被富集了,毫无区分度。

正确的背景基因应该是:你这次实验中所有检测到了表达量的基因,也就是所有进入差异表达分析的基因。比如你用DESeq2做了两组的比较,总共有16000个基因进入了检验,其中300个被判定为差异基因,那背景就是16000,而不是全基因组的20000多个。理由很直白:你的检测体系决定了你能看到哪些基因,如果某个基因在所有人里都没有表达、压根进不了定量矩阵,那本来就不可能出现在差异列表里,拿它做分母没有意义。

我见过很多线上教程教大家直接不传background参数,导致结果和真实生物学背景差得很远。后面实操部分我会给出明确的背景基因构造方法,这一步做好了,后面的结果才是可信的。

2. GO和KEGG这两套体系分别是什么

2.1 GO:三层结构的标准功能词典

GO(Gene Ontology)是一个标准化的基因功能注释体系。它把基因功能分成三个维度:Biological Process(生物学过程,BP)、Cellular Component(细胞组分,CC)、Molecular Function(分子功能,MF)。BP回答"这个基因参与了什么过程",比如DNA修复、炎症反应;CC回答"这个基因在细胞的哪个位置干活",比如线粒体基质、核小体;MF回答"这个基因在分子层面做什么",比如ATP结合、转录因子活性。

三个维度不是平级的,而是从不同角度描述同一个基因。一个基因可以同时注释到BP的"细胞分裂"、CC的"纺锤体"和MF的"微管结合",三个描述合在一起才是完整的功能画像。做GO富集时可以分成三个ont分别分析,也可以让软件一起跑。实操中BP的结果最多也最有解释价值,CC和MF通常作为辅助信息。

GO的注释是逐层细化的,从很宽泛的条目如"代谢过程"到很具体的条目如"线粒体电子传递NADH到泛醌"。真正做富集的时候要注意一个现象:如果显示的结果全是"代谢过程""生物调节"这种顶层大条目,那基本是筛选阈值放得太宽了,后面我会讲怎么收紧。

2.2 KEGG:从基因到通路网络

KEGG(Kyoto Encyclopedia of Genes and Genomes)是另一套体系,核心是KEGG Pathway数据库。它不像GO那样描述单个基因的属性,而是把基因放进代谢和信号转导的网络里,告诉你这些基因共同参与了一条通路。

比如你富集到"p53 signaling pathway",KEGG会展示这张通路图,里面有ATM、MDM2、CDKN1A、BAX等基因,有激活和抑制关系,有箭头指向下游效应。这种网络信息是GO给不了的,也是很多科研人员做机制研究时最需要的证据之一。

KEGG的物种覆盖是有差异的。人类(hsa)、小鼠(mmu)、大鼠(rno)这些模式物种注释非常完整,但一些非模式物种可能只有很少的通路注释,甚至根本没有收录。做之前先确认你的物种在不在KEGG数据库里,不然结果里只有一个空表格,白白浪费一天时间。

2.3 富集检验背后的统计模型:超几何分布与Fisher精确检验

富集分析的核心统计检验是超几何分布检验,也叫Fisher精确检验。它的逻辑可以这样理解:你把差异基因看作从全基因组基因池里随机抽出来的一批样本,池子里有一些带"细胞周期"标签的球,你抽出来的球里带这个标签的数量明显偏多,说明抽签过程可能不是随机的,这个通路就是被富集了。

具体计算时,需要构造一个四格表。以某个通路为例:A是差异基因里注释到该通路的数目,B是差异基因里没注释到该通路的数目,C是背景基因里注释到该通路的数目,D是背景基因里没注释到该通路的数目。Fisher精确检验会计算在背景基因的注释比例下,抽到目前这种"差异基因中该通路占比"的概率。如果这个概率很小,就说明差异基因在该通路上的出现是显著偏多的。

p值算出来后还不能直接用,因为你会拿同一个差异列表去检验几百上千条通路,每一轮有5%的假阳性概率,几千轮下来假阳性会堆得很高。所以必须做多重检验校正。最常见的做法是BH校正(Benjamini-Hochberg方法),控制False Discovery Rate(FDR),体现在结果里就是p.adjust或padj列。clusterProfiler默认的qvalueCutoff参数就是在这个基础上再算一个qvalue。我看到很多新手只看p值不看padj,结果被一波假阳性坑惨,这个问题后面还会点名说。

3. 工具选型:R包还是在线平台

3.1 为什么推荐clusterProfiler

目前做GO和KEGG富集,R语言里最主流的工具就是clusterProfiler,Y叔开发的,Bioconductor生态里下载量常年靠前。我推荐它不只是因为它热,而是它有几个实打实的优势。

第一,它自带ID转换和物种注释包接口,org.Hs.eg.db、org.Mm.eg.db这些一挂上去就能用。第二,富集结果可以无缝衔接可视化,dotplot、barplot、cnetplot、emapplot这些函数都是配套的,不需要把结果导出来再跑到另一个软件里画图。第三,它的simplify函数可以做GO条目的去冗余,这个功能对GO结果里大量相似条目扎堆的情况特别有用。

当然它也有门槛。你得会用R、懂一点数据框操作,还得装对版本。Bioconductor的安装规则和CRAN不同,很多人卡在这一步,第一次用应该用BiocManager,具体命令下面实操部分写清楚。

3.2 在线工具DAVID、Metascape、Enrichr适合什么场景

有些朋友不熟R,或者只是快速验证一下结论,在线平台会更顺手。DAVID(Database for Annotation, Visualization and Integrated Discovery)是老牌子,适合一次性的小规模分析,输入基因列表、选择物种和ID类型,点几下就出结果。Metascape整合GO、KEGG和多种通路数据库,输出图表颜值高,适合拼接图用。Enrichr则更偏向快速查询和交互,数据集丰富,适合做基因列表的多数据库交叉验证,支持"基因列表VS参考集"的快速富集。

这些在线工具的共同问题是:数据更新不如R包及时、自定义背景基因能力有限、批量处理不方便。你拿100个基因贴进去没问题,但你要是做10个样本对比,每组都跑一遍在线工具,点鼠标点到怀疑人生。所以我的习惯是:正式分析用clusterProfiler,结果风格统一、参数可复制;只在探索阶段或给同事快速验证时才用在线工具。

3.3 物种注释包与基因ID转换

clusterProfiler做GO分析时,需要用到对应物种的OrgDb注释包。人类的org.Hs.eg.db、小鼠的org.Mm.eg.db、大鼠的org.Rn.eg.db、斑马鱼的org.Dr.eg.db,这些都是Bioconductor上的成熟注释包,内含基因ID、GO注释、KEGG注释的对应关系。

超级常见的坑是基因ID类型不一致。你差异表达分析出来的是symbol,比如TP53、BRCA1,但富集分析内部很多函数默认用Entrez ID,直接丢进去会提示匹配不到基因。正确流程是用bitr函数做转换,把symbol转成ENTREZID,再喂给富集函数。转换时有一个小经验:转换率低于70%说明你的输入ID本身就有一批是废弃的,得回头检查基因命名版本。

还有一个提示:现在由于许多个体态条件和KEGG数据库接口调整,clusterProfiler里enrichKEGG的keyType参数建议显式指定为"ncbi-geneid"或"kegg",不要用旧教程里的"kegg_geneid"。我在5.2节还会专门讲这个报错。

4. 实操全流程:从差异基因到富集结果

4.1 环境准备与数据格式要求

先检查R版本,建议用4.2以上,并安装BiocManager。然后用以下命令安装所需包:

if (!requireNamespace("BiocManager", quietly = TRUE)) { install.packages("BiocManager") } BiocManager::install(c("clusterProfiler", "org.Hs.eg.db", "DOSE", "enrichplot"))

安装完成后,你手上需要有一个差异基因表格。最少两列:基因symbol列、log2FC和padj列。这里我习惯从DESeq2的results里读进来,代码如下:

library(DESeq2) res <- results(dds, contrast = c("condition", "treatment", "control")) res_df <- as.data.frame(res) res_df$gene <- rownames(res_df) # 筛选差异基因:padj < 0.05 且 |log2FoldChange| > 1 deg <- subset(res_df, padj < 0.05 & abs(log2FoldChange) > 1)

如果没有DESeq2的完整结果,只有一份差异基因列表Excel,那也够用,只要保证基因ID格式统一即可。我建议把差异基因存成CSV,包含symbol列。背景基因建议直接取res_df里所有非NA的基因,也就是所有被检测过的基因。

4.2 GO富集完整代码与参数说明

先做symbol到Entrez ID的转换:

library(clusterProfiler) library(org.Hs.eg.db) deg_entrez <- bitr(deg$gene, fromType = "SYMBOL", toType = "ENTREZID", OrgDb = org.Hs.eg.db) bg_entrez <- bitr(res_df$gene, fromType = "SYMBOL", toType = "ENTREZID", OrgDb = org.Hs.eg.db)

然后执行GO富集:

ego <- enrichGO(gene = deg_entrez$ENTREZID, universe = bg_entrez$ENTREZID, OrgDb = org.Hs.eg.db, keyType = "ENTREZID", ont = "ALL", pAdjustMethod = "BH", pvalueCutoff = 0.05, qvalueCutoff = 0.2, readable = TRUE)

这里几个参数逐个说一下。

  • universe是背景基因的Entrez ID向量,很多人为空,这里我建议务必传入。
  • ont = "ALL"会一次性输出BP、CC、MF三个维度的结果;如果只想跑BP,可以改成ont = "BP"。
  • pvalueCutoff = 0.05筛掉未通过的条目,qvalueCutoff = 0.2是qvalue的阈值,后者比p值校正更严格,实际看结果时我主要盯qvalue。
  • readable = TRUE会在结果里加一列symbol,方便直接看是哪些基因富集到该条目。

跑完后再看结果前几行,用head(as.data.frame(ego)),结果表里有几列核心指标,包括ID、Description、GeneRatio、BgRatio、pvalue、p.adjust、qvalue、geneID、Count。GeneRatio是差异基因中落在该条目的比例,BgRatio是背景基因中落在该条目的比例,Count是实际数量。真正汇报时,我通常报告GeneRatio和p.adjust,辅助报告Count。

4.3 KEGG富集完整代码与参数说明

KEGG的代码和GO非常像,只是物种和注释来源不同。这里以人类为例,organism = "hsa",按需换成mmu、rno:

ekegg <- enrichKEGG(gene = deg_entrez$ENTREZID, universe = bg_entrez$ENTREZID, organism = "hsa", keyType = "ncbi-geneid", pvalueCutoff = 0.05, qvalueCutoff = 0.2)

跑完同样用head(as.data.frame(ekegg))看结果。注意KEGG结果里面有时候会出现一个有点特殊的通路:并不是每个基因都能注释到Pathway,所以结果条目数可能会比GO少很多,这是正常的,不必担心。

还有一个值得注意的地方:很久以前KEGG富集会自动把基因转成KEGG内部ID,但接口更新后经常报错说gene ID类型不对。如果你直接用的是Entrez ID,记得显式加上keyType = "ncbi-geneid"。如果你不确定,可以先在控制台跑一下names(ekegg@kegg_rr)这种调试命令,或者直接用bitr_kegg做显式转换,把symbol或者Entrez ID换算成KEGG ID,再完成富集。

4.4 可视化:气泡图、条形图、通路图和富集网络图

富集结果光有表格远远不够,论文需要图。clusterProfiler几个配套可视化函数非常顺手,我在下面贴出最常用的一组。气泡图和条形图是两件套,几乎所有文章里都会出现:

library(enrichplot) library(ggplot2) p1 <- dotplot(ego, showCategory = 20) p2 <- barplot(ego, showCategory = 20)

气泡图的横轴是GeneRatio,纵轴是GO条目,点的大小对应Count,颜色对应p.adjust。阅读时重点看右上角区域:GeneRatio大、颜色红、点大,说明该条目富集程度高、贡献基因多、统计显著。

如果还想看基因在通路图上的位置,用pathview包或者clusterProfiler配套的viewPathway函数。pathview可以把差异基因的表达值映射到KEGG通路图上,看到哪些节点被上调、哪些被下调,这招在讲机制图的时候非常加分。用法大致是这样:

library(pathview) # 需要先构造一个名为pv_data的命名向量,key是Entrez ID,value是log2FC pathview(gene.data = pv_data, pathway.id = "hsa04110", species = "hsa", out.suffix = "cellcycle")

如果富集到的通路很多,条目之间又有重复基因,cnetplot和emapplot能帮你看出基因与功能之间的网络关系。cnetplot(ego)会画出基因和GO条目的连线网络,emapplot(ego)按基因重叠度把相似的GO条目连起来。这两个图适合放在补充材料里,也适合你快速筛出最重要的核心通路。

4.5 富集结果表格怎么读,怎么汇报

学会看结果表格比会跑代码更重要。我在实际带人时发现,很多人跑完富集之后只会截个图,根本不知道要汇报哪一列。

核心指标就三个:GeneRatio、p.adjust、Count。举个例子,结果里有"regulation of cell cycle"这一行,GeneRatio是0.25,p.adjust是1e-6,Count是45,那就说明你的差异基因里四分之一都和细胞周期调控有关,且统计显著性非常强。这个结论可以理直气壮写进文章。

汇报时还有个常见误区:只看排名最靠前的条目,不管它是否真的和你的生物学背景吻合。富集结果不会替你判断生物学意义,它只给你统计线索。比如肿瘤样本富集到"immune response"相关通路,这很合理;但富集到一个和实验模型毫无关联的嗅觉传导通路,即使p值再小,多半也是假阳性或者基因注释噪声,需要理性排除。

5. 实操踩坑实录与常见问题排查

5.1 基因ID转换后结果大量丢失

这是我见过最多的报错场景。差异基因表里的symbol明明都很标准,一跑富集提示匹配到0个基因。排查思路是先把bitr的结果看一下转换量。如果你输入1000个symbol,bitr只返回300个ENTREZID,那问题要么是基因命名版本不一致,要么是Excel自动把部分基因名改成了日期格式。比如有个基因叫SEPT1、SEPT2,在旧命名系统里会被Excel当成9月1日、9月2日。这种问题处理办法很简单:读入时加参数check.names = FALSE,或者在Excel里先把该列设成文本格式再导出。

5.2 KEGG富集报错:接口更新导致无法完成

clusterProfiler里的enrichKEGG依靠在线KEGG API,KEGG官方接口调整后,很多旧版本clusterProfiler会报类似API call blocked或wrong keyType的错误。解决办法有几个方向。

  • 升级clusterProfiler到最新版本。
  • 显式设置keyType = "ncbi-geneid",并且确保传入的基因ID是Entrez ID。
  • 如果你用的是比较老的教程代码,可能在enrichKEGG里传了gene = symbol而没有做转换,这时候KEGG找不到对应关系,果断回到bitr把symbol转成ENTREZID再跑。
  • 实在不行就退一步,用在线KEGG Mapper或DAVID跑,结果一样可以导出表。

这个坑我前前后后踩了两三次,现在固定流程是:GO用symbol转Entrez后直接跑,KEGG一定显式加keyType = "ncbi-geneid",再没有出过问题。

5.3 背景基因不设置导致的假富集陷阱

之前提过背景基因的坑,这里单独展开。假设你只把200个差异基因丢进enrichGO,而不给universe参数,clusterProfiler默认会用你传入的基因列表自身作为背景。这时富集检验会变得极其"宽松":本来需要对比"差异基因里的比例vs全基因组里的比例",现在变成拿差异基因自己和自己比,结果就是大量条目看起来显著,实则没有任何参考价值。

正确做法是把所有进入差异检验的基因作为backgound,代码看我4.2节里bg_entrez的构造部分。还有一个变体操作:有些人想把背景收紧到"在样本中表达的基因",这也可行,只要你能给出对应的基因列表。但要注意一致性,差异基因和背景基因必须来自同一套定量结果,不能差异基因来自A数据、背景来自B数据。

5.4 GO结果条目太多、太泛怎么收敛

BP结果一次出来几百条,看不过来是常态。我常用的收敛策略是三层。第一层,提高过滤阈值,把pvalueCutoff从0.05收紧到0.01,qvalueCutoff从0.2收紧到0.05。第二层,用simplify函数按语义相似度去冗余,它会保留代表性条目、合并相似的表述:ego_simp <- simplify(ego, cutoff = 0.7, by = "p.adjust", select_fun = min)。第三层,手动挑最贴近研究背景的功能条目往下挖,不要试图把所有条目都在文章里解释一遍,那不是信息量大,是没重点。

5.5 在线工具之间的结果差异

同一份基因列表,跑DAVID、Metascape、clusterProfiler,结果经常会有不同,尤其是具体条目的p值排序。差异来源主要是数据库版本、注释来源、背景基因逻辑和ID映射方式的区别。这不是bug,是每个工具的设计选择不同。我的建议是:以clusterProfiler的结果为主稿,用Metascape或Enrichr做交叉验证,如果核心通路在两个工具里都能稳定出现,那基本是靠谱的。如果只有某一个在线工具冒出来一个孤零零的显著通路,先别激动,回到基因列表里看一下是哪些基因贡献了富集,确认没有可疑的注释噪声再说。

6. 富集结果如何讲出真正有价值的生物学故事

6.1 从一堆显著条目中抓主线

很多人分析时能跑出图,但汇报时只会念条目名,把"富集到细胞周期、DNA复制、p53信号通路"三行字念出来就结束了。真正有价值的做法是,把这些条目归类到几个上层的主题里。

比如你发现GO-BP里大量富集到"DNA修复""细胞周期检查点""p53信号通路",同时KEGG里富集到"Homologous recombination""Fanconi anemia pathway",那主线就很清晰:你的处理可能诱导了DNA损伤应答和同源重组修复。下一步你再去看这些通路里的关键基因是不是差异表达,如果核心驱动基因表达趋势一致,那你整篇文章最核心的生物学故事就成立了。

我自己的习惯是做一个简单的三层逻辑图:差异基因层、功能主题层、表型验证层。差异基因是原料,功能主题是中间桥梁,表型验证对应你的实验设计。富集分析帮你在第二层把原料组织起来,但第三层必须靠你自己的实验和文献积累。

6.2 结合GSEA和趋势分析做交叉验证

富集分析还有一种常见补充方法叫GSEA(Gene Set Enrichment Analysis),它不需要先筛差异基因,而是拿所有基因的表达变化和排序去检验,可以在差异不显著的基因里发现协同变化的通路信号。简单来说,传统富集只看"差异基因列表里某通路占比高不高",GSEA看的是"按表达变化排序的所有基因中,某通路的基因是否整体偏向一端"。

我把GSEA当富集分析的交叉验证器。比如富集结果提示某个炎症通路显著,但差异基因列表里这个通路只有两三个基因,我通常会用GSEA再看一遍。如果GSEA也显示该通路显著富集,那说明这个通路里的更多基因在低幅度但一致地变化,结论更稳。如果你有兴趣,后面我可以单独写一篇GSEA的实操流程,包括输入格式和fgsea/rrvgo这些包的用法。

6.3 一张图搞定结果汇报:推荐组合与排版

文章里最常用的富集图组合方式是:一张GO气泡图加一张KEGG气泡图,或者直接用cnetplot画一个基因-功能关系网络。如果通路图比较清晰,再加一张pathview的通路图,图注里写明红色高表达、绿色低表达即可。

排版上有个小技巧:在dotplot里限制展示条目数到10到15个,太多点的图反而没有冲击力。颜色渐变区间建议用scale_colour_gradient(low = "red", high = "blue")或更保守的蓝白红,别用彩虹色。字体大小建议统一用theme_classic()配合base_size调整,这样图片放到PPT和论文里都协调。

7. 我自己的操作习惯与最后建议

这几次跑项目下来,我给自己总结了一套固定流程,现在每次拿到新数据都按这套来:先看差异基因总数和上下调比例,再跑GO富集,随后跑KEGG富集。跑完后我会把两个结果里的显著条目做一个交集,交集部分基本就是我要重点写的生物学主题。每次报告前,我还习惯随手做一步验证:到NCBI里查一下该条目里排名前几的基因,确认注释来源可靠,这个习惯帮我挡掉了至少两次注释版本导致的"乌龙"结论。

另外一件特别想说的事是:不要为了追求显著而反复调整阈值和参数。富集分析的参数在文章里是必须公开透明的,P值、Q值、背景基因都写清楚。你私下里多试几组参数做灵敏度分析没问题,但最后定下来的标准一定要符合领域惯例,并且能经得住别人用你上传的数据重新分析。数据分析和实验一样,可复现才是有价值的。

最后分享一个我自己常用的补充思路:如果你做的是时间序列或者多组别比较,可以考虑先把每一组差异基因分别做富集,再比较不同组别富集到的通路差异。这比把所有差异基因合并在一起跑一次富集更有层次,能直接看出"先激活了什么,后激活了什么"。我在多个项目里用这个思路做出来的通路动态变化图,审稿人反馈都比较好。

如果你也在做转录组,希望这篇对你有用。结合你自己的数据和实验背景去跑一轮富集,一定会有新的发现。

返回列表