Search code examples
rheatmappheatmap

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


Solution

  • 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.