BioF3 组学数据分析

04 火山图、热图与富集可视化

导出日期:2026年6月27日

04 火山图、热图与富集可视化

前三章把数据、差异和富集跑完,这一章只做一件事:把结果变成能直接用于论文和汇报的图。每张图都配一个最常见的用途,源码一起放出来。

配套脚本 bulk04_visualization_sci.R 把 DESeq2 + 富集的流程重跑一次(继承自 02 和 03),然后输出 6 张可视化成品:

Rscript scripts/bulk04_visualization_sci.R

一篇论文的 figure 怎么排

具体画图之前,先想清楚每张图在论文里要承担的角色。一篇典型的 RNA-seq 文章 main figure 大约 4-6 张图,结构通常是:

Figure 内容 本章对应
Fig 1 样本概览 + QC — PCA、距离热图、库大小 02 章的图 1-3
Fig 2 差异分析整体 — 火山图 + top DE 热图 本章 图 1-2
Fig 3 关键基因深入 — 已知通路基因表达分布 本章 图 3
Fig 4 功能富集 — KEGG / GO bubble + cnetplot 本章 图 4-5
Fig 5 跨数据集验证 看项目

一张图回答一个问题,不要堆 8 个 panel 的 supplementary 怪物。每张图问自己:"读者看完这张图能讲清楚什么?" 讲不清楚就拆开。

图 1:火山图

Volcano plot

火山图是每篇转录组文章的标配 —— 横轴是 log2 fold change,纵轴是 -log10(padj)。每个点是一个基因,颜色表示它在哪个象限。两条虚线标出 padj = 0.05 和 |LFC| = 1 的阈值。

这张图的关键是标注:默认标全部显著基因会完全盖掉点云。脚本里只标 top 10 up + top 10 down(按 padj 排序),用 ggrepel 自动避让。

核心代码:

ggplot(vol, aes(x = log2FoldChange, y = -log10(padj), color = sig)) +
  geom_point(size = 0.6, alpha = 0.5) +
  geom_text_repel(data = label_df, aes(label = SYMBOL), ...) +
  geom_vline(xintercept = c(-1, 1), linetype = "dashed") +
  geom_hline(yintercept = -log10(0.05), linetype = "dashed")

sig 列的阈值、head(10) 的数量、颜色都很方便。

图 2:Top DE 基因热图

Heatmap of top 40 DE genes

取 padj 最小的 40 个基因,在 VST 归一化后做 row z-score,画热图。列注释标出了处理状态和细胞系。

这张图在报告里的作用是给火山图"补一刀" —— 火山图只能看统计显著性,热图能直接看到"这些基因在每个样本里到底是什么表达水平"。列聚类会把处理样本和对照样本各自聚到一起;行聚类会把上调和下调基因分开。

pheatmap

mat_z <- t(scale(t(mat)))  # row z-score
pheatmap(mat_z,
  annotation_col = anno_col,
  color = colorRampPalette(c("blue", "white", "red"))(100),
  breaks = seq(-3, 3, length.out = 101))

breaks 参数很重要:限制颜色范围能避免个别极端基因把色阶拉歪。[-3, 3] 是 z-score 的常用范围。

图 3:精选基因的表达分布

Curated glucocorticoid response panel

选几个已知的糖皮质激素响应基因(DUSP1 / FKBP5 / ANGPTL4 / KLF15 / SPARCL1 / ZBTB16)画 boxplot + jitter,y 轴是 log10 的 normalized counts。

这种"已知通路验证"的图在讨论部分特别有用:文献说这些基因是地塞米松响应基因,我们的数据里也确实看到了这个响应,能直接把自己的数据和已有知识对接上。

ggplot(count_long, aes(x = dex, y = count, fill = dex)) +
  geom_boxplot() + geom_jitter() +
  facet_wrap(~ symbol, scales = "free_y") + scale_y_log10()

图 4:KEGG 富集气泡图

KEGG enrichment bubble plot

dotplot(ekegg) 的默认版本更有信息量:点大小是 gene count,颜色是 -log10(p.adj),横轴是 gene ratio。一张图同时显示"通路覆盖度"、"显著性"、"通路里命中了多少基因"三个维度。

这个 bubble 是纯 ggplot 写的,不依赖 enrichplot,完全可以移植到任何富集结果(enrichGOenrichPathwayGSEA 都行)。

ggplot(ekegg_df,
       aes(x = GeneRatio_num, y = reorder(Description, GeneRatio_num),
           size = Count, color = -log10(p.adjust))) +
  geom_point() +
  scale_color_gradient(low = "blue", high = "red") +
  scale_size_continuous(range = c(3, 9))

图 5:cnetplot 基因-通路网络

Gene-term cnetplot network

enrichplot::cnetplot 把 GO term 和驱动这些 term 的基因画成二部图:浅灰色大圆点是 term,彩色小点是基因,颜色按 log2FC 着色。

这张图的价值是一眼看到"哪些基因被多条通路共享" —— 共享基因往往是生物学上最关键的 master regulator。讨论部分里可以直接点出"图里 A 基因被 3 条通路共享,说明它很可能是这次处理的上游枢纽"。

图 6:GSEA NES forest 图

GSEA NES forest plot

GSEA 的瀑布图能表达排序,但多个 term 一起看、带显著性的信息,forest 图更合适。横轴是 NES,纵轴是 term 名字,点大小是 -log10(p.adj),颜色区分激活 / 抑制。

相比 waterfall,这张图额外能读出 NES 到 0 的距离(segment 线段)和 显著性强度(点大小),信息密度更高。

常见坑

  • 颜色:火山图用蓝 / 红 / 灰三色是惯例,别换成 viridis 这种连续色;读者会困惑"到底哪边是上调"
  • 标注:标签超过 30 个就没法读;能标 top 10-20 就够了
  • 基因 ID:热图 / 火山图要标 SYMBOL 不是 Ensembl。一定要在最后可视化之前把 SYMBOL 映射上
  • 坐标轴范围:火山图如果有极端 padj(比如 1e-300),用 pmax(padj, 1e-300) 封顶避免压扁整张图
  • 导出:论文图建议 dpi = 300 直接存 PNG;不要存 JPG(有压缩伪影);有时需要 TIFF / PDF 向量图,见 ggsave()device 参数

补充几条容易踩的坑:

坑:热图行做 z-score 但没限制 breaks

scale(t(mat)) 出来的 z-score 范围可能 [-10, 30],pheatmap 默认会把整个范围映射到色阶 — 结果 99% 的基因颜色都接近白色,几个极端基因把图"拉歪"。breaks = seq(-3, 3, length.out = 101) 是常用上限,超出范围的值压到端点。

坑:火山图 padj = 0 的基因显示为 Inf

部分 DE 工具会输出 padj = 0(数值下溢),-log10(0) = Inf 让 ggplot 报错或丢点。pmax(padj, 1e-300) 封顶再画。

坑:cnetplot 的基因数太多挤成一团

cnetplot(showCategory = 5) 默认显示前 5 条 term,如果 term 之间共享基因多,几十个基因挤成一坨可读性归零。显式 showCategory = 3 + node_label = "category" 只标 term 不标基因,更清晰。

坑:ggsave PDF 字体丢失

ggsave(file = "fig.pdf") 在某些字体下会输出"被吃掉部分字体"的 PDF,投稿时被拒。device = cairo_pdf 强制嵌入字体,或者直接存 png 300dpi。

坑:facet_wrap 的 free_y 让 boxplot 跨基因不可比

facet_wrap(~ symbol, scales = "free_y") 让每个基因的 y 轴独立 — 视觉上"差异都看上去差不多大"。展示绝对幅度差异时用 scales = "fixed",让读者能看出"基因 A 的 fold 比基因 B 大多少"。

下载资源

bulk04_visualization_sci.R
16 KB
下载 airway 可视化完整脚本

下一步

bulk RNA-seq 专栏目前到这一章为止。后面会补:

  • 多时间点或剂量响应的差异分析(LRT vs Wald
  • 批次效应更复杂的场景(RUVseq、SVA)
  • 面向发表的结果组织(fgsea + MSigDB Hallmarks + Reactome)
  • 单细胞 vs bulk 的方法学对照

接着深入

横向延伸

  • FigCode 火山图 — 在线把你的 DE 表替换 demo 数据出图
  • FigCode 热图 — 复杂热图(多列 annotation、自定义聚类)的样板代码

参考资源