D.2 Essentials of color in R
Issues of colors are complicated — even when only considering those in R. As our ways of perceiving colors are based on biology, experience, and language, a theoretical understanding of color is an ambitious inter-disciplinary endeavor at the intersection of aesthetics, humanities, and science. (See the Wikipedia articles on color vision and color theory to get started.)
From a practical point of view, most people using R for creating beautiful visualizations never need to worry about the theoretical underpinnings of color systems.43 Fortunately, R comes loaded with tools for dealing with color and a range of packages offer an abundance of pre-defined color palettes.
When looking for nice colors, most people’s needs fall into 1 of 2 categories:
Find a color or color palette that suits my current goals.
Define a new color or color palette that suits my current goals.
We will discuss ways of addressing both needs in the following sections.
D.2.1 Basic R colors
R base colors
If you want to specify your own colors, you need to name them or select a color scale. R comes with 657 predefined colors, whose names can be viewed by evaluating
colors() in the console, or running
demo("colors"). Figure D.1 shows a random sample of 100 colors (from all 657 colors, but excluding over 200 shades of
This figure shows that there is no shortage of vibrant colors in R. However, choosing and combining colors from the list of
colors() resembles a lottery: We can get lucky, but in most cases the outcome will be disappointing. A more promising approach is using functions that generate color palettes that follow the same principles and thus are more likely to fit together.
R base color palettes
The grDevices package that is included in R comes with a range of functions that allow defining color palettes. To obtain continuous color palettes, the grDevices package of R traditionally offers several functions to define vectors of
n <- 10 p1 <- cm.colors(n) p2 <- rainbow(n) p3 <- heat.colors(n) p4 <- terrain.colors(n) p5 <- topo.colors(n) # Example plots: pie(rep(1, n), col = p1, main = "Pie plot with rainbow(n = 10)") barplot(seq(1:n), col = p4, main = "Bar plot with terrain.colors(n = 10)")
The color palettes returned as the output of these color functions are vibrant and bright:
# Comparing color ranges: library(unikn) seecol(list(p1, p2, p3, p4, p5), col_brd = "white", lwd_brd = 4, title = "Basic grDevices color functions (n = 10)")
However, obtaining truly beautiful color palettes requires more than just automatically drawing colors from some function. The following color palettes have in common that they include an element of design.
HCL color palettes
Starting with R 3.6.0 (released on 2019-04-26), the
hcl.colors() function of the grDevices package provides a basic and frugal implementation of the prespecified palettes in the colorspace package (Ihaka et al., 2019) (see this blog post for details). From this version onwards, the default colors for
filled.contour() are based on
hcl.colors(). In addition, palette-generating functions (like
gray.colors()) feature a new
rev argument to facilitate reversing the order of colors (which can also be done by using
rev() to reverse the output vector of a color function).
hcl.pals() to obtain the names of 110 pre-defined HCL palettes. Here are some examples of palettes included in
n <- 10 p1 <- hcl.colors(n, palette = "Dynamic") p2 <- hcl.colors(n, palette = "Earth") p3 <- hcl.colors(n, palette = "Berlin") p4 <- hcl.colors(n, palette = "Fall") p5 <- hcl.colors(n, palette = "Sunset") library(unikn) seecol(list(p1, p2, p3, p4, p5), col_brd = "white", lwd_brd = 4, title = "Example palettes from hcl.colors(n = 10)")
# `hcl.pals()` prints the names of pre-defined HCL palettes.
hcl.colors() incorporates an immense range of color palettes from other packages (e.g., ColorBrewer, viridis, scico), this powerful functionality renders many other color packages obsolete. Nevertheless, the following color packages provide additional support for special purposes.
D.2.2 Color packages
Most packages that primarily deal with visualizations — like ggplot2, lattice, or plotly — load or provide predefined color palettes.
To reach beyond the 657 predefined colors of R and the color palettes included in grDevices, the primary resource are packages that provide additional — and typically more specialized — color support.
There is a large number of R packages that provide dedicated color support (i.e., define colors and color scales, and corresponding functions) for all means and purposes. As a consequence, many users of R never define a new color palette, but use the color palettes provided by others. However, to choose nice colors, we have to know which options exist and how they can be chosen and compared. As choosing colors is not just an art, but also a matter of taste, we merely mention some personal preferences in this section.
The colors at http://colorbrewer2.org (Brewer, 2019) were primarily designed to color maps. Beyond cartography, the vibrant color palettes are widely used in the R community, thanks to the popular RColorBrewer package (Neuwirth, 2014) and their integration into the ggplot2 package (Wickham et al., 2019).
Actually, unless you happen to need very special colors, RColorBrewer provides nice and useful color palettes that are sufficient for most purposes.
library(RColorBrewer) # load package
The following command prints all color palettes included in RColorBrewer:
Designing for color vision deficiencies
When using color, we must keep in mind that about 10% of the population is color vision impaired or color blind. Here are some ways of coping with this fact and still using color palettes that look visually friendly and beautiful for people with color vision.
# color blind friendly palette (with grey): cbf_1 <- c("#999999", "#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7") # color_blind_friendly palette (with black): cbf_2 <- c("#000000", "#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7")
Note that the
cbf_2 color palettes were simply defined as vectors (with the colors specified as characters of HEX code) and only differ in the 1st color, which is either grey or black:
In Section D.2.3 (below) we will show how to use RGB values to define a palette that implements Color Universal Design (CUD) recommendations (Okabe & Ito, 2008). But besides defining your own color palettes, we can also benefit from the designs of other people by using dedicated packages that target this issue.
The color scales of the viridis package (Garnier, 2018a) were specifically designed to be perceptually-uniform, both in regular form and when converted to black-and-white. Its color palettes
inferno can also be perceived by readers with the most common form of color blindness, and the
cividis scale is even suited for people with color vision deficiency.
library(viridis) # load package 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)
x <- y <- seq(-6*pi, 6*pi, len = 36) r <- sqrt(outer(x^2, y^2, "+")) filled.contour(cos(r^2) * exp(-r/(2*pi)), axes = FALSE, color.palette = inferno, asp = 1)
In recent versions of ggplot2, the viridis scales are integrated and can be used by specifying
scale_color_viridis_c() (for continuous color scales) and
scale_fill_viridis_d() (for discrete color scales).
(See also Chapter 12.3 Using a Colorblind-Friendly Palette in the R Graphics Cookbook.)
library(viridisLite) # load package vir_10 <- viridis(n = 10) seecol(vir_10, col_brd = "white", lwd_brd = 4, title = "Example of a viridis color palette (n = 10)")
The following functions each define a color scale of
n <- 20 # number of colors # define 5 different color scales (n colors each): c1 <- viridis(n) c2 <- magma(n) c3 <- inferno(n) c4 <- plasma(n) c5 <- cividis(n) # See and compare color scales: seecol(list(c1, c2, c3, c4, c5), col_brd = "white", lwd_brd = 4, title = "Various viridis color palettes (n = 20)")
?viridisLite::viridis for additional information.
Another option for obtaining perceptually ordered and uniform color palettes are the Scientific colour maps by Fabio Crameri (Crameri, 2018). They are provided in many different formats — implemented by the scico package in R — friendly to people with color vision deficiency, and still readable in black-and-white print.
Finally, the dichromat package also focuses on palettes for color-impaired viewers.
The unikn package (Hansjoerg Neth & Gradwohl, 2019) implements the color schemes of the University of Konstanz and is used throughout this book. Besides providing a range of pre-defined color palettes, the package provides some color functions that are generally useful:
- The package provides the
seecol()function that allows either seeing (or printing the details of) a particular color palette:
library(unikn) # load package seecol(pal = pal_unikn) # see/print details of a particular color palette
or comparing multiple color palettes:
seecol(pal = "all") # compare all available color palettes
- The unikn package also provides a
usecol()function that makes creating new and modifying existing color palettes very easy:
mix_1 <- usecol(pal = c(Karpfenblau, "white", Peach), n = 15) # define color palette from 3 colors mix_2 <- usecol(pal = c(rev(pal_seeblau), "white", pal_pinky)) # combining 2 color palettes mix_3 <- usecol(pal = c(rev(pal_bordeaux), "white", pal_petrol), n = 15) # mix and extend color palettes # Compare color palettes: seecol(list(mix_1, mix_2, mix_3), col_brd = "white", lwd_brd = 4, title = "Comparing palettes mixed from unikn colors") # show and compare a list of color palettes
usecol() function also provides a wrapper for using and modifying (pre-defined or newly created) color palettes in graphs:
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, col = usecol(mix_2, n = 20), asp = 1)
Even when not using the color palettes of the University of Konstanz, the
seecol() functions can be useful, as they also work with the color palettes provided by other packages. For instance, we can use the
seecol() function to compare (and modify) sets of color palettes from different packages. The following code creates 4 palettes from
hcl.pals() of grDevices (R Core Team, 2019), 4 palettes from RColorBrewer (Neuwirth, 2014), and 4 palettes from yarrr (Phillips, 2017), and then uses the
seecol() function to view and compare these color palettes:
# (a) get some HCL palettes (assuming R 3.6.0+): hcl_1 <- hcl.colors(n = 10, palette = "Fall") hcl_2 <- hcl.colors(n = 10, palette = "Geyser") hcl_3 <- hcl.colors(n = 10, palette = "Berlin") hcl_4 <- hcl.colors(n = 10, palette = "Zissou 1") # (b) get some palettes from RColorBrewer: library(RColorBrewer) brew_1 <- brewer.pal(n = 11, name = "BrBG") brew_2 <- brewer.pal(n = 11, name = "PRGn") brew_3 <- brewer.pal(n = 11, name = "Spectral") brew_4 <- brewer.pal(n = 8, name = "Pastel1") # display.brewer.all() # shows all Brewer palettes # (c) get some palettes from yarrr: library(yarrr) yarrr_1 <- yarrr::piratepal(palette = "basel") yarrr_2 <- yarrr::piratepal(palette = "appletv") yarrr_3 <- yarrr::piratepal(palette = "espresso") yarrr_4 <- yarrr::piratepal(palette = "info") # yarrr::piratepal() # shows all pirate palettes # Compare color palettes: library(unikn) seecol(list(hcl_1, hcl_2, hcl_3, hcl_4, brew_1, brew_2, brew_3, brew_4, yarrr_1, yarrr_2, yarrr_3, yarrr_4), col_brd = "white", lwd_brd = 2, title = "Using seecol() to see RColorBrewer and yarrr palettes")
Similarly, we can use the
usecol() function to mix and modify color palettes from various sources and then inspect the results with
# Mix, extend and modify some palettes (from other packages): brew_mix <- usecol(c(rev( brewer.pal(n = 4, name = "Reds")), "white", brewer.pal(n = 4, name = "Blues")), n = 13) brew_ext <- usecol(brewer.pal(n = 11, name = "Spectral"), n = 12) yarrr_mix <- usecol(c(piratepal("nemo"), piratepal("bugs"))) yarrr_mod <- usecol(c(piratepal("ipod")), n = 9) # Compare new color palettes: seecol(list(brew_mix, brew_ext, yarrr_mix, yarrr_mod), col_brd = "white", lwd_brd = 2, title = "Using usecol() and seecol() to mix and modify palettes")
D.2.3 Defining colors and color palettes
Defining individual colors
As there are many different ways to describe a color, there are many different ways to define a color. Here, we will only discuss 3 common ways of defining a color in R:
- by color name (e.g.,
col = c("black", "white"))
colors() for the list of 657 color names available in base R — and note that every color is represented in character type.
- by HEX (hexadecimal) code (e.g.,
col = c("#000000", "#FFFFFF"))
Such HEX codes essentially specify a triplet of RGB values in hexadecimal notation. The 3 bytes represent a color’s red, green and blue components by a number in the range 00 to FF (in hexadecimal notation), corresponding to 0 to 255 (in decimal notation). As this way of representing color is popular online (in HTML), they are also known as web colors. Note that, in R, each HEX code is represented in character type, with the hash tag
# as a prefix.
- by RGB (red-green-blue) values (e.g.,
col = c(rgb(red = 0, green = 0, blue = 0, maxColorValue = 255), rgb(255, 255, 255, maxColorValue = 255)))
Such RGB values are more traditional and can be entered and converted in most computer systems. In R, we can use the
rgb() function to enter the
blue value of a color, as well as an optional transparency value
Note that we need to specify the
maxColorValue = 255 to scale these values in the most common fashion (from 0 to 255).
Importantly, any particular color can be expressed in any system. For instance, the following graph shows the visual representation, color name, HEX codes, and RGB values of a vector
my_cols that describes 4 colors:
my_cols <- c("black", "orange", "gray", "steelblue")
The 3 ways of expressing each individual color are interchangeable. In the graph above, we can see that the color palette
my_cols could have been defined in the following ways:
p1 <- c("black", "orange", "gray", "steelblue") # by color name p2 <- c("#000000", "#FFA500", "#BEBEBE", "#4682B4") # by HEX code p3 <- c(rgb( 0, 0, 0, maxColorValue = 255), # by RGB value rgb(255, 165, 0, maxColorValue = 255), rgb(190, 190, 190, maxColorValue = 255), rgb( 70, 130, 180, maxColorValue = 255)) p4 <- c("black", # mixing names, "#FFA500", "#BEBEBE", # HEX codes, and rgb( 70, 130, 180, maxColorValue = 255)) # RGB values
and these 4 ways all result in exactly the same color palette, as the following comparison graph shows:
seecol(list(p1, p2, p3, p4), col_brd = "white", lwd_brd = 4, title = "4 ways of defining the same color palette")
Knowing that colors can be described by their names, HEX codes, and RGB values is perfectly sufficient for most R users. Nevertheless, there are many additional ways to define and express colors. For instance, the HSV (hue-saturation-value) system is a simple transformation of the RGB color space and is used in many software systems (see
?hsv for corresponding R functions). By contrast, the HCL (hue-chroma-luminance) system is much more suitable for describing human color perception (see
?hcl, and the
hcl.colors() function available in grDevices from R 3.6.0 onwards).
R also comes with powerful color conversion functions that allow translating color values between the different systems. For instance, we can use the
col2rgb() function of grDevices to obtain the RGB values that correspond to specific R color names. As
col2rgb() requires a matrix-like object (rather than a vector) as its input to its
col argument, we use the
newpal() function of unikn with
as_df = TRUE to define a color palette
new_cols as a data frame:
library(unikn) # Define a vector with colors: col_names <- c("black", "grey", "white", "firebrick", "forestgreen", "gold", "steelblue") # Define corresponding color palette: new_cols <- newpal(col = col_names, names = col_names, as_df = TRUE) # Verify data structure: is.vector(new_cols) #>  FALSE is.data.frame(new_cols) #>  TRUE
To obtain the RGB values of
new_cols, we can either use the
col2rgb() function (to obtain a matrix of RGB values) or the
seecol() function of unikn (for visual inspection):
col2rgb(new_cols) #> black grey white firebrick forestgreen gold steelblue #> red 0 190 255 178 34 255 70 #> green 0 190 255 34 139 215 130 #> blue 0 190 255 34 34 0 180 seecol(new_cols, col_brd = "black", lwd_brd = 2, title = "See the colors, HEX codes, and RGB values of new_cols")
When frequently needing to convert colors between different color spaces, consider using the colorspace package, which defines color-class objects that can be converted into a variety of color spaces (including RGB, sRGB, HSV, HLS, XYZ, LUV, LAB, polarLUV, and polarLAB).
Defining color palettes
In Section D.1.1, we distinguished between individual colors and color palettes, which are sets of colors to be used together. Defining our own color palettes is a great way to maintain a consistent color scheme for multiple graphs in a report or thesis.
It is easy to define our own color palettes. Corresponding to the 3 common ways of defining a color in R, we can define new color palettes in 3 ways. To illustrate them, we will use the
seecol() functions of unikn.
- Starting from R color names:
pal_flag_de <- newpal(col = c("black", "firebrick3", "gold"), names = c("Schwarz", "Rot", "Gold")) seecol(pal_flag_de, col_brd = "white", lwd_brd = 4, title = "Colors of the flag of Germany")
- Starting from HEX values:
# (a) Google logo colors: # Source: https://www.schemecolor.com/google-logo-colors.php color_google <- c("#4285f4", "#34a853", "#fbbc05", "#ea4335") names_google <- c("blueberry", "sea green", "selective yellow", "cinnabar") pal_google <- newpal(color_google, names_google) seecol(pal_google, col_brd = "white", lwd_brd = 6, title = "Colors of the Google logo")
- Starting from RGB values, we can implement the Color Universal Design (CUD) recommendations of https://jfly.uni-koeln.de/color/ (see Figure 16 of Okabe & Ito, 2008):
# Barrier-free color palette # Source: Okabe & Ito (2008): Color Universal Design (CUD): # Fig. 16 of <https://jfly.uni-koeln.de/color/>: # (a) Vector of colors (as RGB values): o_i_colors <- c(rgb( 0, 0, 0, maxColorValue = 255), # black rgb(230, 159, 0, maxColorValue = 255), # orange rgb( 86, 180, 233, maxColorValue = 255), # skyblue rgb( 0, 158, 115, maxColorValue = 255), # green rgb(240, 228, 66, maxColorValue = 255), # yellow rgb( 0, 114, 178, maxColorValue = 255), # blue rgb(213, 94, 0, maxColorValue = 255), # vermillion rgb(204, 121, 167, maxColorValue = 255) # purple ) # (b) Vector of color names: o_i_names <- c("black", "orange", "skyblue", "green", "yellow", "blue", "vermillion", "purple") # (c) Use newpal() to combine colors and names: pal_okabe_ito <- newpal(col = o_i_colors, names = o_i_names) seecol(pal_okabe_ito, title = "Color-blind friendly color scale (Okabe & Ito, 2002)")
By creating a list of our new color palettes, we can print and compare them using the
# Compare custom color palettes: my_pals <- list(pal_flag_de, pal_google, pal_okabe_ito) seecol(my_pals, col_brd = "white", lwd_brd = 6, title = "Comparing custom color palettes")
usecol() functions also allow specifying
n for extending color palettes:
seecol(pal_flag_de, n = 5, col_brd = "white", lwd_brd = 5, title = "Extending custom color palette pal_de_flag (n = 5)")
or modifying color palettes further (e.g., by adding transparency values
seecol(my_pals, n = 10, alpha = .50, col_brd = "white", lwd_brd = 8, title = "Comparing custom color palettes")
Using custom palettes in base R
Different color resources provide colors in different ways. Color palettes are either defined as functions that return an output vector, data frame, or matrix,
or as R objects that are vectors, data frames, or matrices.
As a uniform interface to various ways of defining and representing color palettes, the unikn package provides the
# Define 3 palettes: p1 <- usecol(c("black", "#ea4335", rgb(red = 251, green = 188, blue = 5, maxColorValue = 255))) p2 <- usecol(terrain.colors(10)) # using a color function p3 <- usecol(pal_unikn) # using a color object # Example plots: pie(rep(1, 6), col = p1, main = "Pie plot using p1") barplot(runif(10, 4, 8), col = p2, main = "Bar plot using p2") barplot(runif(10, 4, 8), col = p3, main = "Bar plot using p3")
usecol() function also allows mixing combinations of colors and color palettes:
# Mixing a new color palette: p1 <- usecol(pal = c(rev(pal_seeblau), "white", pal_pinky)) # Mixing, extending a color palette (and adding transparency): p2 <- usecol(pal = c(rev(pal_seegruen), "white", pal_bordeaux), n = 15, alpha = .8) # Defining and using a custom color palette: p3 <- usecol(c("#E77500", "white", "black"), n = 7) # Show set of color palettes: seecol(list(p1, p2, p3), col_brd = "white", lwd_brd = 2, title = "Using usecol() to mix and modify color palettes")
Using custom palettes in ggplot2
- To use a particular color in
ggplot, we can pass its name (as a character string) to functions that includes a
library(ggplot2) # Using an individual color (as an argument): ggplot(mpg) + geom_point(aes(x = displ, y = hwy), color = "steelblue", size = 3) + theme_bw()
Note that placing the
color = "steelblue" specification outside of the
aes() function changed all points of our plot to this particular color.
- To define and use a new color palette
ggplot, we need to add a
scale_color_manualline that instructs ggplot to use a custom color scale:
# Define a color palette: my_pal <- usecol(c("steelblue", "gold", "#ea4335", rgb(red = 52, green = 168, blue = 83, maxColorValue = 255))) # Use color palette in ggplot: ggplot(mpg) + geom_point(aes(x = displ, y = hwy, color = factor(cyl)), size = 4, alpha = .6) + scale_color_manual(values = my_pal) + theme_bw()
Brewer, C. (2019). ColorBrewer 2.0: Color advice for cartography. Retrieved from http://www.colorbrewer2.org
Crameri, F. (2018). Scientific colour-maps. Zenodo. https://doi.org/10.5281/zenodo.1243862
Garnier, S. (2018a). viridis: Default color maps from ’matplotlib’. Retrieved from https://CRAN.R-project.org/package=viridis
Garnier, S. (2018b). viridisLite: Default color maps from ’matplotlib’ (lite version). Retrieved from https://CRAN.R-project.org/package=viridisLite
Ihaka, R., Murrell, P., Hornik, K., Fisher, J. C., Stauffer, R., Wilke, C. O., … Zeileis, A. (2019). colorspace: A toolbox for manipulating and assessing colors and palettes. Retrieved from https://CRAN.R-project.org/package=colorspace
Neth, H., & Gradwohl, N. (2019). unikn: Graphical elements of the University of Konstanz’s corporate design. Retrieved from https://CRAN.R-project.org/package=unikn
Neuwirth, E. (2014). RColorBrewer: ColorBrewer palettes. Retrieved from https://CRAN.R-project.org/package=RColorBrewer
Okabe, M., & Ito, K. (2008). Color universal design (CUD): How to make figures and presentations that are friendly to colorblind people. J*Fly: Data Depository for Drosophila Researchers. Retrieved from https://jfly.uni-koeln.de/color/
Phillips, N. (2017). yarrr: A companion to the e-book “yarrr!: The pirate’s guide to R”. Retrieved from https://CRAN.R-project.org/package=yarrr
R Core Team. (2019). R: A language and environment for statistical computing. Retrieved from https://www.R-project.org
Wickham, H., Chang, W., Henry, L., Pedersen, T. L., Takahashi, K., Wilke, C., … Yutani, H. (2019). ggplot2: Create elegant data visualisations using the grammar of graphics. Retrieved from https://CRAN.R-project.org/package=ggplot2
A basic understanding of color perception typically certainly helps in choosing better color palettes, but understanding the details of color theory is not a necessary prerequisite for creating beautiful visualizations.↩