← 返回 Tessera
热图3 张图模拟表达矩阵(60 基因 × 18 样本,含 8 个缺失)2026-04-13

expression_heatmap

一个矩阵里,哪些行和哪些列是一起动的?而且这些模式和已知分组对得上吗?

需要的输入
数值矩阵(行 = 特征、列 = 样本)+ 可选的行/列注释表
示例数据
模拟表达矩阵(60 基因 × 18 样本,含 8 个缺失)
依赖
ComplexHeatmap · circlize · grid
配色
未命名#B1615C #FFFFFF #5A5A83
成图预览另有 2 张在配方里
expression_heatmap

什么时候用它

矩阵热图和上一族的相关性热图形态相同,但回答的问题不同:相关性热图的矩阵是算出来的(两两相关系数),这里的矩阵是测出来的(表达量、丰度、强度)。

它的核心能力是双向聚类:行和列同时重排,把模式相似的挤到一起。所以图上出现的块状结构不是数据自带的顺序,是算法找出来的。

ComplexHeatmap 而不是 pheatmap 或 ggplot,图省的是两件事:注释条,和横向拼接——同一批行可以把表达、甲基化、突变几个矩阵用 ht1 + ht2 + ht3 并排接起来,行顺序由第一个热图的聚类决定、其余自动跟着对齐。这是它名字里那个 "Complex" 的实际含义。

怎么读

  1. 先看色阶方向。 高低两端和中间锚点各是什么色。
  2. 看块。 聚类后出现的连续色块就是"一起动"的行列组合。
  3. 对照注释条。 这是整张图最要紧的一步——如果 Tumor 样本恰好聚成一块,说明矩阵里确实有和样本类型相关的信号;如果没聚在一起,那也是结论,而且往往更重要。
  4. 看树状图的高度。 分支越早分开,两簇差别越大。
  5. 看缺失格。 灰格是 NA,不是低值。

常见陷阱

  • 注释表的行名必须和矩阵对齐。 错位了照样能画出来,只是注释贴到了别的基因或样本上。这是这张图最危险的一个坑,因为图看起来完全正常。
  • 聚类方法会改变结论。 completeward.D2 给出的分块可能完全不同。方法要写进图注,不然这张图不可复现。
  • 没标准化就聚类。 表达量量级差几个数量级时,聚类会被高表达基因主导。多数情况下该先按行做 z-score。
  • 色阶范围被离群值拉爆。 一个极端值会把整张图压成一片中性色。用分位数截断(比如 1%–99%)而不是最大最小值。
  • 行太多标签就没法看。 超过五六十行就该关掉行名——但关掉之后读者就不知道哪一行是谁了,所以得把重点那几个单独引出来:rowAnnotation(mark = anno_mark(at = which(rownames(expr_mat) %in% genes), labels = genes)),它会在侧边用连线把标签牵到对应的行上。这比全开行名干净得多,也是关掉行名之后的必要配套,不是可选装饰。
  • 导出走"开设备 → draw()dev.off()"。 Heatmap() 返回的是对象不是 ggplot,ggsave() 存不了;也不像 base graphics 那样画完就已经在设备上。另有两件事只在导出时才暴露:画布一大图例容易被裁,draw() 时补 padding = unit(c(2, 20, 2, 20), "mm");而 use_raster = TRUE 的主体是按设备尺寸栅格化的,导 300 dpi 时 raster_quality = 2 会糊,要往上调。
  • 块状结构不等于生物学。 聚类只是把相似的排到一起,它总能找出"块",哪怕数据是纯噪声——这一页的矩阵就是 rnorm() 生成的。

配色

主体是连续量,用发散色阶:低端暖红 #B1615C、中间白、高端冷紫 #5A5A83,锚点落在中位数上。发散而不是渐进,是因为表达数据关心的是"比中间水平高还是低",有天然的两侧。

注释条则用定性色——样本类型、性别、通路都是分类变量,深浅没有意义。这个分工必须清楚:主体连续、注释定性,混了读者就分不清哪个通道在编码什么。

配方

方法绘图系统什么时候选它
AHeatmap() 基础版grid(ComplexHeatmap)只看矩阵本身的模式,没有已知分组要对照
B+ 注释条与分块grid(ComplexHeatmap)有样本类型、通路这类已知分组——判断聚类是不是"真的"就靠它

数据准备

library(ComplexHeatmap)
library(circlize)
library(grid)

set.seed(123)

expr_mat <- matrix(rnorm(60 * 18, mean = 5, sd = 2), nrow = 60, ncol = 18)
rownames(expr_mat) <- paste0("Gene", seq_len(nrow(expr_mat)))
colnames(expr_mat) <- paste0("Sample", seq_len(ncol(expr_mat)))
expr_mat[sample(length(expr_mat), 8)] <- NA   # 挖 8 个洞,演示 NA 怎么显示

# 注释表的行名必须和矩阵的行名/列名一致 —— 错位不会报错,只会贴错
row_anno_df <- data.frame(
  Pathway    = sample(c("PathwayA", "PathwayB", "PathwayC"), nrow(expr_mat), TRUE),
  Regulation = sample(c("Up", "Down"), nrow(expr_mat), TRUE),
  row.names  = rownames(expr_mat)
)

col_anno_df <- data.frame(
  Type      = sample(c("Tumor", "Normal"), ncol(expr_mat), TRUE),
  Sex       = sample(c("Male", "Female"), ncol(expr_mat), TRUE),
  Age_Group = sample(c("Young", "Middle", "Old"), ncol(expr_mat), TRUE),
  row.names = colnames(expr_mat)
)

# 主体色阶:三个断点 = 最小 / 中位 / 最大,中位落在白色上
# 真实数据建议把 0 和 1 换成 0.01 和 0.99,免得一个离群值把整张图压平
expr_col_fun <- circlize::colorRamp2(
  breaks = c(quantile(expr_mat, 0, na.rm = TRUE),
             median(expr_mat, na.rm = TRUE),
             quantile(expr_mat, 1, na.rm = TRUE)),
  colors = c("#b1615c", "white", "#5a5a83")
)

row_anno_colors <- list(
  Pathway    = c(PathwayA = "#2980b9", PathwayB = "#16a085", PathwayC = "#c0392b"),
  Regulation = c(Up = "#fb9a99", Down = "#b2df8a")
)

col_anno_colors <- list(
  Type      = c(Tumor = "#BC3C29", Normal = "#00468B"),
  Sex       = c(Male = "#E18727", Female = "#20854E"),
  Age_Group = c(Young = "#7876B1", Middle = "#6F99AD", Old = "#FFDC91")
)

方法 A · 基础热图

ComplexHeatmap 不是 ggplot 图层——Heatmap() 返回一个对象,要 draw() 才画出来。

#| fig: basic
#| fig-width: 6
#| fig-height: 6
basic_ht <- Heatmap(
  matrix = expr_mat,
  name   = "Expression",
  col    = expr_col_fun,
  na_col = "grey90",              # NA 用中性灰,别用色阶里的任何一端
  cluster_rows    = TRUE,
  cluster_columns = TRUE,
  clustering_method_rows    = "complete",   # 方法要写进图注,换一个分块就变
  clustering_method_columns = "ward.D2",
  show_row_names    = FALSE,      # 60 行标签挤不下
  show_column_names = TRUE,
  column_names_gp   = gpar(fontsize = 8),
  heatmap_legend_param = list(title = "Expression", direction = "vertical")
)

draw(basic_ht)
expression_heatmap — basic
expression_heatmap-basic

方法 B · 加注释条与分块

#| fig: annotated
#| fig-width: 8
#| fig-height: 7
row_ha <- rowAnnotation(
  df = row_anno_df, col = row_anno_colors,
  annotation_name_gp = gpar(fontsize = 8)
)

col_ha <- HeatmapAnnotation(
  df = col_anno_df, col = col_anno_colors,
  annotation_name_gp = gpar(fontsize = 8)
)

annotated_ht <- Heatmap(
  matrix = expr_mat, name = "Expression", col = expr_col_fun, na_col = "grey90",
  cluster_rows = TRUE, cluster_columns = TRUE,
  clustering_method_rows = "complete", clustering_method_columns = "ward.D2",
  show_row_names = FALSE, show_column_names = TRUE,
  column_names_gp = gpar(fontsize = 8),
  left_annotation = row_ha,
  top_annotation  = col_ha,
  # 固定尺寸而不是让它填满设备 —— 否则图例一多,热图本体就被挤扁
  heatmap_width  = unit(14, "cm"),
  heatmap_height = unit(14, "cm"),
  border = "white",
  use_raster = TRUE,      # 大矩阵栅格化,否则 PDF 里会有几万个矢量方块
  raster_quality = 2
)

draw(annotated_ht, heatmap_legend_side = "right", annotation_legend_side = "right")
expression_heatmap — annotated
expression_heatmap-annotated

还可以不靠聚类、直接按已知分组切块——把 row_split / column_split 传进去,聚类只在每个块内部进行:

#| fig: split
#| fig-width: 8
#| fig-height: 7
split_ht <- Heatmap(
  matrix = expr_mat, name = "Expression", col = expr_col_fun, na_col = "grey90",
  row_split    = row_anno_df$Pathway,   # 按通路切行
  column_split = col_anno_df$Type,      # 按样本类型切列
  cluster_rows = TRUE, cluster_columns = TRUE,
  show_row_names = FALSE, column_names_gp = gpar(fontsize = 8),
  left_annotation = row_ha, top_annotation = col_ha,
  heatmap_width = unit(14, "cm"), heatmap_height = unit(14, "cm"),
  border = "white", use_raster = TRUE, raster_quality = 2
)

draw(split_ht, heatmap_legend_side = "right", annotation_legend_side = "right")
expression_heatmap — split
expression_heatmap-split

两种方法怎么选

  1. 先用 A 看一眼矩阵有没有结构。 没有注释干扰,纯粹看聚类能不能分出块。
  2. 有已知分组就必须上 B。 注释条是判断"这个块有没有意义"的唯一依据——没有它,块状结构说明不了任何事(这一页的数据是纯随机数,照样聚出了块)。
  3. 分块(split)比聚类更强硬。 它假定分组是对的,只在组内聚类。适合"我已经知道分组、想看组间差异";不适合"我还在找有没有分组"。
运行环境3 个包 · 2026-08-03

R version 4.5.1 (2025-06-13 ucrt) · x86_64-w64-mingw32

  • circlize 0.4.16
  • ComplexHeatmap 2.24.0
  • grid 4.5.1