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:

  1. Find a color or color palette that suits my current goals.

  2. 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 grey and gray):

100 random (non-gray) colors and their names in R.

Figure D.1: 100 random (non-gray) colors and their names in R.

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 colors:

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 image() and filled.contour() are based on hcl.colors(). In addition, palette-generating functions (like rainbow() and 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).

Evaluate hcl.pals() to obtain the names of 110 pre-defined HCL palettes. Here are some examples of palettes included in hcl.colors():

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.

As 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.

RColorBrewer

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:

display.brewer.all()

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.

A simple way to define a palette of colors that can still be distinguished by most color-blind people is discussed in the R Graphics Cookbook in this link:

# 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_1 and 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.

viridis/viridisLite

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 viridis, magma, plasma, and 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() and scale_color_viridis_c() (for continuous color scales) and scale_fill_viridis_d() and scale_fill_viridis_d() (for discrete color scales).

(See also Chapter 12.3 Using a Colorblind-Friendly Palette in the R Graphics Cookbook.)

The viridisLite package (Garnier, 2018b) is a simpler version of the viridis package and sufficient for most purposes:

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 colors:

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)")

Check out ?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.

unikn

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:

  1. 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

  1. 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

The 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 usecol() and 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 seecol():

# 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:

  1. by color name (e.g., col = c("black", "white"))

See colors() for the list of 657 color names available in base R — and note that every color is represented in character type.

  1. 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.

  1. 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 red, green, and blue value of a color, as well as an optional transparency value alpha.
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).

Converting colors

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)
#> [1] FALSE
is.data.frame(new_cols)
#> [1] 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&nbspD.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 newpal() and seecol() functions of unikn.

  1. 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")

  1. 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")

  1. 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 seecol() function:

# 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")

By seecol() and 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 alpha):

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 usecol() function:

# 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")

The 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

  1. To use a particular color in ggplot, we can pass its name (as a character string) to functions that includes a color argument:
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.

  1. To define and use a new color palette my_colors in ggplot, we need to add a scale_color_manual line 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()

This plot illustrates the grouping function of color, which is why we defined my_pal as a qualitative color palette (see Sections D.1.2 and D.1.3.

References

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


  1. 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.