allankey allankey - 2 months ago 21
R Question

R - Legend title or units when using Pheatmap

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

Answer

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.