library(tmap)
tmap_options(scale = 0.75)In a dot density map, each dot represents a number of units, for
instance inhabitants. Dense areas get many dots, sparse areas few. This
example shows the population of the Netherlands for each of its 3340
districts (NLD_dist), first with one color, and then
colored by income class.
Dot density maps are made with a dot pattern (see the vignette
about fill patterns): the map variable pattern_density
determines the number of dots per area. With the scale
tm_scale_continuous_area(), the number of dots is
proportional to the data, so each dot represents the same number of
inhabitants.
Population density
The population density (inhabitants per km2) has the units 1/km2, since the area of the districts is in km2:
NLD = NLD_dist
NLD$pop_dens = NLD$population / NLD$area
summary(NLD$pop_dens)
#> Min. 1st Qu. Median Mean 3rd Qu. Max.
#> 0.0 96.9 375.6 1969.1 3083.2 28672.4At the scale of the whole country, the dots should be small and close
together: otherwise, most districts are smaller than the area of one
dot, and each dot would represent many inhabitants. Therefore, the
density ranges from 0 to 5 (values = c(0, 5)) instead of
from 0 to 1, and the dots are small (pattern_size = 0.5).
The size of the dots does not change the number of inhabitants per dot,
which only depends on the density; in the densest areas, the dots merge.
The few districts with more than 10,000 inhabitants per km2
get the densest pattern (outliers.trunc). Instead of the
district borders, the province borders are drawn:
dens_scale = tm_scale_continuous_area(
limits = c(0, 10000),
values = c(0, 5),
outliers.trunc = c(FALSE, TRUE),
ticks = c(1000, 5000, 10000))
tm_shape(NLD) +
tm_polygons(fill = "white", col = NULL,
pattern = "stipple", pattern_size = 0.5, pattern_col = "grey10",
pattern_density = "pop_dens",
pattern_density.scale = dens_scale,
pattern_density.legend = tm_legend(
"Inhabitants per km2",
dot.value = "1 dot ≈ {n} inhabitants")) +
tm_shape(NLD_prov) +
tm_borders(col = "grey50", lwd = 0.8)
The number of inhabitants per dot depends on the map scale, and is
computed when the map is drawn (legend argument dot.value).
The dots are spread over each district; they do not show where people
live within a district. A district that is smaller than the area of one
dot gets either one dot or none, so the number of dots is only correct
on average.
Interactive version
In view mode, the number of inhabitants per dot is updated after zooming. To keep the map light, the district polygons are simplified (which is hardly visible at this scale), popups are disabled, and the district names are shown when hovering:
NLD_simple = sf::st_simplify(NLD, preserveTopology = TRUE, dTolerance = 100)
tmap_mode("view")
tm_shape(NLD_simple) +
tm_polygons(fill = "white", col = NULL,
popup = FALSE, id = "name",
pattern = "stipple", pattern_size = 0.5, pattern_col = "grey10",
pattern_density = "pop_dens",
pattern_density.scale = dens_scale,
pattern_density.legend = tm_legend(
"Inhabitants per km2",
dot.value = "1 dot \u2248 {n} inhabitants")) +
tm_shape(NLD_prov) +
tm_borders(col = "grey50", lwd = 1.5)
tmap_mode("plot")In view mode, the dots keep their size and spacing on the screen when
zooming in, so zooming in shows more dots, each representing fewer
inhabitants. With opt_tm_polygons(pattern.zoom = TRUE), the
dots stick to the map instead, and each dot keeps representing the same
number of inhabitants.
Income classes
The data contain the percentage of individuals with a low income (the lowest 40% nationwide) and a high income (the highest 20% nationwide). The remaining percentage has a middle income. The population density of each class is the population density times this percentage:
NLD$income_middle = 100 - NLD$income_low - NLD$income_high
NLD$dens_low = NLD$pop_dens * NLD$income_low / 100
NLD$dens_middle = NLD$pop_dens * NLD$income_middle / 100
NLD$dens_high = NLD$pop_dens * NLD$income_high / 100A polygon can have only one pattern, so each class is drawn as a
separate layer, with its own dot color. To make each dot represent the
same number of inhabitants in all three layers, the same scale is used
for each layer. The dots of the three layers would be placed at the same
positions in a district where two classes have the same density.
Therefore, each layer uses a different seed for the random
placement of the dots (see tm_patterns()).
The dots are slightly larger than in the map above
(pattern_size = 0.7), so that their colors can be
distinguished. The legends of the three layers would only differ in
color. Instead, the first one is shown in gray (the legend argument
pattern_col), and the colors are explained in a manual
legend (tm_add_legend()):
cols = c(low = "#D95F02", middle = "#1B9E77", high = "#7570B3")
dot_layer = function(var, col, seed, legend = tm_legend_hide(), fill = NULL) {
tm_polygons(fill = fill, col = NULL,
pattern = tm_patterns("stipple", seed = seed),
pattern_size = 0.7, pattern_col = col,
pattern_density = var,
pattern_density.scale = dens_scale,
pattern_density.legend = legend)
}
tm_shape(NLD) +
dot_layer("dens_low", cols["low"], seed = 1, fill = "white",
legend = tm_legend("Inhabitants per km2",
dot.value = "1 dot ≈ {n} inhabitants",
pattern_col = "grey20")) +
dot_layer("dens_middle", cols["middle"], seed = 2) +
dot_layer("dens_high", cols["high"], seed = 3) +
tm_add_legend(type = "symbols", title = "Income",
labels = c("Low (lowest 40%)", "Middle", "High (highest 20%)"),
fill = cols, col = NA, size = 0.5) +
tm_shape(NLD_prov) +
tm_borders(col = "grey50", lwd = 0.8)
The share of high incomes is largest in the provinces Utrecht, Noord-Holland, and Zuid-Holland, especially around Amsterdam, Utrecht, and The Hague, whereas the share of low incomes is largest in the north (Groningen and Friesland). For 196 districts with few inhabitants, the income data are missing, so no dots are drawn there.
Note that the order of the layers matters: dots of later layers are drawn on top of dots of earlier layers. Since the dots are small and spread randomly, few dots overlap.
