--- title: "Beyond the choropleth" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Beyond the choropleth} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", message = FALSE, warning = FALSE, fig.width = 7, fig.height = 4, fig.align = "center", dpi = 96 ) library(countryatlas) library(ggplot2) snap <- world_snapshot$countries # The sf geometry backend needs all three, which is what need_pkg() gates # on in geometry.R: sf alone is not enough, and a chunk guarded on sf # alone fails the vignette build wherever the data packages are absent. has_maps <- requireNamespace("maps", quietly = TRUE) has_sf <- requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE) && requireNamespace("rnaturalearthdata", quietly = TRUE) ``` "World data on a map" has many honest forms. A choropleth is only the first. The package offers a full vocabulary; this vignette tours the ones that need only `maps` -- itself a suggested package, so the map chunks are skipped when it is absent -- and points to the rest, which need `sf` or `biscale` as well. ## Proportional-symbol (bubble) maps For *totals*, a choropleth misleads: large values hide in small countries. Sized circles at centroids are the right idiom. ```{r eval = has_maps, fig.alt = "Bubble map: population drawn as proportional circles at country centroids."} bubble_map(snap, population) ``` ## Spike maps The same "totals" job as bubbles, with a different overplotting trade-off: spikes only grow upward, so dense regions (Europe, the Caribbean) stay legible. ```{r eval = has_maps, fig.alt = "Spike map: population drawn as vertical spikes rising from country centroids."} spike_map(snap, population) ``` Both verbs place one symbol per country centroid, and the bundled centroid table does not cover every code in the codelist. On this snapshot five countries with population -- Hong Kong, Macao, Gibraltar, the British Virgin Islands and Tuvalu -- have no centroid and so no symbol. Each verb warns and names them, and counts them as missing rather than shown: ```{r eval = has_maps} cov <- attr(suppressWarnings(bubble_map(snap, population)), "countryatlas_provenance")$coverage unlist(cov[c("n_total", "n_shown", "n_missing")]) cov$missing_iso3c ``` ## Equal-area tile grids Give every country the same visual weight so micro-states are visible. The bundled grid covers 239 countries -- see `?world_tiles` for the ten it omits. ```{r fig.height = 5, fig.alt = "Equal-area tile grid: one identically sized tile per country, shaded by GDP per capita."} tile_map(snap, gdp_per_capita) ``` ## Flow maps Great-circle arcs between country pairs from an origin–destination table. ```{r eval = has_maps, fig.alt = "Flow map: great-circle arcs joining four origin-destination country pairs, width by volume."} od <- data.frame( from = c("China", "Germany", "Brazil", "Nigeria"), to = c("United States", "France", "Argentina", "India"), weight = c(500, 200, 90, 60) ) flow_map(od, from, to, weight) ``` ## Small multiples `facet_map()` splits one choropleth into per-group panels, the static counterpart to `animate_world()`, for print and side-by-side comparison: ```{r eval = has_maps, fig.height = 5, fig.alt = "Small multiples: one GDP per capita choropleth panel per continent."} world_poly <- attach_geometry(snap, geometry = "polygon") |> dplyr::filter(!is.na(continent)) facet_map(world_poly, gdp_per_capita, continent, style = "quantile", ncol = 3) ``` ## Labels Centroid-anchored labels (names, ISO codes or flag emoji), with `ggrepel` collision avoidance when available. Zoom with `coord_quickmap()` rather than `coord_cartesian()` -- both replace the map's coordinate system, but only the former keeps the latitude-dependent aspect ratio that stops Europe coming out stretched sideways. ```{r eval = has_maps, fig.alt = "Choropleth of Europe with ISO codes labelled at country centroids."} mapdf <- attach_geometry( dplyr::filter(snap, continent == "Europe"), geometry = "polygon" ) world_map(mapdf, gdp_per_capita) + geom_country_labels(repel = FALSE, size = 2.5) + ggplot2::coord_quickmap(xlim = c(-25, 45), ylim = c(34, 72)) ``` ## Maps that need optional packages The remaining displays follow the same one-call pattern but require optional packages, so they are shown here as code: ```{r eval = FALSE} # Bivariate choropleth (two variables at once): needs `biscale` + `sf` world_data(2020, c(gdp = "NY.GDP.PCAP.KD", life = "SP.DYN.LE00.IN"), geometry = "sf") |> bivariate_map(gdp, life) # Area-honest cartogram: needs `cartogram` + `sf` world_data(2020, c(pop = "SP.POP.TOTL"), geometry = "sf") |> cartogram_map(pop, type = "dorling") # The same Dorling cartogram as a first-class verb, with its tuning exposed world_data(2020, c(pop = "SP.POP.TOTL"), geometry = "sf") |> dorling_map(pop, k = 4) # The fast flow-based cartogram (Gastner-Seguy-More): needs `cartogramR` world_data(2020, c(pop = "SP.POP.TOTL"), geometry = "sf") |> cartogram_map(pop, type = "flow") # Animated choropleth over a year panel: needs `gganimate` world_data(2000:2020, c(gdp = "NY.GDP.PCAP.KD")) |> animate_world(gdp) # Interactive choropleth: needs `leaflet`, `ggiraph` or `plotly` world_data(2020) |> interactive_map(gdp_per_capita, engine = "plotly") ``` ## Value-by-alpha: the cartogram's non-distorting cousin A cartogram equalises a denominator by deforming geometry. `value_by_alpha_map()` does it by spending opacity instead, so the world stays recognisable: colour carries the value, opacity carries population, and a rate computed over a handful of people fades toward the background rather than shouting. ```{r eval = has_maps, fig.alt = "Value-by-alpha map of GDP per capita weighted by population."} mapdf <- attach_geometry(snap, geometry = "polygon") value_by_alpha_map(mapdf, gdp_per_capita, population) ``` It needs no optional packages. The *Honest maps* vignette covers when to reach for it rather than for `cartogram_map()`. ## Country adjacency and distance Two lightweight spatial helpers that aren't choropleths at all. `distance_between()` answers "how far apart" from the bundled `country_meta` centroids, with no `sf` or network required: ```{r} distance_between("France", "Germany") ``` `country_borders()` / `neighbors()` answer "who borders whom", built from polygon topology, so they need `sf`: ```{r eval = has_sf} neighbors("France") ``` Each degrades gracefully: if the optional package is missing you get a clear, actionable message (and `animate_world()` falls back to a faceted small-multiple).