alkey
alkey

Reputation: 1046

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

Upvotes: 11

Views: 45411

Answers (4)

bklempay
bklempay

Reputation: 11

I found a useful solution that is somewhere in between Mike's beautiful but rather complicated solution and Andrew's "dumb and ugly" solution, which I love. I used Andrew's solution to add an extra break label, which you can then transform into a beautiful legend title!

library(pheatmap)

# Generate dummy data
tally <- matrix((1:50) + rnorm(50), nrow = 10, ncol = 5)

nbreaks <- 4 # as many breaks as you want
get.breaks <- seq(from = range(cor(tally))[1],
                  to = range(cor(tally))[2],
                  length.out = nbreaks)

# Create gtable using pheatmap
plot.cor <- pheatmap(cor(tally),
                     # add an additional break, it doesn't matter how you label it
                     legend_breaks = c(get.breaks, max(get.breaks)),
                     legend_labels = c(round(get.breaks, 2), "r"),
                     silent = TRUE)$gtable

# Extract legend grob from gtable:
# The legend is probably the last grob, but you might have to investigate
# plot.cor$grobs if this is not the case
index <- length(plot.cor$grobs)
legend.grob <- plot.cor$grobs[[index]]

# Transform ugly break label into beautiful legend title
legend.grob$children[[2]]$label[nbreaks + 1] <- expression("Pearson's" ~ italic(r))
legend.grob$children[[2]]$hjust <- c(rep(0, nbreaks), 0.75)
legend.grob$children[[2]]$vjust <- c(rep(0.5, nbreaks), -2)
# ... playing with the horizontal and vertical alignment should be sufficient
# to move your legend title exactly where you want it!
legend.grob$children[[2]]$gp$fontsize <- c(rep(8, nbreaks), 11)
plot.cor$grobs[[index]] <- legend.grob

# Plot using 'grid' or 'ggarrange'
library(grid)
grid.newpage()
grid.draw(plot.cor)

Final correlation heatmap with legend title:

Upvotes: 1

Soheil
Soheil

Reputation: 71

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)))

Upvotes: 7

Andrew J. Rech
Andrew J. Rech

Reputation: 466

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

Upvotes: 20

Mike H.
Mike H.

Reputation: 14370

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.

Upvotes: 17

Related Questions