class: center, middle, title-slide .title[ # Color Issues ] .author[ ### Luke Tierney ] .institute[ ### University of Iowa ] .date[ ### 2023-05-06 ] --- <link rel="stylesheet" href="stat4580.css" type="text/css" /> <style type="text/css"> .remark-code { font-size: 85%; } </style> ## Color Issues Color is very effective when used well. -- But using color well is not easy. -- Some of the issues: -- * Perception depends on context. -- * Simple color assignments may not separate equally well. -- * Effectiveness may vary with the medium (screen, projector, print). -- * Some people do not perceive the full spectrum of colors. -- * Grey scale printing. -- * Some colors have cultural significance. -- * Cultural significance may vary among cultures and with time. --- layout: true ## Color Spaces: RGB and HSV --- Computer monitors and projectors work in terms of red, green, and blue light. -- Amounts of red green and blue (and alpha level) are stored as integers in the range between 0 and 255 (8-bit bytes). -- ```r cols <- c("red", "green", "blue", "yellow", "cyan", "magenta") rgbcols <- col2rgb(cols); colnames(rgbcols) <- cols rgbcols ## red green blue yellow cyan magenta ## red 255 0 0 255 0 255 ## green 0 255 0 255 255 0 ## blue 0 0 255 0 255 255 ``` -- Colors are often encoded in _hexadecimal_ form (base 16). -- .pull-left[ ```r rgb(1, 0, 0) ## pure red ## [1] "#FF0000" rgb(0, 0, 1) ## pure blue ## [1] "#0000FF" ``` ] -- .pull-right[ ```r rgb(255, 0, 0, maxColorValue = 255) ## [1] "#FF0000" rgb(0, 0, 255, maxColorValue = 255) ## [1] "#0000FF" ``` ] --- Hue, saturation, value (HSV) is a simple transformation of RGB. -- ```r rgb2hsv(rgbcols) ## red green blue yellow cyan magenta ## h 0 0.3333333 0.6666667 0.1666667 0.5 0.8333333 ## s 1 1.0000000 1.0000000 1.0000000 1.0 1.0000000 ## v 1 1.0000000 1.0000000 1.0000000 1.0 1.0000000 ``` -- HSV is a little more convenient since it allows the hue to be controlled separately. -- But saturation and value attributes are not particularly useful for specifying colors that work well perceptually. --- .pull-left[ A color wheel of fully saturated colors: .hide-code[ ```r wheel <- function(col, radius = 1, ...) pie(rep(1, length(col)), col = col, radius = radius, ...) wheel(rainbow(6)) ``` <img src="color_files/figure-html/unnamed-chunk-5-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-right[ Removing saturation: .hide-code[ ```r library(colorspace) wheel(desaturate(rainbow(6))) ``` <img src="color_files/figure-html/unnamed-chunk-6-1.png" style="display: block; margin: auto;" /> ] Fully saturated yellow is brighter than red, which is brighter than blue. ] --- layout: true ## Color Spaces: HCL --- The _rainbow_ palette of the color wheel is often a default in visualization systems. -- A [blog post](https://www.zeileis.org/news/endrainbow/) illustrates why this is a bad idea. -- The rainbow hues are evenly spaced in the color spectrum, but chroma and luminance are not. -- Luminance in particular is not monotone across the palette. -- The hue, chroma, luminance ([HCL](https://en.wikipedia.org/wiki/HCL_color_space)) space allows separate control of: -- * _Hue_, the color. -- * _Chroma_, the amount of the color. -- * _Luminance_, or perceived brightness. -- HCL makes it easier to create perceptually uniform color palettes. --- A palette with constant chroma, evenly spaced hues and evenly spaced luminance values: .hide-code[ ```r rain6 <- hcl(seq(0, 360 * 5 / 6, len = 6), 50, seq(60, 80, len = 6)) par(mfrow = c(1, 2)) pal(rain6, main = "Uniform Rainbow") pal(desaturate(rain6), main = "Desaturated") ``` <img src="color_files/figure-html/unnamed-chunk-8-1.png" style="display: block; margin: auto;" /> ] --- .pull-left[ For a given hue, not all combinations of chroma and luminance are possible. In particular, for low luminance values the available chroma range is limited. The [`ggplot` book](https://ggplot2-book.org/) contains this visualization of the HCL space. * Hue is mapped to angle. * Chroma is mapped to radius. * Luminance is mapped to facets. The origins with zero chroma are shades of grey. ] .pull-right[ <img src="../img/ggplot2_hclspace.png" width="500" style="display: block; margin: auto;" /> ] --- HCL is a transformation of the [CIEluv](https://en.wikipedia.org/wiki/CIELUV) color space designed for perceptual uniformity. -- The definition of the luminance takes into account the light sensitivity of a standard human observer at various wave lengths. -- Light sensitivity for different wave lengths in daylight conditions (photopic vision) and under dark adapted conditions (scotopic vision): <!-- http://light-measurement.com/images/spectral-luminous-efficiency-function.jpg --> <img src="../img/light-sensitivity.jpg" width="400" style="display: block; margin: auto;" /> <!-- https://www.handprint.com/HP/WCL/color1.html http://light-measurement.com/spectral-sensitivity-of-eye/ --> --- layout: true ## Color Spaces: Munsell --- Another color space, similar to HCL, is the [Munsell system](https://en.wikipedia.org/wiki/Munsell_color_system) developed in the early 1900s. -- This system uses a Hue, Value, Chroma encoding. -- The [`munsell` package](https://github.com/cwickham/munsell) provides an R interface and is used in `ggplot`. -- Munsell specifications are of the form `"H V/C"`, such as `5R 5/10`. -- Possible hues are ```r library(munsell, exclude = "desaturate") mnsl_hues() ## [1] "2.5R" "5R" "7.5R" "10R" "2.5YR" "5YR" "7.5YR" "10YR" "2.5Y" ## [10] "5Y" "7.5Y" "10Y" "2.5GY" "5GY" "7.5GY" "10GY" "2.5G" "5G" ## [19] "7.5G" "10G" "2.5BG" "5BG" "7.5BG" "10BG" "2.5B" "5B" "7.5B" ## [28] "10B" "2.5PB" "5PB" "7.5PB" "10PB" "2.5P" "5P" "7.5P" "10P" ## [37] "2.5RP" "5RP" "7.5RP" "10RP" ``` -- `V` should be an integer between 0 and 10. -- `C` should be an even integer less than 24, but not all combinations are possible. --- Adjusting colors in the value, chroma, and hue dimensions: .pull-left.width-40[ <img src="color_files/figure-html/munsell-blues-1.png" style="display: block; margin: auto;" /> ] .pull-right.width-60.small-code[ ```r my_blue <- "5PB 5/8" plot_mnsl(c( lighter(my_blue, 2), my_blue, darker(my_blue, 2), munsell::desaturate(my_blue, 2), my_blue, saturate(my_blue, 2), rygbp(my_blue, 2), my_blue, pbgyr(my_blue, 2))) ``` ] --- Examining available colors: .pull-left[ ```r hue_slice("5R") ``` <img src="color_files/figure-html/unnamed-chunk-12-1.png" style="display: block; margin: auto;" /> ] -- .pull-right[ ```r value_slice(5) ``` <img src="color_files/figure-html/unnamed-chunk-13-1.png" style="display: block; margin: auto;" /> ] --- Complementary colors: ```r complement_slice("5R") ``` <img src="color_files/figure-html/unnamed-chunk-14-1.png" style="display: block; margin: auto;" /> --- layout: false ## Opponent Process Theory The _Opponent Process Model_ of vision says that the brain divides the visual signal among three opposing contrast pairs: -- * black and white; -- * red and green; -- * yellow and blue. -- The black/white pair corresponds to luminance in HCL -- Hue and chroma in HCL span the two chromatic axes. -- The luminance axis has higher resolution than the two chromatic axes. -- The major form of color vision deficiency reflects an inability to distinguish differences along the red/green axis. -- Impairment along the yellow/blue axis does occur as well but is much rarer. --- layout: true ## Contrast and Comparisons --- Vision reacts to differences, not absolutes. -- * Contrast is very important. -- * Context is very important. -- _Simultaneous brightness contrast_: a grey patch on a dark background looks lighter than the same grey patch on a light background. .hide-code[ ```r plot(0, 0, type = "n", xlim = c(0, 1), ylim = c(0, 1), axes = FALSE, xlab = "", ylab = "") rect(0, 0, 0.5, 1, col = "lightgrey", border = NA) rect(0.5, 0, 1, 1, col = "darkgrey", border = NA) rect(0.2, 0.3, 0.3, 0.7, col = "grey", border = NA) rect(0.7, 0.3, 0.8, 0.7, col = "grey", border = NA) ``` <img src="color_files/figure-html/unnamed-chunk-15-1.png" style="display: block; margin: auto;" /> ] --- An example we saw earlier: ![](../img/chess1.png) ![](../img/chess2.png) --- Some more are available [here](https://blog.revolutionanalytics.com/2018/08/luminance-illusion.html), including: <img src="../img/luminanim.gif" style="display: block; margin: auto;" /> --- Using luminance or grey scale alone does not work well for encoding categorical variables against a key. -- .pull-left[ Grey scale can be effective for showing continuous transitions in pseudo-color images. <img src="color_files/figure-html/unnamed-chunk-17-1.png" style="display: block; margin: auto;" /> ] -- .pull-right[ Grey scale is less effective for segmented maps, or choropleth maps; only a few levels can be accurately decoded. ] --- layout: true ## Interactions with Size, Background and Proximity --- .pull-left[ For small items more contrast and more saturated colors are needed: ] -- .pull-right[ .hide-code[ ```r x <- runif(6, 0.1, 0.9) y <- runif(6, 0.1, 0.9) cols <- c("red", "green", "blue", "yellow", "cyan", "magenta") f <- function(size = 1, black = FALSE) { plot(x, y, type = "n", xlim = c(0, 1), ylim = c(0, 1)) if (black) rect(0, 0, 1, 1, col = "black") text(x, y, cols, col = cols, cex = size) } opar <- par(mfrow = c(2, 2)) f(1) f(4) f(1, TRUE) f(4, TRUE) ``` <img src="color_files/figure-html/unnamed-chunk-18-1.png" style="display: block; margin: auto;" /> ```r par(opar) ``` ] ] --- <!-- foo --> Background: `hcl(h = 0, c = 35, l = 85)` .pull-left[ <img src="color_files/figure-html/unnamed-chunk-19-1.png" style="display: block; margin: auto;" /> ] --- <!-- foo --> Background: `hcl(h = 0, c = 35, l = 85)` .pull-left[ <img src="color_files/figure-html/unnamed-chunk-20-1.png" style="display: block; margin: auto;" /> ] -- .pull-right.width-40[ Variations in luminance are particularly helpful for seeing fine structure, such as small text or small symbols. {{content}} ] -- Ware recommends a luminance contrast of at least 3:1 for small text; 10:1 is preferable. --- <!-- foo --> Background: `hcl(h = 0, c = 35, l = 85)` .pull-left[ <img src="color_files/figure-html/unnamed-chunk-21-1.png" style="display: block; margin: auto;" /> ] --- <!-- foo --> Background: `hcl(h = 0, c = 35, l = 85)` .pull-left[ <img src="color_files/figure-html/unnamed-chunk-22-1.png" style="display: block; margin: auto;" /> ] -- .pull-right.width-40[ Chrominance (hue and chroma) differences alone are not sufficient for small items. ] --- Small areas also need variation in more than hue: <img src="color_files/figure-html/unnamed-chunk-23-1.png" style="display: block; margin: auto;" /> --- Contrasting borders can help for larger areas with similar luminance: -- * outlines on text; -- * borders on symbols; -- * borders on regions, e.g in [maps](https://colorbrewer2.org) -- <img src="color_files/figure-html/unnamed-chunk-24-1.png" style="display: block; margin: auto;" /> --- layout: false ## Selecting Colors to Use A large number of _named colors_ are available (currently 657). -- Some examples: <style type="text/css"> .forestgreen { color: forestgreen; } .deepskyblue { color: deepskyblue; } .firebrick { color: firebrick } </style> * .forestgreen[forestgreen] * .deepskyblue[deepskyblue] * .firebrick[firebrick] -- The available named colors follow a widely used [standard](https://www.w3schools.com/colors/colors_x11.asp). -- These colors include the 140 [_web colors_](https://htmlcolorcodes.com/color-names/) supported on modern browsers. -- Individual colors can also be specified using `rgb()` or `hcl()` or as hexadecimal specifications. -- Color pickers can help: -- * Google search: https://www.google.com/search?q=color+picker -- * `colourPicker()` in the `colourpicker` package. -- When a set of colors is needed to encode variable values it is usually best to use a suitable _palette_. --- layout: true ## Color Palettes --- _Color palettes_ are collections of colors that work well together. -- It is useful to distinguish three kinds of palettes: -- * qualitative/categorical palettes; -- * sequential palettes; -- * diverging palettes. -- Tools for selecting palettes include: -- * [ColorBrewer](https://colorbrewer2.org); available in the `RColorBrewer` package. -- * [HCL Wizard](https://hclwizard.org/); also available as `hclwizard` in the `colorspace` package. -- A [blog post](https://flowingdata.com/2018/04/12/visualization-color-picker-based-on-perception-research/) with some further options. -- Some [current US government work](https://designsystem.digital.gov/components/colors/) on color palettes; [more extensive notes](https://xdgov.github.io/data-design-standards/components/colors) and [code](https://github.com/uswds/uswds). --- R color palette functions: -- * `rainbow()` * `heat.colors()` * `terrain.colors()` * `topo.colors()` * `cm.colors()` * `grey.colors()` * `gray.colors()` -- These all take the number of colors as an argument, as well as some additional optional arguments. -- The `hcl.color()` function provides access to the palettes defined in the `colorspace` package. --- `colorRampPalette()` can be used to create a _palette function_ that interpolates between a set of colors using -- * RGB space or Lab (similar to HCL) space; -- * linear or spline interpolation. -- .pull-left[ ```r rwb <- colorRampPalette( c("red", "white", "blue")) ``` ```r filled.contour(volcano, color.palette = rwb, asp = 1) ``` ] -- .pull-right[ <img src="color_files/figure-html/palette-function-example-1.png" style="display: block; margin: auto;" /> ] --- With more perceptually comparable extremes (from the Blue-Red palette of HCL Wizard): .pull-left[ ```r rwb1 <- colorRampPalette( c("#8E063B", "white", "#023FA5")) filled.contour(volcano, color.palette = rwb1, asp = 1) ``` ] .pull-right[ <img src="color_files/figure-html/palette-function-muted1-1.png" style="display: block; margin: auto;" /> ] --- An alternative uses the `muted` function from package `scales`: .pull-left[ ```r rwb2 <- colorRampPalette( c(scales::muted("red"), "white", scales::muted("blue"))) filled.contour(volcano, color.palette = rwb2, asp = 1) ``` ] .pull-right[ <img src="color_files/figure-html/palette-function-muted2-1.png" style="display: block; margin: auto;" /> ] --- Most base and `lattice` functions allow a vector of colors to be specified. -- Some, like `filled.contour()` and `levelplot()` allow a palette function to be provided. -- `ggplot` provides a framework for specifying palette functions to use with `scale_color_xyz()` and `scale_fill_xyz()` functions. -- Packages like `colorspace` and `viridis` provide additional `scale_color_xyz()` and `scale_fill_xyz()` functions. --- layout: true ## RColorBrewer Palettes --- .pull-left[ The available palettes: ```r library(RColorBrewer) display.brewer.all() ``` Palettes in the first group are _sequential_. The second group are _qualitative_. The third group are _diverging_. ] .pull-right[ <img src="color_files/figure-html/brewer-palletes-1.png" style="display: block; margin: auto;" /> ] --- The `"Blues"` palette: .pull-left[ ```r display.brewer.pal(9, "Blues") ``` As RGB values: ```r brewer.pal(9, "Blues") ## [1] "#F7FBFF" "#DEEBF7" "#C6DBEF" "#9ECAE1" "#6BAED6" "#4292C6" "#2171B5" ## [8] "#08519C" "#08306B" ``` The palettes are limited to a maximum number of levels. To obtain more levels you can interpolate. ] .pull-right[ <img src="color_files/figure-html/blues-palette-1.png" style="display: block; margin: auto;" /> ] --- layout: true ## Colorspace Palettes --- The `colorspace` package provides a wide range of pre-defined palettes: ```r library(colorspace) hcl_palettes(plot = TRUE) ``` <img src="color_files/figure-html/unnamed-chunk-28-1.png" style="display: block; margin: auto;" /> --- A particular number of colors from one of these palettes can be obtained with ```r qualitative_hcl(4, palette = "Dark 3") ## [1] "#E16A86" "#909800" "#00AD9A" "#9183E6" ``` -- The functions `sequential_hcl()` and `diverging_hcl()` are analogous. -- For use with `ggplot2` the package provides scale functions like `scale_fill_discrete_qualitative()` and `scale_color_continuous_sequential()`. -- A [package vignette](https://cran.r-project.org/package=colorspace/vignettes/colorspace.html) provides more details and background. --- layout: false ## Viridis Palettes .pull-left[ <img src="color_files/figure-html/unnamed-chunk-30-1.png" style="display: block; margin: auto;" /> ] -- .pull-right[ These are provided in package `viridisLite`. {{content}} ] -- Palette functions are `viridis()`, `mako()`, etc.. {{content}} -- They are also available via the `hcl.colors()` function. {{content}} -- For use in `ggplot` they can be specified in the viridis color scale functions. --- layout: true ## Palettes in `ggplot` --- `ggplot` uses `scale_color_xyz()` or `scale_fill_xyz()`. -- For discrete scales the choices for `xyz` include * `hue` varies the hue (default for unordered factors); * `grey` uses grey scale; * `brewer` uses ColorBrewer palettes; * `manual` allows explicit specification. * `viridis_d` for an alternative palette family (default for orderer factors). -- For continuous scales the choices for `xyz` include * `gradient` interpolates between two colors, low and high; * `gradient2` interpolates between three colors, low, medium, high; * `gradientn` interpolates between a vector of colors; * `distiller` for ColorBrewer palettes. * `viridis_c` for an alternative palette family. -- Others are available in packages such as `colorspace`. --- The default qualitative and sequential discrete palettes: .hide-code[ ```r library(gapminder) gap_2007 <- filter(gapminder, year == 2007) %>% slice_max(pop, n = 20) p <- mutate(gap_2007, country = reorder(country, pop)) %>% ggplot(aes(x = gdpPercap, y = lifeExp, fill = continent)) + scale_size_area(max_size = 10) + scale_x_log10() + geom_point(size = 4, shape = 21) + guides(fill = guide_legend(override.aes = list(size = 4))) p1 <- p + ggtitle("Hue") p2 <- p + scale_fill_viridis_d() + ggtitle("Viridis") library(patchwork) p1 + p2 ``` <img src="color_files/figure-html/unnamed-chunk-31-1.png" style="display: block; margin: auto;" /> ] --- Discrete examples for `brewer`, `colorspace` and `manual`: .hide-code[ ```r p1 <- p + scale_fill_brewer(palette = "Set1") + ggtitle("Brewer Set1") p2 <- p + scale_fill_brewer(palette = "Set2") + ggtitle("Brewer Set2") p3 <- p + scale_fill_discrete_qualitative("Dark 3") + ggtitle("Colorspace Dark 3") p4 <- p + scale_fill_manual(values = c(Africa = "red", Asia = "blue", Americas = "green", Europe = "grey")) + ggtitle("Manual") (p1 + p2) / (p3 + p4) ``` <img src="color_files/figure-html/unnamed-chunk-32-1.png" style="display: block; margin: auto;" /> ] --- The default for continuous scales is `gradient` from a dark blue to a light blue: .hide-code[ ```r V <- data.frame(x = rep(seq_len(nrow(volcano)), ncol(volcano)), y = rep(seq_len(ncol(volcano)), each = nrow(volcano)), z = as.vector(volcano)) p <- ggplot(V, aes(x, y, fill = z)) + geom_raster() + coord_fixed() p ``` <img src="color_files/figure-html/unnamed-chunk-33-1.png" style="display: block; margin: auto;" /> ] --- Some alternatives: .pull-left.width-60[ <img src="color_files/figure-html/alt-continuous-1.png" style="display: block; margin: auto;" /> ] -- .pull-right.width-40[ .hide-code[ ```r p1 <- p + scale_fill_gradient2( low = "red", mid = "white", high = "blue", midpoint = median(volcano)) + ggtitle("Red-White-Blue Gradient") p2 <- p + scale_fill_viridis_c() + ggtitle("Viridis") p3 <- p + scale_fill_gradientn( colors = terrain.colors(8)) + ggtitle("Terrain") vbins <- seq(80, by = 20, length.out = 7) nc <- length(vbins) - 1 p4 <- ggplot(mutate(V, z = fct_rev(cut(z, vbins))), aes(x, y, fill = z)) + geom_raster() + scale_fill_manual(values = rev(terrain.colors(nc))) + ggtitle("Discretized Terrain") (p1 + p2) / (p3 + p4) ``` ] Discretizing a continuous range to a modest number of levels can make decoding values from a legend easier. ] --- layout: true ## Reduced Color Vision --- Color vision deficiency affects about 10% of males, a smaller percentage of females. -- The most common form is reduced ability to distinguish red and green. -- Some web sites provide tools to simulate how a visualization would look to a color vision deficient viewer. -- The R packages `dichromat`, `colorspace`, and `colorblindr` provide tools for simulating how colors would look to a color vision deficient viewer for three major types of color vision deficiency: -- * deuteranomaly (green cone cells defective); * protanomaly (red cone cells defective); * tritanomaly (blue cone cells defective). -- An article explaining the color vision impairment simulation is available [here](http://colorspace.r-forge.r-project.org/articles/color_vision_deficiency.html) <!-- https://arxiv.org/abs/1903.06490 --> Using some tools from packages `colorspace` and `colorblinder` we can simulate what a plot would look like in grey scale and to someone with some of the major types of color impairment. --- .pull-left.width-35[ A plot with the default discrete color palette: .hide-code[ ```r p <- ggplot(gap_2007, aes(gdpPercap, lifeExp, color = continent)) + geom_point(size = 4) + scale_x_log10() + guides(color = guide_legend(override.aes = list(size = 4))) p ``` <img src="color_files/figure-html/unnamed-chunk-34-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-right.width-60[ .hide-code[ <!-- ## nolint start: object_usage --> ```r library(colorblindr) library(colorspace) library(grid) color_check <- function(p) { p1 <- edit_colors(p + ggtitle("Desaturated"), desaturate) p2 <- edit_colors(p + ggtitle("deutan"), deutan) p3 <- edit_colors(p + ggtitle("protan"), protan) p4 <- edit_colors(p + ggtitle("tritan"), tritan) gridExtra::grid.arrange(p1, p2, p3, p4, nrow = 2) } color_check(p) ``` <img src="color_files/figure-html/cvd-examples-1.png" style="display: block; margin: auto;" /> <!-- ## nolint end --> ] ] --- .pull-left.width-35[ For the Viridis palette: .hide-code[ ```r pv <- p + scale_color_viridis_d() pv ``` <img src="color_files/figure-html/unnamed-chunk-35-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-right.width-60[ .hide-code[ ```r color_check(pv) ``` <img src="color_files/figure-html/unnamed-chunk-36-1.png" style="display: block; margin: auto;" /> ] ] --- The `swatchplot()` function in the `colorspace` package can be used with the `cvd = TRUE` argument to simulate how specific palettes work for different color vision deficiencies: -- .pull-left.small-code[ ```r colorspace::swatchplot(rainbow(6), cvd = TRUE) ``` <img src="color_files/figure-html/unnamed-chunk-37-1.png" style="display: block; margin: auto;" /> ] -- .pull-right.small-code[ ```r colorspace::swatchplot(hcl.colors(6), cvd = TRUE) ``` <img src="color_files/figure-html/unnamed-chunk-38-1.png" style="display: block; margin: auto;" /> ] --- layout: true ## Caution: Missing Values --- It is common for default settings to not assign a color for missing values. -- In a choropleth map with (made-up) data where one state's value is missing this might not be noticed. -- .pull-left[ .hide-code[ ```r m <- map_data("state") d <- data.frame(region = unique(m$region), val = ordered(sample(1 : 4, 49, replace = TRUE))) m <- left_join(m, d, "region") pm <- ggplot(m) + geom_polygon(aes(long, lat, group = group, fill = val)) + coord_map() + ggthemes::theme_map() dnm <- mutate(m, val = replace(val, region == "michigan", NA)) pm %+% dnm ``` <img src="color_files/figure-html/unnamed-chunk-39-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-right.width-40[ Unless the viewer is very familiar with US geography. {{content}} ] -- Or is from Michigan. --- In a scatterplot there are even fewer cues: .hide-code[ ```r gnc <- mutate(gap_2007, continent = replace(continent, country == "China", NA)) pv + pv %+% gnc ## Warning: Removed 1 rows containing missing values (`geom_point()`). ``` <img src="color_files/figure-html/unnamed-chunk-40-1.png" style="display: block; margin: auto;" /> ] --- Specifying `na.value = "red"`, or some other color, will make sure `NA` values are visible: .hide-code[ ```r (pm %+% dnm + scale_fill_viridis_d(na.value = "red") + theme(legend.position = "top")) + (pv %+% gnc + scale_color_viridis_d(na.value = "red")) ## Scale for colour is already present. ## Adding another scale for colour, which will replace the existing scale. ``` <img src="color_files/figure-html/unnamed-chunk-41-1.png" style="display: block; margin: auto;" /> ] --- Using outlines can also help: .hide-code[ ```r p1 <- pm %+% dnm + geom_polygon(aes(long, lat, group = group), fill = NA, color = "black", linewidth = 0.1) + theme(legend.position = "top") p2 <- pv %+% gnc + geom_point(shape = 21, fill = NA, color = "black", size = 4) p1 + p2 ## Warning: Removed 1 rows containing missing values (`geom_point()`). ``` <img src="color_files/figure-html/unnamed-chunk-42-1.png" style="display: block; margin: auto;" /> ] -- A final plot might handle missing values differently, but for initial explorations it is a good idea to make sure they are clearly visible. --- layout: true ## Caution: Aligning Diverging Palettes --- Diverging palettes are very useful for showing deviations above or below a baseline. -- .hide-code[ ```r par(mfrow = c(1, 2)) RColorBrewer::display.brewer.pal(7, "PRGn") RColorBrewer::display.brewer.pal(6, "PRGn") ``` <img src="color_files/figure-html/unnamed-chunk-43-1.png" style="display: block; margin: auto;" /> ] -- For a diverging palette to work properly, the palette base line needs to be aligned with the data baseline. -- How to do this will depend on the palette, but you do need to keep this in mind when using a diverging palette. --- Just using `scale_fill_brewer` is not enough when the value range is not symmetric around the baseline: .hide-code[ ```r m <- map_data("state") d <- data.frame(region = unique(m$region), val = ordered(sample((1 : 6) - 3, 49, replace = TRUE))) m <- left_join(m, d, "region") p <- ggplot(m) + geom_polygon(aes(long, lat, group = group, fill = val)) + coord_map() + ggthemes::theme_map() + theme(legend.position = "right") p + scale_fill_brewer(palette = "PRGn") ``` <img src="color_files/figure-html/unnamed-chunk-44-1.png" style="display: block; margin: auto;" /> ] --- Setting the scale limits explicitly forces a 7-category symmetric scale that aligns the zero value with the middle color: -- .pull-left[ ```r lims <- -3 : 3 p + scale_fill_brewer(palette = "PRGn", limits = lims) ``` <img src="color_files/figure-html/unnamed-chunk-45-1.png" style="display: block; margin: auto;" /> ] -- .pull-right[ This shows a category in the legend for -3 that does not appear in the map. {{content}} ] -- This is often what you want. --- But if you want to drop the -3 category, one option is to use a manual scale: ```r vals <- RColorBrewer::brewer.pal(7, "PRGn") names(vals) <- lims p + scale_fill_manual(values = vals[-1]) ``` <img src="color_files/figure-html/unnamed-chunk-46-1.png" style="display: block; margin: auto;" /> --- layout: false ## Bivariate Palettes It is possible to encode two variables in a palette. -- Some sample palettes: <img src="../img/bivpal.png" width="1000" style="display: block; margin: auto;" /> -- Bivariate palettes are sometimes used in [bivariate choropleth maps](https://www.joshuastevens.net/cartography/make-a-bivariate-choropleth-map/). -- Some recommendations from Cynthia Brewer are available [here](http://www.personal.psu.edu/cab38/ColorSch/Schemes.html). -- A [discussion](https://junkcharts.typepad.com/junk_charts/2023/04/bivariate-choropleths.html) of a recent example. -- Unless one variable is binary, and the palette is very well chosen, it is hard to decode a visualization using a binary palette without constantly referring to the key. --- layout: true ## Culture, Tradition, and Conventions --- <img src="../img/ColoursInCulture.png" width="750" style="display: block; margin: auto;" /> --- Colors can have different meanings in different cultures and at different times. * A [visual representation](https://www.informationisbeautiful.net/visualizations/colours-in-cultures/) -- * A [blog post](https://www.huffpost.com/entry/what-colors-mean-in-other_b_9078674) at the Huffington Post. -- * A [similar post](https://www.shutterstock.com/blog/color-symbolism-and-meanings-around-the-world) at Shutterstock. -- Conventions can also give colors particular meanings: -- * red/green in traffic lights; -- * red/green colors in microarray heatmaps; -- * red states and blue states; -- * pink for breast cancer; -- * pink for girls, blue for boys; -- * black for mourning. --- layout: false ## Traffic Lights [Traffic lights](https://www.autoevolution.com/news/automotive-wiki-why-are-traffic-lights-red-yellow-and-green-42557.html) use red/green, even though this is a major axis of color vision deficiency. -- The convention comes from railroads. -- The red used generally contains some orange and the green contains blue to help with red/green color vision deficiency. -- Position provides an alternate encoding. Orientations do vary. <!-- http://rgb.to/keyword/17779/1/traffic-light-green http://encycolorpedia.com/cc0605 https://www.autoevolution.com/news/automotive-wiki-why-are-traffic-lights-red-yellow-and-green-42557.html https://www.thrillist.com/cars/nation/traffic-light-colors-history --> --- layout: true ## Red States and Blue States --- It is now standard in the US to refer to Republican-leaning states as red states and Democrat leaning states as blue states. -- This is a fairly recent convention, dating back to the 2000 presidential election. -- Prior to 1980 it was somewhat more traditional to use red for more left-leaning Democrats. -- A map of the 1960 election results uses these more traditional colors. <img src="../img/e1960_ecmap.GIF" style="display: block; margin: auto;" /> --- In 1996 the _New York Times_ used blue for Democrat, red for Republican, but the _Washington Post_ used the opposite color scheme. -- The long, drawn out process of the 2000 election may have contributed to fixing the color schema at the current convention. --- layout: false ## Some Things to Remember -- Points need more saturation, luminance than areas. -- False color images may benefit from discretizing. -- Bivariate encodings (e.g. `x = hue, y = luminance`) are possible but tricky and not often a good idea. Best if at least one is binary. -- Providing a second encoding, e.g. shape, position can help for color vision deficient viewers and photocopying. -- In area plots and maps it is important to distinguish between base line values and missing values. -- If observed values only cover part of a possible range, it is sometimes appropriate to use a color coding that applies to the entire possible range. -- For diverging palettes, some care may be needed to make sure the neutral color and the neutral value are properly aligned. --- layout: true ## References --- > Few, Stephen. "Practical rules for using color in charts." Visual > Business Intelligence Newsletter 11 > (2008): 25. ([PDF](http://www.perceptualedge.com/articles/visual_business_intelligence/rules_for_using_color.pdf)) > Harrower, M. A. and Brewer, C. M. (2003). ColorBrewer.org: An online > tool for selecting color schemes for maps. > _The Cartographic Journal_, 40, 27--37. [ColorBrewer web > site](https://colorbrewer2.org). The `RColopBrewer` package provides > an R interface. > Ihaka, R. (2003). Colour for presentation graphics, in K. Hornik, > F. Leisch, and A. Zeileis (eds.), [_Proceedings of the 3rd_ > _International Workshop on Distributed Statistical_ > _Computing_](https://www.r-project.org/conferences/DSC-2003/Proceedings/), > Vienna, > Austria. [PDF](https://www.r-project.org/conferences/DSC-2003/Proceedings/Ihaka.pdf). > See also the `colorspace` package and > [vignette](https://cran.r-project.org/package=colorspace/vignettes/hcl-colors.pdf). > Lumley, T. (2006). Color coding and color blindness in statistical > graphics. _ASA Statistical Computing & Graphics Newsletter_, 17(2), > 4--7. [PDF](http://stat-computing.org/newsletter/issues/scgn-17-2.pdf). > Munzner, T. (2014), _Visualization Analysis and Design_, Chapter 10. --- > Lisa Charlotte Muth (2021). 4-part series of blog posts on choosing > color scales. [Part > 1](https://blog.datawrapper.de/which-color-scale-to-use-in-data-vis/); > [Part > 2](https://blog.datawrapper.de/quantitative-vs-qualitative-color-scales/); > [Part > 3](https://blog.datawrapper.de/diverging-vs-sequential-color-scales/); > [Part > 4](https://blog.datawrapper.de/classed-vs-unclassed-color-scales/). > Lisa Charlotte Muth (2022). A detailed guide to colors in data vis > style guides. [Blog > post](https://blog.datawrapper.de/colors-for-data-vis-style-guides/). > Treinish, Lloyd A. "Why Should Engineers and Scientists Be Worried > About Color?." IBM Thomas J. Watson Research Center, Yorktown > Heights, NY (2009): 46. ([pdf](https://www.researchgate.net/profile/Ahmed_Elhattab2/post/Please_suggest_some_good_3D_plot_tool_Software_for_surface_plot/attachment/5c05ba35cfe4a7645506948e/AS%3A699894335557644%401543879221725/download/Why+Should+Engineers+and+Scientists+Be+Worried+About+Color_.pdf)) > Ware, C. (2012), _Information Visualization: Perception for Design_, > 3rd ed, Chapters 3 > & 4. > Zeileis, A., Murrell, P. and Hornik, K. (2009). Escaping RGBland: > Selecting colors for statistical graphics, _Computational Statistics > & Data Analysis_, 53(9), 3259-–3270 > ([PDF](https://www.zeileis.org/papers/Zeileis+Hornik+Murrell-2009.pdf)). > Achim Zeileis, Paul Murrell (2019). HCL-Based Color Palettes in > `grDevices`. [R Blog > post](https://developer.r-project.org/Blog/public/2019/04/01/hcl-based-color-palettes-in-grdevices/index.html). > Achim Zeileis et al. (2020). “colorspace: A Toolbox for Manipulating > and Assessing Colors and Palettes.” Journal of Statistical Software, > 96(1), > 1-49. [doi:10.18637/jss.v096.i01](https://doi.org/10.18637/jss.v096.i01). --- layout: false ## Reading Section [_Perception and Data Visualization_](https://socviz.co/lookatdata.html#perception-and-data-visualization) in [_Data Visualization_](https://socviz.co/). Chapter [_Color scales_](https://clauswilke.com/dataviz/color-basics.html) in [_Fundamentals of Data Visualization_](https://clauswilke.com/dataviz/).
//adapted from Emi Tanaka's gist at //https://gist.github.com/emitanaka/eaa258bb8471c041797ff377704c8505