Center continuous palettes in ggplot2

Using a divergent color palette can be beneficial when you want to draw attention to some values compared to a fixed point. Like temperature around freezing, monetary values around zero and so on. However it can be hard to align 0 to the middle of a continuous color scale. This post will explain how to do this correctly for scale_colour_distiller and scale_fill_distiller, and this will also work for extension packages such as scico.

Packages and data

library(ggplot2)
library(scico)

theme_set(theme_minimal())

example_data <- data.frame(name = letters[1:10],
                           value = -2:7 + 0.5)

The problem

First lets construct a simple chart, we have a bar chart where some of the bars go up, and some of the bars go down.

ggplot(example_data, aes(name, value)) +
  geom_col()

Next lets add some color by assigning the value to the fill aesthetic.

ggplot(example_data, aes(name, value, fill = value)) +
  geom_col()

Using a sequential palette for a chart like this doesn’t give us much insight. Lets add a divergent scale with scale_fill_gradient2(). While it is doing its job, you still have to define the colors yourself.

ggplot(example_data, aes(name, value, fill = value)) +
  geom_col() +
  scale_fill_gradient2()

Lets instead use the scale_fill_distiller() function to access the continuous versions of the brewer scales.

ggplot(example_data, aes(name, value, fill = value)) +
  geom_col() +
  scale_fill_distiller(type = "div")

But look! some of the upwards facing bars are colored green instead of orange.

The solution

The solution is to manually specify the the limits of the color palette such that the center of the palette appears in the middle of the range. This is simply done by finding the absolute maximum of the range of the variable to are mapping to the color. We then set the limits to go from negative max to positive max, thus making zero appear in the middle.

limit <- max(abs(example_data$value)) * c(-1, 1)

ggplot(example_data, aes(name, value, fill = value)) +
  geom_col() +
  scale_fill_distiller(type = "div", limit = limit)

This approach also works with the scico package.

limit <- max(abs(example_data$value)) * c(-1, 1)

ggplot(example_data, aes(name, value, fill = value)) +
  geom_col() +
  scale_fill_scico(palette = "cork", limit = limit) 

session information


─ Session info ───────────────────────────────────────────────────────────────
 setting  value                       
 version  R version 3.6.0 (2019-04-26)
 os       macOS Mojave 10.14.6        
 system   x86_64, darwin15.6.0        
 ui       X11                         
 language (EN)                        
 collate  en_US.UTF-8                 
 ctype    en_US.UTF-8                 
 tz       America/Los_Angeles         
 date     2020-04-23                  

─ Packages ───────────────────────────────────────────────────────────────────
 ! package     * version date       lib source        
 P assertthat    0.2.1   2019-03-21 [?] CRAN (R 3.6.0)
 P backports     1.1.6   2020-04-05 [?] CRAN (R 3.6.0)
 P blogdown      0.18    2020-03-04 [?] CRAN (R 3.6.0)
 P bookdown      0.18    2020-03-05 [?] CRAN (R 3.6.0)
 P cli           2.0.2   2020-02-28 [?] CRAN (R 3.6.0)
 P clipr         0.7.0   2019-07-23 [?] CRAN (R 3.6.0)
 P colorspace    1.4-1   2019-03-18 [?] CRAN (R 3.6.0)
 P crayon        1.3.4   2017-09-16 [?] CRAN (R 3.6.0)
 P desc          1.2.0   2018-05-01 [?] CRAN (R 3.6.0)
 P details     * 0.2.1   2020-01-12 [?] CRAN (R 3.6.0)
 P digest        0.6.25  2020-02-23 [?] CRAN (R 3.6.0)
 P dplyr         0.8.5   2020-03-07 [?] CRAN (R 3.6.0)
 P ellipsis      0.3.0   2019-09-20 [?] CRAN (R 3.6.0)
 P evaluate      0.14    2019-05-28 [?] CRAN (R 3.6.0)
 P fansi         0.4.1   2020-01-08 [?] CRAN (R 3.6.0)
 P ggplot2     * 3.3.0   2020-03-05 [?] CRAN (R 3.6.0)
 P glue          1.4.0   2020-04-03 [?] CRAN (R 3.6.0)
 P gtable        0.3.0   2019-03-25 [?] CRAN (R 3.6.0)
 P htmltools     0.4.0   2019-10-04 [?] CRAN (R 3.6.0)
 P httr          1.4.1   2019-08-05 [?] CRAN (R 3.6.0)
 P knitr       * 1.28    2020-02-06 [?] CRAN (R 3.6.0)
 P lifecycle     0.2.0   2020-03-06 [?] CRAN (R 3.6.0)
 P magrittr      1.5     2014-11-22 [?] CRAN (R 3.6.0)
 P munsell       0.5.0   2018-06-12 [?] CRAN (R 3.6.0)
 P pillar        1.4.3   2019-12-20 [?] CRAN (R 3.6.0)
 P pkgconfig     2.0.3   2019-09-22 [?] CRAN (R 3.6.0)
 P png           0.1-7   2013-12-03 [?] CRAN (R 3.6.0)
 P purrr         0.3.3   2019-10-18 [?] CRAN (R 3.6.0)
 P R6            2.4.1   2019-11-12 [?] CRAN (R 3.6.0)
 P Rcpp          1.0.4.6 2020-04-09 [?] CRAN (R 3.6.0)
   renv          0.9.3   2020-02-10 [1] CRAN (R 3.6.0)
 P rlang         0.4.5   2020-03-01 [?] CRAN (R 3.6.0)
 P rmarkdown     2.1     2020-01-20 [?] CRAN (R 3.6.0)
 P rprojroot     1.3-2   2018-01-03 [?] CRAN (R 3.6.0)
 P rstudioapi    0.11    2020-02-07 [?] CRAN (R 3.6.0)
 P scales        1.1.0   2019-11-18 [?] CRAN (R 3.6.0)
 P scico       * 1.1.0   2018-11-20 [?] CRAN (R 3.6.0)
 P sessioninfo   1.1.1   2018-11-05 [?] CRAN (R 3.6.0)
 P stringi       1.4.6   2020-02-17 [?] CRAN (R 3.6.0)
 P stringr       1.4.0   2019-02-10 [?] CRAN (R 3.6.0)
 P tibble        3.0.1   2020-04-20 [?] CRAN (R 3.6.2)
 P tidyselect    1.0.0   2020-01-27 [?] CRAN (R 3.6.0)
 P vctrs         0.2.4   2020-03-10 [?] CRAN (R 3.6.0)
 P withr         2.1.2   2018-03-15 [?] CRAN (R 3.6.0)
 P xfun          0.13    2020-04-13 [?] CRAN (R 3.6.2)
 P xml2          1.3.0   2020-04-01 [?] CRAN (R 3.6.2)
 P yaml          2.2.1   2020-02-01 [?] CRAN (R 3.6.0)

[1] /Users/emilhvitfeldthansen/Desktop/blogv4/renv/library/R-3.6/x86_64-apple-darwin15.6.0
[2] /private/var/folders/m0/zmxymdmd7ps0q_tbhx0d_26w0000gn/T/Rtmpi7dq5O/renv-system-library

 P ── Loaded and on-disk path mismatch.