Don
Don

Reputation: 190

Setting Midpoint for continuous diverging color scale on a heatmap

I need to adjust the midpoint location for a heatmap via ggplot2. I've googled around and have seen scale_fill_gradient2 be a great fit but the colors don't seem to match up to what I'm looking for. I know z needs a range from 0 to 1. Example dataset generation below:

library(ggplot2)
library(tibble)
library(RColorBrewer)

set.seed(5)
df <- as_tibble(expand.grid(x = -5:5, y = 0:5, z = NA))
df$z <- runif(length(df$z), min = 0, max = 1)

I tried plotting with the scale_fill_gradient2 but the blue color isn't coming as "dark" as I'd like.

ggplot(df, aes(x = x, y = y)) + 
  geom_tile(aes(fill = z)) + 
  scale_fill_gradient2(
    low = 'red', mid = 'white', high = 'blue',
    midpoint = 0.7, guide = 'colourbar', aesthetics = 'fill'
  ) + 
  scale_x_continuous(expand = c(0, 0), breaks = unique(df$x)) + 
  scale_y_continuous(expand = c(0, 0), breaks = unique(df$y))

Version 1

Therefore, I'm using scale_fill_distiller with the color palette 'RdBu' which comes out with the color scale I need but the ranges and the midpoints aren't right.

ggplot(df, aes(x = x, y = y)) + 
  geom_tile(aes(fill = z)) +
  scale_fill_distiller(palette = 'RdBu') + 
  scale_x_continuous(expand = c(0, 0), breaks = unique(df$x)) +
  scale_y_continuous(expand = c(0, 0), breaks = unique(df$y))

Version 2

Is there a way to get the 2nd color scale but with the option to set midpoint range as the first?

Upvotes: 6

Views: 11568

Answers (2)

teunbrand
teunbrand

Reputation: 37933

Claus' answer is great (and I'm a fan of his work), but I'd like to add that you can retain control within vanilla ggplot as well if you use the scale_fill_gradientn() function:

library(ggplot2)
library(tibble)

set.seed(5)
df <- as_tibble(expand.grid(x = -5:5, y = 0:5, z = NA))
df$z <- runif(length(df$z), min = 0, max = 1)

ggplot(df, aes(x = x, y = y)) + 
  geom_tile(aes(fill = z)) + 
  scale_fill_gradientn(
    colours = c("red", "white", "blue"),
    values = c(0, 0.7, 1)
  ) + 
  scale_x_continuous(expand = c(0, 0), breaks = unique(df$x)) + 
  scale_y_continuous(expand = c(0, 0), breaks = unique(df$y))

enter image description here

A notable downside is that you'd have to provide the values argument in rescaled space, so between 0-1. Consider if your fill values range from 0-10 instead and want the midpoint on 0.7, you'd have to provide values = c(0, 0.07, 1).

Upvotes: 9

Claus Wilke
Claus Wilke

Reputation: 17790

The color scales provided by the colorspace package will generally allow you much more fine-grained control. First, you can use the same colorscale but set the mid-point.

library(ggplot2)
library(tibble)
library(colorspace)

set.seed(5)
df <- as_tibble(expand.grid(x = -5:5, y = 0:5, z = NA))
df$z <- runif(length(df$z), min = 0, max = 1)

ggplot(df, aes(x = x, y = y)) + 
  geom_tile(aes(fill = z)) + 
  scale_fill_continuous_divergingx(palette = 'RdBu', mid = 0.7) + 
  scale_x_continuous(expand = c(0, 0), breaks = unique(df$x)) + 
  scale_y_continuous(expand = c(0, 0), breaks = unique(df$y))

However, as you see, this creates the same problem as before, because you'd have to be further away from the midpoint to get darker blues. Fortunately, the divergingx color scales allow you to manipulate either branch independently, and so we can create a scale that turns to dark blue much faster. You can play around with l3, p3, and p4 until you get the result you want.

ggplot(df, aes(x = x, y = y)) + 
  geom_tile(aes(fill = z)) + 
  scale_fill_continuous_divergingx(palette = 'RdBu', mid = 0.7, l3 = 0, p3 = .8, p4 = .6) + 
  scale_x_continuous(expand = c(0, 0), breaks = unique(df$x)) + 
  scale_y_continuous(expand = c(0, 0), breaks = unique(df$y))

Created on 2019-11-05 by the reprex package (v0.3.0)

Upvotes: 13

Related Questions