Anirban_Mitra
Anirban_Mitra

Reputation: 59

How to overlay two heatmaps via ggplot2 with two different scales_fill_gradient?

I have a data which has two variables and I want to see a single plot with heatmap for each of them overlaid on one another and showing two color scales for the two different variables. My code while not correct should clearly indicate what I am trying to achieve.

I have looked through several examples none of those indicate how to do this for geom_tile(). It would have been easy for geom_point. I am providing a synthetic example to show what I am doing. I get the error saying "Scale for 'fill' is already present. Adding another scale for 'fill', which will replace the existing scale." Evidently it is accepting only the second scale_fill_gradient, but I would like to view both the color gradients corresponding to the variables in the same heatmap. It would be great if I could find a way to get this plot. Thank you!

library(reshape2)
library(ggplot2)

set.seed(2)
m1 = matrix(rnorm(100), nrow=10)
m2 = matrix(rnorm(100), nrow=10)
M1 = melt(m1)
M2 = melt(m2)
names(M1)  = c("Var1", "Var2", "value1")
names(M2)  = c("Var1", "Var2", "value2")
pp1 <- ggplot() +
  geom_tile(data=M1, aes(x=Var1, y=Var2, fill=value1)) +
  scale_fill_gradient(low="white", high="red") +
  geom_tile(data=M2, aes(x=Var1, y=Var2, fill=value2)) +
  scale_fill_gradient(low="blue", high="yellow")
pp1

Upvotes: 0

Views: 3811

Answers (3)

Pablo Rod
Pablo Rod

Reputation: 669

A bivariate color legend. The intervals should maybe be the corresponding quantile.

library(tidyverse)
library(cowplot)

set.seed(2)
m1 = matrix(rnorm(100), nrow=10)
m2 = matrix(rnorm(100), nrow=10)
M1 = melt(m1)
M2 = melt(m2)
names(M1)  = c("Var1", "Var2", "value1")
names(M2)  = c("Var1", "Var2", "value2")

M1$value_cut <- cut(M1$value1, breaks = 3)
M2$value_cut <- cut(M2$value2, breaks = 3)

M1$value_cut2 <- M2$value_cut
M1$cuts <- paste(M1$value_cut, M1$value_cut2, sep = "-")

levels_comb <- expand.grid(lev1 = levels(M1$value_cut), lev2 = levels(M2$value_cut))
levels_comb$cuts <- paste(levels_comb$lev1, levels_comb$lev2, sep = "-")
levels_comb$filling <- c("#be64ac","#8c62aa","#3b4994","#dfb0d6","#a5add3","#5698b9","#e8e8e8","#ace4e4","#5ac8c8")

data_m <- left_join(M1, levels_comb, by = "cuts")

plot_tile <- ggplot(data_m, aes(x = Var1, y = Var2, fill = filling)) +
  geom_tile() +
  scale_fill_identity() +
  coord_equal() +
  theme_minimal()

legend_tile <- ggplot(levels_comb, aes(x = lev1, y = lev2, fill = filling)) +
  geom_tile() +
  scale_fill_identity() +
  coord_equal() +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

ggdraw() +
  draw_plot(plot_tile, 0, 0, 1, 1) +
  draw_plot(legend_tile, .75, .4, .3, .3)

enter image description here

Upvotes: 3

teunbrand
teunbrand

Reputation: 38053

So the legends themselves are no problem with the ggnewscale package, the problem lies in choosing the actual colours that you want to display. So let's make a new matrix with the actual colours you want to display:

library(ggnewscale)
library(scales)

r <- rescale(M1$value1)
# 1 - rescaled value because yellow should be bottom
g <- 1 - rescale(M2$value2)

# Second scale goes from yellow (low) to blue (high)
# Yellow is 100% blue, 100% green, so blue stays invariant
rgb <- rgb(r, g, 1)

# Make new matrix
M3 <- M1
M3$value1 <- rgb

And now plotting would occur as follows:

ggplot(mapping = aes(x = Var1, y = Var2)) +
  # This bit is for making scales
  geom_tile(data=M1, aes(fill = value1)) +
  scale_fill_gradient(low = "white", high = "red") +
  new_scale_fill() +
  geom_tile(data=M2, aes(fill=value2)) +
  scale_fill_gradient(low="yellow", high="blue") +
  new_scale_fill() +
  # This is the actual colours
  geom_tile(data=M3, aes(fill = M3$value1)) +
  scale_fill_identity()

enter image description here

The legends aren't 100% accurate since ggplot mixes colours in 'Lab' space, while we've mixed colours in rgb space, but you could replace the scale_fill_gradient() with for example scale_fill_gradientn(colours = rgb(seq(0, 1, length.out = 100), 0, 0)). Also be aware that the white-to-red scale should technically be a black-to-red scale in this example.

Upvotes: 4

Nate
Nate

Reputation: 10671

I find geom_col() + facet_grid() to be a useful pattern to get at your goal of visualizing the multiple values from the same area together.

There is a little set-up overhead from your starting data:

names(M1)  = c("Var1", "Var2", "value")
names(M2)  = c("Var1", "Var2", "value")
M1$type <- "M1"
M2$type <- "M2"

M <- rbind(M1, M2)

But the plot is straight forward. You don't really need the fill scale anymore, but I like to keep for highlighting the value changes.

ggplot(M) +
  geom_col(aes(type, value, fill = value)) +
  facet_grid(Var2 ~ Var1) +
  scale_fill_gradient(low="blue", high="yellow")

enter image description here

Not sure if this is palatable for you or not, but at least you get to see an alternative viz option.

Upvotes: 0

Related Questions