R - Legend title or units when using Pheatmap
Asked Answered
B

3

9

I am using pheatmap to create a heatmap of values and would like to label the legend with the units of the z values in the matrix. In this example I would like the top of the legend to say Temperature [°C]. I have read the guidelines here for pheatmap, and it seems the only manipulation of the legend is to add a list of default numbers to be displayed in place of the scale. I cannot see any option to add a legend title per se.

Here is a generic block of code to generate a matrix and plot using pheatmap. I would really appreciate any advice on how to add a title to the legend.

test <- matrix(rexp(200, rate=.1), ncol=20)
colnames(test) = paste("Room", 1:20, sep = "")
rownames(test) = paste("Building", 1:10, sep = "")

pheatmap(test, legend = TRUE, cluster_rows = FALSE, cluster_cols = FALSE)

enter image description here

Bergerac answered 25/4, 2016 at 22:17 Comment(0)
E
16

OK so since someone has yet to answer this, I'll give you one possible option if you absolutely must use the pheatmap function. This is much easier to do using ggplot, but here it goes:

First we are going to want to generate our plot so we can use all the plot objects to create our own plot, with an edited legend.

#Edited to add in library names
library(gtable)
library(grid)

#Starting with data and generating initial plot
test <- matrix(rexp(200, rate=.1), ncol=20)
colnames(test) = paste("Room", 1:20, sep = "")
rownames(test) = paste("Building", 1:10, sep = "")

p<-pheatmap(test, legend = TRUE, cluster_rows = FALSE, cluster_cols = FALSE)



#Get grobs we want - will use these to create own plot later
plot.grob <- p$gtable$grob[[1]]
xlab.grob <- p$gtable$grob[[2]]  
ylab.grob <- p$gtable$grob[[3]]  
legend.grob <- p$gtable$grob[[4]]  

Now once we have our objects, we actually want to shift the legend down a little to make room for the title.

#Shift both down by 1 inch
legend.grob$children[[1]]$y <- legend.grob$children[[1]]$y - unit(0.85,"inches") 
legend.grob$children[[2]]$y <- legend.grob$children[[2]]$y - unit(0.85,"inches") 
legend.grob$children[[1]]$x <- legend.grob$children[[1]]$x + unit(0.4,"inches") 
legend.grob$children[[2]]$x <- legend.grob$children[[2]]$x + unit(0.4,"inches") 

Since we've made room for the legend, now we can create the legend textGrob and add it to the legend grobTree (just set of graphical objects in what we want our legend to be)

#New legend label grob
leg_label <- textGrob("Temperature [°C]",x=0,y=0.9,hjust=0,vjust=0,gp=gpar(fontsize=10,fontface="bold"))

#Add label to legend grob
legend.grob2 <- addGrob(legend.grob,leg_label)

If you want to check out what our legend will look like try:

grid.draw(legend.grob2)

Now we actually need to build our gtable object. To do this we will use a similar layout (with some modifications) as the plot generated by the pheatmap function. Also note that the pheatmap function generates a gtable object which can be accessed by:

p$gtable

In order to see the widths/heights of each of the "sectors" in our gtable object all we need to do is:

p$gtable$heights
p$gtable$widths

These will serve as our reference values. For a more graphical display try:

gtable_show_layout(p$gtable)

Which yields this image:

enter image description here

Ok, so now that we have the grobs we want, all we need to do is build our gtable based on what we saw for the gtable built by pheatmap. Some sample code I've written is:

my_new_gt <- gtable(widths=  unit.c(unit(0,"bigpts") + unit(5,"bigpts"),
                                    unit(0,"bigpts"),
                                    unit(1,"npc") - unit(1,"grobwidth",plot.grob) + unit(10,"bigpts") - max(unit(1.1,"grobwidth",plot.grob), (unit(12,"bigpts")+1.2*unit(1.1,"grobwidth",plot.grob))) + unit(5,"bigpts") - unit(3,"inches"),
                                    unit(1,"grobwidth",ylab.grob) + unit(10,"bigpts"),
                                    max(unit(1,"grobwidth",legend.grob2),unit(12,"bigpts")+1.2*unit(1.1,"grobwidth",legend.grob2)) + unit(1,"inches") ,
                                    max(unit(0,"bigpts"),unit(0,"bigpts"))
                                    ),
                                    
                    
                    
                    height = unit.c(unit(0,"npc"),
                                    unit(5,"bigpts"),
                                    unit(0,"bigpts"),
                                    unit(1,"npc") - unit(1,"grobheight",xlab.grob) + unit(15,"bigpts") - unit(0.2,"inches"),
                                    unit(1,"grobheight",xlab.grob) + unit(15,"bigpts")     
                      ))

Finally, we can add all our objects to our new gtable to get a very similar plot to the one generated by pheatmap with the added legend title.

#Adding each grob to the appropriate spot
gtable <- gtable_add_grob(my_new_gt,plot.grob,4,3)
gtable <- gtable_add_grob(gtable,xlab.grob,5,3)
gtable <- gtable_add_grob(gtable,ylab.grob,4,4)
gtable <- gtable_add_grob(gtable,legend.grob2,4,5)

grid.draw(gtable)

Finally the generated output is:

enter image description here

Hope this helped. You can fiddle around with the different sizing to try to make the layout more dynamic, but I think this is a good setup and gets you what you wanted - the pheatmap with a legend.

EDIT - ggplot option:

Since I recommended ggplot as an alternative here is some code to accomplish it:

library(ggplot2)
library(reshape)
test <- as.data.frame(matrix(rexp(200, rate=.1), ncol=20))
colnames(test) = paste("Room", 1:20, sep = "")
test$building = paste("Building", 1:10, sep = "")

#Get the sorting right
test$sort <- 1:10

#Melting data so we can plot it with GGplot
test.m <- melt(test,id.vars = c("building","sort"))

#Resetting factors
test.m$building <- factor(test.m$building, levels=(test.m$building)[order(test.m$sort)])

#Creating the plot itself
plot <- ggplot(test.m,aes(variable,building)) + geom_tile(aes(fill=value),color = "white") +
        #Creating legend
        guides(fill=guide_colorbar("Temperature [°C]")) +
        #Creating color range
        scale_fill_gradientn(colors=c("skyblue","yellow","tomato"),guide="colorbar") +
        #Rotating labels
        theme(axis.text.x = element_text(angle = 270, hjust = 0,vjust=-0.05))
plot

Which produces this plot:

enter image description here

As you can see the ggplot2 method is much faster. All you have to do is convert your data to a dataframe and then melt it. Once that's done, you can easily change the legend titles.

Effluent answered 26/4, 2016 at 20:9 Comment(1)
Sorry for my late reply but thank you so much for this amazing response. Your answer is so detailed and helps me understand pheatmap so much better! I was actually about to ask you the difference between ggplot and pheatmap but you already answered it! The one thing I like about pheatmap is how it can handle NA values by plotting them as a different colour to the scale. This is useful for me as I am dealing with a dataset that has some NA values. Although I have not tried with ggplot yet. Thanks again so much.Bergerac
C
19

MikeyMike's answer is incredible; I also learned a lot by reading it.

However, I needed a dumb, ugly, 10 second solution:

test <- matrix(rexp(200, rate=.1), ncol=20)
colnames(test) = paste("Room", 1:20, sep = "")
rownames(test) = paste("Building", 1:10, sep = "")

pheatmap(test, legend_breaks = c(10, 20, 30, 40, max(test)), 
main = "", legend_labels = c("10", "20", "30", "40", "title\n"),
legend = TRUE, cluster_rows = FALSE, cluster_cols = FALSE)

Which produces this heatmap:

enter image description here

Cleocleobulus answered 26/9, 2016 at 2:51 Comment(1)
This worked great for me too as a quick fix even if it's "dumb and ugly". I also want to highlight that setting main = "" is necessary, otherwise the legend title will be cutoff!Beaumont
E
16

OK so since someone has yet to answer this, I'll give you one possible option if you absolutely must use the pheatmap function. This is much easier to do using ggplot, but here it goes:

First we are going to want to generate our plot so we can use all the plot objects to create our own plot, with an edited legend.

#Edited to add in library names
library(gtable)
library(grid)

#Starting with data and generating initial plot
test <- matrix(rexp(200, rate=.1), ncol=20)
colnames(test) = paste("Room", 1:20, sep = "")
rownames(test) = paste("Building", 1:10, sep = "")

p<-pheatmap(test, legend = TRUE, cluster_rows = FALSE, cluster_cols = FALSE)



#Get grobs we want - will use these to create own plot later
plot.grob <- p$gtable$grob[[1]]
xlab.grob <- p$gtable$grob[[2]]  
ylab.grob <- p$gtable$grob[[3]]  
legend.grob <- p$gtable$grob[[4]]  

Now once we have our objects, we actually want to shift the legend down a little to make room for the title.

#Shift both down by 1 inch
legend.grob$children[[1]]$y <- legend.grob$children[[1]]$y - unit(0.85,"inches") 
legend.grob$children[[2]]$y <- legend.grob$children[[2]]$y - unit(0.85,"inches") 
legend.grob$children[[1]]$x <- legend.grob$children[[1]]$x + unit(0.4,"inches") 
legend.grob$children[[2]]$x <- legend.grob$children[[2]]$x + unit(0.4,"inches") 

Since we've made room for the legend, now we can create the legend textGrob and add it to the legend grobTree (just set of graphical objects in what we want our legend to be)

#New legend label grob
leg_label <- textGrob("Temperature [°C]",x=0,y=0.9,hjust=0,vjust=0,gp=gpar(fontsize=10,fontface="bold"))

#Add label to legend grob
legend.grob2 <- addGrob(legend.grob,leg_label)

If you want to check out what our legend will look like try:

grid.draw(legend.grob2)

Now we actually need to build our gtable object. To do this we will use a similar layout (with some modifications) as the plot generated by the pheatmap function. Also note that the pheatmap function generates a gtable object which can be accessed by:

p$gtable

In order to see the widths/heights of each of the "sectors" in our gtable object all we need to do is:

p$gtable$heights
p$gtable$widths

These will serve as our reference values. For a more graphical display try:

gtable_show_layout(p$gtable)

Which yields this image:

enter image description here

Ok, so now that we have the grobs we want, all we need to do is build our gtable based on what we saw for the gtable built by pheatmap. Some sample code I've written is:

my_new_gt <- gtable(widths=  unit.c(unit(0,"bigpts") + unit(5,"bigpts"),
                                    unit(0,"bigpts"),
                                    unit(1,"npc") - unit(1,"grobwidth",plot.grob) + unit(10,"bigpts") - max(unit(1.1,"grobwidth",plot.grob), (unit(12,"bigpts")+1.2*unit(1.1,"grobwidth",plot.grob))) + unit(5,"bigpts") - unit(3,"inches"),
                                    unit(1,"grobwidth",ylab.grob) + unit(10,"bigpts"),
                                    max(unit(1,"grobwidth",legend.grob2),unit(12,"bigpts")+1.2*unit(1.1,"grobwidth",legend.grob2)) + unit(1,"inches") ,
                                    max(unit(0,"bigpts"),unit(0,"bigpts"))
                                    ),
                                    
                    
                    
                    height = unit.c(unit(0,"npc"),
                                    unit(5,"bigpts"),
                                    unit(0,"bigpts"),
                                    unit(1,"npc") - unit(1,"grobheight",xlab.grob) + unit(15,"bigpts") - unit(0.2,"inches"),
                                    unit(1,"grobheight",xlab.grob) + unit(15,"bigpts")     
                      ))

Finally, we can add all our objects to our new gtable to get a very similar plot to the one generated by pheatmap with the added legend title.

#Adding each grob to the appropriate spot
gtable <- gtable_add_grob(my_new_gt,plot.grob,4,3)
gtable <- gtable_add_grob(gtable,xlab.grob,5,3)
gtable <- gtable_add_grob(gtable,ylab.grob,4,4)
gtable <- gtable_add_grob(gtable,legend.grob2,4,5)

grid.draw(gtable)

Finally the generated output is:

enter image description here

Hope this helped. You can fiddle around with the different sizing to try to make the layout more dynamic, but I think this is a good setup and gets you what you wanted - the pheatmap with a legend.

EDIT - ggplot option:

Since I recommended ggplot as an alternative here is some code to accomplish it:

library(ggplot2)
library(reshape)
test <- as.data.frame(matrix(rexp(200, rate=.1), ncol=20))
colnames(test) = paste("Room", 1:20, sep = "")
test$building = paste("Building", 1:10, sep = "")

#Get the sorting right
test$sort <- 1:10

#Melting data so we can plot it with GGplot
test.m <- melt(test,id.vars = c("building","sort"))

#Resetting factors
test.m$building <- factor(test.m$building, levels=(test.m$building)[order(test.m$sort)])

#Creating the plot itself
plot <- ggplot(test.m,aes(variable,building)) + geom_tile(aes(fill=value),color = "white") +
        #Creating legend
        guides(fill=guide_colorbar("Temperature [°C]")) +
        #Creating color range
        scale_fill_gradientn(colors=c("skyblue","yellow","tomato"),guide="colorbar") +
        #Rotating labels
        theme(axis.text.x = element_text(angle = 270, hjust = 0,vjust=-0.05))
plot

Which produces this plot:

enter image description here

As you can see the ggplot2 method is much faster. All you have to do is convert your data to a dataframe and then melt it. Once that's done, you can easily change the legend titles.

Effluent answered 26/4, 2016 at 20:9 Comment(1)
Sorry for my late reply but thank you so much for this amazing response. Your answer is so detailed and helps me understand pheatmap so much better! I was actually about to ask you the difference between ggplot and pheatmap but you already answered it! The one thing I like about pheatmap is how it can handle NA values by plotting them as a different colour to the scale. This is useful for me as I am dealing with a dataset that has some NA values. Although I have not tried with ggplot yet. Thanks again so much.Bergerac
C
6

Another quick solution is just to replace pheatmap::pheatmap with ComplexHeatmap::pheatmap and then add the heatmap_legend_param argument supported by ComplexHeatmap::pheatmap:

ComplexHeatmap::pheatmap(test, legend = TRUE, cluster_rows = FALSE, cluster_cols = FALSE, heatmap_legend_param = list(title = "title", at = c(10,20,30,40)))
Centroid answered 24/5, 2023 at 18:2 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.