做转录组、蛋白组或者代谢组的同学,应该都有过这种经历:分析流程跑完,得到一张KEGG富集表,上千个通路名排在面前,然后被组会问到“你这张表怎么变成图”。KEGG条形图就是解决这个问题的最常见方案——把富集到的通路名称、差异基因富集数目、显著性P值这三个核心信息,压缩成一张几秒钟就能看懂的横向条形图,直接放进论文结果部分。它是组学文章里出场率最高的图之一,适合所有需要展示蛋白功能、代谢通路或疾病机制的研究场景。这篇文章我会从KEGG富集分析结果到底怎么看讲起,结合TBtools和R语言两种常用出图方式,把数据筛选、排序、配色、避坑一次讲清楚。
1. 从富集表格到图形的设计思路
1.1 KEGG条形图到底在画什么
KEGG是一个通路数据库,核心思路是根据测序得到的基因列表,去匹配数据库中已知的通路信息,判断这些差异基因集中参与哪些生物学过程,这个分析过程叫“富集分析”。富集分析的输出通常是表格,表格里每一行是一条通路,每一列是通路名、基因数、显著性等指标。条形图要做的,就是把这个表格里的关键维度转成图形:通路名称沿纵轴排列,条形长度代表富集到的基因数目或者比例,颜色深浅代表显著性P值。
按我的习惯,这里的“条形图”一般指横向条形图。原因很简单,KEGG通路名字都很长,比如“PI3K-Akt signaling pathway”这一串,放在横轴作分类标签根本排不开,旋转成竖排又不方便审稿人阅读。横向排布时,通路名在左侧纵向展开,条形向右延伸,版面利用率最高。x轴可以选Count(富集到该通路的基因数目),也可以选GeneRatio(富集基因数占输入基因总数的比例);颜色则固定映射p.adjust一类显著性指标。一张图同时承载三个维度的信息,这才是富集条形图和普通柱状图的本质区别。
1.2 为什么优先选条形图而不是气泡图
很多新手会问,KEGG富集结果不是还有气泡图(dotplot)吗?为什么论文里条形图依然很常见。我的看法是,条形图本质上和气泡图展示的信息几乎是同一批,但条形图在四个场景下有不可替代的优势。第一,通路名称足够长时,条形图横向排列,标签排得开,气泡图如果不做分面,文字容易重叠。第二,黑白打印时条形图依然可读,气泡图一旦转成灰度,点的大小和颜色同时表达信息,很容易混淆。第三,条形图更容易做分组、分面,比如按KEGG一级通路类别分块排列,代谢通路一组、遗传信息处理一组,版面逻辑非常清楚。第四,绘图工具门槛低,无论是TBtools这种图形界面软件,还是几行R代码,都能快速出图。
不过条形图不是万能的。如果显著通路非常多,比如超过40条,所有条形挤在一起基本没有可读性,这时候要么收缩到Top20或Top30,要么换成气泡图控制点的大小,要么用富集圈图这类能展示基因和通路关系的形式。另外,如果你重点想强调的是每个通路里具体富集了哪些基因,条形图表达不了,需要配合KEGG通路图或者基因-通路网络图。判断清楚再动手,能省掉大量返工时间。
1.3 一张KEGG条形图应该包含哪些必备元素
按我自己的投稿经验,一张能被审稿人接受的KEGG条形图,至少要有五个要素:通路名称、条形长度指标、显著性标记、样本或比较组说明、坐标轴和图例。通路名称不用说我直接用Description列;条形长度指标在图中要标注清楚是基因数还是基因比例,否则读者不知道条形长短代表什么;显著性标记一般在图例里说明P值矫正方法;比较组说明通常放在图注里,写明是哪两个条件做差异分析;坐标轴和图例则要符合期刊格式要求。
如果还想让图更专业,可以把通路按KEGG一级分类着色,或者在图旁边加一条通路所属类别的注释列。很多高分的组学文章里,KEGG条形图都是分类着色的,一眼能看出差异基因集中在“代谢”还是“疾病通路”,这比清一色红蓝渐变更信息量高。后面第3部分我会给出这种分类着色的实现代码。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 绘图前的数据准备与字段理解
2.1 富集表格里每个字段到底是什么意思
无论你的数据来自clusterProfiler、DAVID、TBtools还是其他在线工具,最终交付的富集表核心字段基本一致。我逐个拆开讲一遍,免得画图时搞混。
- ID:KEGG通路编号,格式通常是
path:map04930或map04930。这个编号是后续映射到通路图的关键,也是判断通路类别的依据。 - Description:通路名称,比如“Pathways in cancer”或“PI3K-Akt signaling pathway”。画图时显示的标签就是这一列。
- GeneRatio:富集到该通路的差异基因数除以输入的总差异基因数,格式通常是
20/500。这个比值直接反映了富集强度,比单纯计数更公平。 - BgRatio:背景基因中位于该通路的基因数占背景基因总数的比例,通常用来参与富集检验计算,不需要直接画到图里。
- pvalue:富集检验的原始P值,反映该通路是否显著富集。
- p.adjust:多重比较校正后的P值,一般用BH方法计算,这是最常用的显著性过滤条件,也经常用来映射图的颜色。
- qvalue:错误发现率FDR对应的q值,与p.adjust概念相近,部分工具会同时输出。
- Count:富集到该通路的差异基因数目,很多条形图直接拿它做x轴长度。
- geneID:富集到该通路的差异基因符号列表,逗号分隔。做通路图时可以直接用,但画条形图用不上。
很多人只盯着pvalue,忽略GeneRatio,这是个误区。如果你的不同样本差异基因总数差异很大,比如样本A有200个差异基因、样本B有1000个差异基因,直接比Count不公平,用GeneRatio更合理。我会在数据清洗时把GeneRatio拆成数值,后面按需选择画图指标。
2.2 pvalue、p.adjust和qvalue,到底用哪个做排序和颜色
这个问题几乎每次公开课都有人问。pvalue是单次检验的原始P值,只要做了多次通路比较,就必须做多重检验校正,否则假阳性很多。p.adjust和qvalue在概念上很接近,都是控制多重比较错误率,只是算法细节略有区别,clusterProfiler输出里两者数值经常一样。画图时统一用p.adjust排序并作为填充颜色,科学性和可复现性都更好。
但实际分析中有个棘手问题:非模式生物或者样本量小的时候,富集结果可能完全不显著,严格执行p.adjust<0.05,图上一个通路都没有。这时候我建议退一步,展示“候选通路”,用原始pvalue<0.05过滤,并在图注里写明“p-value < 0.05, unadjusted”,审稿人一般能接受。还有一个妥协方案:直接按pvalue从小到大取Top10或Top15,不承诺显著性,只展示富集趋势。比硬画一张空图强得多。第3部分的代码里,我会把这个判断逻辑写进脚本。
2.3 筛选Top N的两种逻辑和适用场景
筛选前N条通路,有人直接head(10),这没问题,但很容易出现一种尴尬:前10条全是p值最小的,但其中几条通路功能高度重复,比如“Pathways in cancer”和“Proteoglycans in cancer”包含的基因几乎一样,图里看起来像复制粘贴。为了避免这种冗余,我会多做一个步骤:先按p.adjust过滤显著,然后按Count降序排列取前N,这样能兼顾富集强度和基因覆盖度。如果你特别关心某个研究方向,也可以手动指定保留哪些通路,比如只保留代谢相关的几条,再补几条疾病通路凑数。
第二种场景是显著通路非常多,几十上百条,全画上去会密成一团。这种时候不能只是简单head,最好先按通路一级分类聚合,比如“Metabolism”类里取前3条、“Human Diseases”类里取前3条,这样每个功能模块都有代表性通路,整体图面信息量更大。分类信息可以从KEGG官网下载,也可以用clusterProfiler的enrichKEGG结果里的ID前缀去匹配,后面第3.3部分我会给代码思路。
2.4 把原始富集表整理成可直接画图的数据
假设你已经从clusterProfiler拿到了KEGG富集结果,数据框里包含前面说的那几列。我建议保存成CSV之后,先做一次标准化清洗,把所有要用的列名统一成英文,去掉空格,再拆出GeneRatio数值。下面这段R代码是我最常用的清洗逻辑:
r复制library(dplyr)
library(tidyr)
kegg_raw <- read.csv("kegg_enrichment.csv", stringsAsFactors = FALSE)
# 把GeneRatio拆成数值,方便后续排序和计算
kegg_clean <- kegg_raw %>%
separate(GeneRatio, into = c("enriched", "total"), sep = "/", remove = FALSE) %>%
mutate(
enriched = as.numeric(enriched),
total = as.numeric(total),
GeneRatioValue = enriched / total
)
# 先按padjust筛选显著,不足5条就放宽到pvalue
sig <- kegg_clean %>% filter(p.adjust < 0.05)
if (nrow(sig) < 5) {
sig <- kegg_clean %>% filter(pvalue < 0.05)
}
if (nrow(sig) < 5) {
sig <- kegg_clean %>% arrange(pvalue) %>% head(10)
}
# 取前15条,按p.adjust排序,保持图面不过密
plot_data <- sig %>% arrange(p.adjust) %>% head(15)
# 转成因子并反向排序,让最显著的通道出现在图顶部
plot_data$Description <- factor(plot_data$Description,
levels = rev(unique(plot_data$Description)))
write.csv(plot_data, "kegg_plot_data.csv", row.names = FALSE)
这段代码里有几个细节。separate函数来自tidyr包,如果你没有加载会直接报错。levels = rev(unique(plot_data$Description))这一行的作用是让p.adjust最小的通路显示在图的最上方,符合生物学文章从上往下阅读的习惯。如果你更想让基因数最多的通路在最上面,可以先按Count排序再设置因子。我实测下来,先用unique()而不是直接factor(plot_data$Description)可以避免重复因子级别的警告,尤其在数据有多行重复通路名的时候。
3. 实操:R语言ggplot2绘制KEGG条形图
3.1 环境准备与基础包安装
用R画KEGG条形图,核心包就三个:ggplot2、dplyr、forcats。forcats提供fct_reorder这类专门处理因子排序的函数,比手工factor省事不少。先安装并加载:
r复制install.packages(c("ggplot2", "dplyr", "forcats", "tidyr", "stringr"))
library(ggplot2)
library(dplyr)
library(forcats)
library(tidyr)
library(stringr)
除此之外,如果你要读取Excel格式的富集表,可以额外装readxl;如果要导出PDF,cairo设备在Windows上需要Rtools支持,但一般ggsave自带pdf设备就够了。数据约定也很简单,使用第2.4部分生成的kegg_plot_data.csv,里面至少有Description、Count或GeneRatioValue、p.adjust这几列。如果后期想按KEGG一级通路分组,还需要增加Category列,这个可以由你自己映射补充。
3.2 一组能直接运行的ggplot2绘图代码
第一版代码不做花哨修饰,先把图跑通:
r复制plot_data <- read.csv("kegg_plot_data.csv", stringsAsFactors = FALSE)
# 用fct_reorder让条形按Count排序,desc=TRUE让最大的条形在上方
plot_data$Description <- fct_reorder(plot_data$Description, plot_data$Count, .desc = TRUE)
p <- ggplot(plot_data, aes(x = Count, y = Description, fill = p.adjust)) +
geom_col(width = 0.7) +
scale_fill_gradient(low = "#d73027", high = "#4575b4", name = "p.adjust") +
labs(x = "Number of genes", y = NULL, title = "KEGG enrichment") +
theme_minimal(base_size = 12) +
theme(
panel.grid.major.y = element_blank(),
panel.grid.minor = element_blank(),
legend.position = "right"
)
print(p)
逐行解释关键点。aes(x = Count, y = Description, fill = p.adjust)里,Count控制条形长度,Description是分类轴,p.adjust映射到颜色。这里我没有用geom_bar(stat = "identity"),而是直接用geom_col(),因为geom_col默认统计的就是identity,代码更简洁。fill = p.adjust是连续变量,所以用scale_fill_gradient来定义渐变颜色。low设成红色系#d73027,high设成蓝色系#4575b4,意味着p.adjust越小越红、越不显著越蓝。这是我个人偏好的配色,红色一眼就能让人抓出最显著的通路。
如果你想让颜色反过来,把low和high互换就行。注意theme_minimal()会让网格线变得很浅,我在theme里额外去掉了y轴的主要网格线和所有次要网格线,这样条形和标签之间的视觉干扰会小很多。图例默认在右边,如果通路名很长,建议保留右侧图例而不是底部,否则宽度很紧张。
3.3 让条形图真正达到投稿级别:换行、分组、配色
第一版能跑,但离投稿还有距离。我总结四个必调项。
第一个是通路名换行。KEGG通路名长到离谱,直接显示会横向溢出图外。建议用stringr::str_wrap在固定宽度处自动换行:
r复制plot_data$Description_wrap <- stringr::str_wrap(plot_data$Description, width = 40)
width=40表示在40个字符宽度处换行。具体宽度根据你的最终图片宽度调整,一般双栏图用40,全宽图可以用60。
第二个是按KEGG一级分类分组。clusterProfiler的enrichKEGG结果里,ID列会保留通路编号,你可以根据编号前缀映射到一级分类。更省事的方式是在富集表里预先加一列Category,然后做一个前缀标记:
r复制plot_data <- plot_data %>%
mutate(
Description_wrap = str_wrap(Description, width = 40),
Category = case_when(
grepl("map01|map02|map03|map04|map05|map06|map07|map08|map09|map10|map11|map12", ID) ~ "Metabolism",
grepl("map03", ID) ~ "Genetic Information Processing",
TRUE ~ "Other"
)
)
这个映射规则不能照抄,KEGG的map编号分类需要查数据库说明,不同版本不一样,最可靠的办法是下载KEGG分类表手动合并。分组之后,可以用分面画图,也可以把Category拼到Description前面做视觉分组。我常用的是拼前缀方案:
r复制if ("Category" %in% colnames(plot_data)) {
plot_data <- plot_data %>%
mutate(Description_wrap = paste0(Category, "_", Description_wrap))
}
这样同一类的通路会连续排列,并且分类名直接出现在纵轴标签里。
第三个是色阶变换。p.adjust经常跨度好几个数量级,从1e-06到0.04都有,直接用线性色阶会让多数条形颜色差不多。我建议加trans = "log10":
r复制p <- ggplot(plot_data, aes(x = Count, y = Description_wrap, fill = p.adjust)) +
geom_col(width = 0.7) +
scale_fill_gradient(
low = "#d73027", high = "#4575b4",
name = "p.adjust",
trans = "log10",
breaks = c(0.001, 0.01, 0.05),
labels = c("0.001", "0.01", "0.05")
)
唯一需要注意的是,p.adjust如果等于0,log10变换会变成负无穷,触发警告。遇到这种情况,先把0替换成一个比最小值还小的数,比如:
r复制plot_data$p.adjust[plot_data$p.adjust == 0] <-
min(plot_data$p.adjust[plot_data$p.adjust != 0]) * 0.1
第四个是图例和主题细节。我会把主标题去掉,因为论文里图注会写清楚,避免和正文重复。网格线保留横向的浅色线,帮助读者对齐条形末端。图例位置如果通路数少、图面窄,就放右边;如果通路数和条形都很多,放底部并拉长色条:
r复制theme_bw(base_size = 12) +
theme(
panel.grid.minor = element_blank(),
legend.position = "bottom",
legend.key.width = unit(1.5, "cm")
)
3.4 导出高质量图片的尺寸与格式
画完之后,保存成PDF和PNG两版。PDF是矢量图,后续在AI或Illustrator里可以继续标注;PNG用于快速预览和贴进草稿文档。保存代码:
r复制ggsave("KEGG_barplot.pdf", p, width = 8, height = 6, dpi = 300, device = "pdf")
ggsave("KEGG_barplot.png", p, width = 8, height = 6, dpi = 300)
尺寸按通路数量来定。15条通路用8x6英寸合适,如果增加到25条,高度要提到8到9英寸,否则每根条形挤在一起像梳子。宽度一般控制在7到8英寸,投稿双栏排版时缩放后也够清晰。dpi用到300,个别期刊要求600,那就直接用600导出。
中文通路名在这里是个大坑。ggplot2的默认pdf设备不支持中文字体,导出后中文字符会变方框。解决方法是改用cairo_pdf(),或者把字体明确指定为中文安装字体。最稳妥的方案是在数据清洗时统一把通路名换成英文,反正KEGG数据库本身标椎名称就是英文,检索修改也更方便。
4. 实操:TBtools方案与两种方式对比
4.1 TBtools里KEGG注释和富集的基本流程
如果你不太写R,用TBtools也能画出KEGG条形图。TBtools是华南农业大学团队开发的图形界面工具,在基因组和转录组分析里非常常见,尤其适合没有编程基础的同学。热度词里提到的“TBtools基因组GO和KEGG注释”,就是指用TBtools把基因组蛋白序列批量注释到GO和KEGG数据库,拿到功能注释结果后,再进一步做富集分析和绘图。
用TBtools做KEGG注释的大致流程是:准备蛋白序列FASTA;用BLAST比对到KEGG数据库;提取KO号;将KO号映射到通路;得到注释结果。这个过程在TBtools菜单里有对应功能模块,不同版本菜单名称有差异。我用的版本路径是文档里叫“KEGG Pathway Annotation”的入口。关键是,注释结果最终会形成一个类似富集表的文件,里面包含通路描述、注释基因数、显著性等列,条形图直接吃这个文件就够了。
4.2 TBtools里绘制KEGG条形图的直接操作步骤
如果你已经拿到了富集表格,画图操作大概是这样的。打开TBtools,找到与Bar Plot相关的工具,导入富集表。
- 先确认数据格式:建议把表格整理成三列或者四列,分别是Description、Value、Pvalue,再多一列Category可选。千万别直接在原始富集表上画,那玩意几十列容易列匹配错。
- 在工具界面里指定哪一列是通路描述、哪一列是数值、哪一列是P值。这个步骤里,TBtools往往通过列号来匹配,一旦你表格里多了一列ID,就要仔细核对。
- 排序选项:选择按Value降序排列,通常就是按Count或GeneRatio降序。
- 颜色样式:可以选择渐变模式,让条形按P值大小上色;也可以统一颜色,再加标签标注P值。
- 输出:导出PDF、SVG或PNG,设置好尺寸和dpi。
我在实际操作里踩过最深的坑是列匹配。TBtools有些Bar Plot模块只显示列号,不显示列名,比如“Column 1”指第一列。我把ID列放在第一列、Description放第二列,结果默认匹配把编号当下拉项,画出来的条形全错。后来我学乖了,上传前先把文件整理成干净的三列:Description、Value、Pvalue,严格按照这个顺序放。列数量越少,选错概率越低。
4.3 R语言与TBtools怎么选:一张选型表说清楚
| 对比维度 | R语言 + ggplot2 | TBtools |
|---|---|---|
| 学习门槛 | 需要基础R知识 | 图形界面,几天能上手 |
| 数据可溯源性 | 完整脚本可复现 | 依赖点击操作,复现性一般 |
| 绘图灵活性 | 极高,配色、布局、分面自由控制 | 低,只有预设选项 |
| 批量出图 | 适合循环处理几十张图 | 一次一张,手动操作多 |
| 投稿适用性 | 高,可直接输出矢量图 | 中,输出质量足够但修改空间小 |
| 适合人群 | 愿意花一点时间学R的人 | 纯湿实验背景、追求快速出图 |
我的习惯是:前期探索用TBtools快速看富集通路大概有哪些,之后的正式出图一定用R做一版,并把脚本存进项目仓库。原因很简单,论文审稿人越来越看重分析可复现性,给脚本比说“我在软件里点了哪里”正式得多。如果你身处一个完全没有R基础的课题组, TBtools出的图也够用,关键是图注里写清楚分析工具和参数版本即可。
5. 常见问题与排查技巧实录
5.1 我遇到过的一批坑和解决办法
这些年帮不少同学改图,下面这些问题是出现频率最高的,我整理成了速查表。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 条形顺序混乱,按字母排 | Description列是字符而非因子 | 用fct_reorder或factor()设置levels |
| 通路名太长互相重叠 | 标签默认单行显示 | str_wrap换行,或增大图高,或减少条数 |
| 颜色全部一个色调 | p.adjust方差太小,或fill映射配错 | 用log10变换色阶,检查fill参数 |
| 图例显示1e-04科学计数 | 默认格式未调整 | 在scale_fill_gradient里设置breaks和labels |
| 中文通路名乱码 | PDF设备不支持中文字体 | 导出PNG,或cairo_pdf,或统一用英文名 |
| 没有显著通路,图全空 | p.adjust过滤太严格 | 放宽到pvalue,或展示TopN并注明 |
| 读取CSV后列名带X. | read.csv默认处理非法列名 | 加check.names = FALSE |
| TBtools画出0值条形 | 列匹配错位 | 核对列名,用干净的三列数据 |
5.2 没有足够显著通路时怎么办
非模式生物、小样本或者基因数量少的时候,KEGG富集结果不显著是常态。我的经验是先看差异基因总数,如果本身只有几十个基因,富集到通路的数量少很正常,这时候不应该硬做一步“假富集”。更好的办法是把pvalue阈值放宽到0.1,并明确在图注里写“p-value < 0.1, unadjusted”。另一种思路是图中不展示P值,只用Count画一个简单条形图,同时把pvalue放在补充表格里供有需要的读者查阅。这样既保留了信息,也不会误导审稿人。
5.3 最重要的一条经验:画图前先检查基因集合
最后分享一个我自己的习惯。画KEGG条形图之前,先花五分钟检查一下富集表格里的差异基因数目,是不是所有输入基因都被算进去了。很多人直接从差异分析结果里筛出显著基因,连线粒体基因、核糖体基因都算进去,结果富集出来的全是管家基因通路,看起来很“标准”,但实际上没有任何生物学特异性。我的做法是富集前先对基因做一次功能注释筛选,去掉核糖体、线粒体翻译这类“万金油”通路基因,或者至少在图上把核糖体通路高亮并解释一句,省得审稿人问为什么全是这些通路。
再补充一点,KEGG是通路型数据库,通路名是层次化结构。画图前可以先把通路按一级分类整理成子类,我用clusterProfiler的时候会手动从KEGG官网下载通路分类表,把每个富集通路打上一级分类标签。这样条形图可以进一步分面,审稿人一眼看出你的差异基因集中在代谢还是疾病通路,比单纯Top20的信息量高很多。
我个人实际操作的体会是,KEGG条形图看着简单,真正要画得好看且不被审稿人挑出毛病,关键还是数据清洗和排序逻辑。TBtools适合快速扫一眼趋势,R语言ggplot2适合出终稿。先学会看懂富集表格里的每个字段,再动手画,比我当初一上来就套模板高效得多。如果你手里正好有一张富集表,拿上面的代码直接跑一遍,基本能解决80%的画图需求。剩下20%,大多是数据本身的生物学问题,那可不是改图能补救的了。
