热图可视化:pheatmap与ComplexHeatmap

2327 字
12 分钟
热图可视化:pheatmap与ComplexHeatmap

热图是表达数据可视化最常用的图表类型。pheatmap 提供三行代码出图的基础方案,ComplexHeatmap 则支持高级布局、行列注释和多图拼接,适合发表场景。本文覆盖两个 R 包的数据标准化、配色调色板、注释配置和导出选项。

实测环境:R 4.4.1,pheatmap 1.0.12,ComplexHeatmap 2.18.0,Debian 13。

1. 数据准备——生信热图的数据长什么样#

热图的输入本质就是一张数值矩阵。生信中最常见的场景:

# 模拟一个 RNA-seq 表达矩阵:100个基因 × 10个样本
set.seed(42)
expr_matrix <- matrix(
rnorm(1000, mean = 5, sd = 2),
nrow = 100,
ncol = 10,
dimnames = list(
paste0("Gene", 1:100),
paste0("Sample", 1:10)
)
)
# 人为制造一些差异(让热图有层次感)
expr_matrix[1:30, 1:5] <- expr_matrix[1:30, 1:5] + 3 # 前30个基因在前5个样本高表达
expr_matrix[71:100, 6:10] <- expr_matrix[71:100, 6:10] + 3 # 后30个基因在后5个样本高表达
head(expr_matrix[, 1:5])

真实场景中这个矩阵来自 DESeq2 的 normalized counts、limma 的差异基因列表、或者 WGCNA 的模块表达值。几行代码就能从 DESeq2 提取:

# 从 DESeq2 对象提取标准化表达矩阵
# dds <- DESeqDataSetFromMatrix(...)
# dds <- DESeq(dds)
# vsd <- vst(dds, blind = FALSE)
# expr_matrix <- assay(vsd)

2. pheatmap——三行代码出热图#

library(pheatmap)
# 最简单的热图
pheatmap(expr_matrix)

就这么简单。但默认参数出来的图有这些问题:基因名太多挤成一团、颜色默认红蓝不太友好、没做行标准化看不出相对差异。

2.1 加上标准化和聚类#

pheatmap(expr_matrix,
scale = "row", # 按行做 Z-score 标准化
cluster_rows = TRUE, # 行聚类
cluster_cols = TRUE, # 列聚类
show_rownames = FALSE, # 基因太多,不显示名字
show_colnames = TRUE,
main = "RNA-seq Expression Heatmap"
)

scale = "row" 是生信热图最关键的一个参数。表达式原始值的量级差异很大(基因A表达量 0.5,基因B表达量 5000),不标准化的话热图只能看到少数高表达基因,其他全是暗色。

Z-score 的公式:

zij=xijμiσiz_{ij} = \frac{x_{ij} - \mu_i}{\sigma_i}

其中 μi\mu_i 是基因 ii 在所有样本中的均值,σi\sigma_i 是标准差。标准化后每个基因的均值 = 0,标准差 = 1,不同基因之间就”可比”了。

2.2 配色——告别红蓝默认色#

红蓝配色在色盲群体中难以区分,而且生信文献里几乎没人用。主流选择:

# 白-橙-红(表达量热图)
pheatmap(expr_matrix,
scale = "row",
color = colorRampPalette(c("white", "orange", "red"))(100),
show_rownames = FALSE,
main = "White-Orange-Red"
)
# 蓝-白-红(差异热图,白=不变,蓝=下调,红=上调)
pheatmap(expr_matrix,
scale = "row",
color = colorRampPalette(c("navy", "white", "firebrick3"))(100),
show_rownames = FALSE,
main = "Blue-White-Red (common in papers)"
)
# viridis 配色(色盲友好,越来越流行)
library(viridis)
pheatmap(expr_matrix,
scale = "row",
color = viridis(100),
show_rownames = FALSE,
main = "Viridis (colorblind-friendly)"
)

colorRampPalette 生成 100 个渐变色阶,越多越平滑。我一般杂志投哪个就用哪个配色,投 Nature 系列可以用 viridis,投 Cell 系列蓝白红更常见。

2.3 添加注释——这张图里最加分的部分#

纯热图只告诉你”这些基因在这些样本里高表达”,加上注释后可以同时展示样本分组、临床信息、突变状态等。

# 样本注释(列注释)
annotation_col <- data.frame(
Group = factor(rep(c("Control", "Treatment"), each = 5)),
Batch = factor(rep(c("Batch1", "Batch2"), times = 5)),
row.names = paste0("Sample", 1:10)
)
# 基因注释(行注释,比如标记哪些是差异基因)
annotation_row <- data.frame(
GeneClass = factor(c(
rep("Oncogene", 30),
rep("Tumor Suppressor", 30),
rep("Other", 40)
)),
row.names = paste0("Gene", 1:100)
)
# 注释配色
ann_colors <- list(
Group = c(Control = "#4DBBD5", Treatment = "#E64B35"),
Batch = c(Batch1 = "#00A087", Batch2 = "#7E6148"),
GeneClass = c(Oncogene = "#DC0000", `Tumor Suppressor` = "#3C5488", Other = "#8491B4")
)
pheatmap(expr_matrix,
scale = "row",
color = colorRampPalette(c("navy", "white", "firebrick3"))(100),
annotation_col = annotation_col,
annotation_row = annotation_row,
annotation_colors = ann_colors,
show_rownames = FALSE,
main = "Expression Heatmap with Annotations",
cutree_rows = 3, # 把行聚类切成3个模块
cutree_cols = 2 # 把列聚类切成2组
)

cutree_rowscutree_cols 会自动在热图上画出聚类分割线——审稿人看到这条线就知道你做了聚类。

2.4 保存图片#

# png 格式
png("heatmap.png", width = 8, height = 10, units = "in", res = 300)
pheatmap(expr_matrix, scale = "row", show_rownames = FALSE)
dev.off()
# pdf 格式(矢量,适合投稿)
pdf("heatmap.pdf", width = 8, height = 10)
pheatmap(expr_matrix, scale = "row", show_rownames = FALSE)
dev.off()
# tiff 格式(部分期刊要求)
tiff("heatmap.tiff", width = 8, height = 10, units = "in", res = 300)
pheatmap(expr_matrix, scale = "row", show_rownames = FALSE)
dev.off()

注意 pheatmap 的输出是 grid 对象,不能直接用 ggsave。用基础的 png()/pdf()/tiff() + dev.off() 即可。

3. ComplexHeatmap——进阶玩家的选择#

pheatmap 能应付 80% 的场景,剩下的 20%——比如拼多列热图、自定义图例位置、oncoprint 等——需要 ComplexHeatmap。我一般简单探索用 pheatmap,要发表的图用 ComplexHeatmap。

3.1 基础用法#

library(ComplexHeatmap)
library(circlize) # ComplexHeatmap 依赖 circlize 做配色
Heatmap(expr_matrix,
name = "Expression", # 图例标题
col = colorRamp2(c(-2, 0, 2), c("navy", "white", "firebrick3")),
row_names_gp = gpar(fontsize = 6),
column_names_gp = gpar(fontsize = 8),
show_row_names = FALSE,
cluster_rows = TRUE,
cluster_columns = TRUE
)

colorRamp2colorRampPalette 更精确——你指定断点和对应的颜色,它自动插值。对于 Z-score 标准化数据,c(-2, 0, 2) 覆盖了约 95% 的数据范围。

3.2 添加注释——ComplexHeatmap 的方式#

# 列注释
column_ha <- HeatmapAnnotation(
Group = annotation_col$Group,
Batch = annotation_col$Batch,
col = list(
Group = c("Control" = "#4DBBD5", "Treatment" = "#E64B35"),
Batch = c("Batch1" = "#00A087", "Batch2" = "#7E6148")
),
annotation_legend_param = list(
Group = list(title = "Group"),
Batch = list(title = "Batch")
)
)
# 行注释
row_ha <- rowAnnotation(
GeneClass = annotation_row$GeneClass,
col = list(GeneClass = c(
"Oncogene" = "#DC0000",
"Tumor Suppressor" = "#3C5488",
"Other" = "#8491B4"
))
)
Heatmap(expr_matrix,
name = "Z-score",
col = colorRamp2(c(-2, 0, 2), c("navy", "white", "firebrick3")),
top_annotation = column_ha,
right_annotation = row_ha,
show_row_names = FALSE,
column_title = "RNA-seq Expression Heatmap",
column_title_gp = gpar(fontsize = 14, fontface = "bold"),
row_km = 3, # k-means 分成3个基因模块
column_km = 2 # 样本分成2组
)

ComplexHeatmap 把注释对象化——HeatmapAnnotation 管列注释,rowAnnotation 管行注释,逻辑清晰但代码量确实比 pheatmap 多。

3.3 拼图——ComplexHeatmap 的杀手锏#

多组数据并排展示是 ComplexHeatmap 最强的地方:

# 假设你有三个独立的表达矩阵(不同组织或不同处理)
set.seed(123)
mat1 <- matrix(rnorm(500, 5, 2), 50, 10)
mat2 <- matrix(rnorm(500, 5, 2), 50, 10)
mat3 <- matrix(rnorm(500, 5, 2), 50, 10)
rownames(mat1) <- rownames(mat2) <- rownames(mat3) <- paste0("Gene", 1:50)
# 分别画三张热图然后用 + 拼起来
ht1 <- Heatmap(mat1, name = "Tissue1", col = colorRamp2(c(-2,0,2), c("navy","white","red")),
show_row_names = FALSE, column_title = "Normal")
ht2 <- Heatmap(mat2, name = "Tissue2", col = colorRamp2(c(-2,0,2), c("navy","white","red")),
show_row_names = FALSE, column_title = "Tumor")
ht3 <- Heatmap(mat3, name = "Tissue3", col = colorRamp2(c(-2,0,2), c("navy","white","red")),
show_row_names = FALSE, column_title = "Metastasis")
# 横向拼接
ht1 + ht2 + ht3
# 保存
pdf("multi_heatmap.pdf", width = 15, height = 8)
draw(ht1 + ht2 + ht3)
dev.off()

这个功能在需要展示”同一批基因在不同条件下”的表达模式时特别有用。你还可以用 row_order 让三张热图的基因顺序保持一致。

4. 踩坑记录#

坑1:pheatmap 报错 “margins too large”#

症状:基因名/样本名太多,R 报 figure margins too large

解决:

# 方法1:调小字体
pheatmap(expr_matrix, fontsize_row = 4, fontsize_col = 6)
# 方法2:不显示行名
pheatmap(expr_matrix, show_rownames = FALSE)
# 方法3:增大画布(在 png/pdf 前设置)
pdf("heatmap.pdf", width = 20, height = 30)
pheatmap(expr_matrix)
dev.off()

坑2:scale=“row” 遇到全是相同值的行#

如果某基因在所有样本中表达量完全一样,标准差 = 0,scale("row") 会算出 NaN,热图里的该行是一片灰色。

# 排查
row_sds <- apply(expr_matrix, 1, sd)
table(row_sds == 0)
# 处理:过滤掉恒定基因,或给标准差加个极小值
expr_filtered <- expr_matrix[row_sds > 0.01, ]

实际数据中完全恒定的基因很少见(除非是 spike-in),但低表达基因近似恒定值的蛮多。

坑3:pheatmap 和 ComplexHeatmap 的行列顺序不一致#

两个包的聚类算法参数默认值不同,pheatmap 默认 clustering_method = "complete",ComplexHeatmap 可以自定义。如果先画了 pheatmap 探索数据,再用 ComplexHeatmap 出图,聚类结果可能不一样。

# 统一聚类方法
Heatmap(expr_matrix, clustering_method_rows = "complete", clustering_method_columns = "complete")

坑4:ComplexHeatmap 图例标题不显示#

ComplexHeatmap 的图例标题 name 参数和 HeatmapAnnotation 里的 title 是两个东西。如果图例没标题,检查:

# Heatmap 的 name 必须设
Heatmap(expr_matrix, name = "Expression") # 正确
# HeatmapAnnotation 里每个注释单独设 title
HeatmapAnnotation(
Group = ...,
annotation_legend_param = list(Group = list(title = "Sample Group"))
)

坑5:colorRamp2 颜色看起来不对#

colorRamp2 的颜色插值是线性的,但如果中间断点不对称,颜色过渡会偏:

# 不对称断点导致白色不在中间
colorRamp2(c(-3, 0, 1), c("blue", "white", "red"))
# 白-红区间只有1个单位,蓝-白有3个单位
# 尽量设对称断点
colorRamp2(c(-2, 0, 2), c("blue", "white", "red"))

坑6:中文标签在 PDF 里变成方框#

生信文章虽然以英文为主,但如果你要给国内报告画中文标签:

# PDF 里显示中文需要额外指定字体
library(showtext)
showtext_auto()
font_add("SimHei", "/usr/share/fonts/truetype/simhei.ttf") # 黑体
# 然后在 Heatmap 里指定
Heatmap(expr_matrix,
column_title = "基因表达热图",
column_title_gp = gpar(fontfamily = "SimHei"),
...
)

5. 小结#

需求推荐工具代码量灵活性
快速探索数据pheatmap3行★★☆
加注释出图pheatmap15行★★★
复杂排版拼图ComplexHeatmap30行+★★★★★
发表级单图两者都可
oncoprint/UpSetComplexHeatmap★★★★★

我的习惯:探索数据时在 RStudio 里用 pheatmap 快速预览,确定分组和配色方案后,用 ComplexHeatmap 出最终版。colorRamp2 + HeatmapAnnotation 的组合基本能满足 95% 的生信热图需求。

配色小抄:

  • 表达量热图:白→橙→红,或 viridis
  • 差异分析热图:蓝→白→红
  • 相关性热图:白→粉→深红
  • 甲基化热图:蓝→黄→红

本文于 2025-09-14 在 Debian 13 + R 4.4.1 上实测完成。pheatmap 1.0.12,ComplexHeatmap 2.18.0。所有代码可直接复制运行。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

热图可视化:pheatmap与ComplexHeatmap
https://fg.ink/posts/heatmap-visualization-r/
作者
风观
发布于
2026-01-01
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
风观
风有来路,观有所思
分类
标签
站点统计
文章
50
分类
1
标签
29
总字数
61,837
运行时长
0
最后活动
0 天前

文章目录