library(tmap)
library(sf)
library(dplyr)
library(cols4all)
tmap_mode("view") # all maps in this exercise set are interactive
data("World"); data("World_rivers"); data("metro")
data("NLD_prov"); data("NLD_muni"); data("NLD_dist")
Datasets used — all from the
tmappackage, no downloads needed.
Object Rows Type Key variables World177 polygon continent,gdp_cap_est,life_exp,well_being,pop_est_dens,economyWorld_rivers1511 line scalerankmetro436 point pop1960…pop2030,nameNLD_prov12 polygon code,nameNLD_muni345 polygon population,dwelling_value,dwelling_ownership,employment_rate,income_low,income_high,edu_appl_sci,urbanity,provinceNLD_dist3340 polygon same variables as NLD_muni, district level
?World to find out what variables it contains.
Make a (static) map of World, where the polygons have white
border lines (line width 2) and are filled with purple.tmap_mode("plot")
tm_shape(World) +
tm_polygons(fill = "purple", lwd = 2, col = "white")
"view" mode and reproduce the map. Pan, zoom,
and click a country — what happens?tmap_mode("view")
tm_shape(World) +
tm_polygons(fill = "purple", lwd = 2, col = "white")
Clicking a country opens a popup showing all of its attribute values.
fill to the variable name "life_exp".
Experiment with country border lines — which colour and line width work
well?tm_shape(World) +
tm_polygons(fill = "life_exp", col = "white", lwd = 0.5)
Thin white borders (
lwd = 0.5) separate countries without competing with the fill colour.
tm_crs("auto") to apply an equal-area projection,
and tm_basemap(NULL) to disable the basemap. Compared to
the map in (c), which do you prefer and why?tm_shape(World) +
tm_polygons(fill = "life_exp", col = "white", lwd = 0.5) +
tm_crs("auto") +
tm_basemap(NULL)
The equal-area projection avoids the size distortion of Web Mercator (where Greenland appears as large as Africa). However, it loses the familiar basemap context. Preference is subjective — equal-area is more honest for comparing regions by size; Web Mercator is more recognisable.
World (grey fill, no
borders), World_rivers (blue lines), and metro
(gold bubbles). Click the layer control icon (top-left in the viewer) to
toggle layers on and off. Experiment with line thickness and bubble
size.tm_shape(World) +
tm_fill(fill = "grey90") +
tm_shape(World_rivers) +
tm_lines(col = "steelblue", lwd = 2) +
tm_shape(metro) +
tm_bubbles(fill = "gold", size = 0.5)
World_rivers dataset contains a variable
"strokelwd" with pre-computed line widths. Assign it to
lwd and set lwd.scale = tm_scale_asis() so the
values are used directly rather than treated as a data variable.tm_shape(World) +
tm_fill(fill = "grey90") +
tm_shape(World_rivers) +
tm_lines(col = "steelblue", lwd = "strokelwd", lwd.scale = tm_scale_asis()) +
tm_shape(metro) +
tm_bubbles(fill = "gold", size = 0.5)
tm_scale_asis()passes the values straight to the renderer with no rescaling and no legend entry.
metro dataset contains population estimates per
city, e.g. "pop2020". Assign this to size in
tm_symbols().tm_shape(World) +
tm_fill(fill = "grey90") +
tm_shape(World_rivers) +
tm_lines(col = "steelblue", lwd = "strokelwd", lwd.scale = tm_scale_asis()) +
tm_shape(metro) +
tm_symbols(fill = "gold", size = "pop2020")
group
argument in the layer function
(e.g. tm_fill(..., group = "Land")). Check that the names
appear correctly in the layer control.tm_shape(World) +
tm_fill(fill = "grey90", group = "Land") +
tm_shape(World_rivers) +
tm_lines(col = "steelblue", lwd = "strokelwd",
lwd.scale = tm_scale_asis(), group = "Rivers") +
tm_shape(metro) +
tm_symbols(fill = "gold", size = "pop2020", group = "Cities")
NLD_prov, NLD_muni, and
NLD_dist. Make three separate interactive maps — one per
dataset — each filled with "steelblue". Zoom in and out on
each to appreciate the resolution difference.tm_shape(NLD_prov) +
tm_polygons(fill = "steelblue")
tm_shape(NLD_muni) +
tm_polygons(fill = "steelblue")
tm_shape(NLD_dist) +
tm_polygons(fill = "steelblue")
"dwelling_value" at district level.tm_shape(NLD_dist) +
tm_polygons(fill = "dwelling_value")
col = NULL). Which border thicknesses and colours
work well?tm_shape(NLD_dist) +
tm_polygons(fill = "dwelling_value", col = NULL) +
tm_shape(NLD_muni) +
tm_borders(lwd = 1, col = "black") +
tm_shape(NLD_prov) +
tm_borders(lwd = 2, col = "black")
Removing district borders (
col = NULL) reduces visual noise across 3340 polygons. Province borders (thicker) provide orientation; municipality borders (thinner) add detail when zoomed in.
tm_scale_intervals() and
tm_scale_continuous_pseudo_log(). Make one map with each
and explore their arguments (e.g. style and n
for the interval scale, or manual breaks). Which do you
prefer and why?tm_shape(NLD_dist) +
tm_polygons(
fill = "dwelling_value",
col = NULL,
fill.scale = tm_scale_intervals(style = "kmeans", n = 5)) +
tm_shape(NLD_muni) + tm_borders(lwd = 1) +
tm_shape(NLD_prov) + tm_borders(lwd = 2)
tm_shape(NLD_dist) +
tm_polygons(
fill = "dwelling_value",
col = NULL,
fill.scale = tm_scale_continuous_pseudo_log()) +
tm_shape(NLD_muni) + tm_borders(lwd = 1) +
tm_shape(NLD_prov) + tm_borders(lwd = 2)
The pseudo-log scale stretches the lower end of the distribution, revealing variation among the many lower-value districts that interval classes might merge into a single bin. For a right-skewed variable like property values, this is often more informative. Manual breaks are also possible:
tm_scale_intervals(breaks = c(75, 150, 250, 500, 750, Inf)).
cols4all:c4a_gui()
values = "<palette name>" inside the scale
function.tm_shape(NLD_dist) +
tm_polygons(
fill = "dwelling_value",
col = NULL,
fill.scale = tm_scale_intervals(style = "kmeans", n = 5,
values = "plasma")) +
tm_shape(NLD_muni) + tm_borders(lwd = 1) +
tm_shape(NLD_prov) + tm_borders(lwd = 2)
"WOZ value (×€1 000)") and move the legend to the
top-right corner. Do this with the fill.legend argument:
tm_legend(title = "...", position = c("right", "top")).tm_shape(NLD_dist) +
tm_polygons(
fill = "dwelling_value",
col = NULL,
fill.scale = tm_scale_intervals(style = "kmeans", n = 5,
values = "plasma"),
fill.legend = tm_legend(title = "WOZ value (×€1 000)",
position = c("right", "top"))) +
tm_shape(NLD_muni) + tm_borders(lwd = 1) +
tm_shape(NLD_prov) + tm_borders(lwd = 2)
tm_scalebar().tm_shape(NLD_dist) +
tm_polygons(
fill = "dwelling_value",
col = NULL,
fill.scale = tm_scale_intervals(style = "kmeans", n = 5,
values = "plasma"),
fill.legend = tm_legend(title = "WOZ value (×€1 000)",
position = c("right", "top"))) +
tm_shape(NLD_muni) + tm_borders(lwd = 1) +
tm_shape(NLD_prov) + tm_borders(lwd = 2) +
tm_scalebar()
tm_basemap("<provider name>") at the top of the map.
Which basemap complements the choropleth best?tm_basemap("CartoDB.Positron") +
tm_shape(NLD_dist) +
tm_polygons(
fill = "dwelling_value",
col = NULL,
fill.scale = tm_scale_intervals(style = "kmeans", n = 5,
values = "plasma"),
fill.legend = tm_legend(title = "WOZ value (×€1 000)",
position = c("right", "top"))) +
tm_shape(NLD_muni) + tm_borders(lwd = 1) +
tm_shape(NLD_prov) + tm_borders(lwd = 2) +
tm_scalebar()
Light neutral basemaps like
CartoDB.Positronkeep the choropleth colours prominent. Satellite imagery (Esri.WorldImagery) works well when geographic context matters, but can compete with the fill colours.