大家好,我是专注于R语言数据可视化的技术博主。在阅读生物信息学或组学文献时,我们常常会被那些设计精美、信息密度极高的热图所吸引,尤其是那些突破传统矩形布局的圆形热图(Circular Heatmap),它们不仅能展示矩阵数据,还能揭示层次结构或周期规律,让人过目不忘。然而,当自己动手想复现时,却往往卡在如何将数据与复杂的图形元素对齐上。
本文将以一个具体的文献复现案例为线索,手把手带你使用R语言中的“可视化利器”——ComplexHeatmap包,从头开始绘制一张发表级的圆形热图。无论你是刚接触R语言可视化,还是想深入掌握ComplexHeatmap的高级功能,这篇文章都将提供一套从数据准备、图形构建到细节调优的完整闭环方案。你将学到的不只是一个绘图命令,更是一套解决复杂可视化需求的系统性思路。
1. 背景与核心概念:为何选择圆形热图与ComplexHeatmap?
在深入代码之前,我们有必要厘清两个核心概念:圆形热图的应用场景与ComplexHeatmap包的优势。
圆形热图本质上是将传统的矩形热图矩阵“卷”成一个圆环。这种布局的独特优势在于:
- 空间利用高效:能够在一个有限的空间内展示更多的行或列,尤其适合展示基因表达、物种丰度等行数极多的数据。
- 揭示周期模式:非常适合展示具有周期性或循环性的数据,例如昼夜节律基因表达、细胞周期相关蛋白、月度或季度性数据。
- 集成多维度信息:可以轻松地将热图与外部轨道(track)结合,在同一张图上同时展示数值矩阵、分类注释、柱状图、折线图等多种图形,信息呈现高度集成。
而ComplexHeatmap包是R语言中用于创建高度可定制化热图的顶级工具,由Zuguang Gu开发。相比于pheatmap或ggplot2的geom_tile,它的强大之处在于:
- 模块化设计:将热图主体、行/列注释、标题、图例等视为独立对象,可以自由组合与嵌套。
- 无与伦比的灵活性:几乎可以控制热图的每一个视觉元素,包括颜色、字体、间距、图形元素等。
- 强大的布局能力:支持热图行列的聚类、分割、排序,并且能轻松实现圆形布局,这是其他包难以企及的。
- 完美的多图整合:可以方便地将多个热图或注释图形排列在一起,形成复杂的复合图形。
因此,当我们需要复现文献中那些结构复杂、信息丰富的圆形热图时,ComplexHeatmap几乎是目前R生态下的不二之选。
2. 环境准备与版本说明
工欲善其事,必先利其器。首先确保你的R环境已经就绪。
R版本:建议使用R 4.0及以上版本。本文示例在R 4.3.2环境下测试通过。安装必要的包:我们将主要依赖ComplexHeatmap,它可以通过Bioconductor安装。同时,为了数据处理和绘图美化,我们也会用到tidyverse系列包。
打开R或RStudio,执行以下命令进行安装和加载:
# 安装Bioconductor管理器(如果尚未安装) if (!require("BiocManager", quietly = TRUE)) install.packages("BiocManager") # 通过Bioconductor安装ComplexHeatmap BiocManager::install("ComplexHeatmap") # 安装并加载数据处理和图形辅助包 install.packages("tidyverse") # 包含ggplot2, dplyr, tidyr等 install.packages("circlize") # ComplexHeatmap依赖此包进行颜色映射和圆形布局 # 加载所有需要的包 library(ComplexHeatmap) library(tidyverse) library(circlize)版本检查:安装后,可以通过以下命令查看核心包版本,确保功能兼容。
packageVersion("ComplexHeatmap") # 本文基于版本 2.18.0 packageVersion("circlize") # 本文基于版本 0.4.16如果你的安装过程遇到网络问题(例如causalweight包装不上的情况),请检查R的镜像源是否设置为国内镜像(如清华、中科大),或者尝试在稳定的网络环境下进行。
3. 核心原理与语法拆解:理解Heatmap对象与圆形布局
在动手画图前,理解ComplexHeatmap的核心对象Heatmap和HeatmapAnnotation以及圆形布局函数circos.heatmap至关重要。
3.1 Heatmap对象:不仅仅是绘图函数
在ComplexHeatmap中,Heatmap()函数并不直接画出图形,而是创建一个热图对象。这种设计允许我们将多个热图对象组合起来,最后再用draw()函数统一渲染。
一个最基本的矩形热图创建如下:
# 创建一个随机矩阵作为示例数据 set.seed(123) mat <- matrix(rnorm(100), nrow = 10, ncol = 10) rownames(mat) <- paste0("Gene", 1:10) colnames(mat) <- paste0("Sample", 1:10) # 创建热图对象 ht_obj <- Heatmap(mat, name = "value", # 图例标题 col = colorRamp2(c(-2, 0, 2), c("blue", "white", "red")), # 颜色映射 show_row_names = TRUE, show_column_names = TRUE) # 此时图形并未显示 class(ht_obj) # 查看类型,应为"Heatmap" # 绘制图形 draw(ht_obj)关键参数解析:
name: 图例的标题,对应数据矩阵中的值。col: 定义颜色映射。colorRamp2()函数来自circlize包,用于创建连续颜色梯度,其第一个参数是数值断点,第二个参数是对应的颜色。show_row/column_names: 控制是否显示行/列名。
3.2 HeatmapAnnotation对象:为热图添加信息轨道
注释(Annotation)是丰富热图信息的关键。我们可以为行或列添加各种注释,如样本分组、基因类型、数值型指标等。
# 为列(样本)创建注释 column_ha <- HeatmapAnnotation( Group = c(rep("Control", 5), rep("Treatment", 5)), # 分组信息 Score = anno_barplot(runif(10)), # 条形图注释 annotation_name_side = "left" ) # 将注释关联到热图 ht_obj_with_anno <- Heatmap(mat, name = "value", top_annotation = column_ha, # 将注释放在热图上方 ... # 其他参数 )3.3 圆形布局的核心:circos.heatmap与轨道思维
实现圆形热图的精髓在于circlize包提供的环形坐标系。circos.heatmap函数并不是ComplexHeatmap的一部分,但我们可以利用circlize的底层功能,结合ComplexHeatmap生成的热图数据来绘制圆形扇区。
基本思路是:
- 初始化圆形画布:使用
circos.initialize定义扇区(sectors)。 - 绘制热图轨道:在指定的扇区内,使用
circos.heatmap绘制热图矩阵。这个函数会将矩阵的行映射为圆环的角度,列映射为半径方向上的不同轨道。 - 添加其他注释轨道:使用
circos.track等函数在热图轨道的内圈或外圈添加柱状图、折线图、点图等注释。 - 添加装饰:最后添加轴线、标签等装饰元素。
这个过程要求我们对数据结构和图形布局有更精细的控制,这也是复现文献图的挑战所在。
4. 完整实战案例:复现一张文献级圆形热图
假设我们要复现的文献图展示的是24个时间点(列)下,一组周期表达基因(行)的表达量变化,并在外圈添加了基因所属通路的注释条带,在内圈添加了平均表达水平的柱状图。
4.1 模拟与准备数据
我们首先创建符合场景的模拟数据。
# 设置随机种子保证结果可重复 set.seed(2024) # 1. 模拟基因表达矩阵:50个基因,24个时间点 n_genes <- 50 n_timepoints <- 24 timepoints <- paste0("T", 1:n_timepoints) # 生成具有周期性的表达数据(使用正弦波叠加噪声) expr_mat <- matrix(NA, nrow = n_genes, ncol = n_timepoints) for (i in 1:n_genes) { base_phase <- runif(1, 0, 2*pi) # 随机初始相位 period <- sample(c(12, 24), 1) # 周期为12或24小时 expr_mat[i, ] <- sin(2 * pi * (1:n_timepoints) / period + base_phase) + rnorm(n_timepoints, sd=0.3) } rownames(expr_mat) <- paste0("Gene", sprintf("%03d", 1:n_genes)) colnames(expr_mat) <- timepoints # 查看数据前几行和前几列 print(expr_mat[1:5, 1:5]) # 2. 创建基因注释信息(例如通路) pathways <- c("Cell Cycle", "Metabolism", "Immune Response", "Signal Transduction") gene_pathway <- sample(pathways, n_genes, replace = TRUE, prob = c(0.3, 0.3, 0.2, 0.2)) names(gene_pathway) <- rownames(expr_mat) # 3. 创建每个基因的平均表达水平(用于内圈柱状图) gene_mean <- rowMeans(expr_mat)4.2 构建传统的矩形热图对象(作为数据基础)
虽然最终目标是圆形图,但我们可以先利用ComplexHeatmap的强大功能(如聚类、颜色映射)来处理数据并创建一个“虚拟”的热图对象,从中提取排序后的数据。
# 定义颜色映射函数:表达量从低到高,对应颜色从蓝到红 col_fun <- colorRamp2(c(-2, 0, 2), c("#4575B4", "#FFFFBF", "#D73027")) # 创建行注释(基因通路) row_ha <- rowAnnotation( Pathway = gene_pathway, # 分类注释 col = list(Pathway = c("Cell Cycle" = "#E41A1C", "Metabolism" = "#377EB8", "Immune Response" = "#4DAF4A", "Signal Transduction" = "#984EA3")), annotation_legend_param = list(title = "Pathway"), show_annotation_name = FALSE ) # 创建热图对象,并进行行聚类(这将决定基因在圆环上的顺序) ht <- Heatmap(expr_mat, name = "Expression\nZ-score", # 图例标题,换行更美观 col = col_fun, right_annotation = row_ha, show_row_names = FALSE, # 圆形图中通常不显示具体基因名 show_column_names = TRUE, column_names_side = "top", column_names_gp = gpar(fontsize = 9), row_dend_side = "right", # 行聚类树显示在右侧 row_title = NULL, cluster_rows = TRUE, # 对行进行聚类 cluster_columns = FALSE, # 时间点按顺序排列,不聚类 heatmap_legend_param = list(title_position = "topcenter", legend_direction = "horizontal") ) # 注意:此时我们并不绘制它,只是利用它来获取排序后的行顺序和列顺序。 # 提取排序后的行索引 row_order <- row_order(ht) # 这是一个列表,因为可能有多行聚类 # 对于单一行聚类,取第一个元素 if(is.list(row_order)) row_order <- row_order[[1]] # 按照聚类顺序重新排列表达矩阵和注释信息 expr_mat_ordered <- expr_mat[row_order, ] gene_pathway_ordered <- gene_pathway[rownames(expr_mat_ordered)] gene_mean_ordered <- gene_mean[rownames(expr_mat_ordered)]4.3 切换到圆形布局进行绘制
这是最关键的一步,我们将使用circlize包在圆形画布上绘制热图。
# 加载circlize包(如果尚未加载) library(circlize) # 1. 初始化图形设备,设置边距 pdf("Circular_Heatmap_Replication.pdf", width = 10, height = 10) par(mar = c(1, 1, 1, 1)) # 设置图形边距 # 2. 初始化圆形画布 # 扇区(sectors)是我们的基因,每个基因是一个扇区 circos.initialize(factors = rownames(expr_mat_ordered), # 扇区因子 xlim = c(0, ncol(expr_mat_ordered))) # 每个扇区的x轴范围(0到时间点数量) # 3. 添加最外圈的轨道:基因通路注释 # 首先为通路定义颜色,与之前保持一致 pathway_col <- c("Cell Cycle" = "#E41A1C", "Metabolism" = "#377EB8", "Immune Response" = "#4DAF4A", "Signal Transduction" = "#984EA3") circos.track(ylim = c(0, 1), # y轴范围,这里只画一个条带,所以是0-1 track.height = 0.05, # 轨道高度 panel.fun = function(x, y) { sector.index <- CELL_META$sector.index # 获取当前扇区名(基因名) pathway <- gene_pathway_ordered[sector.index] # 获取该基因的通路 # 用对应颜色填充整个扇区 circos.rect(0, 0, # 矩形左下角坐标(x起点, y起点) ncol(expr_mat_ordered), 1, # 矩形右上角坐标(x终点, y终点) col = pathway_col[pathway], border = NA) # 无边框 }, bg.border = NA) # 轨道背景无边框 # 4. 添加核心热图轨道 # 这是最宽的一个轨道,用于展示表达矩阵 circos.heatmap(expr_mat_ordered, col = col_fun, # 使用之前定义的颜色映射函数 track.height = 0.4, # 轨道高度 dend.side = "inside", # 将聚类树画在轨道内侧(可选) rownames.side = "outside", # 行名(基因名)放在轨道外侧,但通常不显示 show.sector.labels = FALSE) # 不显示扇区标签(基因名太多) # 5. 添加内圈轨道:平均表达水平柱状图 circos.track(ylim = range(gene_mean_ordered), track.height = 0.15, panel.fun = function(x, y) { sector.index <- CELL_META$sector.index mean_val <- gene_mean_ordered[sector.index] # 绘制从0开始的柱状图 circos.rect(0, 0, 0.8, mean_val, # x位置固定为0到0.8,y从0到mean_val col = ifelse(mean_val > 0, "#FF9999", "#99CCFF"), border = NA) }, bg.border = NA) # 6. 添加时间点标签(列名) # 在热图轨道的内侧添加 circos.track(track.index = get.current.track.index(), # 获取当前轨道(热图轨道)的索引 panel.fun = function(x, y) { # 在每个扇区(基因)的中间位置,为每个时间点画标签 for(i in 1:ncol(expr_mat_ordered)) { circos.text(i-0.5, -0.2, # x坐标,y坐标(负值表示在轨道内侧下方) colnames(expr_mat_ordered)[i], facing = "clockwise", # 标签朝向 niceFacing = TRUE, # 自动调整朝向使其可读 adj = c(0.5, 1), # 对齐方式 cex = 0.7) # 字体大小 } }, bg.border = NA) # 7. 添加图例和标题(在圆形图外部) # 先结束圆形布局绘制 circos.clear() # 使用ComplexHeatmap的图例函数添加颜色图例 lgd <- Legend(col_fun = col_fun, title = "Expression Z-score", direction = "horizontal") draw(lgd, x = unit(0.5, "npc"), y = unit(0.05, "npc"), just = c("center", "bottom")) # 添加通路颜色图例 pathway_lgd <- Legend(labels = names(pathway_col), legend_gp = gpar(fill = pathway_col), title = "Pathway", nrow = 1) draw(pathway_lgd, x = unit(0.5, "npc"), y = unit(0.01, "npc"), just = c("center", "bottom")) # 添加主标题 grid.text("Circular Heatmap of Circadian Gene Expression", x = 0.5, y = 0.95, gp = gpar(fontsize = 16, fontface = "bold")) # 8. 关闭图形设备,保存文件 dev.off()运行以上代码后,你将在当前工作目录下得到一个名为Circular_Heatmap_Replication.pdf的高质量PDF文件,里面就是一张复现的文献级圆形热图。
5. 常见问题与排查思路
在复现过程中,你可能会遇到以下典型问题:
| 问题现象 | 可能原因 | 解决思路 |
|---|---|---|
| 安装ComplexHeatmap失败 | 1. R版本过低。 2. 未安装Bioconductor。 3. 网络问题或镜像源错误。 | 1. 升级R至4.0+。 2. 先运行 install.packages("BiocManager")。3. 更换R镜像源为国内源(如 options(repos = c(CRAN="https://mirrors.tuna.tsinghua.edu.cn/CRAN/"))),或使用BiocManager::install("ComplexHeatmap", site_repository = "https://bioconductor.org")指定源。 |
错误:could not find function "circos.heatmap" | circlize包未加载或版本过低。 | 确保已通过library(circlize)加载包。circos.heatmap是circlize包的一部分。检查并更新circlize包。 |
| 圆形图中元素重叠或错位 | 1. 轨道高度(track.height)设置不合理。2. 文本标签位置( cex,adj)或朝向(facing)不当。3. 扇区初始化时的 xlim范围与数据维度不匹配。 | 1. 调整track.height参数,从0.05开始逐步增加,确保轨道间有足够空间。2. 仔细调整 circos.text中的y坐标(负值更向内)和facing参数。使用niceFacing = TRUE自动优化。3. 检查 circos.initialize中xlim的第二项是否等于矩阵的列数。 |
| 颜色映射不符合预期 | colorRamp2()函数中定义的断点(breaks)与数据范围不匹配。 | 使用range(expr_mat)查看数据实际范围,调整colorRamp2的断点向量,使其覆盖数据范围。例如,c(min(expr_mat), median(expr_mat), max(expr_mat))。 |
| 图形保存为空白或格式错误 | 1.pdf()和dev.off()调用不匹配。2. 在RStudio的 Plots面板中预览时,圆形图可能显示不全。 | 1. 确保每个pdf()调用都有对应的dev.off()。建议将绘图代码完整包裹在pdf(...)和dev.off()之间。2. 对于复杂圆形图,强烈建议直接输出为PDF或PNG文件,而不是在交互窗口查看。使用 png("plot.png", width=2000, height=2000, res=300)替代pdf()。 |
| 运行速度非常慢 | 数据矩阵过大(行或列超过数千)。 | 1. 考虑对数据进行降维或抽样。 2. 绘制时先使用子集测试代码。 3. 对于最终图,可以适当调低分辨率或分块绘制。 |
6. 最佳实践与工程建议
掌握了基本绘制方法后,以下建议能帮助你制作出更专业、更易于维护的图形:
数据预处理标准化:在绘制热图前,通常需要对行(基因)或列(样本)进行Z-score标准化或归一化,使得不同基因/样本间的比较更有意义。可以使用
scale()函数。# 对行进行Z-score标准化 expr_mat_scaled <- t(scale(t(expr_mat)))颜色方案选择:科学可视化中,颜色应兼具美观性与功能性。
- 连续型数据:使用
colorRampPalette()或viridis、RColorBrewer包中的渐变色。避免使用红-绿渐变(对色盲不友好)。 - 分类型数据:使用明显区分的颜色,如
RColorBrewer::Set3或Set2。
- 连续型数据:使用
代码模块化与函数化:如果你需要频繁绘制类似图形,将核心步骤封装成函数是极佳实践。
create_circular_heatmap <- function(expr_matrix, row_annotation, col_annotation, output_file = "plot.pdf") { # 参数检查 # 数据排序与处理 # 初始化圆形画布 # 绘制各层轨道 # 添加图例和标题 # 保存图形 # 返回图形对象或文件路径 }图形元数据管理:将图形中使用的颜色映射、分组信息等存储在单独的列表或数据框中,便于统一修改和复用。
plot_config <- list( expr_col = colorRamp2(c(-2,0,2), c("blue", "white", "red")), pathway_col = c("Path1"="red", "Path2"="blue"), track_heights = c(0.05, 0.4, 0.15) ) # 在绘图代码中引用 plot_config$expr_col版本控制与可重复性:在脚本开头使用
sessionInfo()记录R和所有包的版本。将绘图代码和生成的数据一起保存,确保任何人在未来都能复现完全相同的图形。生产环境输出:用于发表的图形通常需要高分辨率(如300 DPI或更高)和特定格式(TIFF, EPS)。
pdf()和png()函数可以方便地设置尺寸和分辨率。对于极其复杂的图形,如果PDF渲染慢,可以考虑先输出为SVG格式,再用其他工具转换。
通过本教程,你不仅学会了如何用ComplexHeatmap和circlize包绘制一张复杂的圆形热图,更重要的是掌握了一套“分解-构建-装饰”的可视化思维。从理解数据、规划布局,到编写代码、调试细节,每一步都是将抽象数据转化为直观见解的过程。下次再遇到令人惊艳的文献图时,不妨静下心来,用这里学到的方法尝试拆解和复现,这无疑是提升你数据可视化能力的最佳途径。