2025-03-20

Heatmaps

We will see part of the ComplexHeatmap package. It is very huge … as you can see at https://jokergoo.github.io/ComplexHeatmap-reference/book/index.html

The ComplexHeatmap package is inspired by the pheatmap package, but contains lots more functions.

We will see how to create a single heatmap, annotate it and combine heatmaps.

Create a single heatmap

The basic function is Heatmap() (remember the “H”).

library(ComplexHeatmap)

set.seed(18494)

# Let's create some data
a=rnorm(n = 15, mean = 0,   sd = 1.5)
b=rnorm(n = 15, mean = 0,   sd = 1.5)
c=rnorm(n = 15, mean = 0,   sd = 1.5)
d=rnorm(n = 15, mean = 0,   sd = 1.5)
e=rnorm(n = 15, mean = 0,   sd = 1.5)



mat = cbind(a,b,c,d,e)

nr=nrow(mat)
nc=ncol(mat)

rownames(mat) = paste0("row", seq_len(nr))
colnames(mat) = paste0("column", seq_len(nc))


mat
##          column1     column2     column3    column4    column5
## row1  -1.3465244  1.00936743  1.64244961 -1.5059708  0.8218742
## row2   2.0324465 -1.53864930 -0.67816605 -0.7309508 -0.4663479
## row3  -0.8861685  2.24298887  3.58247869 -0.4668248  1.3957509
## row4   2.3264709  0.59494845 -0.22488257 -2.2380533 -1.3736806
## row5  -1.3293604  2.07012406 -0.52288830  0.3878225 -0.5951234
## row6  -2.4663162  0.18830544  1.80684537  0.2605575  0.8862452
## row7   1.6711606 -2.13292014 -3.16089187  1.7503011  0.1221308
## row8   2.3750055  0.36904930 -0.75873504 -2.1066430 -0.8081975
## row9  -1.2459425  0.09876895  1.65984869  1.3861493 -1.0240513
## row10  0.8906801  1.02709999 -1.57741951 -2.2797123 -0.7204702
## row11 -1.5657081  2.52207354  0.04639403  1.3276764 -1.5855088
## row12  0.5667032  0.65457395  0.26647537 -1.1475006 -1.3251155
## row13  0.7981030  0.88466693  0.46592507  0.2284560  2.0234594
## row14  0.7155088 -0.40292172  3.20243917  1.4620738  1.9165075
## row15  1.2392599 -2.46904817 -0.39922026  0.2712318 -1.6328971
Heatmap(mat)

# remember that if you use Heatmap() function in a loop you have to assign it to a variable and then use draw()

Customize your heatmap

You can customize your heatmap by choosing dimensions, removing automatic clustering on both rows and columns, changing colors, assigning it a title, changing names and so on…

library(grid)
Heatmap(mat, width=unit(0.5*nc, "cm"), height=unit(0.5*nr, "cm"))

Heatmap(mat, width=unit(0.5*nc, "cm"), height=unit(0.5*nr, "cm"),cluster_rows = F)

Heatmap(mat,width=unit(0.5*nc, "cm"), height=unit(0.5*nr, "cm"), cluster_columns = F)

Heatmap(mat,width=unit(0.5*nc, "cm"), height=unit(0.5*nr, "cm"), cluster_rows = F, cluster_columns = F)

Heatmap(mat, width=unit(0.5*nc, "cm"), height=unit(0.5*nr, "cm"), name = "Heatmap N.1")

Heatmap(mat,width=unit(0.5*nc, "cm"), height=unit(0.5*nr, "cm"), border = T)

Heatmap(mat,width=unit(0.5*nc, "cm"), height=unit(0.5*nr, "cm"), row_title = "Rows", column_title = "Cols")

Heatmap(mat,width=unit(0.5*nc, "cm"), height=unit(0.5*nr, "cm"),row_split = c(rep("A", 10), rep("B", 5)),column_split = c(rep("C", 2), rep("D", 3)) )

library(circlize)

col_fun = colorRamp2(c(min(unlist(mat)), 0, max(unlist(mat))), c("green", "white", "magenta"))

Heatmap(mat, width=unit(0.5*nc, "cm"), height=unit(0.5*nr, "cm"),col = col_fun)

# Let's create a matrix with soma missing values (NA)

mat2=mat

mat2[12,4]=NA
mat2[15,5]=NA
mat2[3,2]=NA
mat2[6,1]=NA


Heatmap(mat2,width=unit(0.5*nc, "cm"), height=unit(0.5*nr, "cm"), col = col_fun, na_col = "black")

# There are also functions that allow to customize borders

Heatmap(mat, width=unit(0.5*nc, "cm"), height=unit(0.5*nr, "cm"),name = "mat", rect_gp = gpar(col = "white", lwd = 2),
    column_title = "Cell borders", col=col_fun)

# We can also customize titles, font, sizes etc (refer to the user guide for examples)

Annotate the heatmap

You can add annotations both on rows and columns.

ann_bar1=1:5
ann_bar2=sample(size = 15, x=1:100)

col_fun1 = colorRamp2(c(1,5), c("green", "orange"))
col_fun2 = colorRamp2(c(1,100), c("cyan", "pink"))

# Build the column annotation

haC = HeatmapAnnotation(
    barC = anno_barplot(ann_bar1, gp = gpar(fill = col_fun1(ann_bar1))),  # annotation name and values
    groupC = c(rep("C", 2), rep("D", 3)),# other annotation name and values
    
    # assign colors
    col = list(
               groupC = c("C" = "cornflowerblue", "D" = "yellow")
    ),
    gp = gpar(col = "black"),
    which = "column"
)



haR = HeatmapAnnotation(
    barR = anno_barplot(ann_bar2, gp = gpar(fill = col_fun2(ann_bar2))),  # annotation name and values
    groupR = c(rep("A", 10), rep("B", 5)),# other annotation name and values
    
    # assign colors
    col = list(
               groupR = c("A" = "blue", "B" = "red")
    ),
    gp = gpar(col = "black"),
    which = "row"
)



h3=Heatmap(mat, name = "mat", width=unit(0.5*nc, "cm"), height=unit(0.5*nr, "cm"), col=col_fun,
        right_annotation = haR,
        top_annotation = haC)
h3

# Notice that we "manually" coloured barplots, so legend is not inserted... insert it manually

lgd1 = Legend(col_fun = col_fun1, title = "barC")
lgd2 = Legend(col_fun = col_fun2, title = "barR")

pd = packLegend(lgd1, lgd2)

draw(h3, annotation_legend_list=pd)

Combine heatmaps

If you have to plot together different heatmaps that share row names you can assign them to variables and then use + to combine them.

h1=Heatmap(mat, name = "mat", rect_gp = gpar(col = "white", lwd = 2),
    column_title = "Cell borders", col=col_fun, width=unit(0.5*nc, "cm"), height=unit(0.5*nr, "cm"))

h2=Heatmap(mat2, col = col_fun, na_col = "black", width=unit(0.5*nc, "cm"), height=unit(0.5*nr, "cm"))



h_list = h1 + h2
class(h_list)
## [1] "HeatmapList"
## attr(,"package")
## [1] "ComplexHeatmap"
# explore draw() function to see customizations

h_list

Venn diagrams

A Venn diagram shows relations between two ore more sets. In particular, it is useful if we want to see if there are terms in common between two groups. Note that with more than 3 sets, it is better to switch to an upset plot, otherwise intersections become confusing.

We will use the VennDiagram package.

venn.diagram() function require a list of character vectors that represent our groups (they can be gene names, transcript names, sample names … whatever you need)

library(VennDiagram)
 
# Generate 3 groups of 1000 words
group1 <- paste0(rep("word_" , 1000) , sample(c(1:100000) , 1000 , replace=F))
group2 <- paste0(rep("word_" , 1000) , sample(c(1:100000) , 1000 , replace=F))
group3 <- paste0(rep("word_" , 1000) , sample(c(1:100000) , 1000 , replace=F))
 
str(group1)
##  chr [1:1000] "word_4544" "word_51040" "word_13966" "word_85877" ...
head(group1)
## [1] "word_4544"  "word_51040" "word_13966" "word_85877" "word_47898"
## [6] "word_27259"
# Plot

venn.diagram(
  x = list(group1, group2, group3),
  category.names = c("Group1" , "Group2" , "Group3"),
  filename = "venn_diagram.png",imagetype = "png",   # possible imege types are: "png", "tiff" or "svg"
  output=TRUE
)
## [1] 1

We can customize our diagram:

library(MetBrewer)

my_palette=met.brewer(name = "Klimt", n=3, type = "discrete")

venn.diagram(
        x = list(group1, group2, group3),
        category.names = c("Group1" , "Group2" , "Group3"),
        filename = "venn_diagram_manipulated.png",
        output=TRUE,
        
        # Output features
        imagetype="png" ,
        height = 480 , 
        width = 480 , 
        resolution = 300,
        
        # Circles
        lwd = 2,
        lty = 'blank',
        fill = my_palette,
        
        # Numbers
        cex = .6,
        fontface = "bold",
        fontfamily = "sans",
        
        # Set names
        cat.cex = 0.6,
        cat.fontface = "bold",
        cat.default.pos = "outer",
        cat.pos = c(-27, 27, 135),
        cat.dist = c(0.055, 0.055, 0.085),
        cat.fontfamily = "sans",
        rotation = 1
)
## [1] 1

Let’s make other two groups and try to intersect them through a Venn diagram:

library(VennDiagram)
 
# Generate 3 groups of 1000 words
group4 <- paste0(rep("word_" , 1000) , sample(c(1:100000) , 1000 , replace=F))
group5 <- paste0(rep("word_" , 1000) , sample(c(1:100000) , 1000 , replace=F))

 
my_palette=met.brewer(name = "Klimt", n=5, type = "discrete")

venn.diagram(
        x = list(group1, group2, group3, group4, group5),
        category.names = c("G1" , "G2" , "G3", "G4", "G5"),
        filename = "venn_diagram_many_groups.png",
        output=TRUE,
        
        # Output features
        imagetype="png" ,
        
        # Circles
        lwd = 2,
        lty = 'blank',
        fill = my_palette
        )
## [1] 1

It becomes difficult to distinguish intersection between all groups. We can use the upset plot:

Upset plot

library(UpSetR)

 # Define some vectors

group1 <- paste0(rep("word_" , 400) , sample(c(1:100000) , 400 , replace=F))
group2 <- paste0(rep("word_" , 30) , sample(c(1:100000) , 30 , replace=F))
group3 <- paste0(rep("word_" , 590) , sample(c(1:100000) , 590 , replace=F))
group4 <- paste0(rep("word_" , 400) , sample(c(1:100000) , 400 , replace=F))
group5 <- paste0(rep("word_" , 300) , sample(c(1:100000) , 300 , replace=F))
group6 <- paste0(rep("word_" , 800) , sample(c(1:100000) , 800 , replace=F))

# Make a names list with vectors

group_list=list("Group1"=group1, 
                "Group2"=group2,
                "Group3"=group3,
                "Group4"=group4,
                "Group5"=group5,
                "Group6"=group6)

# Make the upset

upset(fromList(group_list),nsets = 6, sets.bar.color = "violet" , main.bar.color = "navyblue")

# as you can see you can customise your plot... explore parameters!!