Author(s): Xiaofan Lu
Date: 2025-10-04

Academic Citation

If you use this code in your work or research, we kindly request that you cite our publication:

Xiaofan Lu, et al. (2025). FigureYa: A Standardized Visualization Framework for Enhancing Biomedical Data Interpretation and Research Efficiency. iMetaMed. https://doi.org/10.1002/imm3.70005

If you use circlize in published research, please cite:

Gu, Z. circlize implements and enhances circular visualization in R. Bioinformatics 2014.

需求描述

我想实现一个多种算法的风险值和免疫细胞相关性热图(文中Figure 7)

Requirement Description

I would like to draw a heat map of risk value and immune cell correlation for multiple algorithms (Figure 7 in the article)

出自https://www.ijbs.com/v17p0702.htm fromhttps://www.ijbs.com/v17p0702.htm

Figure 7. Heatmap for immune responses based on CIBERSORT, ESTIMATE, MCPcounter, ssGSEA, and TIMER algorithms among high and low risk group.

应用场景

多个热图画在一起。

例文绘制不同免疫富集分析得到结果的综合热图。

不仅限于免疫富集分析结果,还可以用于WGCNA、多组学分析结果的展示。

原文使用了pheatmap的方式对不同的算法做了行注释,我这里提供两种方法;

①原文的pheatmap::pheatmap展示 ②采用ComplexHeatmap::pheatmap对不同算法采用不同颜色的热图,后垂直衔接热图展示

图中各种打分的计算方法可参考我们实现过的FigureYa:

Application Scenarios

Multiple heat maps are drawn together.

In this example, a comprehensive heat map of the results obtained from different immune enrichment analyses is plotted.

It is not limited to the results of immune enrichment analysis, but can also be used to display the results of WGCNA and multi-omics analysis.

The original article uses pheatmap to make line comments on different algorithms, and I provide two methods here;

  1. The original pheatmap: :p heatmap display
  2. ComplexHeatmap: :p heatmap uses different colors for different algorithms, and then vertically connects the heatmap to display

The calculation methods of various scores in the figure can be referred to:

环境设置

Environment settings

source("install_dependencies.R")
## Starting R package installation...
## ===========================================
## 
## Installing CRAN packages...
## Package already installed: RColorBrewer 
## Package already installed: gplots 
## Package already installed: oompaBase 
## Package already installed: pheatmap 
## Package already installed: viridis 
## 
## Installing Bioconductor packages...
## Package already installed: ComplexHeatmap 
## Package already installed: circlize 
## 
## ===========================================
## Package installation completed!
## You can now run your R scripts in this directory.
library(RColorBrewer)
library(circlize)
## ========================================
## circlize version 0.4.16
## CRAN page: https://cran.r-project.org/package=circlize
## Github page: https://github.com/jokergoo/circlize
## Documentation: https://jokergoo.github.io/circlize_book/book/
## 
## If you use it in published research, please cite:
## Gu, Z. circlize implements and enhances circular visualization
##   in R. Bioinformatics 2014.
## 
## This message can be suppressed by:
##   suppressPackageStartupMessages(library(circlize))
## ========================================
library(gplots)
## 
## Attaching package: 'gplots'
## The following object is masked from 'package:stats':
## 
##     lowess
library(viridis)
## Loading required package: viridisLite
library(oompaBase)
## 
## Attaching package: 'oompaBase'
## The following object is masked from 'package:gplots':
## 
##     redgreen
Sys.setenv(LANGUAGE = "en") #显示英文报错信息 # error messages are displayed in English
options(stringsAsFactors = FALSE) #禁止chr转成factor # chr is not allowed to be converted to factor

自定义函数 Custom functions

standarize.fun <- function(indata=NULL, halfwidth=NULL, centerFlag=T, scaleFlag=T) {  
  outdata=t(scale(t(indata), center=centerFlag, scale=scaleFlag))
  if (!is.null(halfwidth)) {
    outdata[outdata>halfwidth]=halfwidth
    outdata[outdata<(-halfwidth)]= -halfwidth
  }
  return(outdata)
}

输入文件

easy_input.csv,画热图所需的数据。这里是多种算法获得的免疫浸润数据,每行一个sample,每列一种免疫细胞。可以灵活替换为其他需要画热图的数据,例如基因表达矩阵,那么这里的免疫细胞就替换为基因/蛋白质/通路等等。

热图自上而下分成一块一块的多个类,两种方式提供分类信息:

easy_input_group.csv,分组信息和连续变量,用于画热图上方的注释。这里的连续变量是risk score(根据它的大小为sample排序),分组信息是risk type。读懂之后可以按照现有的格式添加更多行注释。

Input files

easy_input.csv, the data needed to draw a heat map. Here are the immune infiltration data obtained by multiple algorithms, one sample per row, one type of immune cell per column. It can be flexibly replaced with other data that needs to be heatmapped, such as gene expression matrix, then the immune cells here are replaced with genes/proteins/pathways, etc.

The heatmap is divided into multiple classes from top to bottom, and provides classification information in two ways:

easy_input_group.csv, group information and continuous variables for annotations above the heatmap. Here the continuous variable is the risk score (sorted by its size) and the grouping information is the risk type. Once you have read it, you can add more lines of comments in the existing format.

# 加载热图数据
# Load heatmap data
hmdat <- read.csv("easy_input.csv",row.names = 1,check.names = F,stringsAsFactors = F,header = T)
# 或许你的数据行列与此相反,就这样转置一下
# Maybe your data rows and columns are the opposite, so transpose them like this
#hmdat <- t(hmdat)

# 加载分组
# Load grouping
risk <- read.csv("easy_input_group.csv",row.names = 1,check.names = F,stringsAsFactors = F,header = T)
rownames(risk) <- paste0(rownames(risk),"-01")
dim(hmdat)
## [1] 11070   119
hmdat[1:3,1:3]
##                 B cell_TIMER T cell CD4+_TIMER T cell CD8+_TIMER
## TCGA-OR-A5J1-01    0.1082566         0.1170244         0.2011764
## TCGA-OR-A5J2-01    0.1144747         0.1067882         0.2131930
## TCGA-OR-A5J3-01    0.1024409         0.1056148         0.2026024
dim(risk)
## [1] 350   4
head(risk)
##                      time stat  riskScore risk
## TCGA-BR-8588-01 1.0657534    0 7.48064777 high
## TCGA-VQ-A8DU-01 0.4547945    1 6.20594391 high
## TCGA-HU-A4G9-01 2.0164384    0 0.08195701  low
## TCGA-CD-A4MG-01 0.5479452    1 1.06863580  low
## TCGA-HU-8602-01 1.8602740    0 0.52410019  low
## TCGA-VQ-A91S-01 2.7397260    0 0.30966267  low
# 从easy_input_type.csv读取分类信息
# Read classification information from easy_input_type.csv
type <- read.csv("easy_input_type.csv", row.names = 1)
head(type)
##                              Methods
## B cell_TIMER                   TIMER
## T cell CD4+_TIMER              TIMER
## T cell CD8+_TIMER              TIMER
## Neutrophil_TIMER               TIMER
## Macrophage_TIMER               TIMER
## Myeloid dendritic cell_TIMER   TIMER
# 取出共有样本更新数据
# Take out the common sample and update the data
comsam <- intersect(rownames(risk),rownames(hmdat))
hmdat <- hmdat[comsam,]
risk <- risk[comsam,,drop = F]
dim(hmdat)
## [1] 349 119
# 拆分不同算法结果,获得类的名字
# Split the results of different algorithms to get the name of the class
#immMethod <- sapply(strsplit(colnames(hmdat),"_",fixed = T),"[",2) #用easy_input.csv列名里的算法信息 # Use the algorithm information in the easy_input.csv column name
immMethod <- type$Methods # 用easy_input_type.csv的算法那一列 # Use the easy_input_type.csv algorithm column

开始画图

1. 原文pheatmap版

Start drawing

1. Original pheatmap version

# 用pheatmap画图
# Draw with pheatmap
library(pheatmap)

# 定义颜色
# Define colors
methods.col <- brewer.pal(n = length(unique(immMethod)),name = "Paired")

# 创建注释
# 列注释,位于热图顶端
# Create annotations
# Column annotations, located at the top of the heatmap
annCol <- data.frame(RiskScore = scale(risk$riskScore),
                     RiskType = risk$risk,
                     # 以上是risk score和risk type两种注释,可以按照这样的格式继续添加更多种类的注释信息,记得在下面的annColors里设置颜色 # The above are risk score and risk type annotations, you can continue to add more kinds of annotation information according to this format, remember to set the color in the following annColors
                     row.names = rownames(risk),
                     stringsAsFactors = F)

# 行注释,位于热图左侧
# row annotation, located on the left side of the heatmap
annRow <- data.frame(Methods = factor(immMethod,levels = unique(immMethod)),
                     row.names = colnames(hmdat),
                     stringsAsFactors = F)

# 为各注释信息设置颜色
# Set the color for each annotation message
annColors <- list(Methods = c("TIMER" = methods.col[1], #行注释的颜色 # The color of the row comment
                              "CIBERSORT" = methods.col[2],
                              "CIBERSORT-ABS" = methods.col[3],
                              "QUANTISEQ" = methods.col[4],
                              "MCPCOUNTER" = methods.col[5],
                              "XCELL" = methods.col[6],
                              "EPIC" = methods.col[7]),
                  # 下面是列注释的颜色,可依此设置更多注释的颜色
                  # The following is the color of the column annotation, you can set the color of more annotations accordingly
                  "RiskScore" = greenred(64), 
                  "RiskType" = c("high" = "red","low" = "blue"))

# 数据标准化
# Data standardization
indata <- t(hmdat)
indata <- indata[,colSums(indata) > 0] # 确保没有富集全为0的细胞 # Make sure there are no cells with an enrichment of all 0s
plotdata <- standarize.fun(indata,halfwidth = 2)

# 样本按risk score排序
# Samples are sorted by risk score
samorder <- rownames(risk[order(risk$riskScore),])

# pheatmap绘图
# pheatmap plotting
pheatmap::pheatmap(mat = as.matrix(plotdata[,samorder]), # 标准化后的数值矩阵 # Normalized numerical matrix
                   border_color = NA, # 无边框色 # There is no border color
                   color = bluered(64), # 热图颜色为红蓝 # The heatmap color is red and blue
                   cluster_rows = F, # 行不聚类 # Rows are not clustered
                   cluster_cols = F, # 列不聚类 # Columns are not clustered
                   show_rownames = T, # 显示行名 # Show the Rowname
                   show_colnames = F, # 不显示列名 # Column names are not displayed
                   annotation_col = annCol[samorder,,drop = F], # 列注释 # Column Annotation
                   annotation_row = annRow, # 行注释 # Rows comments
                   annotation_colors = annColors, # 注释颜色 # Annotation colors
                   gaps_col = table(annCol$RiskType)[2], # 列分割 # Column splitting
                   gaps_row = cumsum(table(annRow$Methods)), # 行分割 # Line splitting
                   cellwidth = 0.8, # 元素宽度 # Element width
                   cellheight = 10, # 元素高度 # Element height
                   filename = "immune heatmap by pheatmap.pdf")

2. ComplexHeatmap衔接版

ComplexHeatmap里的pheatmap跟pheatmap里的pheatmap用法相同。

本质上还是用pheatmap画图,画颜色不同的多个热图,这样从颜色上就能一目了然地看出热图里的信息多了一层(分类信息,这里是不同算法),然后用ComplexHeatmap把多个热图拼在一起。

2. ComplexHeatmap Convergence Edition

The pheatmap in ComplexHeatmap is used in the same way as the pheatmap in pheatmap.

In essence, we still use pheatmap to draw multiple heatmaps with different colors, so that you can see at a glance that there is an extra layer of information in the heatmap (classification information, here are different algorithms), and then use ComplexHeatmap to stitch together multiple heatmaps.

library(ComplexHeatmap) 
## Loading required package: grid
## ========================================
## ComplexHeatmap version 2.24.1
## Bioconductor page: http://bioconductor.org/packages/ComplexHeatmap/
## Github page: https://github.com/jokergoo/ComplexHeatmap
## Documentation: http://jokergoo.github.io/ComplexHeatmap-reference
## 
## If you use it in published research, please cite either one:
## - Gu, Z. Complex Heatmap Visualization. iMeta 2022.
## - Gu, Z. Complex heatmaps reveal patterns and correlations in multidimensional 
##     genomic data. Bioinformatics 2016.
## 
## 
## The new InteractiveComplexHeatmap package can directly export static 
## complex heatmaps into an interactive Shiny app with zero effort. Have a try!
## 
## This message can be suppressed by:
##   suppressPackageStartupMessages(library(ComplexHeatmap))
## ========================================
## ! pheatmap() has been masked by ComplexHeatmap::pheatmap(). Most of the arguments
##    in the original pheatmap() are identically supported in the new function. You 
##    can still use the original function by explicitly calling pheatmap::pheatmap().
## 
## Attaching package: 'ComplexHeatmap'
## The following object is masked from 'package:pheatmap':
## 
##     pheatmap
# 最新版ComplexHeatmap好像有一些bug,使用里面的pheatmap函数会报错
# There seem to be some bugs in the latest version of ComplexHeatmap, and an error will be reported when using the pheatmap function in it
# 因此我们从脚本直接加载pheatmap函数
# So we load the pheatmap function directly from the script
source("pheatmap_translate.R") # 位于当前文件夹,出自ComplexHeatmap_2.7.9.tar.gz # Located in the current folder, from ComplexHeatmap_2.7.9.tar.gz

ht_opt$message = FALSE

# 创建注释
# Create annotations
annCol <- data.frame(RiskScore = scale(risk$riskScore),
                     RiskType = risk$risk,
                     row.names = rownames(risk),
                     stringsAsFactors = F)
annRow <- data.frame(row.names = colnames(hmdat),
                     Methods = factor(immMethod,levels = unique(immMethod)),
                     stringsAsFactors = F)

annColors <- list("RiskScore" = greenred(64),
                  "RiskType" = c("high" = "red","low" = "blue"))

# 数据标准化
# Data standardization
indata <- t(hmdat)
indata <- indata[,colSums(indata) > 0] # 确保没有富集全为0的细胞 # Make sure there are no cells with an enrichment of all 0s
plotdata <- standarize.fun(indata,halfwidth = 2)

# 样本按risk score排序
# Samples are sorted by risk score
samorder <- rownames(risk[order(risk$riskScore),])

# 拆分各算法的结果
# Split the results of each algorithm
plotdata1 <- plotdata[rownames(annRow[which(annRow$Methods == "TIMER"),,drop = F]),]
plotdata2 <- plotdata[rownames(annRow[which(annRow$Methods == "CIBERSORT"),,drop = F]),]
plotdata3 <- plotdata[rownames(annRow[which(annRow$Methods == "CIBERSORT-ABS"),,drop = F]),]
plotdata4 <- plotdata[rownames(annRow[which(annRow$Methods == "QUANTISEQ"),,drop = F]),]
plotdata5 <- plotdata[rownames(annRow[which(annRow$Methods == "MCPCOUNTER"),,drop = F]),]
plotdata6 <- plotdata[rownames(annRow[which(annRow$Methods == "XCELL"),,drop = F]),]
plotdata7 <- plotdata[rownames(annRow[which(annRow$Methods == "EPIC"),,drop = F]),]

# 分别画7次热图(参数基本同pheatmap里的pheatmap)
# Draw 7 heat maps separately (the parameters are basically the same as the pheatmap in the pheatmap)
hm1 <- pheatmap(mat = as.matrix(plotdata1[,samorder]),
                                border_color = NA,
                                #color = bluered(64), 
                                cluster_rows = F,
                                cluster_cols = F,
                                show_rownames = T,
                                show_colnames = F,
                                annotation_col = annCol[samorder,,drop = F],
                                annotation_colors = annColors,
                                cellwidth = 0.8,
                                cellheight = 10,
                                gaps_col = table(annCol$RiskType)[2],
                                name = "TIMER") # 为子热图的图例命名 # Name the legend of the subheatmap

hm2 <- pheatmap(mat = as.matrix(plotdata2[,samorder]),
                                border_color = NA,
                                color = greenred(64), 
                                cluster_rows = F,
                                cluster_cols = F,
                                show_rownames = T,
                                show_colnames = F,
                                cellwidth = 0.8,
                                cellheight = 10,
                                gaps_col = table(annCol$RiskType)[2],
                                name = "CIBERSORT")

hm3 <- pheatmap(mat = as.matrix(plotdata3[,samorder]),
                                border_color = NA,
                                color = blueyellow(64), 
                                cluster_rows = F,
                                cluster_cols = F,
                                show_rownames = T,
                                show_colnames = F,
                                cellwidth = 0.8,
                                cellheight = 10,
                                gaps_col = table(annCol$RiskType)[2],
                                name = "CIBERSORT-ABS")

hm4 <- pheatmap(mat = as.matrix(plotdata4[,samorder]),
                                border_color = NA,
                                color = bluered(64), 
                                cluster_rows = F,
                                cluster_cols = F,
                                show_rownames = T,
                                show_colnames = F,
                                cellwidth = 0.8,
                                cellheight = 10,
                                gaps_col = table(annCol$RiskType)[2],
                                name = "QUANTISEQ")

hm5 <- pheatmap(mat = as.matrix(plotdata5[,samorder]),
                                border_color = NA,
                                color = inferno(64), 
                                cluster_rows = F,
                                cluster_cols = F,
                                show_rownames = T,
                                show_colnames = F,
                                cellwidth = 0.8,
                                cellheight = 10,
                                gaps_col = table(annCol$RiskType)[2],
                                name = "MCPCOUNTER")

hm6 <- pheatmap(mat = as.matrix(plotdata6[,samorder]),
                                border_color = NA,
                                color = viridis(64), 
                                cluster_rows = F,
                                cluster_cols = F,
                                show_rownames = T,
                                show_colnames = F,
                                cellwidth = 0.8,
                                cellheight = 10,
                                gaps_col = table(annCol$RiskType)[2],
                                name = "XCELL")

hm7 <- pheatmap(mat = as.matrix(plotdata7[,samorder]),
                                border_color = NA,
                                color = magma(64), 
                                cluster_rows = F,
                                cluster_cols = F,
                                show_rownames = T,
                                show_colnames = F,
                                cellwidth = 0.8,
                                cellheight = 10,
                                gaps_col = table(annCol$RiskType)[2],
                                name = "EPIC")

pdf("immune heatmap by ComplexHeatmap.pdf", width = 10,height = 20) # 保存前请注意RGUI里不能有任何显示的图像,否则不会pdf打不开 # Please note that there can be no displayed images in RGUI before saving, otherwise you will not be able to open the pdf
draw(hm1 %v% hm2 %v% hm3 %v% hm4 %v% hm5 %v% hm6 %v% hm7, # 垂直连接子热图 # Vertically connected with subheatmaps
     heatmap_legend_side = "bottom", # 热图注释放底部 # Heatmap note release bottom
     annotation_legend_side = "bottom") # 顶部注释放底部 # Top Note releases bottom
invisible(dev.off())

Session Info

sessionInfo()
## R version 4.5.1 (2025-06-13)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 24.04.3 LTS
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
## LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0
## 
## locale:
##  [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
##  [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
##  [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
## [10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   
## 
## time zone: UTC
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] grid      stats     graphics  grDevices utils     datasets  methods  
## [8] base     
## 
## other attached packages:
## [1] ComplexHeatmap_2.24.1 pheatmap_1.0.13       oompaBase_3.2.10     
## [4] viridis_0.6.5         viridisLite_0.4.2     gplots_3.2.0         
## [7] circlize_0.4.16       RColorBrewer_1.1-3   
## 
## loaded via a namespace (and not attached):
##  [1] sass_0.4.10         generics_0.1.4      bitops_1.0-9       
##  [4] KernSmooth_2.23-26  shape_1.4.6.1       gtools_3.9.5       
##  [7] digest_0.6.37       magrittr_2.0.4      caTools_1.18.3     
## [10] evaluate_1.0.5      iterators_1.0.14    fastmap_1.2.0      
## [13] foreach_1.5.2       doParallel_1.0.17   jsonlite_2.0.0     
## [16] GlobalOptions_0.1.2 gridExtra_2.3       scales_1.4.0       
## [19] codetools_0.2-20    jquerylib_0.1.4     cli_3.6.5          
## [22] crayon_1.5.3        rlang_1.1.6         cachem_1.1.0       
## [25] yaml_2.3.10         tools_4.5.1         parallel_4.5.1     
## [28] dplyr_1.1.4         colorspace_2.1-2    ggplot2_4.0.0      
## [31] BiocGenerics_0.54.0 GetoptLong_1.0.5    vctrs_0.6.5        
## [34] R6_2.6.1            png_0.1-8           stats4_4.5.1       
## [37] matrixStats_1.5.0   lifecycle_1.0.4     S4Vectors_0.46.0   
## [40] IRanges_2.42.0      clue_0.3-66         cluster_2.1.8.1    
## [43] pkgconfig_2.0.3     pillar_1.11.1       bslib_0.9.0        
## [46] gtable_0.3.6        glue_1.8.0          xfun_0.53          
## [49] tibble_3.3.0        tidyselect_1.2.1    knitr_1.50         
## [52] rjson_0.2.23        farver_2.1.2        htmltools_0.5.8.1  
## [55] rmarkdown_2.30      Cairo_1.6-5         compiler_4.5.1     
## [58] S7_0.2.0