Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Diagonal labels orientation on x-axis in heatmap(s)

Creating heatmaps in R has been a topic of many posts, discussions and iterations. My main problem is that it's tricky to combine visual flexibility of solutions available in lattice levelplot() or basic graphics image(), with effortless clustering of basic's heatmap(), pheatmap's pheatmap() or gplots' heatmap.2(). It's a tiny detail I want to change - diagonal orientation of labels on x-axis. Let me show you my point in the code.

#example data d <- matrix(rnorm(25), 5, 5) colnames(d) = paste("bip", 1:5, sep = "") rownames(d) = paste("blob", 1:5, sep = "") 

You can change orientation to diagonal easily with levelplot():

require(lattice) levelplot(d, scale=list(x=list(rot=45))) 

enter image description here

but applying the clustering seems pain. So does other visual options like adding borders around heatmap cells.

Now, shifting to actual heatmap() related functions, clustering and all basic visuals are super-simple - almost no adjustment required:

heatmap(d) 

enter image description here

and so is here:

require(gplots) heatmap.2(d, key=F) 

enter image description here

and finally, my favourite one:

require(pheatmap) pheatmap(d)  

enter image description here

But all of those have no option to rotate the labels. Manual for pheatmap suggests that I can use grid.text to custom-orient my labels. What a joy it is - especially when clustering and changing the ordering of displayed labels. Unless I'm missing something here...

Finally, there is an old good image(). I can rotate labels, in general it' most customizable solution, but no clustering option.

image(1:nrow(d),1:ncol(d), d, axes=F, ylab="", xlab="") text(1:ncol(d), 0, srt = 45, labels = rownames(d), xpd = TRUE) axis(1, label=F) axis(2, 1:nrow(d), colnames(d), las=1) 

enter image description here

So what should I do to get my ideal, quick heatmap, with clustering and orientation and nice visual features hacking? My best bid is changing heatmap() or pheatmap() somehow because those two seem to be most versatile in adjustment. But any solutions welcome.

like image 605
Geek On Acid Avatar asked Mar 19 '13 16:03

Geek On Acid


People also ask

How do you angle the x axis labels in Seaborn?

Use the setp() Function to Rotate Labels on on Seaborn Axes We will take the tick label values using the xtick() function and rotate them using the rotation parameter of the setp() function.

How do you rotate an X label in Python?

Rotate X-Axis Tick Labels in Matplotlib There are two ways to go about it - change it on the Figure-level using plt. xticks() or change it on an Axes-level by using tick. set_rotation() individually, or even by using ax.


2 Answers

To fix pheatmap, all you really want to do is to go into pheatmap:::draw_colnames and tweak a couple of settings in its call to grid.text(). Here's one way to do that, using assignInNamespace(). (It may need additional adjustments, but you get the picture ;):

library(grid)     ## Need to attach (and not just load) grid package library(pheatmap)  ## Your data d <- matrix(rnorm(25), 5, 5) colnames(d) = paste("bip", 1:5, sep = "") rownames(d) = paste("blob", 1:5, sep = "")  ## Edit body of pheatmap:::draw_colnames, customizing it to your liking draw_colnames_45 <- function (coln, ...) {     m = length(coln)     x = (1:m)/m - 1/2/m     grid.text(coln, x = x, y = unit(0.96, "npc"), vjust = .5,          hjust = 1, rot = 45, gp = gpar(...)) ## Was 'hjust=0' and 'rot=270' }  ## For pheatmap_1.0.8 and later: draw_colnames_45 <- function (coln, gaps, ...) {     coord = pheatmap:::find_coordinates(length(coln), gaps)     x = coord$coord - 0.5 * coord$size     res = textGrob(coln, x = x, y = unit(1, "npc") - unit(3,"bigpts"), vjust = 0.5, hjust = 1, rot = 45, gp = gpar(...))     return(res)}  ## 'Overwrite' default draw_colnames with your own version  assignInNamespace(x="draw_colnames", value="draw_colnames_45", ns=asNamespace("pheatmap"))  ## Try it out pheatmap(d) 

enter image description here

like image 64
Josh O'Brien Avatar answered Nov 06 '22 06:11

Josh O'Brien


It is a little more complex than my comment presumed, because heatmap breaks up the plotting region in order to draw the dendrograms and the last plot region is not the image plot to which you want to attach the labels.

There is a solution though as heatmap provides the add.expr argument which takes an expression to be evaluated when the image is drawn. One also needs to know the reordering of the labels that takes place due to the dendrogram ordering. The last bit involves a bit of an inelegant hack as I will draw the heatmap first to get the reordering information and then use that to draw the heatmap properly with the angled labels.

First an example from ?heatmap

 x  <- as.matrix(mtcars)  rc <- rainbow(nrow(x), start = 0, end = .3)  cc <- rainbow(ncol(x), start = 0, end = .3)  hv <- heatmap(x, col = cm.colors(256), scale = "column",                RowSideColors = rc, ColSideColors = cc, margins = c(5,10),                xlab = "specification variables", ylab =  "Car Models",                main = "heatmap(<Mtcars data>, ..., scale = \"column\")") 

At this stage, the labels aren't how we want them, but hv contains the information we need to reorder the colnames of mtcars in its component $colInd:

> hv$colInd  [1]  2  9  8 11  6  5 10  7  1  4  3 

You use this like you would the output from order e.g.:

> colnames(mtcars)[hv$colInd]  [1] "cyl"  "am"   "vs"   "carb" "wt"   "drat" "gear" "qsec" "mpg"  "hp"   [11] "disp" 

Now use that to generate the labels we want in the correct order:

 labs <- colnames(mtcars)[hv$colInd] 

Then we re-call heatmap but this time we specify labCol = "" to suppress the labelling of the column variables (using zero length strings). We also use a call to text to draw the labels at the desired angle. The call to text is:

text(x = seq_along(labs), y = -0.2, srt = 45, labels = labs, xpd = TRUE) 

which is essentially what you have in your question. Play with the value of y as you need to adjust this to the length of the strings so as to have the labels not overlap with the image plot. We specify labels = labs to pass in the labels we want draw in the order required. The entire text call is passed to add.expr unquoted. Here is the entire call:

 hv <- heatmap(x, col = cm.colors(256), scale = "column",                RowSideColors = rc, ColSideColors = cc, margins = c(5,10),                xlab = "specification variables", ylab =  "Car Models",                labCol = "",                main = "heatmap(<Mtcars data>, ..., scale = \"column\")",                add.expr = text(x = seq_along(labs), y = -0.2, srt = 45,                                labels = labs, xpd = TRUE)) 

Which results in:

enter image description here

like image 45
Gavin Simpson Avatar answered Nov 06 '22 07:11

Gavin Simpson