library(dots)
library(ggplot2)
library(sf)
#> Linking to GEOS 3.9.1, GDAL 3.3.2, PROJ 7.2.1; sf_use_s2() is TRUE
dots
PackageDot density maps are useful to display the relative distribution of
people across geographies. These maps are often used in election
research and litigation to highlight the distribution of voters where
choropleths tell a different story. dots
simplifies this
process, allowing for a tidy, simple interface. Maps can be created
automatically with dots::dots()
. The points behind these
maps can be generated with dots::dots_points()
.
Now, various methods for creating random points exist in different R
ecosystems, notably in terra
, sf
, and
sp
. Each of these different inputs and produce completely
different outputs. To standardize these to one interface,
dots
creates a set of wrapper engine_*
functions. Each engine_*
function takes the same arguments:
shp
and col
. Shape is an sf
tibble and col
is the string name of a column in
shp
. Within each of the engine functions, additional work
is done to standardize the outputs so that they will be largely
comparable.
To make a dot density map, we need a shapefile with populations,
votes, or some other numeric attribute tied to each geography. Here, we
load 2020 Census data for Suffolk County, MA, included with the
dots
package.
data("suffolk")
To make a basic dot density map for the Hispanic voting age population, we can run the following:
::dots(shp = suffolk, cols = vap_hisp) dots
This works because vap_hisp
is a numeric column in
suffolk
. Any numeric column would work. We can even use
multiple columns. cols
uses tidyselect
to
select multiple columns. For example, to make a map using the White and
Hispanic voting age population, we could do the following:
::dots(shp = suffolk, cols = c(vap_hisp, vap_white)) dots
Now, the points here are a bit crowded, so we can raise the number of
adults that each point stands for by increasing the divisor
argument. The default is 250, but here we will raise it to 1250.
::dots(shp = suffolk, cols = c(vap_hisp, vap_white), divisor = 1250) dots
The color of the dots uses the color aesthetic, so we can change it
using different scale_color
functions:
::dots(shp = suffolk, cols = c(vap_hisp, vap_white), divisor = 1250) +
dotsscale_color_brewer(name = 'Race/Ethnicity', palette = 'Accent')
Now, this is kind of bland, but it is a ggplot
output,
so we can update it using any of the regular tools. For example, we can
clean up the background and give it clearer labels.
::dots(shp = suffolk, cols = c(vap_hisp, vap_white), divisor = 1250) +
dotsscale_color_brewer(name = 'Race/Ethnicity', palette = 'Accent') +
labs(
title = 'White and Hispanic Adults in Suffolk County, MA',
caption = 'One point per 1250 adults.'
+
) theme_void()
The downside of using the automatic plot generated by
dots::dots()
is that it limits us to a few plot defaults.
If we switch to dots::dots_points()
, we can make more
specific choices by generating the plot.
To make the same basic plot, we could run the following:
::dots_points(shp = suffolk, cols = c(vap_hisp, vap_white), divisor = 1250) %>%
dotsggplot() +
geom_sf(data = suffolk, fill = NA, color = 'black') +
geom_sf(aes(color = dots_type)) +
scale_color_brewer(name = 'Race/Ethnicity', palette = 'Accent') +
labs(
title = 'White and Hispanic Adults in Suffolk County, MA',
caption = 'One point per 1250 adults.'
+
) theme_void()
Then, we can customize additional aspects of it, again using
ggplot2
functions.
::dots_points(shp = suffolk, cols = c(vap_hisp, vap_white), divisor = 1250) %>%
dots::mutate(dots_type = dplyr::case_when(
dplyr== 'vap_white' ~ 'White',
dots_type == 'vap_hisp' ~ 'Hispanic'
dots_type %>%
)) ggplot() +
geom_sf(data = suffolk, fill = NA, color = 'black') +
geom_sf(aes(color = dots_type), size = 0.75) + # change size
scale_color_brewer(name = 'Race/Ethnicity', palette = 'Accent') +
labs(
title = 'White and Hispanic Adults in Suffolk County, MA',
caption = 'One point per 1250 adults.'
+
) theme_void()