1

I'm having trouble trying to fit heatmap with two dendrograms. Right now it looks like this (I use dashed lines just to show plots' borders):

enter image description here

My goal is to get something like this without using any magic constants in plot.margin (this particular image was created using them), so I could use one script to get heatmaps with dendrograms for hundreds of data sets, not just one:

enter image description here

(note free space between dendrogram on the top and heatmap as well as heatmap's labels and the bottom edge of the plot).

I think that main issue is that I don't really know how to calculate heatmap's width and height properly as well as space for axes' text.

Full reproducable example code:

library(ggplot2)
library(ggdendro)
library(reshape2)
library(gridExtra)

n_samples <- 20
n_modules <- 11

my_data <- dget("http://pastebin.com/raw/bKSXY5kP")

base_size <- 9
longest_sample_name <- max(nchar(my_data$names))
heatmap_height <- round((10 / 27.1) * n_samples, digits=3)
heatmap_width <- round((10 / 27.1) * n_modules, digits=3)
title_width <- round((10 / 27.1) * 0.25 * longest_sample_name, digits=3)

heatmap <- 
    ggplot(my_data, aes(Module, Sample)) + 
    geom_tile(aes(fill = value), colour = "black", size=0.5) + 
    scale_fill_gradientn(colours=c("blue", "blue", "white", "red", "red"), space="Lab") + 
    theme_grey(base_size=base_size, base_family = "mono") +
    scale_x_discrete(expand=c(0,0)) + 
    scale_y_discrete(expand=c(0,0)) +
    coord_fixed() +
    theme(
      panel.border = element_rect(linetype = "dashed", colour = "black", fill=NA),    
      plot.margin=unit(c(0, 0, 0, 0), "in"),
      legend.position ="none",
      axis.title = element_blank(),
      axis.text.y = element_text(size=base_size * 1.2, colour="black"), 
      axis.text.x = element_text(size=base_size * 1.2, colour="black"))

x <- matrix(my_data$value, nrow = n_samples, ncol = n_modules + 1)

dd.col <- as.dendrogram(hclust(dist(t(x))))
dd.row <- as.dendrogram(hclust(dist(x)))

ddata_x <- dendro_data(dd.row)
ddata_y <- dendro_data(dd.col)

theme_dendro <- theme(
  panel.border = element_rect(linetype = "dashed", colour = "black", fill=NA),
  plot.margin=unit(c(0, 0, 0, 0), "in"),
  panel.grid.major = element_blank(),
  panel.grid.minor = element_blank(),
  panel.background = element_blank(),
  axis.title = element_blank(),
  axis.text = element_blank(),
  axis.ticks = element_blank(),
  axis.line = element_blank())

dendro_horizontal <- 
  ggplot(segment(ddata_x)) + 
  geom_segment(aes(x=x, y=y, xend=xend, yend=yend)) + 
  scale_x_discrete(expand=c(0,0)) + 
  scale_y_discrete(expand=c(0,0)) + 
  theme_dendro +
  coord_flip()

dendro_vertical <- 
  ggplot(segment(ddata_y)) + 
  geom_segment(aes(x=x, y=y, xend=xend, yend=yend)) + 
  scale_x_discrete(expand=c(0,0)) + 
  scale_y_discrete(expand=c(0,0)) + 
  theme_dendro

layout_matrix <- rbind(c(NA, 1, NA),
                       c(2,  2, 3))

widths <- c(title_width, heatmap_width, heatmap_width * 2 / 3)
heights <- c(heatmap_height / 2, heatmap_height)

grobs <- arrangeGrob(grobs = list(dendro_vertical, heatmap, dendro_horizontal), 
                     layout_matrix = layout_matrix,
                     widths = widths,
                     heights = heights)

ggsave("heatmap_with_dendro.png", 
       plot = grobs, 
       width = sum(widths),
       height = sum(heights),
       units = "in")

This is how I calculate grobs' sizes ('magic' numbers are inherited from previous coder, didn't dare to touch them):

longest_sample_name <- max(nchar(my_data$names))
heatmap_height <- round((10 / 27.1) * n_samples, digits=3)
heatmap_width <- round((10 / 27.1) * n_modules, digits=3)
title_width <- round((10 / 27.1) * 0.25 * longest_sample_name, digits=3)

How I create heatmap:

heatmap <- 
    ggplot(my_data, aes(Module, Sample)) + 
    geom_tile(aes(fill = value), colour = "black", size=0.5) + 
    scale_fill_gradientn(colours=c("blue", "blue", "white", "red", "red"), space="Lab") + 
    theme_grey(base_size=base_size, base_family = "mono") +
    scale_x_discrete(expand=c(0,0)) + 
    scale_y_discrete(expand=c(0,0)) +
    coord_fixed() +
    theme(
      panel.border = element_rect(linetype = "dashed", colour = "black", fill=NA),    
      plot.margin=unit(c(0, 0, 0, 0), "in"),
      legend.position ="none",
      axis.title = element_blank(),
      axis.text.y = element_text(size=base_size * 1.2, colour="black"), 
      axis.text.x = element_text(size=base_size * 1.2, colour="black"))

And dendrograms:

x <- matrix(my_data$value, nrow = n_samples, ncol = n_modules + 1)

dd.col <- as.dendrogram(hclust(dist(t(x))))
dd.row <- as.dendrogram(hclust(dist(x)))

ddata_x <- dendro_data(dd.row)
ddata_y <- dendro_data(dd.col)

theme_dendro <- theme(
  panel.border = element_rect(linetype = "dashed", colour = "black", fill=NA),
  plot.margin=unit(c(0, 0, 0, 0), "in"),
  panel.grid.major = element_blank(),
  panel.grid.minor = element_blank(),
  panel.background = element_blank(),
  axis.title = element_blank(),
  axis.text = element_blank(),
  axis.ticks = element_blank(),
  axis.line = element_blank())

dendro_horizontal <- 
  ggplot(segment(ddata_x)) + 
  geom_segment(aes(x=x, y=y, xend=xend, yend=yend)) + 
  scale_x_discrete(expand=c(0,0)) + 
  scale_y_discrete(expand=c(0,0)) + 
  theme_dendro +
  coord_flip()

dendro_vertical <- 
  ggplot(segment(ddata_y)) + 
  geom_segment(aes(x=x, y=y, xend=xend, yend=yend)) + 
  scale_x_discrete(expand=c(0,0)) + 
  scale_y_discrete(expand=c(0,0)) + 
  theme_dendro

How I arrange them with gridExtra:

layout_matrix <- rbind(c(NA, 1, NA),
                       c(2,  2, 3))

widths <- c(title_width, heatmap_width, heatmap_width * 2 / 3)
heights <- c(heatmap_height / 2, heatmap_height)

grobs <- arrangeGrob(grobs = list(dendro_vertical, heatmap, dendro_horizontal), 
                     layout_matrix = layout_matrix,
                     widths = widths,
                     heights = heights)

And eventually save image:

ggsave("heatmap_with_dendro.png", 
       plot = grobs, 
       width = sum(widths),
       height = sum(heights),
       units = "in")
Nikita Gousak
  • 128
  • 2
  • 8
  • please provide a reproducible example... – Cyrus Mohammadian Sep 16 '16 at 23:19
  • @CyrusMohammadian Sure, forgot about that, added most of the code I use to create plots. Should I provide my data (and code I use to process this data) as well, or is this sufficient? – Nikita Gousak Sep 16 '16 at 23:35
  • you need to add the actual data, it'll make it easier on us to help. see [here](http://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example) for help – Cyrus Mohammadian Sep 16 '16 at 23:47
  • 1
    @CyrusMohammadian Thanks a lot for this link! Added fully reproducible example – Nikita Gousak Sep 17 '16 at 00:13
  • I'm not sure I see what the problem is. Are you concerned that theres no white space between the top of your plot and the bottom? and whats wrong with the labels, i.e. I'm not sure what your desired output is. Perhaps there's an example you can point to. – Cyrus Mohammadian Sep 17 '16 at 00:19
  • @CyrusMohammadian Sorry, I guess my english is not good enough to make it clear without [an image](http://imgur.com/a/c1VVc) of what I want to get as an output. Of course I can fit dendrograms using some 'magic' number in `plot.margin`, but I will have to do that for every single data set and I have hundreds of them so I'm looking for a way to automize the fitting process. – Nikita Gousak Sep 17 '16 at 00:25
  • I still dont know what you mean by free space... do you mean your dendrogram protrudes out from the axes too far? – Cyrus Mohammadian Sep 17 '16 at 00:26
  • @CyrusMohammadian not exactly, my issue is that I cant find a good way to calculate such height/width for heatmap plot that there will be equal (maybe zero) margins around the heatmap plot. Right now with my roughly calculated height/width I get different margins on different datasets. [example](https://i.imgur.com/ble0Y5t.png) – Nikita Gousak Sep 17 '16 at 00:40

0 Answers0