
做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三层结构的标准功能词典GOGene 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从基因到通路网络KEGGKyoto Encyclopedia of Genes and Genomes是另一套体系核心是KEGG Pathway数据库。它不像GO那样描述单个基因的属性而是把基因放进代谢和信号转导的网络里告诉你这些基因共同参与了一条通路。比如你富集到p53 signaling pathwayKEGG会展示这张通路图里面有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 RateFDR体现在结果里就是p.adjust或padj列。clusterProfiler默认的qvalueCutoff参数就是在这个基础上再算一个qvalue。我看到很多新手只看p值不看padj结果被一波假阳性坑惨这个问题后面还会点名说。3. 工具选型R包还是在线平台3.1 为什么推荐clusterProfiler目前做GO和KEGG富集R语言里最主流的工具就是clusterProfilerY叔开发的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或者只是快速验证一下结论在线平台会更顺手。DAVIDDatabase 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、rnoekegg - 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(ekeggkegg_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 IDvalue是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.25p.adjust是1e-6Count是45那就说明你的差异基因里四分之一都和细胞周期调控有关且统计显著性非常强。这个结论可以理直气壮写进文章。汇报时还有个常见误区只看排名最靠前的条目不管它是否真的和你的生物学背景吻合。富集结果不会替你判断生物学意义它只给你统计线索。比如肿瘤样本富集到immune response相关通路这很合理但富集到一个和实验模型毫无关联的嗅觉传导通路即使p值再小多半也是假阳性或者基因注释噪声需要理性排除。5. 实操踩坑实录与常见问题排查5.1 基因ID转换后结果大量丢失这是我见过最多的报错场景。差异基因表里的symbol明明都很标准一跑富集提示匹配到0个基因。排查思路是先把bitr的结果看一下转换量。如果你输入1000个symbolbitr只返回300个ENTREZID那问题要么是基因命名版本不一致要么是Excel自动把部分基因名改成了日期格式。比如有个基因叫SEPT1、SEPT2在旧命名系统里会被Excel当成9月1日、9月2日。这种问题处理办法很简单读入时加参数check.names FALSE或者在Excel里先把该列设成文本格式再导出。5.2 KEGG富集报错接口更新导致无法完成clusterProfiler里的enrichKEGG依靠在线KEGG APIKEGG官方接口调整后很多旧版本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.01qvalueCutoff从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 recombinationFanconi anemia pathway那主线就很清晰你的处理可能诱导了DNA损伤应答和同源重组修复。下一步你再去看这些通路里的关键基因是不是差异表达如果核心驱动基因表达趋势一致那你整篇文章最核心的生物学故事就成立了。我自己的习惯是做一个简单的三层逻辑图差异基因层、功能主题层、表型验证层。差异基因是原料功能主题是中间桥梁表型验证对应你的实验设计。富集分析帮你在第二层把原料组织起来但第三层必须靠你自己的实验和文献积累。6.2 结合GSEA和趋势分析做交叉验证富集分析还有一种常见补充方法叫GSEAGene 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值、背景基因都写清楚。你私下里多试几组参数做灵敏度分析没问题但最后定下来的标准一定要符合领域惯例并且能经得住别人用你上传的数据重新分析。数据分析和实验一样可复现才是有价值的。最后分享一个我自己常用的补充思路如果你做的是时间序列或者多组别比较可以考虑先把每一组差异基因分别做富集再比较不同组别富集到的通路差异。这比把所有差异基因合并在一起跑一次富集更有层次能直接看出先激活了什么后激活了什么。我在多个项目里用这个思路做出来的通路动态变化图审稿人反馈都比较好。如果你也在做转录组希望这篇对你有用。结合你自己的数据和实验背景去跑一轮富集一定会有新的发现。