316 lines
13 KiB
Plaintext
Raw Permalink Normal View History

2025-01-12 00:52:51 +08:00
---
title: "Introduction to the viridis color maps"
author:
- "Bob Rudis, Noam Ross and Simon Garnier"
date: "`r Sys.Date()`"
output:
rmarkdown::html_vignette:
toc: true
toc_depth: 1
vignette: >
%\VignetteIndexEntry{Introduction to the viridis color maps}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
<style>
img {
max-width: 100%;
max-height: 100%;
}
</style>
# tl;dr
Use the color scales in this package to make plots that are pretty,
better represent your data, easier to read by those with colorblindness, and
print well in gray scale.
Install **viridis** like any R package:
```
install.packages("viridis")
library(viridis)
```
For base plots, use the `viridis()` function to generate a palette:
```{r setup, include=FALSE}
library(viridis)
knitr::opts_chunk$set(echo = TRUE, fig.retina=2, fig.width=7, fig.height=5)
```
```{r tldr_base, message=FALSE}
x <- y <- seq(-8*pi, 8*pi, len = 40)
r <- sqrt(outer(x^2, y^2, "+"))
filled.contour(cos(r^2)*exp(-r/(2*pi)),
axes=FALSE,
color.palette=viridis,
asp=1)
```
For ggplot, use `scale_color_viridis()` and `scale_fill_viridis()`:
```{r, tldr_ggplot, message=FALSE}
library(ggplot2)
ggplot(data.frame(x = rnorm(10000), y = rnorm(10000)), aes(x = x, y = y)) +
geom_hex() + coord_fixed() +
scale_fill_viridis() + theme_bw()
```
---
# Introduction
[`viridis`](https://cran.r-project.org/package=viridis), and its companion
package [`viridisLite`](https://cran.r-project.org/package=viridisLite)
provide a series of color maps that are designed to improve graph readability
for readers with common forms of color blindness and/or color vision deficiency.
The color maps are also perceptually-uniform, both in regular form and also when
converted to black-and-white for printing.
These color maps are designed to be:
- **Colorful**, spanning as wide a palette as possible so as to make differences
easy to see,
- **Perceptually uniform**, meaning that values close to each other have
similar-appearing colors and values far away from each other have more
different-appearing colors, consistently across the range of values,
- **Robust to colorblindness**, so that the above properties hold true for
people with common forms of colorblindness, as well as in grey scale printing, and
- **Pretty**, oh so pretty
`viridisLite` provides the base functions for generating the color maps in base
`R`. The package is meant to be as lightweight and dependency-free as possible
for maximum compatibility with all the `R` ecosystem. [`viridis`](https://cran.r-project.org/package=viridis)
provides additional functionalities, in particular bindings for `ggplot2`.
---
# The Color Scales
The package contains eight color scales: "viridis", the primary choice, and
five alternatives with similar properties - "magma", "plasma", "inferno",
"civids", "mako", and "rocket" -, and a rainbow color map - "turbo".
The color maps `viridis`, `magma`, `inferno`, and `plasma` were created by
Stéfan van der Walt ([@stefanv](https://github.com/stefanv)) and Nathaniel Smith ([@njsmith](https://github.com/njsmith)). If you want to know more about the
science behind the creation of these color maps, you can watch this
[presentation of `viridis`](https://youtu.be/xAoljeRJ3lU) by their authors at
SciPy 2015.
The color map `cividis` is a corrected version of 'viridis', developed by
Jamie R. Nuñez, Christopher R. Anderton, and Ryan S. Renslow, and originally
ported to `R` by Marco Sciaini ([@msciain](https://github.com/marcosci)). More
info about `cividis` can be found in
[this paper](https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0199239).
The color maps `mako` and `rocket` were originally created for the `Seaborn`
statistical data visualization package for Python. More info about `mako` and
`rocket` can be found on the
[`Seaborn` website](https://seaborn.pydata.org/tutorial/color_palettes.html).
The color map `turbo` was developed by Anton Mikhailov to address the
shortcomings of the Jet rainbow color map such as false detail, banding and
color blindness ambiguity. More infor about `turbo` can be found
[here](https://ai.googleblog.com/2019/08/turbo-improved-rainbow-colormap-for.html).
```{r for_repeat, include=FALSE}
n_col <- 128
img <- function(obj, nam) {
image(1:length(obj), 1, as.matrix(1:length(obj)), col=obj,
main = nam, ylab = "", xaxt = "n", yaxt = "n", bty = "n")
}
```
```{r begin, message=FALSE, include=FALSE}
library(viridis)
library(scales)
library(colorspace)
library(dichromat)
```
```{r show_scales, echo=FALSE, fig.height=3.575}
par(mfrow=c(8, 1), mar=rep(1, 4))
img(rev(viridis(n_col)), "viridis")
img(rev(magma(n_col)), "magma")
img(rev(plasma(n_col)), "plasma")
img(rev(inferno(n_col)), "inferno")
img(rev(cividis(n_col)), "cividis")
img(rev(mako(n_col)), "mako")
img(rev(rocket(n_col)), "rocket")
img(rev(turbo(n_col)), "turbo")
```
---
# Comparison
Let's compare the viridis and magma scales against these other commonly used
sequential color palettes in R:
- Base R palettes: `rainbow.colors`, `heat.colors`, `cm.colors`
- The default **ggplot2** palette
- Sequential [colorbrewer](https://colorbrewer2.org/) palettes, both default
blues and the more viridis-like yellow-green-blue
```{r 01_normal, echo=FALSE}
par(mfrow=c(7, 1), mar=rep(1, 4))
img(rev(rainbow(n_col)), "rainbow")
img(rev(heat.colors(n_col)), "heat")
img(rev(seq_gradient_pal(low = "#132B43", high = "#56B1F7", space = "Lab")(seq(0, 1, length=n_col))), "ggplot default")
img(gradient_n_pal(brewer_pal(type="seq")(9))(seq(0, 1, length=n_col)), "brewer blues")
img(gradient_n_pal(brewer_pal(type="seq", palette = "YlGnBu")(9))(seq(0, 1, length=n_col)), "brewer yellow-green-blue")
img(rev(viridis(n_col)), "viridis")
img(rev(magma(n_col)), "magma")
```
It is immediately clear that the "rainbow" palette is not perceptually uniform;
there are several "kinks" where the apparent color changes quickly over a short
range of values. This is also true, though less so, for the "heat" colors.
The other scales are more perceptually uniform, but "viridis" stands out for its
large *perceptual range*. It makes as much use of the available color space as
possible while maintaining uniformity.
Now, let's compare these as they might appear under various forms of colorblindness,
which can be simulated using the **[dichromat](https://cran.r-project.org/package=dichromat)**
package:
### Green-Blind (Deuteranopia)
```{r 02_deutan, echo=FALSE}
par(mfrow=c(7, 1), mar=rep(1, 4))
img(dichromat(rev(rainbow(n_col)), "deutan"), "rainbow")
img(dichromat(rev(heat.colors(n_col)), "deutan"), "heat")
img(dichromat(rev(seq_gradient_pal(low = "#132B43", high = "#56B1F7", space = "Lab")(seq(0, 1, length=n_col))), "deutan"), "ggplot default")
img(dichromat(gradient_n_pal(brewer_pal(type="seq")(9))(seq(0, 1, length=n_col)), "deutan"), "brewer blues")
img(dichromat(gradient_n_pal(brewer_pal(type="seq", palette = "YlGnBu")(9))(seq(0, 1, length=n_col)), "deutan"), "brewer yellow-green-blue")
img(dichromat(rev(viridis(n_col)), "deutan"), "viridis")
img(dichromat(rev(magma(n_col)), "deutan"), "magma")
```
### Red-Blind (Protanopia)
```{r 03_protan, echo=FALSE}
par(mfrow=c(7, 1), mar=rep(1, 4))
img(dichromat(rev(rainbow(n_col)), "protan"), "rainbow")
img(dichromat(rev(heat.colors(n_col)), "protan"), "heat")
img(dichromat(rev(seq_gradient_pal(low = "#132B43", high = "#56B1F7", space = "Lab")(seq(0, 1, length=n_col))), "protan"), "ggplot default")
img(dichromat(gradient_n_pal(brewer_pal(type="seq")(9))(seq(0, 1, length=n_col)), "protan"), "brewer blues")
img(dichromat(gradient_n_pal(brewer_pal(type="seq", palette = "YlGnBu")(9))(seq(0, 1, length=n_col)), "protan"), "brewer yellow-green-blue")
img(dichromat(rev(viridis(n_col)), "protan"), "viridis")
img(dichromat(rev(magma(n_col)), "protan"), "magma")
```
### Blue-Blind (Tritanopia)
```{r 04_tritan, echo=FALSE}
par(mfrow=c(7, 1), mar=rep(1, 4))
img(dichromat(rev(rainbow(n_col)), "tritan"), "rainbow")
img(dichromat(rev(heat.colors(n_col)), "tritan"), "heat")
img(dichromat(rev(seq_gradient_pal(low = "#132B43", high = "#56B1F7", space = "Lab")(seq(0, 1, length=n_col))), "tritan"), "ggplot default")
img(dichromat(gradient_n_pal(brewer_pal(type="seq")(9))(seq(0, 1, length=n_col)), "tritan"), "brewer blues")
img(dichromat(gradient_n_pal(brewer_pal(type="seq", palette = "YlGnBu")(9))(seq(0, 1, length=n_col)), "tritan"), "brewer yellow-green-blue")
img(dichromat(rev(viridis(n_col)), "tritan"), "viridis")
img(dichromat(rev(magma(n_col)), "tritan"), "magma")
```
### Desaturated
```{r 05_desatureated, echo=FALSE}
par(mfrow=c(7, 1), mar=rep(1, 4))
img(desaturate(rev(rainbow(n_col))), "rainbow")
img(desaturate(rev(heat.colors(n_col))), "heat")
img(desaturate(rev(seq_gradient_pal(low = "#132B43", high = "#56B1F7", space = "Lab")(seq(0, 1, length=n_col)))), "ggplot default")
img(desaturate(gradient_n_pal(brewer_pal(type="seq")(9))(seq(0, 1, length=n_col))), "brewer blues")
img(desaturate(gradient_n_pal(brewer_pal(type="seq", palette = "YlGnBu")(9))(seq(0, 1, length=n_col))), "brewer yellow-green-blue")
img(desaturate(rev(viridis(n_col))), "viridis")
img(desaturate(rev(magma(n_col))), "magma")
```
We can see that in these cases, "rainbow" is quite problematic - it is not
perceptually consistent across its range. "Heat" washes
out at bright colors, as do the brewer scales to a lesser extent. The ggplot scale
does not wash out, but it has a low perceptual range - there's not much contrast
between low and high values. The "viridis" and "magma" scales do better - they cover a wide perceptual range in brightness in brightness and blue-yellow, and do not rely as much on red-green contrast. They do less well
under tritanopia (blue-blindness), but this is an extrememly rare form of colorblindness.
---
# Usage
The `viridis()` function produces the `viridis` color scale. You can choose
the other color scale options using the `option` parameter or the convenience
functions `magma()`, `plasma()`, `inferno()`, `cividis()`, `mako()`, `rocket`()`,
and `turbo()`.
Here the `inferno()` scale is used for a raster of U.S. max temperature:
```{r tempmap, message=FALSE}
library(terra)
library(httr)
par(mfrow=c(1,1), mar=rep(0.5, 4))
temp_raster <- "http://ftp.cpc.ncep.noaa.gov/GIS/GRADS_GIS/GeoTIFF/TEMP/us_tmax/us.tmax_nohads_ll_20150219_float.tif"
try(GET(temp_raster,
write_disk("us.tmax_nohads_ll_20150219_float.tif")), silent=TRUE)
us <- rast("us.tmax_nohads_ll_20150219_float.tif")
us <- project(us, y="+proj=aea +lat_1=29.5 +lat_2=45.5 +lat_0=37.5 +lon_0=-96 +x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs")
image(us, col=inferno(256), asp=1, axes=FALSE, xaxs="i", xaxt='n', yaxt='n', ann=FALSE)
```
The package also contains color scale functions for **ggplot**
plots: `scale_color_viridis()` and `scale_fill_viridis()`. As with `viridis()`,
you can use the other scales with the `option` argument in the `ggplot` scales.
Here the "magma" scale is used for a cloropleth map of U.S. unemployment:
```{r, ggplot2}
library(maps)
library(mapproj)
data(unemp, package = "viridis")
county_df <- map_data("county", projection = "albers", parameters = c(39, 45))
names(county_df) <- c("long", "lat", "group", "order", "state_name", "county")
county_df$state <- state.abb[match(county_df$state_name, tolower(state.name))]
county_df$state_name <- NULL
state_df <- map_data("state", projection = "albers", parameters = c(39, 45))
choropleth <- merge(county_df, unemp, by = c("state", "county"))
choropleth <- choropleth[order(choropleth$order), ]
ggplot(choropleth, aes(long, lat, group = group)) +
geom_polygon(aes(fill = rate), colour = alpha("white", 1 / 2), linewidth = 0.2) +
geom_polygon(data = state_df, colour = "white", fill = NA) +
coord_fixed() +
theme_minimal() +
ggtitle("US unemployment rate by county") +
theme(axis.line = element_blank(), axis.text = element_blank(),
axis.ticks = element_blank(), axis.title = element_blank()) +
scale_fill_viridis(option="magma")
```
The ggplot functions also can be used for discrete scales with the argument
`discrete=TRUE`.
```{r discrete}
p <- ggplot(mtcars, aes(wt, mpg))
p + geom_point(size=4, aes(colour = factor(cyl))) +
scale_color_viridis(discrete=TRUE) +
theme_bw()
```
# Gallery
Here are some examples of viridis being used in the wild:
James Curley uses **viridis** for matrix plots ([Code](https://gist.github.com/jalapic/9a1c069aa8cee4089c1e)):
[![](http://pbs.twimg.com/media/CQWw9EgWsAAoUi0.png)](http://pbs.twimg.com/media/CQWw9EgWsAAoUi0.png)
Christopher Moore created these contour plots of potential in a dynamic
plankton-consumer model:
[![](http://pbs.twimg.com/media/CQWTy7wWcAAa-gu.jpg)](http://pbs.twimg.com/media/CQWTy7wWcAAa-gu.jpg)