---
title: "Choropleth maps with tricolore"
author: "Jonas Schöley"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Choropleth maps with tricolore}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
%\VignetteDepends{shiny, sf, leaflet, tricolore, dplyr, ggplot2, ggtern, httpuv}
---
```{r setup, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
tidy = FALSE,
comment = "#>",
fig.width = 6, fig.height = 6
)
```
Here I demonstrate how to use the `tricolore` library to generate ternary choropleth maps using both `ggplot2` and `leaflet`.
The data
--------
```{r}
library(tricolore)
```
The data set `euro_example` contains the administrative boundaries for the European NUTS-2 regions in the column `geometry`. This data can be used to plot a choropleth map of Europe using the `sf` package. Each region is represented by a single row. The name of a region is given by the variable `name` while the respective [NUTS-2](https://en.wikipedia.org/wiki/Nomenclature_of_Territorial_Units_for_Statistics) geocode is given by the variable `id`. For each region some compositional statistics are available: Variables starting with `ed` refer to the relative share of population ages 25 to 64 by educational attainment in 2016 and variables starting with `lf` refer to the relative share of workers by labor-force sector in the European NUTS-2 regions 2016.
Take the first row of the data set as an example: in the Austrian region of "Burgenland" (`id` = `AT11`) 16.5% of the population aged 25--64 had attained an education of "Lower secondary or less" (`ed_0to2`), 55.7% attained "upper secondary" education (`ed_3to4`), and 27.9% attained "tertiary" education. In the very same region 4.4% of the labor-force works in the primary sector, 26.8% in the secondary and 68.2% in the tertiary sector.
The education and labor-force compositions are *ternary*, i.e. made up from three elements, and therefore can be color-coded as the weighted mixture of three primary colors, each primary mapped to one of the three elements. Such a color scale is called a *ternary balance scheme*^[See for example Dorling (2012) and Brewer (1994).]. This is what `tricolore` does.
`ggplot2` for ternary choropleth maps
-------------------------------------
Here I show how to create a choropleth map of the regional distribution of education attainment in Europe 2016 using `ggplot2`.
**1. Using the `Tricolore()` function, color-code each educational composition in the `euro_example` data set and add the resulting vector of hex-srgb colors as a new variable to the data frame. Store the color key separately.**
```{r}
# color-code the data set and generate a color-key
tric <- Tricolore(euro_example, p1 = 'ed_0to2', p2 = 'ed_3to4', p3 = 'ed_5to8')
```
`tric` contains both a vector of color-coded compositions (`tric$rgb`) and the corresponding color key (`tric$key`). We add the vector of colors to the map-data.
```{r}
# add the vector of colors to the `euro_example` data
euro_example$rgb <- tric$rgb
```
**2. Using `ggplot2` and the joined color-coded education data and geodata, plot a ternary choropleth map of education attainment in the European regions. Add the color key to the map.**
The secret ingredient is `scale_fill_identity()` to make sure that each region is colored according to the value in the `rgb` variable of `euro_educ_map`.
```{r}
library(ggplot2)
plot_educ <-
# using sf dataframe `euro_example`...
ggplot(euro_example) +
# ...draw a polygon for each region...
geom_sf(aes(fill = rgb, geometry = geometry), size = 0.1) +
# ...and color each region according to the color code in the variable `rgb`
scale_fill_identity()
plot_educ
```
Using `annotation_custom()` and `ggplotGrob` we can add the color key produced by `Tricolore()` to the map. Internally, the color key is produced with the [`ggtern`](https://CRAN.R-project.org/package=ggtern) package. In order for it to render correctly we need to load `ggtern` *after* loading `ggplot2`. Don't worry, the `ggplot2` functions still work.
```{r}
library(ggtern)
plot_educ +
annotation_custom(
ggplotGrob(tric$key),
xmin = 55e5, xmax = 75e5, ymin = 8e5, ymax = 80e5
)
```
Because the color key behaves just like a `ggplot2` plot we can change it to our liking.
```{r}
plot_educ <-
plot_educ +
annotation_custom(
ggplotGrob(tric$key +
theme(plot.background = element_rect(fill = NA, color = NA)) +
labs(L = '0-2', T = '3-4', R = '5-8')),
xmin = 55e5, xmax = 75e5, ymin = 8e5, ymax = 80e5
)
plot_educ
```
Some final touches...
```{r}
plot_educ +
theme_void() +
coord_sf(datum = NA) +
labs(
title = 'European inequalities in educational attainment',
subtitle = 'Regional distribution of ISCED education levels for people aged 25-64 in 2016.'
)
```
`leaflet` for ternary choropleth maps
-------------------------------------
The `ggplot2` example above is easily adapted to `leaflet`. This time I use a continuous color scale.
```{r}
# color-code the data set and generate a color-key
tric <- Tricolore(euro_example, p1 = 'ed_0to2', p2 = 'ed_3to4', p3 = 'ed_5to8',
breaks = Inf)
# add the vector of colors to the `euro_example` data
euro_example$rgb <- tric$rgb
```
`leaflet` requires geodata in spherical coordinates (longitude-latitude format). Therefore I reproject the data to a [suitable crs](https://spatialreference.org/ref/epsg/4326/) using the `sf` package.
```{r}
library(sf)
library(leaflet)
euro_example %>%
st_transform(crs = 4326) %>%
leaflet() %>%
addPolygons(smoothFactor = 0.1, weight = 0,
fillColor = euro_example$rgb,
fillOpacity = 1)
```
Adding a background map gives geographical context to the map. I also add a mouse pop-up of the actual data.
```{r}
euro_example %>%
st_transform(crs = 4326) %>%
leaflet() %>%
addProviderTiles(providers$Esri.WorldTerrain) %>%
addPolygons(smoothFactor = 0.1, weight = 0,
fillColor = euro_example$rgb,
fillOpacity = 1,
popup =
paste0(
'', euro_example$name, '',
'Primary: ',
formatC(euro_example$ed_0to2*100,
digits = 1, format = 'f'), '%',
'Secondary: ',
formatC(euro_example$ed_3to4*100,
digits = 1, format = 'f'), '%',
'Tertiary: ',
formatC(euro_example$ed_5to8*100,
digits = 1, format = 'f'), '%'
)
)
```
Adding the legend to the leaflet map requires a bit of a [hack](https://github.com/rstudio/leaflet/issues/51#issuecomment-213108125).
```{r}
makePlotURI <- function(expr, width, height, ...) {
pngFile <- shiny::plotPNG(function() { expr }, width = width, height = height, ...)
on.exit(unlink(pngFile))
base64 <- httpuv::rawToBase64(readBin(pngFile, raw(1), file.size(pngFile)))
paste0("data:image/png;base64,", base64)
}
legend_symbol <- makePlotURI({
print(tric$key +
theme(plot.background = element_rect(fill = NA, color = NA)) +
labs(L = '0-2', T = '3-4', R = '5-8'))
}, 200, 200, bg = "transparent")
df <- data.frame(
lng = 30,
lat = 70,
plot = legend_symbol,
stringsAsFactors = FALSE
)
euro_example %>%
st_transform(crs = 4326) %>%
leaflet() %>%
addProviderTiles(providers$Esri.WorldGrayCanvas) %>%
addPolygons(smoothFactor = 0.1, weight = 0,
fillColor = euro_example$rgb,
fillOpacity = 1,
popup =
paste0(
'', euro_example$name, '',
'Primary: ',
formatC(euro_example$ed_0to2*100,
digits = 1, format = 'f'), '%',
'Secondary: ',
formatC(euro_example$ed_3to4*100,
digits = 1, format = 'f'), '%',
'Tertiary: ',
formatC(euro_example$ed_5to8*100,
digits = 1, format = 'f'), '%'
)
) %>%
addMarkers(data = df, icon = ~icons(plot))
```
Literature
----------
Brewer, C. A. (1994). Color Use Guidelines for Mapping and Visualization. In A. M. MacEachren & D. R. F. Taylor (Eds.), Visualization in Modern Cartography (pp. 123–147). Oxford, UK: Pergamon.
Dorling, D. (2012). The Visualization of Spatial Social Structure. Chichester, UK: Wiley.
Schöley, J. (2021). The centered ternary balance scheme: A technique to visualize surfaces of unbalanced three-part compositions. Demographic Research (44).