| Title: | Join World Bank Data, Country Codes and Maps on the ISO Spine |
|---|---|
| Description: | A complete toolkit for getting country data onto honest maps. Country names rarely line up across data sources ("US", "U.S.", "United States", "United States of America" are one country, but a naive join treats them as four), so 'countryatlas' makes ISO codes the universal join key, and matches names in many languages. It joins indicators from the World Bank, Our World in Data, the European Union's statistical office and any statistical data and metadata exchange service to bundled Natural Earth borders and the 'countrycode' Rosetta stone, recording each value's source, unit and release, and can pin a World Bank release so a result reproduces; treats country-year panels as first class, with dated income groups, regions and group memberships, lags keyed on the year and aggregates that report their coverage; adds analysis helpers (per-capita, inequality, convergence, distribution dynamics, rates, and spatial statistics with multiple-testing control); and draws a full vocabulary of equal-area maps (classed and continuous choropleths, proportional-symbol, spike, bivariate, ternary, value-by-alpha, cartogram, tile-grid, flow, small-multiple, animated, globe and interactive), and can hand its curated tables to 'ggsql' for database-side rendering. Honesty is treated as a feature rather than a slogan: maps state their coverage and source, draw small states rather than dropping them and describe themselves in alt text, classification methods can be compared side by side, and the distortion each projection introduces can be measured and drawn. Heavy spatial dependencies stay optional, and a bundled offline snapshot lets every example, test and vignette run without the network. |
| Authors: | Youzhi Yu [aut, cre] |
| Maintainer: | Youzhi Yu <[email protected]> |
| License: | GPL (>= 3) |
| Version: | 4.0.0 |
| Built: | 2026-10-03 02:12:44 UTC |
| Source: | https://github.com/pursuitofdatascience/countryatlas |
fetch_indicator() plus country_join() in one step: pull an indicator from
any registered source and attach it to a frame you already have, matched on
the ISO spine (and on year too, when both sides are panels).
add_indicator(data, source, indicator, countries = NULL, years = NULL, ...)add_indicator(data, source, indicator, countries = NULL, years = NULL, ...)
data |
A frame with |
source, indicator, countries, years, ...
|
Passed to |
data with the indicator column(s) added.
countries actually savescountries bounds the result, not necessarily the download. Only some
providers accept a country filter in the request:
| Source | countries reaches the provider? |
comtrade |
yes -- sent as reporter |
wdi |
partly -- the year range is sent, countries are filtered here |
owid, eurostat, oecd |
no -- the full dataset is downloaded, then filtered |
So add_indicator(one_row, "owid", "life-expectancy") still transfers every
country and year that dataset holds in order to keep a single value. When
that matters, narrow with years (which the wdi and comtrade adapters do
push down), or fetch once into a variable and reuse it rather than calling
this per subset. The on-disk cache means a repeated wdi fetch is free;
the other sources are memoised for the session only.
fetch_indicator(), country_join()
## Not run: world_snapshot$countries |> add_indicator("wdi", c(unemployment = "SL.UEM.TOTL.ZS"), years = 2020) ## End(Not run)## Not run: world_snapshot$countries |> add_indicator("wdi", c(unemployment = "SL.UEM.TOTL.ZS"), years = 2020) ## End(Not run)
Roll countries up to a group such as the EU or the OECD using the members of
each row's own year, not today's list. An "EU" series built from the current
27 members misstates every year before 2020 and every year before an
accession; this builds it from whoever was a member at the time, under the
same coverage rule as aggregate_regions().
aggregate_groups( data, value, groups, as_of = NULL, fun = "sum", weight = NULL, min_coverage = 2/3 )aggregate_groups( data, value, groups, as_of = NULL, fun = "sum", weight = NULL, min_coverage = 2/3 )
data |
A country-level frame with an |
value |
The value column to aggregate (unquoted). |
groups |
One or more group names (see |
as_of |
|
fun, weight
|
As in |
min_coverage |
The smallest share of the group's members that has to
report before an aggregate is computed. Default |
A tibble of group (and year for a panel), the aggregated value,
n_countries (members on that date), n_reporting, coverage, and for a
weighted mean coverage_weighted.
Coverage is measured against the group's full membership on that date,
taken from country_groups_history, not against the members that happen to
be in data: a member with no row is counted as missing. That is the
difference from aggregate_regions(), which can only count what it is
given. For fun = "weighted_mean" the weighted share is computed over the
members present, since an absent member's weight is unknown. A group with no
dated history (Commonwealth, G20, OPEC) warns and uses the current
membership for every year.
aggregate_regions(), in_group(), country_groups_history
pan <- data.frame(iso3c = rep(c("GBR", "FRA", "DEU", "HRV"), each = 2), year = rep(c(2012, 2021), 4), gdp = 1:8) # The United Kingdom counts in 2012 and not in 2021; Croatia the reverse. aggregate_groups(pan, gdp, "EU", min_coverage = 0)pan <- data.frame(iso3c = rep(c("GBR", "FRA", "DEU", "HRV"), each = 2), year = rep(c(2012, 2021), 4), gdp = 1:8) # The United Kingdom counts in 2012 and not in 2021; Croatia the reverse. aggregate_groups(pan, gdp, "EU", min_coverage = 0)
Aggregate a country-level value to a coarser grouping, optionally with population-weighted means.
aggregate_regions( data, value, by = "region", fun = "sum", weight = NULL, min_coverage = 2/3, coverage_weight = NULL )aggregate_regions( data, value, by = "region", fun = "sum", weight = NULL, min_coverage = 2/3, coverage_weight = NULL )
data |
A country-level data frame. |
value |
The value column to aggregate (unquoted). |
by |
Grouping column(s) (character), default |
fun |
Aggregation: |
weight |
Optional weight column (unquoted) for |
min_coverage |
The smallest share of a group that has to report before
an aggregate is computed, between 0 and 1. Default |
coverage_weight |
Optional column (unquoted), typically population,
whose share among the reporting members measures coverage instead of the
count of countries. For |
A tibble of by, the aggregated value, n_countries,
n_reporting and coverage, plus coverage_weighted when a coverage
weight applies.
A sum over a region that is missing its largest member is not that region's
total, and nothing about the number says so. So every row reports how much
of the group stands behind it: n_countries (members present in data),
n_reporting (members with a value), coverage (their share) and, with a
coverage weight, coverage_weighted (the share of that weight the reporting
members hold). A group whose coverage is below min_coverage is NA.
The default threshold is the World Bank's rule for the aggregates in the
World Development Indicators: a sum is not computed when more than a third
of the observations are missing, and a weighted mean is not computed when
missing data account for more than a third of the weights. Coverage is
tested on coverage_weighted when there is one, so fun = "weighted_mean"
follows the second rule by default. Note that only members present in data
are counted: a country with no row at all is invisible to this check, so
complete the frame against the full membership first (for example with
country_codes() or country_groups()) when that matters. Rows whose
grouping value is missing are reported but never withheld: they are not a
group with a membership to fall short of.
Missing values are dropped before aggregating, so a group is summarised from
whatever it does have. A group with no non-missing value returns NA
rather than a figure: sum() would otherwise report 0, mean() NaN and
min()/max() -Inf/Inf, each of which reads as a real total for a
region we simply have no data for. Use audit_coverage() to see where those
gaps are.
aggregate_groups() to aggregate by dated group membership.
df <- data.frame(iso3c = c("USA", "CAN", "BRA"), region = c("North America", "North America", "Latin America"), gdp = c(21, 1.7, 1.4)) aggregate_regions(df, gdp, fun = "sum") # A region missing a third of its members is not aggregated by default. ssa <- data.frame(iso3c = c("NGA", "ZAF", "KEN"), region = "Sub-Saharan Africa", gdp = c(NA, 400, 100), pop = c(223, 60, 55)) aggregate_regions(ssa, gdp, coverage_weight = pop, min_coverage = 0)df <- data.frame(iso3c = c("USA", "CAN", "BRA"), region = c("North America", "North America", "Latin America"), gdp = c(21, 1.7, 1.4)) aggregate_regions(df, gdp, fun = "sum") # A region missing a third of its members is not aggregated by default. ssa <- data.frame(iso3c = c("NGA", "ZAF", "KEN"), region = "Sub-Saharan Africa", gdp = c(NA, 400, 100), pop = c(223, 60, 55)) aggregate_regions(ssa, gdp, coverage_weight = pop, min_coverage = 0)
Given a panel from world_data(2000:2020, ...), animate the choropleth over
year via the optional gganimate package, or fall back to a faceted
small-multiple when it is not installed.
animate_world( data, fill, time = year, projection = "equal_earth", breaks_by = c("pooled", "panel"), ... )animate_world( data, fill, time = year, projection = "equal_earth", breaks_by = c("pooled", "panel"), ... )
data |
A panel map-ready frame (polygon or sf) with a |
fill |
The fill column (unquoted). |
time |
The time column (unquoted; default |
projection |
Projection; see |
breaks_by |
|
... |
Passed to |
A gganim object (if gganimate is available) or a faceted
ggplot.
Either backend, as world_map(): the frame decides, and both draw in
projection (Equal Earth by default).
## Not run: world_data(2000:2005, c(gdp = "NY.GDP.PCAP.KD")) |> animate_world(gdp) ## End(Not run)## Not run: world_data(2000:2005, c(gdp = "NY.GDP.PCAP.KD")) |> animate_world(gdp) ## End(Not run)
Hand countryatlas's curated, ISO-reconciled, WDI-joined spatial table to
ggsql so it can be charted with DRAW spatial – the
bridge that lets ggsql draw maps of your override-corrected data instead of
its static bundled world. sf geometry is WKB-encoded so ggsql can decode it.
as_ggsql_source( data, name = "countryatlas_world", format = c("duckdb", "parquet", "arrow"), con = NULL, path = NULL, geometry_col = "geometry" )as_ggsql_source( data, name = "countryatlas_world", format = c("duckdb", "parquet", "arrow"), con = NULL, path = NULL, geometry_col = "geometry" )
data |
A map-ready frame (ideally |
name |
The table name to register/write (default |
format |
|
con |
An existing DuckDB |
path |
Output path for |
geometry_col |
Name for the WKB geometry column (default |
Depending on format: a DuckDB connection (with the table written),
a Parquet file path, or a nanoarrow array stream.
You own the connection that format = "duckdb" returns, and duckdb
keeps its in-memory database alive until the handle is released, so close
it when you are done:
src <- as_ggsql_source(d, format = "duckdb") on.exit(DBI::dbDisconnect(src, shutdown = TRUE))
format = "parquet" needs no such care: it closes the connection it
opened before returning the path. Passing your own con leaves it open
in every case, since it was never ours to close.
## Not run: # Curate in R, render in the database: src <- world_data(2020, geometry = "sf") |> as_ggsql_source(format = "duckdb") ggsql::ggsql_execute(src, world_query(gdp_per_capita)) ## End(Not run)## Not run: # Curate in R, render in the database: src <- world_data(2020, geometry = "sf") |> as_ggsql_source(format = "duckdb") ggsql::ggsql_execute(src, world_query(gdp_per_capita)) ## End(Not run)
The bridge between a one-row-per-country table (e.g. from country_data())
and plotting: bolts polygon or sf geometry onto your data, keyed on
iso3c.
attach_geometry( data, by = "iso3c", geometry = c("polygon", "sf"), scale = "small", region = NULL, projection = "equal_earth", recenter = NULL, overrides = country_overrides(), year = NULL, worldview = NULL )attach_geometry( data, by = "iso3c", geometry = c("polygon", "sf"), scale = "small", region = NULL, projection = "equal_earth", recenter = NULL, overrides = country_overrides(), year = NULL, worldview = NULL )
data |
A data frame with an |
by |
The join key (default |
geometry |
|
scale |
Natural Earth resolution for the |
region |
Optional region subset (see |
projection, recenter
|
Projection, and optional central meridian. The
|
overrides |
Name -> iso3c overrides applied when matching the geometry
backend's country names (default |
year |
Attach historical geometry for this year instead of present-day
borders, via |
worldview |
A Natural Earth point of view, as in |
For "polygon", a tibble with long/lat/group plus your
columns, one row per polygon vertex. For "sf", an sf object, one row
per feature. Both carry every country the backend has – see How many
rows come back.
Geometry is attached once per row, not once per country. That is what a panel wants – one row per country-year, each carrying the shape – but it means a frame that repeats a country by accident draws that country more than once, and only the last one painted is visible. The package cannot tell the two apart (a panel's time column may be called anything), so reduce to one row per country yourself when that is what you meant.
The join keeps only countries the chosen backend actually carries, so rows of
data with no matching geometry are dropped silently – worth checking first
when a country you expected is missing from the map. Coverage differs by
backend and, for "sf", by scale, which changes which countries are
present and not merely how detailed they look. Of the 216 countries in
world_snapshot, "polygon" and "sf" with scale = "medium" carry the
same 215, because both are Natural Earth 1:50m, while "sf" with
scale = "small" (the default, 110m) carries 170: the 110m coastlines omit
most small states, so use the polygon backend or scale = "medium" when
microstates matter. Gibraltar alone is in none of them.
The result is the backend's whole map, not just your rows: every country the
backend carries is present, and the ones absent from data carry NA in
your columns. That is what makes them draw in na.value rather than vanish,
which is the point – a choropleth that quietly omits the countries you have
no data for reads as though they did not exist. It does mean the result is
much larger than data and is not something to summarise directly:
attach_geometry() on three countries returns 237 of them on the polygon
backend and 175 on "sf", whatever data held. The row count is larger
still: "polygon" gives one row per polygon vertex (about 98,000), and
"sf" one row per feature – usually one per country, but a divided
country appears more than once (Cyprus at scale = "small"; Cyprus and
India at "medium"), so an iso3c join against it can fan out. Summarise
data before attaching geometry, or use the verbs in this package, which
de-duplicate to one row per country first.
df <- data.frame(iso3c = c("USA", "CAN"), value = c(1, 2)) attach_geometry(df, geometry = "polygon")df <- data.frame(iso3c = c("USA", "CAN"), value = c(1, 2)) attach_geometry(df, geometry = "polygon")
What is missing, before you trust the map: which countries are unmatched,
the NA rate per indicator, and which World Bank regions / income groups are
under-covered – so a half-empty map is caught before it is published.
audit_coverage(data, indicator = NULL, by = c("region", "income", "continent"))audit_coverage(data, indicator = NULL, by = c("region", "income", "continent"))
data |
A country-level (or map-ready) data frame. |
indicator |
Optional character vector of value columns to report |
by |
Grouping for the coverage breakdown: |
A list of class countryatlas_coverage, with elements unmatched,
na_rates and by_group. It has a print() method, so at the console you
see a formatted report rather than the raw list; reach into the elements by
name to use the numbers programmatically.
unmatched – one row per input value that did not resolve to an ISO
code. Empty is the good case.
na_rates – one row per indicator: n is the number of countries
considered, n_missing how many of them lack a value, and na_rate
is n_missing / n. An infinite value counts as lacking one: no map can
draw it, and coverage_map() shows it as missing too.
by_group – one row per group: n_countries is how many countries are
in that group, and na_rate is the share of those lacking a value.
The counts sum to n.
Both n and by_group's n_countries are denominators – everything
counted, not everything present. That is the opposite orientation from
map_provenance(), whose n_countries is the numerator (countries drawn
with a value) and whose n_total is the denominator. The two verbs
report the same coverage from opposite ends, so on 216 countries with 17
missing this gives n = 216 where map_provenance() gives
n_countries = 199.
audit_coverage(countryatlas::world_snapshot$countries)audit_coverage(countryatlas::world_snapshot$countries)
The time-aware counterpart to audit_coverage(). A join can succeed and
still be wrong about history: South Sudan with 1995 data, Czechoslovakia with
2001 data, the USSR with 2010 data. Those rows survive every check the package
had, because the country resolves and the year is a number.
audit_time_coverage(data, quiet = FALSE)audit_time_coverage(data, quiet = FALSE)
data |
A panel with |
quiet |
Suppress the console summary and return the table silently.
(Unlike |
A tibble of the offending rows: iso3c, country, year, issue,
existed (a human-readable span) and basis (where the dates come
from). issue is "before_existence" or "after_dissolution" – the
country did not exist – or the milder "before_independence": it
existed, but was not yet (or not then) an independent state. Zero rows
means the panel is clean.
Dissolution dates come from historical_codes, which covers the entities the
package curates (USSR, Yugoslavia, Czechoslovakia and the rest), and a
successor state is treated as not existing before its predecessor
dissolved (basis = "historical_codes").
Independence comes from Gleditsch & Ward's list of independent states since
1816 (Gleditsch & Ward 1999, through the states package), with every spell
of independence: Namibia from 1990, Eritrea from 1993, Timor-Leste from
2002, and Estonia from 1918 to 1940 and again from 1991. A year inside an
earlier spell also corrects the crosswalk for a state it dates from a later
succession (Estonia's interwar years). A year outside
every spell is "before_independence" (basis = "gleditsch_ward"). That
is statehood, not data availability – a colony often reports statistics
before independence, and a World Bank series may legitimately start there –
so it is a separate, lower-severity issue, for the caller to judge.
Territories that are not on the list are assumed to have existed
throughout, so a clean result still means "nothing these lists know about
is wrong", not "every date is right".
Gleditsch, K. S. & Ward, M. D. (1999). A revised list of independent states since the congress of Vienna. International Interactions 25(4), 393-413. doi:10.1080/03050629908434958
audit_coverage(), dissolve_country(), country_timeline()
panel <- data.frame( iso3c = c("SSD", "CZE", "FRA"), year = c(1995L, 2001L, 2001L), gdp = c(1, 2, 3) ) audit_time_coverage(panel)panel <- data.frame( iso3c = c("SSD", "CZE", "FRA"), year = c(1995L, 2001L, 2001L), gdp = c(1, 2, 3) ) audit_time_coverage(panel)
Do poor countries grow faster than rich ones? The classic unconditional
beta-convergence test: each country's average log growth rate is regressed
on its log initial level. A significantly negative beta is convergence;
the implied convergence speed and half_life (years to close half the
gap) are derived from it.
beta_convergence(data, value)beta_convergence(data, value)
data |
A panel with |
value |
The value column (unquoted); must be positive (log scale). |
A one-row tibble: beta, se, t_value, p_value, r_squared,
n (countries), speed (annual convergence rate) and half_life
(years).
speed and half_life are NA in two cases: when beta >= 0, because
there is no convergence to put a rate on; and when the panel's per-country
spans are too heterogeneous for any single span to reconcile with the
fitted slope, which is warned about. beta and its inference are
unaffected in both – only the annualised figures need one common span, so
restrict the panel to a shared window if you need them. The fitted stats::lm() object is attached as the
"model" attribute.
sigma_convergence() for the dispersion-over-time counterpart.
set.seed(1) start <- runif(20, 6, 11) # log initial level growth <- 0.05 - 0.004 * start + rnorm(20, 0, 0.002) # poorer grow faster panel <- data.frame( iso3c = rep(sprintf("C%02d", 1:20), each = 2), year = rep(c(2000L, 2020L), 20), gdp = as.vector(rbind(exp(start), exp(start + growth * 20))) ) beta_convergence(panel, gdp)set.seed(1) start <- runif(20, 6, 11) # log initial level growth <- 0.05 - 0.004 * start + rnorm(20, 0, 0.002) # poorer grow faster panel <- data.frame( iso3c = rep(sprintf("C%02d", 1:20), each = 2), year = rep(c(2000L, 2020L), 20), gdp = as.vector(rbind(exp(start), exp(start + growth * 20))) ) beta_convergence(panel, gdp)
The local Moran statistic for two variables (Anselin, Syabri & Smirnov
2002): each country's standardised x against the average standardised
y of its neighbours, so "rich countries surrounded by long-lived
neighbours" is a cluster, and a rich country among short-lived neighbours
an outlier.
bivariate_lisa( data, x, y, weights = NULL, n_perm = 9999, alpha = 0.05, p_adjust = c("fdr", "bonferroni", "holm", "none") )bivariate_lisa( data, x, y, weights = NULL, n_perm = 9999, alpha = 0.05, p_adjust = c("fdr", "bonferroni", "holm", "none") )
data |
A country-level frame with |
x, y
|
The two value columns (unquoted). |
weights, n_perm, alpha, p_adjust
|
As in |
A tibble with one row per country used: iso3c, x, lag_y (the
neighbours' average y), ii, p_value, p_adjusted and cluster
("High-High", "Low-Low", "High-Low", "Low-High" or
"Not significant"), where the first word is the country's x and the
second its neighbours' y. A country needs both values to take part.
Anselin, L., Syabri, I. & Smirnov, O. (2002). Visualizing multivariate spatial correlation with dynamically linked windows. Proceedings, CSISS Workshop on New Tools for Spatial Data Analysis.
snap <- countryatlas::world_snapshot$countries bivariate_lisa(snap, gdp_per_capita, life_expectancy, n_perm = 499)snap <- countryatlas::world_snapshot$countries bivariate_lisa(snap, gdp_per_capita, life_expectancy, n_perm = 499)
A 2-D bivariate choropleth with a built-in 2-D legend (via the optional
biscale package), e.g. GDP per capita x life expectancy in one map.
bivariate_map( data, fill_x, fill_y, palette = "GrPink", dim = 3, projection = "equal_earth", footnote = "auto" )bivariate_map( data, fill_x, fill_y, palette = "GrPink", dim = 3, projection = "equal_earth", footnote = "auto" )
data |
An |
fill_x, fill_y
|
The two value columns (unquoted). |
palette |
A |
dim |
Bivariate dimension: classes per variable, 2, 3 (default) or 4.
A 4 x 4 map needs a palette that has one, such as |
projection |
Projection; see |
footnote |
The caption, as in |
A ggplot object (the map; combine with biscale::bi_legend() for a
standalone legend).
The sf backend only, since biscale classes an sf frame; drawn in
projection (Equal Earth by default).
if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE) && requireNamespace("biscale", quietly = TRUE)) { attach_geometry(countryatlas::world_snapshot$countries, geometry = "sf") |> bivariate_map(gdp_per_capita, life_expectancy) }if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE) && requireNamespace("biscale", quietly = TRUE)) { attach_geometry(countryatlas::world_snapshot$countries, geometry = "sf") |> bivariate_map(gdp_per_capita, life_expectancy) }
Plots sized circles at country centroids – the right idiom for totals (population, total emissions, total GDP), which a choropleth misrepresents because big values hide in small countries and vice versa.
bubble_map( data, size, color = NULL, projection = "equal_earth", backend = c("polygon", "sf"), max_size = 18, alpha = 0.7, footnote = "auto" )bubble_map( data, size, color = NULL, projection = "equal_earth", backend = c("polygon", "sf"), max_size = 18, alpha = 0.7, footnote = "auto" )
data |
A country-level frame with |
size |
The column controlling bubble size (unquoted). |
color |
Optional column controlling bubble colour (unquoted). |
projection |
Projection for the base map (sf path). See |
backend |
|
max_size |
Largest bubble size. |
alpha |
Bubble transparency. |
footnote |
The caption: |
A ggplot object.
snap <- countryatlas::world_snapshot$countries bubble_map(snap, population)snap <- countryatlas::world_snapshot$countries bubble_map(snap, population)
Cartograms fail quietly. An under-converged one looks entirely plausible while still misrepresenting the areas it exists to make honest. This reports the residual error per country, so the failure is visible.
cartogram_diagnostics(x, weight = NULL)cartogram_diagnostics(x, weight = NULL)
x |
A |
weight |
The weight column (unquoted). Required when |
A tibble of iso3c, target_share (the country's share of the
weight), actual_share (its share of the cartogram's area) and
area_error (the relative difference). The summary – mean absolute error,
worst country – is attached as the "countryatlas_cartogram" attribute.
A perfect cartogram has area_error of 0 everywhere. In practice a mean
absolute error under a few percent is good and under 10% is usually
acceptable; a systematically large error, or one concentrated in the small
countries, means the algorithm stopped early. Raise itermax and try again.
cartogram_map(), dorling_map(), gridded_cartogram()
if (requireNamespace("sf", quietly = TRUE) && requireNamespace("cartogram", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE)) { sfd <- attach_geometry(countryatlas::world_snapshot$countries, geometry = "sf") cg <- cartogram_map(sfd, population) cartogram_diagnostics(cg) }if (requireNamespace("sf", quietly = TRUE) && requireNamespace("cartogram", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE)) { sfd <- attach_geometry(countryatlas::world_snapshot$countries, geometry = "sf") cg <- cartogram_map(sfd, population) cartogram_diagnostics(cg) }
Resizes countries by weight (population, GDP, ...) via the optional
cartogram package, defeating the "big empty countries dominate the eye"
bias of world choropleths.
cartogram_map( data, weight, type = c("contiguous", "dorling", "noncontiguous", "flow"), fill = NULL, projection = "equal_earth", footnote = "auto", ... )cartogram_map( data, weight, type = c("contiguous", "dorling", "noncontiguous", "flow"), fill = NULL, projection = "equal_earth", footnote = "auto", ... )
data |
An |
weight |
The column to resize by (unquoted). |
type |
|
fill |
Optional fill column (unquoted); defaults to |
projection |
Projection; an equal-area CRS is recommended. See
|
footnote |
The caption, as in |
... |
Passed to the underlying |
A ggplot object.
"contiguous" (Dougenik) and "dorling"/"noncontiguous" come from
cartogram. "flow" comes from cartogramR and implements the
Gastner-Seguy-More flow-based method, which is both the current state of the
art and far faster than diffusion-based approaches – prefer it for
contiguous cartograms when cartogramR is available.
Cartograms fail quietly: an under-converged one looks plausible while still
misrepresenting the areas it exists to make honest. Pass a larger itermax
if the result still looks close to the true map.
The sf backend only: the cartogram package distorts sf geometry, in
projection (Equal Earth by default).
Gastner, M. T., Seguy, V. & More, P. (2018). Fast flow-based algorithm for creating density-equalizing map projections. Proceedings of the National Academy of Sciences 115(10), E2156-E2164. doi:10.1073/pnas.1712674115
if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE) && requireNamespace("cartogram", quietly = TRUE)) { attach_geometry(countryatlas::world_snapshot$countries, geometry = "sf") |> cartogram_map(population, type = "dorling") }if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE) && requireNamespace("cartogram", quietly = TRUE)) { attach_geometry(countryatlas::world_snapshot$countries, geometry = "sf") |> cartogram_map(population, type = "dorling") }
A report on what will and will not match before you trust the map: the
input, its iso3c, whether it matched, whether it is a historical
(dissolved) entity, and a suggestion (the closest known country name by
string distance) for misses. Surfaced automatically by join_world().
check_country_match( x, origin = "country.name", custom_match = country_overrides(), suggest = TRUE )check_country_match( x, origin = "country.name", custom_match = country_overrides(), suggest = TRUE )
x |
A vector of country names or codes. |
origin |
How to read |
custom_match |
Overrides applied before matching (default
|
suggest |
Whether to compute closest-name suggestions for misses
(requires the optional |
The historical flag matters even for rows that matched: countrycode
silently resolves "USSR" to Russia's RUS, so Soviet-era data becomes
Russian data without a warning. Rows flagged historical should usually be
routed through dissolve_country() instead.
A tibble with columns input, iso3c, matched, historical,
method and suggestion. method says how a name was matched, tier by
tier, each tried only for what is still unmatched: "override" (the
country_overrides() table), "regex_en" (countrycode's English
patterns), "name_de", "name_es", "name_fr" and "name_it" (an
exact match, ignoring case, accents and punctuation, against
countrycode's German, Spanish, French and Italian names), "cldr" (the
same against every name, short name and variant in the Unicode CLDR
tables countrycode carries, in every language), "ambiguous" (a CLDR
name for more than one country, which is left unmatched) or "none".
Exact rather than countrycode's language patterns, which are unanchored
and matched Somaliland to Somalia. For an origin other than a name it
is "code".
dissolve_country() for resolving the entities this flags as
historical to their successor states, and repair_country_names() for
applying the suggestion column automatically.
check_country_match(c("USA", "Cote d'Ivoire", "Yugoslavia", "Wakanda")) # "USSR" matches (to RUS!) but is flagged historical: check_country_match("USSR")check_country_match(c("USA", "Cote d'Ivoire", "Yugoslavia", "Wakanda")) # "USSR" matches (to RUS!) but is flagged historical: check_country_match("USSR")
Cross-references your data against disputed_territories so a contested area does not pass unremarked. Reports both directions: the disputed territories your data covers, and those it is silent about.
check_dispute_coverage(data, quiet = FALSE)check_dispute_coverage(data, quiet = FALSE)
data |
A frame with |
quiet |
Suppress the console summary. |
A tibble of every disputed territory the package knows about, with
in_data saying whether your data covers it. The scope caveat in
disputed_territories applies: this is a documented subset, not every
dispute in the world.
disputed_territories, dispute_policy(), audit_coverage()
check_dispute_coverage(countryatlas::world_snapshot$countries)check_dispute_coverage(countryatlas::world_snapshot$countries)
Simulate how the map's fill colours look with deuteranopia, protanopia and
tritanopia – the Machado, Oliveira & Fernandes (2009) model, through the
optional colorspace – and report the smallest colour difference between
adjacent classes under each. Two classes a reader cannot tell apart are two
classes the map does not have. Roughly one man in twelve has a red-green
deficiency.
check_palette(p, threshold = 10)check_palette(p, threshold = 10)
p |
A map from one of the package's map verbs, or a character vector of colours in class order. |
threshold |
The smallest acceptable difference between adjacent
classes (default |
The difference is CIE 1976 , the distance between the
two colours in CIELAB. A difference under about 10 is hard to tell apart
at the size of a small country on a map; the package's own defaults (the
viridis family) clear it under all three simulations.
A tibble with one row per vision type (normal, deuteranopia,
protanopia, tritanopia): min_delta_e, and between, the two classes
that come closest.
Machado, G. M., Oliveira, M. M. & Fernandes, L. A. F. (2009). A physiologically-based model for simulation of color vision deficiency. IEEE Transactions on Visualization and Computer Graphics 15(6), 1291-1298. doi:10.1109/TVCG.2009.113
Crameri, F., Shephard, G. E. & Heron, P. J. (2020). The misuse of colour in science communication. Nature Communications 11, 5444. doi:10.1038/s41467-020-19160-7
if (requireNamespace("colorspace", quietly = TRUE)) { snap <- countryatlas::world_snapshot$countries check_palette(world_map(attach_geometry(snap), gdp_per_capita)) # A rainbow fails all three simulations: check_palette(grDevices::rainbow(7)) }if (requireNamespace("colorspace", quietly = TRUE)) { snap <- countryatlas::world_snapshot$countries check_palette(world_map(attach_geometry(snap), gdp_per_capita)) # A rainbow fails all three simulations: check_palette(grDevices::rainbow(7)) }
Small multiples of one choropleth, drawn once per classification method, plus the break table and the count of countries in each class. The point is that the choice is consequential and usually unexamined: Brewer & Pickle (2002) found quantiles among the best methods for general choropleth reading and natural breaks (Jenks) below 70% as accurate, which is the reverse of the common GIS default.
classify_compare( data, value, methods = c("quantile", "jenks", "fisher", "headtails", "equal", "pretty"), n_bins = 5, ncol = NULL, ..., n = deprecated() )classify_compare( data, value, methods = c("quantile", "jenks", "fisher", "headtails", "equal", "pretty"), n_bins = 5, ncol = NULL, ..., n = deprecated() )
data |
A map-ready frame (polygon or |
value |
The value column (unquoted). |
methods |
Classification styles to compare. Any of |
n_bins |
Number of classes (default |
ncol |
Number of facet columns. |
... |
Passed to |
n |
A faceted ggplot object, with the per-method break and class-count
table attached as the "countryatlas_classification" attribute (and
readable with map_provenance()). Each method's rows also carry three
measures of fit: gvf, the goodness of variance fit, and tai, the
tabular accuracy index (both Jenks & Caspall 1971; 1 is a perfect fit),
and max_class_share, the share of countries in the fullest class.
GVF and TAI measure how homogeneous the classes are, which is what natural
breaks optimise; they rate a map that puts most countries in one class
highly, and they say nothing about how well readers read the map. They are
reported to inform the choice, never to rank the methods: Brewer & Pickle's
finding that quantiles read best stands beside them, and
max_class_share flags the map whose fullest class swallows the rest.
Brewer, C. A. & Pickle, L. (2002). Evaluation of methods for classifying epidemiological data on choropleth maps in series. Annals of the Association of American Geographers 92(4), 662-681. doi:10.1111/1467-8306.00310
Jenks, G. F. & Caspall, F. C. (1971). Error on choroplethic maps: definition, measurement, reduction. Annals of the Association of American Geographers 61(2), 217-244. doi:10.1111/j.1467-8306.1971.tb00779.x
Jiang, B. (2013). Head/tail breaks: a new classification scheme for data with a heavy-tailed distribution. The Professional Geographer 65(3), 482-494. doi:10.1080/00330124.2012.700499
snap <- countryatlas::world_snapshot$countries cmp <- attach_geometry(snap, geometry = "polygon") |> classify_compare(gdp_per_capita) attr(cmp, "countryatlas_classification")snap <- countryatlas::world_snapshot$countries cmp <- attach_geometry(snap, geometry = "polygon") |> classify_compare(gdp_per_capita) attr(cmp, "countryatlas_classification")
Add the World Bank's income group, region or lending category to a frame,
as in force on a date – each row's own year, by default – from the
bundled country_classifications. A panel spanning 2000 to 2026 gets the
income group each country had in each year, not today's list painted
across every year, and the July 2025 move of Afghanistan and Pakistan from
South Asia into the Middle East and North Africa region falls where it
happened.
classify_countries( data, schemes = c("income", "region"), as_of = NULL, basis = c("in_effect", "data_year") )classify_countries( data, schemes = c("income", "region"), as_of = NULL, basis = c("in_effect", "data_year") )
data |
A frame with an |
schemes |
Which classifications to add: any of |
as_of |
|
basis |
For income: |
data with the requested columns – income a factor in income
order, region and lending character – NA where the table has no
classification for that country on that date (a country not yet a
member, or a date before the table begins). Each added column carries a
source_info() record naming the World Bank source and the fiscal years
used.
The World Bank classifies each economy once a year, on 1 July, and the class holds for its fiscal year: fiscal year t runs from 1 July t-1 to 30 June t, and its classification is set from GNI per capita (Atlas method) for calendar year t-2.
So for a row dated 2020, basis = "in_effect" reads 1 January 2020, which
falls in fiscal year 2020 (1 July 2019 to 30 June 2020), whose classes were
computed from 2018 incomes. basis = "data_year" reads the class computed
from 2020 incomes instead, which is fiscal year 2022's (in force from 1 July
2021). The first answers "how was this country treated at the time"; the
second "where did this year's income put it". Regions and lending
categories have no data year and are always read as in force on the date.
country_classifications, in_group(), world_data()
pan <- data.frame(iso3c = c("VNM", "VNM", "PAK", "PAK"), year = c(2026, 2027, 2025, 2026)) classify_countries(pan, c("income", "region")) # The class computed from a year's income, two fiscal years on: classify_countries(data.frame(iso3c = "VNM", year = 2024), "income", basis = "data_year")pan <- data.frame(iso3c = c("VNM", "VNM", "PAK", "PAK"), year = c(2026, 2027, 2025, 2026)) classify_countries(pan, c("income", "region")) # The class computed from a year's income, two fiscal years on: classify_countries(data.frame(iso3c = "VNM", year = 2024), "income", basis = "data_year")
Empties the memoised in-session cache and, optionally, the on-disk one.
clear_country_cache(source = NULL, disk = FALSE)clear_country_cache(source = NULL, disk = FALSE)
source |
Which source's cache to clear, or |
disk |
Also delete that source's on-disk entries (default |
Invisibly TRUE.
Called with no source, this also drops the two cached geometry backends:
the Natural Earth sf layer held per scale (tens of megabytes at
scale = "medium") and the polygon backend's memoised vertex table (about
98,000 rows per override set). Those are the largest things the package
keeps in memory, and in a long-lived process – a Shiny app or a plumber
API – this is the only way to release them. They rebuild on the next map.
Naming a source leaves geometry alone, since it is not a data source.
The persistent cache goes in the standard per-user cache location,
tools::R_user_dir("countryatlas", "cache"). Point it elsewhere with
options(countryatlas.cache_dir = ), or skip it by passing cache = FALSE
to world_data() or country_data(). Under R CMD check the whole cache
moves to the session temp directory, so a check never writes to the user's
file space.
Entries expire: a fetch from a current release is dropped once it is 30
days old, because providers revise their figures, and past 50 MB per
source the least-recently-used entries go first. Adjust either with
options(countryatlas.cache_max_age = ) (seconds) and
options(countryatlas.cache_max_size = ) (bytes). A pinned World Bank
release (wdi_vintages()) never changes, so its entries never expire.
country_sources(), fetch_indicator()
clear_country_cache()clear_country_cache()
A friendly-name to WDI-code lookup so indicator = common_indicators$population
beats memorising "SP.POP.TOTL".
common_indicatorscommon_indicators
A tibble with columns name (friendly name), code (WDI indicator
code) and description.
World Bank indicator catalogue.
Fetch the same indicator from several providers and put the answers side by side. Sources disagree more often than people expect – different vintages, PPP bases, territorial definitions, revision schedules – and on the ISO spine the comparison is one join. This is the verb that turns "I used OWID" into "I used OWID, and here is where it differs from the World Bank".
compare_sources( indicator, sources = c("wdi", "owid"), year, countries = NULL, tolerance = 0.05, allow_unit_mismatch = FALSE )compare_sources( indicator, sources = c("wdi", "owid"), year, countries = NULL, tolerance = 0.05, allow_unit_mismatch = FALSE )
indicator |
Either one indicator code used for every source, or a named
character vector giving each source its own code:
|
sources |
Source names to compare (default |
year |
A single year. |
countries |
Optional |
tolerance |
Relative difference above which a country counts as a
disagreement (default |
allow_unit_mismatch |
Compare anyway when the sources state different
units (default |
A tibble with one row per country: the value from each source,
n_sources (how many reported it), rel_diff (max relative spread) and
disagrees. The correlation, coverage and disagreement summary is attached
as the "countryatlas_source_summary" attribute, with each source's
stated unit.
GDP per capita in constant 2015 US dollars and in current international
dollars differ for every country, and calling that a disagreement between
the sources would be wrong. Each fetch records the unit its provider states
(see source_info()); when two of them state different units the
comparison is refused with class countryatlas_unit_mismatch, naming them.
Pass allow_unit_mismatch = TRUE to compare anyway – a ratio that is
constant across countries can still be worth seeing – and read
rel_diff accordingly. A source that states no unit is not checked.
fetch_indicator(), country_sources()
## Not run: cmp <- compare_sources(c(wdi = "NY.GDP.PCAP.KD", owid = "gdp_per_capita"), sources = c("wdi", "owid"), year = 2020) attr(cmp, "countryatlas_source_summary") ## End(Not run)## Not run: cmp <- compare_sources(c(wdi = "NY.GDP.PCAP.KD", owid = "gdp_per_capita"), sources = c("wdi", "owid"), year = 2020) attr(cmp, "countryatlas_source_summary") ## End(Not run)
The same indicator, the same year, from several releases of the World Development Indicators side by side. Revisions between releases of one database are large enough to change cross-country results (Johnson, Larson, Papageorgiou & Subramanian 2013 show this for growth rates), and they are easy to miss because a figure looks the same whichever release it came from. This makes the check one call.
compare_vintages(indicator, vintages, year = NULL, countries = NULL)compare_vintages(indicator, vintages, year = NULL, countries = NULL)
indicator |
One WDI indicator code, optionally named. |
vintages |
Two or more releases, as in |
year |
The year to compare. |
countries |
Optional |
A tibble, one row per country and release: iso3c, country,
vintage, value, and the revision and rel_revision from the first
release listed. A summary per release – the number of countries in both,
the median absolute and absolute relative revision, and the shares of
countries revised by more than 1% and 10% – is attached as the
"countryatlas_revisions" attribute.
A series can be rebased between releases – GDP in constant 2010 US$ in one,
constant 2015 US$ in the next – and then every country is "revised" by the
change of base. The series label of each release is compared, and a change
of unit warns (class countryatlas_unit_mismatch).
Johnson, S., Larson, W., Papageorgiou, C. & Subramanian, A. (2013). Is newer better? Penn World Table revisions and their impact on growth estimates. Journal of Monetary Economics 60(2), 255-274. doi:10.1016/j.jmoneco.2012.10.022
wdi_vintages(), compare_sources()
compare_vintages("SP.POP.TOTL", c("2019-07", "current"), year = 2015)compare_vintages("SP.POP.TOTL", c("2019-07", "current"), year = 2015)
Completes a panel so every country has every year, optionally filling missing
values by carry-forward ("locf") or linear interpolation ("linear") so
animations do not flicker on missing years.
complete_years( data, years = NULL, value = NULL, method = c("none", "locf", "linear") )complete_years( data, years = NULL, value = NULL, method = c("none", "locf", "linear") )
data |
A panel with |
years |
The full set of years to complete to. Defaults to the observed min:max. |
value |
Optional value column(s) (character) to fill. If |
method |
|
A completed (and optionally filled) panel tibble – or an sf
frame, if data was one; each invented row carries its country's geometry.
df <- data.frame(iso3c = "USA", year = c(2000L, 2002L), gdp = c(1, 3)) complete_years(df, 2000:2002, method = "linear")df <- data.frame(iso3c = "USA", year = c(2000L, 2002L), gdp = c(1, 3)) complete_years(df, 2000:2002, method = "linear")
Countries do not all converge to one steady state; they converge in groups. This implements the Phillips-Sul (2007) log-t procedure: a regression test for whether a set of countries is converging, applied iteratively to peel off clubs that converge internally even when the whole sample does not.
convergence_club(data, value, min_size = 2, alpha = 0.05)convergence_club(data, value, min_size = 2, alpha = 0.05)
data |
A panel with |
value |
The value column (unquoted); usually income per head. |
min_size |
Smallest club to report (default |
alpha |
Significance level for the one-sided log-t test (default
|
A tibble: iso3c, club (an integer, 1 = highest-level club, NA =
not classified), and the club's log_t statistic. The per-club test results
are attached as the "countryatlas_clubs" attribute. Every country in
data appears: one without a complete series (a missing or non-finite
value in any year) cannot be tested, so it comes back with club = NA and
a warning naming it.
For each country form the relative transition path
, then regress
on over the last part of the
sample, where is the cross-sectional mean of
. The one-sided t statistic on is the log-t
statistic: above the group is converging. Clubs are then formed by
sorting countries on their final-period value and growing a core group while
the test still passes.
A panel needs a reasonable number of periods for this to mean anything – below roughly fifteen the test has very little power, and the function warns.
Phillips, P. C. B. & Sul, D. (2007). Transition modeling and econometric convergence tests. Econometrica 75(6), 1771-1855. doi:10.1111/j.1468-0262.2007.00811.x
beta_convergence(), sigma_convergence()
set.seed(1) # two groups converging to different levels panel <- expand.grid(iso3c = c(paste0("A", 1:5), paste0("B", 1:5)), year = 2000:2024) panel$y <- ifelse(startsWith(as.character(panel$iso3c), "A"), 100, 30) + rnorm(nrow(panel), 0, 2) convergence_club(panel, y)set.seed(1) # two groups converging to different levels panel <- expand.grid(iso3c = c(paste0("A", 1:5), paste0("B", 1:5)), year = 2000:2024) panel$y <- ifelse(startsWith(as.character(panel$iso3c), "A"), 100, 30) + rnorm(nrow(panel), 0, 2) convergence_club(panel, y)
A discoverable wrapper around countrycode::countrycode() exposing the full
set of schemes with first-class shortcuts for the high-value ones: flag
emoji, currency, top-level domain, continent/region and research codes
(Correlates of War, Polity, Gleditsch-Ward, V-Dem, IMF, FAO, FIPS, GAUL).
convert_country( x, to = "iso3c", origin = "country.name", custom_match = country_overrides(), warn = TRUE, from = deprecated() )convert_country( x, to = "iso3c", origin = "country.name", custom_match = country_overrides(), warn = TRUE, from = deprecated() )
x |
A vector of country names or codes. |
to |
Destination scheme. A shortcut ( |
origin |
Origin scheme (default |
custom_match |
Optional overrides (default |
warn |
Whether to warn about inputs that match no country (default
|
from |
A vector of converted codes.
convert_country(c("Japan", "Brazil"), to = "flag") convert_country("Germany", to = "currency") convert_country(c("USA", "France"), to = "continent") convert_country(c("Germany", "United States"), to = "name_fr")convert_country(c("Japan", "Brazil"), to = "flag") convert_country("Germany", to = "currency") convert_country(c("USA", "France"), to = "continent") convert_country(c("Germany", "United States"), to = "name_fr")
Which indicators move together across countries? Computes pairwise
correlations between indicator columns (pairwise-complete, so patchy
coverage doesn't shrink every pair to the common subset), with the per-pair
n reported so a headline r computed on 12 countries can't masquerade as
a world fact.
correlate_indicators( data, ..., method = c("pearson", "spearman"), min_n = 3, by_year = FALSE, weight = NULL )correlate_indicators( data, ..., method = c("pearson", "spearman"), min_n = 3, by_year = FALSE, weight = NULL )
data |
A country-level (or map-ready) data frame; map-ready frames are
reduced to one row per country first, so the reported |
... |
< |
method |
|
min_n |
Minimum number of complete pairs for a correlation to be
reported (default |
by_year |
If |
weight |
Optional column (unquoted), typically population, to weight each country by. Unweighted, every country counts once (Milanovic's "concept 1"); weighted by population, a correlation describes the average person rather than the average country ("concept 2"). A Spearman correlation is weighted on the ranks. |
A tibble with one row per indicator pair: var_x, var_y, r,
n (complete pairs), sorted by |r| descending; with by_year = TRUE,
the same per year, led by year.
correlate_indicators(countryatlas::world_snapshot$countries) # weighted by population: the correlation for the average person correlate_indicators(countryatlas::world_snapshot$countries, gdp_per_capita, life_expectancy, weight = population)correlate_indicators(countryatlas::world_snapshot$countries) # weighted by population: the correlation for the average person correlate_indicators(countryatlas::world_snapshot$countries, gdp_per_capita, life_expectancy, weight = population)
Which countries share a land border with which, as a tidy edge list –
built from polygon topology (sf::st_touches()), so it reflects the same
curated geometry as the rest of the package. Powers neighbors().
country_borders(scale = "small", region = NULL)country_borders(scale = "small", region = NULL)
scale |
Natural Earth resolution to compute adjacency from. This is not
a cosmetic choice – see Which countries the default leaves out below.
|
region |
Optional region subset (see |
A tibble, one row per bordering pair: iso3c_a, country_a,
iso3c_b, country_b. Each unordered pair appears once, with
iso3c_a <= iso3c_b alphabetically.
igraph::graph_from_data_frame() takes the first two columns as the edge
endpoints, so pass only the two code columns – handing it the whole tibble
would build edges from each country's code to its own name:
igraph::graph_from_data_frame(
country_borders()[, c("iso3c_a", "iso3c_b")], directed = FALSE)
Attaching igraph also masks neighbors(), which it exports too, so call
that one as countryatlas::neighbors() from then on.
Adjacency is computed from Natural Earth polygons, and the default
scale = "small" (110m) has no polygon at all for the European microstates.
Andorra, Liechtenstein, Monaco, San Marino and the Vatican are therefore
absent from the table entirely – not merely missing a short border, but
contributing no rows, despite a land border being the whole of their
geography. The default reports 310 pairs over 153 countries; France comes
back with 8 neighbours rather than 10.
scale = "medium" (50m) has all five, giving 322 pairs over 162 countries
and France its full list. Use it whenever the microstates matter:
country_borders(scale = "medium")
The same 110m gap is why morans_i()'s contiguity weights exclude them –
see its n_excluded – and it is the land-border twin of the island problem
described in vignette("honest-maps"). Note the two Guiana borders are real,
not artefacts: French Guiana makes Brazil and Suriname neighbours of France.
if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE)) { head(country_borders(region = "Europe")) # The whole world is only a little dearer: a fraction of a second. nrow(country_borders()) }if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE)) { head(country_borders(region = "Europe")) # The whole world is only a little dearer: a fraction of a second. nrow(country_borders()) }
The World Bank's income groups, regions and lending categories, each row
dated, so a country can be classified as it was on any day the table
covers. classify_countries() reads it; world_data() and
country_data() take their income and region from it, as of the
current fiscal year.
country_classificationscountry_classifications
A tibble with one row per classification spell: iso3c, scheme
("wb_income", "wb_region" or "wb_lending"), value, from and
to (the spell runs from from up to but not including to; NA means
open-ended, or, for from, in force since before the table's records
begin), source and note.
wb_incomeOne row per economy per World Bank fiscal year from
FY1989 to FY2027, from the historical classification by income
(OGHIST). Fiscal year t runs from 1 July t-1 to 30 June t and is
set from GNI per capita for calendar year t-2; see
classify_countries(). An economy the World Bank did not classify in a
year has no row for it.
wb_regionThe current regions, with the July 2025 change dated: Afghanistan and Pakistan are in South Asia until 1 July 2025 and in "Middle East, North Africa, Afghanistan & Pakistan" from then on, and the other members of that region carry its former name, "Middle East & North Africa", until the same date. No earlier region change is recorded.
wb_lendingThe FY2027 lending categories (IDA, IBRD, Blend). Earlier years are not recorded.
The World Bank's codes for two former entities are recoded to ISO
3166-3's (its YUG, Serbia and Montenegro, is SCG; its YUGf,
Yugoslavia, is YUG), and the Channel Islands, which have no ISO code,
are left out. The fiscal year the table reaches is its "vintage"
attribute.
World Bank, "World Bank Country and Lending Groups" (CLASS) and the
historical classification by income (OGHIST), both licensed CC BY 4.0;
the July 2025 region change from the World Bank DataBank Metadata Glossary.
Built by data-raw/country_classifications.R, which checks one class per
economy per fiscal year, agreement with OGHIST's thresholds sheet, and the
current classes against the World Bank API.
The whole countrycode::codelist reshaped into a tidy, pipeable lookup you
can filter() / join() directly – one row per country.
country_codes(codes = NULL)country_codes(codes = NULL)
codes |
Optional character vector of column names to keep (in addition
to |
A tibble, one row per country.
country_codes() country_codes(c("iso2c", "continent", "currency"))country_codes() country_codes(c("iso2c", "continent", "currency"))
The analysis counterpart to world_data(): no polygons, one tidy row per
country (iso3c, iso2c, country, classifications and the requested
indicators). This is what you actually join() / mutate() / summarise()
/ rank() on; attach geometry only at draw time with attach_geometry().
country_data( year, indicator = NULL, latest = FALSE, panel = FALSE, classify = c("income", "continent", "region"), cache = TRUE, language = "en", parallel = TRUE, vintage = NULL )country_data( year, indicator = NULL, latest = FALSE, panel = FALSE, classify = c("income", "continent", "region"), cache = TRUE, language = "en", parallel = TRUE, vintage = NULL )
year |
A single year or a range (with |
indicator |
A named character vector of WDI codes (or |
latest |
For a single year: |
panel |
Return a panel keyed on |
classify |
Which classifications to add. |
cache |
Whether to use the WDI cache. |
language |
WDI language code. |
parallel |
Whether to fetch indicators in parallel. Ignored when the
cache is memory-only; see |
vintage |
The release of the World Development Indicators to read;
see |
A tibble, one row per country (or per country-year for a panel).
iso3c is the stable key; country is a label and its spelling depends on
where the row came from. A successful fetch carries the World Bank's names
("Korea, Rep.", "Congo, Dem. Rep."), while the country spine used when the
fetch returns nothing carries the countrycode names ("South Korea",
"Congo - Kinshasa") – as do convert_country(), standardize_country()
and the rest of the package. Match on iso3c, and relabel with
convert_country(iso3c, to = "country") if you need one consistent set.
With latest = TRUE each indicator takes its own most recent value, so one
row can hold GDP from 2023 beside population from 2021. That is often what
is wanted – the freshest number for each – but dividing one by the other
mixes years, so every indicator carries an <indicator>_year column, and
per_capita(), deflate() and to_ppp() warn (class
countryatlas_mixed_years) when the two columns they combine come from
different years. latest = "common" instead takes, per country, the most
recent year in which every requested indicator is present, so the row is
internally consistent; a country with no such year gets NA throughout.
country_data(2020, c(co2 = "EN.GHG.CO2.MT.CE.AR5"))country_data(2020, c(co2 = "EN.GHG.CO2.MT.CE.AR5"))
A single-country summary drawn from the bundled reference data and, if you ask, live indicators: codes, geography, groups, neighbours and any dissolution history.
country_factsheet(x, indicators = NULL, origin = "country.name")country_factsheet(x, indicators = NULL, origin = "country.name")
x |
One country name or code. |
indicators |
Optional indicator codes to fetch (named, as in
|
origin |
How to read |
A countryatlas_factsheet object – a list of tibbles (identity,
geography, groups, neighbours, indicators) that prints as a
formatted block.
country_meta, country_groups(), neighbors(), world_table()
country_factsheet("Brazil")country_factsheet("Brazil")
Answers the constant question "is this country in the EU / OECD / G7 / G20 /
BRICS / ...?" from a curated membership table. By default that is the current
snapshot (country_groups_tbl); pass as_of to ask the question of a
particular date, which is what a panel needs.
country_groups(group = NULL, as_of = NULL)country_groups(group = NULL, as_of = NULL)
group |
One or more group names: any of |
as_of |
A date (or a year) at which to evaluate membership. |
A tibble of group, iso3c, country; with as_of, also from
and to.
A snapshot silently misstates any panel that spans an accession. An EU panel over 2015-2020 either includes the United Kingdom throughout or excludes it throughout, and both are wrong:
"GBR" %in% country_groups("EU", as_of = 2016)$iso3c # TRUE
"GBR" %in% country_groups("EU", as_of = 2021)$iso3c # FALSE
country_groups_history carries dated membership for nineteen groups: EU,
EuroZone, NATO, OECD, ASEAN, EFTA, GCC, Mercosur, Nordic, Visegrad, BRICS,
G7, SCO, CPTPP, RCEP, EAC, SADC, APEC and the Arab League, with
suspensions as spells of their own: Syria is not counted in the Arab League
from 2011 to 2023. Commonwealth, G20 and OPEC are not dated – their
histories involve suspensions, readmissions and contested dates the sources
give unevenly, and a fabricated date is worse than an absent one. Asking for
as_of on those warns and falls back to the snapshot.
in_group(), country_groups_history, country_timeline()
country_groups("EU") country_groups(c("G7", "BRICS")) # the UK was a member in 2016 and not in 2021 nrow(country_groups("EU", as_of = 2016)) nrow(country_groups("EU", as_of = 2021))country_groups("EU") country_groups(c("G7", "BRICS")) # the UK was a member in 2016 and not in 2021 nrow(country_groups("EU", as_of = 2016)) nrow(country_groups("EU", as_of = 2021))
When each country joined – and where applicable left, or was suspended from – each of nineteen international groups. The dated counterpart to country_groups_tbl, which is a single current snapshot.
country_groups_historycountry_groups_history
A tibble with 288 rows:
Group name.
ISO 3166-1 alpha-3 code.
Country name.
Date the spell took effect.
The first date on which it no longer held (it runs up to the
day before), or NA for one still in force. The United Kingdom's EU
to is therefore 2020-02-01: it left at the end of 31 January 2020.
"member" or "suspended". A country can have several
spells in a group: Seychelles left SADC in 2004 and rejoined in 2008,
and Syria was suspended from the Arab League from 2011 to 2023.
country_groups() and in_group() count the member spells only.
A snapshot silently misstates any panel that spans an accession: an EU panel
over 2015-2020 either includes the United Kingdom throughout or excludes it
throughout, and both are wrong. country_groups() and in_group() read this
table when given as_of.
Nineteen groups are dated: EU, EuroZone, NATO, OECD, ASEAN, EFTA, GCC,
Mercosur, Nordic, Visegrad, BRICS, G7, and since 4.0.0 SCO, CPTPP, RCEP,
EAC, SADC, APEC (member economies, so Hong Kong and Taiwan too) and the
Arab League ("ArabLeague"). Every date carries its source in
data-raw/country_groups_history.R. Commonwealth, G20 and OPEC are
not, and that is a decision rather than an omission: the Commonwealth's
and OPEC's suspensions, lapses and readmissions are dated unevenly by the
sources, and G20's members include the EU and the African Union, which are
not countries. A fabricated date is worse than an absent one, so
country_groups(as_of =) warns and falls back to the snapshot for those.
RCEP leaves out Myanmar, whose ratification the ASEAN Secretariat
questioned and whose date of entry into force is disputed.
Dates are the treaty, accession or decision date where one exists; where the sources give only the month, the 1st of that month (APEC's accessions). The table is validated at build time against country_groups_tbl: the members current today must reproduce the snapshot exactly, for every group covered.
country_groups(), in_group(), country_timeline()
# EFTA is the instructive one: most of its founders left, for the EU subset(country_groups_history, group == "EFTA")# EFTA is the instructive one: most of its founders left, for the EU subset(country_groups_history, group == "EFTA")
A curated, dated membership table for the common country groups.
country_groups_tblcountry_groups_tbl
A tibble with columns group, iso3c, country.
Membership is a snapshot taken on 2026-06-01, carried on the table itself:
attr(country_groups_tbl, "as_of") #> [1] "2026-06-01"
Read the attribute rather than this paragraph if you need the date in code;
it is set from a single constant in data-raw/build_datasets.R, so it
cannot drift from the data the way a hand-written date can. For membership
at any other date use country_groups_history and
country_groups(as_of = ), which is the table this
snapshot is a slice of.
Curated from official membership lists, as of the date above. (This
used to point at the package NEWS for the reference date, where two
different dates were on record – an Rd should not delegate a fact to a
changelog.)
The generic two-table version of the package's whole reason for being: join
any two data frames that each key on country names or codes, by reconciling
both sides to iso3c first. Tables keyed on "Czech Republic" vs
"Czechia", or "South Korea" vs "Korea, Rep.", just work.
country_join( x, y, by_x, by_y, origin_x = "country.name", origin_y = "country.name", type = c("left", "inner", "full"), suffix = c(".x", ".y"), key = c("iso3c", "cowc", "cown", "gwn"), warn = TRUE, also_by = NULL )country_join( x, y, by_x, by_y, origin_x = "country.name", origin_y = "country.name", type = c("left", "inner", "full"), suffix = c(".x", ".y"), key = c("iso3c", "cowc", "cown", "gwn"), warn = TRUE, also_by = NULL )
x, y
|
Data frames to join. |
by_x, by_y
|
The country columns in |
origin_x, origin_y
|
How to read each key (countrycode origin schemes). |
type |
Join type: |
suffix |
Suffix for clashing non-key columns (default
|
key |
Which code system to join on. |
warn |
Whether to report values that resolve to no country (default
|
also_by |
Key columns to join on besides the country: a character
vector of columns present in both tables, or a named one when the names
differ ( |
A tibble joined on a reconciled iso3c key.
one row per country-year:
Two country-year panels are joined country-year to country-year: when both
tables have a year column it becomes part of the key, with a message, so
France 2019 meets France 2019 rather than every year of France meeting every
other. A panel joined to a cross-section (one side has no year) repeats the
cross-section's values across the panel's years, and says that too. Pass
also_by to key on other columns, or also_by = character() to join on the
country alone.
Whatever the keys, a key that appears more than once on both sides pairs
every copy with every copy. dplyr warns about that many-to-many join only
when it is called from the console, never from inside a package, so this
verb checks for itself and warns with class countryatlas_many_to_many,
naming the keys.
the second spine:
ISO 3166 was first published in 1974 and never covered colonies, so iso3c
cannot key anything before about 1970. Correlates of War and Gleditsch-Ward
codes can, they run back to the nineteenth century, and
historical_geometry() is keyed on gwn. Setting key switches the join
onto one of those:
country_join(a, b, country, nation, key = "gwn")
The trade-off is real and worth stating: COW/GW codes cover states ISO never
did, but they omit the dependencies and non-sovereign territories ISO does
cover, so a modern dataset joined on gwn loses Hong Kong, Puerto Rico and
the rest – which the join warns about. Use iso3c unless you are working
before 1970.
a <- data.frame(country = c("Czechia", "South Korea"), gdp = c(1, 2)) b <- data.frame(nation = c("Czech Republic", "Korea, Rep."), pop = c(10, 51)) country_join(a, b, country, nation)a <- data.frame(country = c("Czechia", "South Korea"), gdp = c(1, 2)) b <- data.frame(nation = c("Czech Republic", "Korea, Rep."), pop = c(10, 51)) country_join(a, b, country, nation)
The many-table generalisation of country_join(): reduce-join a list of data
frames that each key on country names or codes, reconciling every one to
iso3c first.
country_join_all( tables, by, origin = "country.name", type = c("full", "left", "inner"), key = c("iso3c", "cowc", "cown", "gwn"), warn = TRUE, also_by = NULL )country_join_all( tables, by, origin = "country.name", type = c("full", "left", "inner"), key = c("iso3c", "cowc", "cown", "gwn"), warn = TRUE, also_by = NULL )
tables |
A list of data frames. |
by |
A single country-column name present in every table, or a character vector giving the column for each table. |
origin |
countrycode origin scheme(s) for the key column(s) (default
|
type |
Join type: |
key |
Which code system to join on, as in |
warn |
Whether to report values that resolve to no country (default
|
also_by |
Key columns to join on besides the country, present in every
table. |
A single tibble joined on key (clashing non-key columns get
dplyr's default .x/.y suffixes).
a <- data.frame(country = c("Czechia", "South Korea"), gdp = c(1, 2)) b <- data.frame(country = c("Czech Republic", "Korea, Rep."), pop = c(10, 51)) d <- data.frame(country = c("Czechia", "Korea"), area = c(79, 100)) country_join_all(list(a, b, d), by = "country")a <- data.frame(country = c("Czechia", "South Korea"), gdp = c(1, 2)) b <- data.frame(country = c("Czech Republic", "Korea, Rep."), pop = c(10, 51)) d <- data.frame(country = c("Czechia", "Korea"), area = c(79, 100)) country_join_all(list(a, b, d), by = "country")
One row per country with the facts people constantly need and currently scrape together by hand.
country_metacountry_meta
A tibble with one row per country and columns iso3c, iso2c,
country, continent, region, un_region, income, capital,
capital_lat, capital_lon, centroid_lat, centroid_lon, area_km2,
currency, tld, landlocked, flag.
Assembled from countrycode::codelist, plus a curated row for Kosovo
(XKX), which countrycode does not carry, so distance_between() and
the k-nearest-neighbour weights can use it. Centroids and areas come
from the bundled Natural Earth 1:50m polygons, the ones the polygon
backend draws, with each country's holes taken out; territories drawn as
part of their country (French Guiana, Svalbard) take theirs from Natural
Earth's map units. Three territories have a row but no centroid or area,
because Natural Earth does not draw them at 1:50m: Bouvet Island,
Gibraltar and the U.S. Minor Outlying Islands.
income and region are the World Bank's for the current fiscal year
(the "classification" attribute names it, "FY2027"), from
country_classifications, so they agree with what world_data()
returns; region falls back to countrycode's where the World Bank
lists no region.
country therefore carries the English names from countrycode
("South Korea", "Congo - Kinshasa"), which differ from the World Bank's for
39 of the 216 countries in world_snapshot ("Korea, Rep.",
"Congo, Dem. Rep."). Each
table is faithful to its own source, so join on iso3c and keep whichever
label you want to display – reconciling the two is what country_join()
is for.
Assembled from countrycode, WDI metadata (capitals), the World Bank's classifications and Natural Earth geometry.
Node- and edge-level summaries of a bilateral flow table: who sends, who
receives, who is central. No igraph required – at country scale the dense
arithmetic is trivial.
country_network( data, from, to, weight = NULL, origin = "country.name", top_n = 20 )country_network( data, from, to, weight = NULL, origin = "country.name", top_n = 20 )
data |
An OD table. |
from, to
|
The origin and destination country columns (unquoted). |
weight |
The flow column (unquoted). If omitted, every pair counts as 1. |
origin |
How to read |
top_n |
How many edges to return in the |
A list of two tibbles:
nodes – iso3c, country, out_flow, in_flow, net_flow,
out_degree, in_degree and strength_share (the country's share of
all flow, in or out).
edges – from, to, weight, share and reciprocity (the
opposite flow as a proportion of this one; NA where there is none).
flow_matrix(), od_map(), flow_map()
od <- data.frame( from = c("China", "China", "Germany", "USA", "Mexico"), to = c("USA", "Germany", "France", "Mexico", "USA"), value = c(500, 100, 80, 300, 320) ) country_network(od, from, to, value)od <- data.frame( from = c("China", "China", "Germany", "USA", "Mexico"), to = c("USA", "Germany", "France", "Mexico", "USA"), value = c(500, 100, 80, 300, 320) ) country_network(od, from, to, value)
A documented custom_match table for entities that geometry sources
(Natural Earth, and the maps package's 2013 copy of it) and data
providers spell in ways name matching gets wrong or leaves without an ISO
code. Earlier versions of the package deleted these regions; now they are
matched instead, so they stop silently disappearing from maps.
country_overrides(extra = NULL)country_overrides(extra = NULL)
extra |
An optional named character vector of additional overrides
(names are country/region names, values are |
The table maps a country/region name (as spelled by the geometry backends) to
an ISO 3166-1 alpha-3 code. Pass the result as the custom_match argument to
standardize_country(), world_data() and friends. Every downstream code
(iso2c, continent, region, flag, ...) is derived from this iso3c, so a
single override is enough.
A named character vector suitable for countrycode(custom_match=).
Every name in this table is plain ASCII, and that is deliberate: ASCII
spellings match in any locale. Accented spellings ("Curacao" with a
cedilla, "Saint Barthelemy" with an acute) are matched natively by
countrycode::countrycode() in a UTF-8 locale, which is why they are not
listed here – but in a non-UTF-8 locale (LC_CTYPE=C) they cannot be
compared reliably and resolve to NA.
Accented spellings also come in two Unicode forms that look identical: the
accent can be one precomposed code point (NFC) or a base letter followed by
a combining mark (NFD, which macOS returns for filenames). Only NFC matches
countrycode::countrycode()'s tables, so a name that resolves to nothing is
retried with its combining marks stripped, which turns an NFD spelling into the
ASCII spelling that resolves anywhere. Only unresolved names are retried, so
this never changes a name that already matched.
If your input may contain accented country names, run in a UTF-8 locale.
De-accenting with iconv(x, to = "ASCII//TRANSLIT") gives ASCII spellings
that resolve everywhere, but it is not an escape from the locale problem:
//TRANSLIT is itself locale-dependent, so under LC_CTYPE=C it returns
NA (or, given an explicit from = "UTF-8", replaces each accent with ?)
and nothing resolves. De-accent while still in a UTF-8 locale, or supply the
ASCII spellings directly.
country_overrides() country_overrides(c(Somaliland = "SOM"))country_overrides() country_overrides(c(Somaliland = "SOM"))
What fetch_indicator() can reach, whether each one's backing package is
actually installed, and how to cite it.
country_sources()country_sources()
A tibble: source, meta, key_col, key_type, cache,
available (is the
backing package installed?) and citation.
register_country_source(), fetch_indicator()
country_sources()country_sources()
When did this country exist, and what came before and after it? Reads the bundled historical_codes crosswalk in both directions – so it answers both "what did the USSR become" and "what was Estonia part of".
country_timeline(x, origin = "country.name", warn = TRUE)country_timeline(x, origin = "country.name", warn = TRUE)
x |
Country names or codes, current or historical ( |
origin |
How to read |
warn |
Whether to report inputs that match neither a historical entity
nor a modern country (default |
A tibble, one row per input: input, iso3c, country,
dissolved (the year it ceased to exist, or NA if it still does),
predecessors and successors (list-columns of iso3c codes). An input
that resolves to neither spine keeps its input and gets NA elsewhere;
with warn = TRUE it is reported rather than left to be spotted.
Both columns hold codes, so an entity ISO never coded cannot appear in
either. ISO 3166-3 only records changes from 1974 on, and three entities in
historical_codes predate it: the United Arab Republic (1961), Tanganyika
and Zanzibar (both 1964). Asking about one of those by name works –
country_timeline("Tanganyika") gives TZA as a successor, because the
table stores that side as a name – but the reverse does not:
country_timeline("TZA") reports no predecessors, since there is no code to
report. The other eleven entities carry codes and round-trip in both
directions. Naming them instead of coding them would break the column's
type; inventing codes for them would be a guess, which this package does
not make.
dissolve_country(), historical_codes, audit_time_coverage()
country_timeline(c("USSR", "Estonia", "France"))country_timeline(c("USSR", "Estonia", "France"))
Build a reusable neighbour-weights object for morans_i(), local_morans(),
gearys_c(), getis_ord() and spatial_lag(). Four schemes, three of which
give every country at least one neighbour – which land-border contiguity,
the default before 4.0.0, cannot do for an island.
country_weights( type = c("knn", "contiguity", "distance", "custom"), countries = NULL, k = 5, cutoff_km = NULL, w = NULL, style = c("W", "B"), scale = "small" )country_weights( type = c("knn", "contiguity", "distance", "custom"), countries = NULL, k = 5, cutoff_km = NULL, w = NULL, style = c("W", "B"), scale = "small" )
type |
|
countries |
Optional |
k |
Neighbours per country for |
cutoff_km |
Distance band for |
w |
For |
style |
|
scale |
Natural Earth resolution for |
A countryatlas_weights object: the weights matrix plus the scheme
that built it. Inspect it by printing; as.matrix() gives the matrix.
Contiguity encodes "shares a border", which is the right relation for
spillovers that cross borders by land. It is the wrong relation for a global
question, because it silently deletes the islands. "knn" is the safe
default for world-scale work: every country participates, and k controls how
local the neighbourhood is. "distance" is right when the process has a real
length scale. "custom" is right when geography is not the relevant space at
all.
morans_i(), local_morans(), lisa_map(), spatial_lag()
# k-nearest neighbours: no sf needed, and islands are included w <- country_weights("knn", k = 4) w snap <- countryatlas::world_snapshot$countries morans_i(snap, gdp_per_capita, weights = country_weights("knn", k = 5), n_perm = 99)# k-nearest neighbours: no sf needed, and islands are included w <- country_weights("knn", k = 4) w snap <- countryatlas::world_snapshot$countries morans_i(snap, gdp_per_capita, weights = country_weights("knn", k = 5), n_perm = 99)
A choropleth of whether a value is present, rather than what it is. The
companion to audit_coverage(), which reports the same thing as a table: a
world map with a large well-covered region and a systematically empty one is
telling you something about the indicator that the headline map hides behind
a uniform grey.
coverage_map(data, value, by = NULL, title = NULL, ...)coverage_map(data, value, by = NULL, title = NULL, ...)
data |
A map-ready frame (polygon or |
value |
The column whose availability to map (unquoted). |
by |
Optional grouping column for a panel: with a |
title |
Optional plot title (defaults to a generated one). |
... |
Passed to |
A ggplot object.
Either backend, as world_map(): the frame decides, and both draw in Equal
Earth unless projection is passed on through ....
snap <- countryatlas::world_snapshot$countries attach_geometry(snap, geometry = "polygon") |> coverage_map(gdp_per_capita)snap <- countryatlas::world_snapshot$countries attach_geometry(snap, geometry = "polygon") |> coverage_map(gdp_per_capita)
A nominal series is not comparable across years. deflate() divides by a
price index rebased to base_year, turning current-price values into constant
base_year prices.
deflate(data, value, base_year, deflator = NULL, suffix = "_real")deflate(data, value, base_year, deflator = NULL, suffix = "_real")
data |
A panel with |
value |
The nominal value column (unquoted). |
base_year |
The year whose prices to express everything in. |
deflator |
Either a column in |
suffix |
Suffix for the new column (default |
data with the constant-price column added.
The GDP deflator is the right default for aggregate output. For household
spending the CPI (FP.CPI.TOTL) is usually preferred, and for cross-country
level comparisons a deflator is not enough at all – you want
to_ppp() as well, because exchange rates do not equalise purchasing power.
Deflating and converting to PPP are different corrections for different
problems, and a cross-country panel over time generally needs both.
to_ppp(), per_capita(), index_to()
d <- data.frame(iso3c = "USA", year = 2000:2002, gdp = c(100, 110, 120), defl = c(90, 100, 105)) deflate(d, gdp, base_year = 2001, deflator = defl)d <- data.frame(iso3c = "USA", year = 2000:2002, gdp = c(100, 110, 120), defl = c(90, 100, 105)) deflate(d, gdp, base_year = 2001, deflator = defl)
Disputed territories are drawn differently by different conventions, and a
map that does not say which one it used is making a choice silently. This
sets the session's convention so world_map() can record it and
map_provenance() can report it.
dispute_policy(policy = NULL, worldview = NULL)dispute_policy(policy = NULL, worldview = NULL)
policy |
One of:
Called with no argument, returns the current policy. |
worldview |
Optional point of view to draw: the viewing country's ISO
alpha-3 code ( |
When called with no argument, the policy currently in effect. When
setting, the policy that was in effect before the call, invisibly – R's
convention for a setter, so
on.exit(dispute_policy(dispute_policy("neutral"))) restores it.
On its own it records a choice and makes it visible. It does not redraw any
boundary, and selecting "de_jure" gives no claimed-boundary geometry, so
it warns. With a worldview it does change the shapes: the sf backend
draws that country's published view. The package ships no worldview of its
own beyond Natural Earth's default, and anyone publishing under an
institutional convention should still verify the shapes against that
institution's own basemap rather than trusting a setting.
disputed_territories, check_dispute_coverage(), world_map()
old <- dispute_policy("neutral") # sets, and returns what it replaced dispute_policy() # "neutral" dispute_policy(old) # put it backold <- dispute_policy("neutral") # sets, and returns what it replaced dispute_policy() # "neutral" dispute_policy(old) # put it back
Territories whose status is contested, recorded so that a map can say so.
disputed_territoriesdisputed_territories
A tibble with 22 rows:
Common name of the territory.
ISO 3166-1 alpha-3 code where one exists, else NA. Most
disputed territories have none, which is why they cannot appear in an
iso3c-keyed dataset at all.
The party in de facto control, or NA where none
is. An ISO 3166-1 alpha-3 code where that party has one – see the note
on codes below.
Semicolon-separated claimants, coded as for
administered_by.
One of "un_member", "un_observer",
"partially_recognised", "administered" or "claimed".
One sentence of context, including why the row is here.
This table records that a dispute exists and who the parties are. It does not adjudicate, rank claims, or imply that any claim is better founded than another. Where it says "administered by" it means de facto control as reported by the mapping sources the package already uses (Natural Earth), not recognition, legitimacy or endorsement.
Nothing in the package does. The internal layer and note builders behind
world_map(disputes = ) and dispute_policy() key on iso3c alone, and no
verb filters on the parties. administered_by and
claimed_by are here for your own filtering – "show me everything Morocco
administers", "drop the territories with more than one claimant" – and are
documented precisely so that such a filter is writable. Their contents are
validated at build time against the placeholder list below.
administered_by and claimed_by
Mostly ISO 3166-1 alpha-3, so they join the iso3c spine directly – but not
entirely, and the exceptions are the point of the table. Six parties here are
entities ISO assigns no code to, and they are written with a mnemonic
placeholder instead: ABK (Abkhazia), CYP-N (Northern Cyprus), OST
(South Ossetia), PMR (Transnistria), SAH (the Sahrawi Arab Democratic
Republic) and SOL (Somaliland).
Five of them – ABK, CYP-N, OST, PMR and SOL – appear in
administered_by for the like-named territory, which has no ISO code of its
own: the entity administers itself and ISO codes neither. SAH is the
exception and worth knowing about: it appears only as a claimant, of
Western Sahara, which ISO does code (ESH) and which MAR administers.
None of the six are ISO codes, so none will resolve through
convert_country() or any other iso3c lookup. Filter them out with
%in% country_codes()$iso3c if you need a strictly ISO-keyed column.
A documented subset, not the roughly 188 disputed areas the EU's data-visualisation guidance counts. The selection criterion is mechanical and checkable: territories that appear as a distinct unit or a contested boundary in Natural Earth at 1:110m or 1:50m, and have an ISO 3166-1 code, a widely-used user-assigned code, or a standard "disputed" label in ISO, UN M49 or World Bank practice. That criterion requires nobody to judge the merits.
dispute_policy(), check_dispute_coverage(), world_map()
disputed_territories[, c("territory", "iso3c", "status")]disputed_territories[, c("territory", "iso3c", "status")]
Historical data is full of countries that no longer exist – the USSR,
Yugoslavia, Czechoslovakia – and they poison naive joins twice over: most
are silently unmatched, and some are silently mismatched
(countrycode resolves "USSR" to Russia alone, so Soviet-era totals
quietly become Russian totals). dissolve_country() resolves a mixed
vector of historical and modern names against the curated
historical_codes crosswalk: dissolved entities expand to one row per
successor state (one-to-many, dated), while modern names pass through as
single rows – so a whole messy column can be piped in unchanged.
dissolve_country(x, warn = TRUE)dissolve_country(x, warn = TRUE)
x |
A character vector of country names (historical and/or modern). |
warn |
Whether to warn about names that match neither a historical
entity nor a modern country (default |
A tibble with one row per (input, successor) pair: input (as
given), historical (canonical dissolved-entity name, NA for modern
countries), dissolved (year the entity ceased to exist, NA for
modern), iso3c and country (the successor state). Unmatched inputs
yield one row with iso3c = NA.
historical_codes for the crosswalk itself and the successor
policy (e.g. Kosovo's inclusion in the Yugoslavia list);
check_country_match(), whose historical column flags these entities;
repair_country_names(), which deliberately leaves them alone.
dissolve_country(c("USSR", "Czechoslovakia", "France")) # One-to-many: Yugoslavia expands to its successor territories dissolve_country("Yugoslavia")dissolve_country(c("USSR", "Czechoslovakia", "France")) # One-to-many: Yugoslavia expands to its successor territories dissolve_country("Yugoslavia")
Haversine distance (km) between two countries' centroids – the lightweight
companion to country_borders() for "how far apart" rather than "do they
touch". Works from the bundled country_meta centroids, so unlike most of
the spatial toolkit it needs neither sf nor the network.
distance_between(a, b, origin = "country.name")distance_between(a, b, origin = "country.name")
a, b
|
Vectors of country names or codes. Either the same length, or one of them length 1 to compare one country against many. |
origin |
How to read |
A numeric vector of great-circle distances in kilometres (NA for
any country that doesn't resolve to a known centroid).
country_meta carries no centroid for three territories Natural Earth does
not draw at 1:50m (Bouvet Island, Gibraltar and the U.S. Minor Outlying
Islands); those inputs return NA here. Kosovo, which
countrycode::codelist does not carry, has a curated row with a centroid.
distance_between("France", "Germany") distance_between("USA", c("Canada", "Mexico"))distance_between("France", "Germany") distance_between("USA", c("Canada", "Mexico"))
Non-overlapping proportional circles sized by weight, positioned to stay
as close as possible to each country's true location – arguably the most
legible cartogram variant, since a microstate's circle is exactly as
visible as a giant country's. A first-class verb for
cartogram_map()(type = "dorling") that surfaces the Dorling-specific
tuning knobs.
dorling_map( data, weight, fill = NULL, k = 5, itermax = 1000, projection = "equal_earth", footnote = "auto" )dorling_map( data, weight, fill = NULL, k = 5, itermax = 1000, projection = "equal_earth", footnote = "auto" )
data |
An |
weight |
The column controlling circle size (unquoted). |
fill |
Optional fill column (unquoted); defaults to |
k |
Share of the bounding box filled by the largest circle (default
|
itermax |
Maximum iterations of the circle-repulsion algorithm
(default |
projection |
Projection; an equal-area CRS is recommended. See
|
footnote |
The caption, as in |
A ggplot object.
The sf backend only: the cartogram package places its circles from sf
geometry, in projection (Equal Earth by default).
if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE) && requireNamespace("cartogram", quietly = TRUE)) { attach_geometry(countryatlas::world_snapshot$countries, geometry = "sf") |> dorling_map(population) }if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE) && requireNamespace("cartogram", quietly = TRUE)) { attach_geometry(countryatlas::world_snapshot$countries, geometry = "sf") |> dorling_map(population) }
Moran's I on raw rates mistakes the noise of small denominators for clustering: a few small states with extreme rates beside one another look like a hot spot. The empirical-Bayes index (Assuncao & Reis 1999) standardises each rate by how much variation its denominator alone would produce before testing for autocorrelation.
eb_morans_i(data, numerator, denominator, weights = NULL, n_perm = 999)eb_morans_i(data, numerator, denominator, weights = NULL, n_perm = 999)
data |
A country-level frame with |
numerator, denominator
|
The counts and their population at risk (unquoted). |
weights |
A |
n_perm |
Permutations for the pseudo p-value (default |
A one-row tibble like morans_i()'s: i, expected, n,
n_excluded, n_links, p_value (one-sided, for positive
autocorrelation) and the list-column excluded.
With rates , the global rate
and the method-of-moments between-country variance (as in
smooth_rates()), each rate is standardised as
and Moran's I is computed on
. It agrees with spdep::EBImoran.mc() on the same weights.
Assuncao, R. M. & Reis, E. A. (1999). A new proposal to adjust Moran's I for population density. Statistics in Medicine 18(16), 2147-2162. doi:10.1002/(SICI)1097-0258(19990830)18:16<2147::AID-SIM179>3.0.CO;2-I
morans_i(), smooth_rates(), rate_funnel()
snap <- countryatlas::world_snapshot$countries snap$births <- snap$population * 0.02 eb_morans_i(snap, births, population, n_perm = 0)snap <- countryatlas::world_snapshot$countries snap$births <- snap$population * 0.02 eb_morans_i(snap, births, population, n_perm = 0)
Facet a choropleth into small multiples (one panel per group or per year) –
the static counterpart to animate_world(), for print and side-by-side
comparison. Builds a world_map() and facets it on facet.
facet_map( data, fill, facet, ncol = NULL, breaks_by = c("pooled", "panel"), ... )facet_map( data, fill, facet, ncol = NULL, breaks_by = c("pooled", "panel"), ... )
data |
A map-ready frame (polygon or sf) containing the |
fill |
The fill column (unquoted). |
facet |
The faceting column (unquoted; e.g. |
ncol |
Number of facet columns (passed to |
breaks_by |
|
... |
Passed to |
A faceted ggplot object.
Either backend, as world_map(): the frame decides, and both draw in
projection (Equal Earth by default).
snap <- countryatlas::world_snapshot$countries mapdf <- attach_geometry(snap, geometry = "polygon") facet_map(mapdf, gdp_per_capita, continent, style = "quantile")snap <- countryatlas::world_snapshot$countries mapdf <- attach_geometry(snap, geometry = "polygon") facet_map(mapdf, gdp_per_capita, continent, style = "quantile")
One verb, many providers. Whatever the source, the result comes back on the
ISO spine: iso3c, year where the data is a panel, and one column per
indicator.
fetch_indicator( source, indicator, countries = NULL, years = NULL, vintage = NULL, ... )fetch_indicator( source, indicator, countries = NULL, years = NULL, vintage = NULL, ... )
source |
A registered source name (see |
indicator |
Indicator code(s). Name the vector to rename the columns,
as in |
countries |
Optional |
years |
Optional numeric year vector; |
vintage |
For |
... |
Passed to the source's own |
A tibble keyed on iso3c (and year, for a panel), with a record
of where each indicator column came from; see source_info().
add_indicator(), compare_sources(), country_sources()
## Not run: fetch_indicator("wdi", c(gdp = "NY.GDP.PCAP.KD"), years = 2020) fetch_indicator("owid", "life_expectancy", years = 2020) ## End(Not run)## Not run: fetch_indicator("wdi", c(gdp = "NY.GDP.PCAP.KD"), years = 2020) fetch_indicator("owid", "life_expectancy", years = 2020) ## End(Not run)
One reader for the statistical agencies that publish through SDMX, the
standard for statistical data exchange: the OECD, the IMF, the ILO,
Eurostat, the European Central Bank, the BIS and UNICEF, or any other SDMX
REST service by URL. It asks for SDMX-CSV, which needs no client package,
and returns the result on the ISO spine. fetch_oecd() is this with
provider = "oecd", and the "imf" and "ilo" sources of
fetch_indicator() go through it too.
fetch_sdmx(provider, flow, key = NULL, countries = NULL, years = NULL, ...)fetch_sdmx(provider, flow, key = NULL, countries = NULL, years = NULL, ...)
provider |
One of |
flow |
The dataflow, as SDMX writes one: |
key |
The SDMX key: one code (or several joined by |
countries |
Optional |
years |
Optional numeric year vector, sent as |
... |
Unused; named so a call written for another adapter says what it passed. |
A tibble of iso3c, year and the value column, with a
source_info() record (the unit and its power of ten, from
UNIT_MEASURE and UNIT_MULT where the provider sends them). The
provider's aggregates ("OECD", "EA20") are not countries and are
dropped.
The package keys on country and year, so every other dimension has to be
pinned. When a dimension still varies – several measures, both sexes, or
quarterly periods – a country-year has several rows, and keeping the
first would be a silent choice; the reader aborts instead and names the
dimensions that vary, so the key can be narrowed. A failed request is a
warning and no rows, unless options(countryatlas.strict = TRUE).
fetch_oecd(), fetch_indicator(), register_country_source()
# Real GDP growth for three countries, filtered by the OECD's server: fetch_sdmx("oecd", c(gdp_growth = "OECD.SDD.NAD,DSD_NAAG@DF_NAAG_I,1.0"), key = "A..B1GQ_R_GR..", countries = c("FRA", "DEU", "JPN"), years = 2020:2023)# Real GDP growth for three countries, filtered by the OECD's server: fetch_sdmx("oecd", c(gdp_growth = "OECD.SDD.NAD,DSD_NAAG@DF_NAAG_I,1.0"), key = "A..B1GQ_R_GR..", countries = c("FRA", "DEU", "JPN"), years = 2020:2023)
Draws great-circle arcs between country pairs from an origin-destination table (trade, migration, flights, remittances), resolving both endpoints to centroids automatically.
flow_map( data, from, to, weight = NULL, origin = "country.name", arc_points = 50, projection = "equal_earth", footnote = "auto", n = deprecated() )flow_map( data, from, to, weight = NULL, origin = "country.name", arc_points = 50, projection = "equal_earth", footnote = "auto", n = deprecated() )
data |
An OD table. |
from, to
|
The origin and destination country columns (unquoted; names
or |
weight |
Optional column controlling arc width/alpha (unquoted). |
origin |
How to read |
arc_points |
Points per arc (default |
projection |
Map projection, as in |
footnote |
The caption: |
n |
A ggplot object.
The polygon backend draws it, in projection (Equal Earth by default), so it
needs no sf.
od <- data.frame(from = c("China", "Germany"), to = c("United States", "France"), value = c(500, 200)) flow_map(od, from, to, value)od <- data.frame(from = c("China", "Germany"), to = c("United States", "France"), value = c(500, 200)) flow_map(od, from, to, value)
Reshape a long bilateral table (trade, migration, flights, remittances) into
a square origin x destination matrix on the ISO spine, with both country
columns standardised. The natural input to a network analysis, and to
country_weights()(type = "custom") – which is how "countries near each
other in trade space" becomes a spatial weights object.
flow_matrix( data, from, to, weight = NULL, origin = "country.name", symmetric = FALSE, fill = 0 )flow_matrix( data, from, to, weight = NULL, origin = "country.name", symmetric = FALSE, fill = 0 )
data |
An OD table. |
from, to
|
The origin and destination country columns (unquoted). |
weight |
The flow column (unquoted). If omitted, every pair counts as 1. |
origin |
How to read |
symmetric |
If |
fill |
Value for pairs with no flow (default |
A square numeric matrix with iso3c row and column names. Rows are
origins, columns destinations.
country_network(), flow_map(), country_weights()
od <- data.frame( from = c("China", "China", "Germany", "USA"), to = c("USA", "Germany", "France", "Mexico"), value = c(500, 100, 80, 300) ) flow_matrix(od, from, to, value)od <- data.frame( from = c("China", "China", "Germany", "USA"), to = c("USA", "Germany", "France", "Mexico"), value = c(500, 100, 80, 300) ) flow_matrix(od, from, to, value)
The other classical global autocorrelation statistic. Where Moran's I is a
correlation-like measure centred on , Geary's C is a
distance-like one centred on 1: below 1 means positive autocorrelation
(neighbours are similar), above 1 means negative. It is more sensitive than
Moran's I to local differences.
gearys_c(data, value, weights = NULL, n_perm = 999)gearys_c(data, value, weights = NULL, n_perm = 999)
data |
A country-level frame with |
value |
The value column (unquoted). |
weights |
A |
n_perm |
Permutations for the pseudo-p-value (default |
A one-row tibble: c (observed), expected (always 1), n
(countries used), n_excluded (countries with data that the weights could
not reach), n_links (non-zero weights), p_value and an excluded
list-column of the excluded iso3c codes. n and n_excluded sum to the
countries supplied with a value, and mean the same here as in
morans_i().
p_value is one-sided on the lower tail:
. The lower tail is
the clustered one, which is the opposite way round from Moran's I:
Geary's C runs from 0 (neighbours identical) through 1 (no
autocorrelation) upwards, so a small c is the evidence of positive
spatial association. Never exactly zero; the floor is
. Set a seed beforehand for a reproducible p_value.
Geary, R. C. (1954). The contiguity ratio and statistical mapping. The Incorporated Statistician 5(3), 115-146. doi:10.2307/2986645
snap <- countryatlas::world_snapshot$countries gearys_c(snap, gdp_per_capita, weights = country_weights("knn", k = 5), n_perm = 99)snap <- countryatlas::world_snapshot$countries gearys_c(snap, gdp_per_capita, weights = country_weights("knn", k = 5), n_perm = 99)
A ggplot2 layer that places labels (names, ISO codes or flag emoji) at
country centroids, with optional ggrepel collision avoidance. Designed for
the polygon backend produced by world_data() / join_world(): it reads the
long, lat and group columns, so it errors on an sf frame and points at
ggplot2::geom_sf_text() instead. Placement is exact only while group is
present – that is what identifies each country's separate pieces, and the
label goes on the largest one.
geom_country_labels( mapping = NULL, data = NULL, repel = TRUE, flag = FALSE, size = 3, ... )geom_country_labels( mapping = NULL, data = NULL, repel = TRUE, flag = FALSE, size = 3, ... )
mapping |
Aesthetic mapping; defaults to |
data |
Optional layer data, as for any |
repel |
Use |
flag |
If |
size |
Label text size. |
... |
Passed to the underlying text geom. |
A ggplot2 layer.
library(ggplot2) snap <- countryatlas::world_snapshot$countries mapdf <- attach_geometry(snap, geometry = "polygon") # Labelling all 188 countries at once is unreadable, and ggrepel responds # by dropping nearly every label. Pass `data` to choose a subset ... world_map(mapdf, gdp_per_capita) + geom_country_labels( data = ~ dplyr::filter(.x, iso3c %in% c("USA", "BRA", "CHN", "IND", "ZAF")) ) # ... or zoom in, where there is room for every label. europe <- attach_geometry( dplyr::filter(snap, continent == "Europe"), geometry = "polygon") world_map(europe, gdp_per_capita) + geom_country_labels(size = 2.5) + coord_quickmap(xlim = c(-25, 45), ylim = c(34, 72))library(ggplot2) snap <- countryatlas::world_snapshot$countries mapdf <- attach_geometry(snap, geometry = "polygon") # Labelling all 188 countries at once is unreadable, and ggrepel responds # by dropping nearly every label. Pass `data` to choose a subset ... world_map(mapdf, gdp_per_capita) + geom_country_labels( data = ~ dplyr::filter(.x, iso3c %in% c("USA", "BRA", "CHN", "IND", "ZAF")) ) # ... or zoom in, where there is room for every label. europe <- attach_geometry( dplyr::filter(snap, continent == "Europe"), geometry = "polygon") world_map(europe, gdp_per_capita) + geom_country_labels(size = 2.5) + coord_quickmap(xlim = c(-25, 45), ylim = c(34, 72))
Global and local : unlike Moran's I, these distinguish
clusters of high values from clusters of low ones, which is what
"hot spot" analysis usually wants.
getis_ord( data, value, weights = NULL, local = TRUE, p_adjust = c("fdr", "bonferroni", "holm", "none") )getis_ord( data, value, weights = NULL, local = TRUE, p_adjust = c("fdr", "bonferroni", "holm", "none") )
data |
A country-level frame with |
value |
The value column (unquoted). |
weights |
A |
local |
If The global |
p_adjust |
For |
With local = TRUE, a tibble of iso3c, gi_star, z_score,
p_value (two-sided, from the normal approximation) and p_adjusted,
one row per country used. With local = FALSE, a one-row tibble of g, expected, n
(countries used – the same count, so the local form returns n rows) and
n_links (non-zero weights).
Getis, A. & Ord, J. K. (1992). The analysis of spatial association by use of distance statistics. Geographical Analysis 24(3), 189-206. doi:10.1111/j.1538-4632.1992.tb00261.x
local_morans(), country_weights()
snap <- countryatlas::world_snapshot$countries getis_ord(snap, gdp_per_capita, weights = country_weights("knn", k = 5))snap <- countryatlas::world_snapshot$countries getis_ord(snap, gdp_per_capita, weights = country_weights("knn", k = 5))
The Gini index of inequality across countries, optionally weighted (weight by population and the statistic describes inequality between people assigned their country's mean, not between country units).
gini(x, weights = NULL, na.rm = TRUE)gini(x, weights = NULL, na.rm = TRUE)
x |
A numeric vector (e.g. GDP per capita by country). |
weights |
Optional non-negative weights (e.g. population), either the
same length as |
na.rm |
Whether to drop |
A single number in [0, 1]: 0 is perfect equality.
theil(), which adds a between/within-group decomposition.
snap <- countryatlas::world_snapshot$countries gini(snap$gdp_per_capita) # between countries gini(snap$gdp_per_capita, weights = snap$population) # between peoplesnap <- countryatlas::world_snapshot$countries gini(snap$gdp_per_capita) # between countries gini(snap$gdp_per_capita, weights = snap$population) # between people
The world as a globe (orthographic projection) centred on lon/lat – the
honest answer to "the whole world on a rectangle exaggerates the poles". Takes
the same fill / style options as world_map(). The default "sf" backend
gives the cleanest limb; the "polygon" backend draws the globe with
ggplot2::coord_map() and needs only mapproj (no sf).
globe_map( data, fill, lon = 0, lat = 20, backend = c("sf", "polygon"), style = c("quantile", "continuous", "binned", "equal", "jenks", "fisher", "headtails", "sd", "fixed", "categorical"), palette = NULL, n_bins = 5, breaks = NULL, midpoint = NULL, borders = TRUE, title = NULL, legend = NULL, na_label = "No data", footnote = "auto", small_states = c("auto", "none", "dots"), small_area_km2 = 1000, interactive = FALSE )globe_map( data, fill, lon = 0, lat = 20, backend = c("sf", "polygon"), style = c("quantile", "continuous", "binned", "equal", "jenks", "fisher", "headtails", "sd", "fixed", "categorical"), palette = NULL, n_bins = 5, breaks = NULL, midpoint = NULL, borders = TRUE, title = NULL, legend = NULL, na_label = "No data", footnote = "auto", small_states = c("auto", "none", "dots"), small_area_km2 = 1000, interactive = FALSE )
data |
A map-ready frame: an |
fill |
The fill column (unquoted). |
lon, lat
|
The longitude / latitude the globe is centred on (the face pointing at the viewer). |
backend |
|
style, palette, n_bins, breaks, midpoint, borders, title, legend, na_label
|
As in |
footnote, small_states, small_area_km2
|
As in |
interactive |
If |
A ggplot object.
# No sf required -- the polygon backend needs only mapproj: if (requireNamespace("mapproj", quietly = TRUE)) { globe_map(countryatlas::world_snapshot$countries, continent, backend = "polygon", style = "categorical") } ## Not run: # The sf backend gives the cleanest limb (needs a World Bank fetch): world_data(2020, geometry = "sf") |> globe_map(gdp_per_capita, lon = 10, lat = 30) ## End(Not run)# No sf required -- the polygon backend needs only mapproj: if (requireNamespace("mapproj", quietly = TRUE)) { globe_map(countryatlas::world_snapshot$countries, continent, backend = "polygon", style = "categorical") } ## Not run: # The sf backend gives the cleanest limb (needs a World Bank fetch): world_data(2020, geometry = "sf") |> globe_map(gdp_per_capita, lon = 10, lat = 30) ## End(Not run)
A gridded (or "waffle") cartogram: the world redrawn as equal cells, each worth a fixed quantity, allocated to countries in proportion to their value and placed near where they belong. Where a Dorling cartogram preserves position and a contiguous one preserves adjacency, this preserves countability – the reader can literally count the cells.
gridded_cartogram( data, value, cells = 1000, fill = NULL, cell_size = 2.5, projection = "equal_earth", footnote = "auto" )gridded_cartogram( data, value, cells = 1000, fill = NULL, cell_size = 2.5, projection = "equal_earth", footnote = "auto" )
data |
A country-level or map-ready frame with |
value |
The column to allocate cells by (unquoted). |
cells |
Total number of cells to distribute (default |
fill |
Optional fill column (unquoted); defaults to |
cell_size |
Grid spacing in degrees of latitude (default |
projection |
Where the blocks are placed: a projection as in
|
footnote |
The caption, as in |
A ggplot object. The per-country cell allocation is attached as the
"countryatlas_cells" attribute – every placeable country, including the
ones that rounded to zero cells, so share sums to 1 and the rounding is
fully visible.
The cells are laid out from the bundled centroids in projection (Equal
Earth by default), so it needs no geometry backend and no sf.
Allocating a whole number of cells to each country cannot be exact, so the
remainder has to go somewhere. This uses the largest-remainder method, which
guarantees the cell total is exactly cells and that no country with a
positive value gets zero cells while a smaller one gets one. The attached
table reports each country's exact share alongside its integer allocation so
the rounding is inspectable rather than hidden.
Each country's block is centred on its own centroid, with no collision
avoidance between countries. That is deliberate – a global packing solve
would push countries away from where they belong – but it means blocks in
crowded regions are drawn on top of one another, and a partly hidden block
cannot be counted or compared. The effect is not marginal: at the defaults
(cells = 1000, cell_size = 2.5) about two fifths of the cells overlap a
cell of a different country, across some eighty countries, and it grows
with cells – at cells = 2500 it is roughly 70%.
cell_size is the lever, because it scales the tiles without moving the
centroids: dropping it to 1.5 cuts the overlap at cells = 1000 to about
a sixth of the cells. Fewer cells also helps. Where exact areas matter
more than geographic position, dorling_map() resolves collisions by
displacing circles instead.
cartogram_map(), dorling_map(), tile_map()
snap <- countryatlas::world_snapshot$countries gridded_cartogram(snap, population, cells = 400)snap <- countryatlas::world_snapshot$countries gridded_cartogram(snap, population, cells = 400)
Adds a growth-rate column to a panel: either the period-over-period change
("yoy") or the compound annual growth rate from the first observed year
("cagr"), computed per country.
growth_rate( data, value, type = c("yoy", "cagr"), suffix = "_growth", by = c("year", "row") )growth_rate( data, value, type = c("yoy", "cagr"), suffix = "_growth", by = c("year", "row") )
data |
A panel with |
value |
The value column (unquoted). |
type |
|
suffix |
Suffix for the new column (default |
by |
How the previous value is found for |
data with a growth-rate column added (a proportion, so 0.03 = 3%).
Rows come back sorted by iso3c then year: the calculation reads each
country's series in time order, so a row-aligned vector held alongside
data will no longer line up.
df <- data.frame(iso3c = "USA", year = 2000:2002, gdp = c(100, 110, 121)) growth_rate(df, gdp)df <- data.frame(iso3c = "USA", year = 2000:2002, gdp = c(100, 110, 121)) growth_rate(df, gdp)
A curated crosswalk from dissolved entities (Soviet Union, Yugoslavia,
Czechoslovakia, ...) to the modern states that succeeded them – one row per
(entity, successor) pair, dated, so historical panels can be brought onto
the modern ISO spine honestly instead of being silently dropped (or worse:
countrycode resolves "USSR" to Russia alone). Consumed by
dissolve_country() and flagged by check_country_match().
historical_codeshistorical_codes
A tibble with one row per (entity, successor):
Canonical name of the dissolved entity.
The alpha-3 code the entity held at dissolution, where
one existed (SUN, YUG, CSK, DDR, ANT, SCG, YMD, ...); it
may since have been inherited by a successor (e.g. YEM).
Year the entity ceased to exist.
The successor state.
How the successor relates to the entity, per successor:
"succession" if it is a genuinely new state created at dissolved, or
"continuation" if the same state carried on (possibly with less
territory) or a state that already existed absorbed the entity. Sudan is
both at once – SDN continued and SSD is new – which is why the
relation is per successor rather than per entity. The distinction
matters: a "continuation" was not created at dissolved, so testing
its data against that year says nothing, and the code did not cease to
exist either. audit_time_coverage() keys on this column for both
reasons.
Kosovo (XKX) is included among the Yugoslavia and Serbia-and-Montenegro
successors on a territory basis (its territory was part of both); filter
it out if your analysis follows strict UN-membership succession.
Curated from ISO 3166-3 and the historical record.
Country polygons as they were, from CShapes 2.0 (Schvitz et al. 2022), which maps states and colonies and dependencies for 1886-2019 with per-polygon validity periods. A 1970 map with 2024 borders is a common and quiet error; this is the fix.
historical_geometry(year, dependencies = FALSE, projection = "equal_earth")historical_geometry(year, dependencies = FALSE, projection = "equal_earth")
year |
The year to draw, or a |
dependencies |
Include colonies and dependencies (default |
projection |
Projection to return the geometry in (see |
An sf frame with gwcode, country, iso3c (where one can be
assigned – see below), status, from, to and geometry. Two further
CShapes columns are passed through when the installed version supplies
them, since they answer the questions this verb is usually asked:
owner – the gwcode of the sovereign a dependency belonged to; a
sovereign state carries its own gwcode here. This is the column that
makes dependencies = TRUE legible: without it a colony and its
metropole are two unrelated rows. owner != gwcode picks out the
dependencies.
capname – the capital's name at that date.
Both were returned but undocumented. Neither is guaranteed: cshapes
decides what its own table holds, so check with names() rather than
assuming.
ISO 3166 was first published in 1974 and never covered colonies, so a
historical map cannot be keyed on iso3c. CShapes uses Gleditsch-Ward
codes, which is why gwcode is the key here and iso3c is a best-effort
extra, read off the GW code: the modern code of the state that holds it.
So it is NA for an entity with no modern counterpart (the German
Democratic Republic, Czechoslovakia, Yugoslavia, the two Yemens), and also
where historical_codes says the modern state did not exist yet – GW give
the USSR and Russia one code, but the 1980 polygon is the Soviet Union, so
it carries NA rather than "RUS". A colony carries the code of the state
it became: CShapes names the 1950 Gold Coast "Ghana" and it comes back as
"GHA", with status saying it was a colony. Join historical data on
gwcode, not on iso3c, and use convert_country()(to = "gwn") to get
there from a modern code.
Schvitz, G., Girardin, L., Ruegger, S., Weidmann, N. B., Cederman, L.-E. & Gleditsch, K. S. (2022). Mapping the international system, 1886-2019: The CShapes 2.0 dataset. Journal of Conflict Resolution 66(1), 144-161. doi:10.1177/00220027211013563
world_geometry(), country_timeline(), world_map()
## Not run: # Africa before decolonisation needs the dependencies historical_geometry(1950, dependencies = TRUE) ## End(Not run)## Not run: # Africa before decolonisation needs the dependencies historical_geometry(1950, dependencies = TRUE) ## End(Not run)
A vectorised membership predicate built on country_groups().
in_group(x, group, origin = "country.name", as_of = NULL)in_group(x, group, origin = "country.name", as_of = NULL)
x |
A vector of country names or codes. |
group |
A single group name (see |
origin |
How to read |
as_of |
A date or year at which to evaluate membership: one value for
every element of |
A logical vector the same length as x. A value origin cannot
resolve to an ISO code answers FALSE – the same as a country that is
genuinely outside the group – so run check_country_match() first if you
need to tell "not a member" from "not recognised". A missing as_of
answers NA: membership at an unknown date is unknown.
country_groups(), country_groups_history
in_group(c("France", "United States", "Japan"), "EU") # membership is a function of time in_group("United Kingdom", "EU", as_of = 2016) in_group("United Kingdom", "EU", as_of = 2021) # one date per row, as a panel needs in_group(c("GBR", "GBR"), "EU", origin = "iso3c", as_of = c(2016, 2021))in_group(c("France", "United States", "Japan"), "EU") # membership is a function of time in_group("United Kingdom", "EU", as_of = 2016) in_group("United Kingdom", "EU", as_of = 2021) # one date per row, as a panel needs in_group(c("GBR", "GBR"), "EU", origin = "iso3c", as_of = c(2016, 2021))
Rescales a value column so the chosen base year equals to (100 by default),
per country – the standard way to compare trajectories that start at very
different levels.
index_to(data, value, base_year, to = 100, suffix = "_index")index_to(data, value, base_year, to = 100, suffix = "_index")
data |
A panel with |
value |
The value column (unquoted). |
base_year |
The year set equal to |
to |
The index value the base year maps to (default |
suffix |
Suffix for the new column (default |
data with an index column added. The column is NA for any
country whose series does not cover base_year (see the note there). A
negative base-year value is indexed as it is, so that country's index
runs opposite to its series.
df <- data.frame(iso3c = "USA", year = 2000:2002, gdp = c(50, 55, 60)) index_to(df, gdp, base_year = 2000) # A base year the data does not have gives NA, not an error: index_to(df, gdp, base_year = 1999)df <- data.frame(iso3c = "USA", year = 2000:2002, gdp = c(50, 55, 60)) index_to(df, gdp, base_year = 2000) # A base year the data does not have gives NA, not an error: index_to(df, gdp, base_year = 1999)
The usual inequality measures for one variable, in one tibble, so a report does not rest on whichever index came to hand. Weight by population for inequality between people rather than between countries.
inequality( x, weights = NULL, measures = c("gini", "theil_t", "theil_l", "atkinson", "cv", "palma", "p90_p10"), epsilon = 1 )inequality( x, weights = NULL, measures = c("gini", "theil_t", "theil_l", "atkinson", "cv", "palma", "p90_p10"), epsilon = 1 )
x |
A numeric vector, such as GDP per capita. |
weights |
Optional non-negative weights (population), the same length
as |
measures |
Any of |
epsilon |
The Atkinson index's inequality aversion (default |
A tibble of measure and value, one row per measure, every one
computed on the same values: the finite, positive values of x with a
non-missing weight. The Theil and Atkinson indices need positive values,
so a zero or negative value is dropped from all of them, with a warning
(class countryatlas_nonpositive_dropped). A measure that is undefined
on the data (too few values) is NA.
Milanovic (2005) separates three concepts. Concept 1 is inequality between
countries as units, each counting once: weights = NULL. Concept 2 weights
each country by its population but still gives everyone their country's
mean: weights = population. Concept 3, inequality between all the
world's people, needs each country's internal distribution, which a
country-level table does not have, so it is out of scope here; concept 2
understates it by exactly the within-country inequality it cannot see.
Atkinson, A. B. (1970). On the measurement of inequality. Journal of Economic Theory 2(3), 244-263. doi:10.1016/0022-0531(70)90039-6
Milanovic, B. (2005). Worlds Apart: Measuring International and Global Inequality. Princeton University Press.
Palma, J. G. (2011). Homogeneous middles vs. heterogeneous tails, and the end of the "inverted-U". Development and Change 42(1), 87-153. doi:10.1111/j.1467-7660.2011.01694.x
gini(), theil(), sigma_convergence()
snap <- countryatlas::world_snapshot$countries inequality(snap$gdp_per_capita) # concept 1 inequality(snap$gdp_per_capita, weights = snap$population) # concept 2snap <- countryatlas::world_snapshot$countries inequality(snap$gdp_per_capita) # concept 1 inequality(snap$gdp_per_capita, weights = snap$population) # concept 2
An interactive choropleth with hover and zoom, for dashboards and
R Markdown / Quarto. Engines are all optional Suggests.
interactive_map( data, fill, tooltip = NULL, engine = c("plotly", "ggiraph", "leaflet", "ggsql", "mapgl"), ... )interactive_map( data, fill, tooltip = NULL, engine = c("plotly", "ggiraph", "leaflet", "ggsql", "mapgl"), ... )
data |
A map-ready frame (polygon or sf). The |
fill |
The fill column (unquoted). |
tooltip |
Optional tooltip column (unquoted). |
engine |
|
... |
Passed to |
An interactive widget.
## Not run: world_data(2020) |> interactive_map(gdp_per_capita) world_data(2020, geometry = "sf") |> interactive_map(gdp_per_capita, engine = "ggsql", transform = "log10") ## End(Not run)## Not run: world_data(2020) |> interactive_map(gdp_per_capita) world_data(2020, geometry = "sf") |> interactive_map(gdp_per_capita, engine = "ggsql", transform = "log10") ## End(Not run)
Interpolate or carry forward missing observations in a panel. Every value this invents is flagged in a companion column, and that flag is not optional – an imputed value that travels through a pipeline looking like data is exactly the failure this package exists to prevent.
interpolate_missing( data, value = NULL, method = c("linear", "locf", "none"), max_gap = 3 )interpolate_missing( data, value = NULL, method = c("linear", "locf", "none"), max_gap = 3 )
data |
A panel with |
value |
Column(s) to fill (character). |
method |
|
max_gap |
Longest run of consecutive missing years to fill. Gaps longer
than this are left alone, because interpolating across a decade is not
interpolation. Default |
data with the gaps filled and, for each filled column, a logical
<column>_imputed companion. The map verbs count those columns when they
write provenance, so they keep working through any verb that preserves
columns. An "countryatlas_imputed" attribute lists the flag columns for
convenience, but nothing in the package reads it, and dplyr drops it as
it drops most attributes – rely on the columns, not the attribute. Rows
come back sorted by iso3c then year.
The flag cannot be turned off. world_map() reads it and refuses to draw
imputed values as though they were observed without at least noting it in the
caption. If you need values with no flag, compute them yourself – the
package will not hand you a frame where invented numbers are indistinguishable
from measured ones.
complete_years(), rate_check(), coverage_map()
p <- data.frame(iso3c = "USA", year = 2000:2005, gdp = c(1, NA, NA, 4, NA, 6)) interpolate_missing(p, "gdp")p <- data.frame(iso3c = "USA", year = 2000:2005, gdp = c(1, NA, NA, 4, NA, 6)) interpolate_missing(p, "gdp")
For a categorical variable – income group, region, a yes/no – count the neighbouring pairs whose two countries fall in the same category, and compare each count with what random labelling would give. Many more same-category joins than chance is clustering; fewer is a checkerboard.
join_counts(data, value, weights = NULL, n_perm = 999)join_counts(data, value, weights = NULL, n_perm = 999)
data |
A country-level frame with |
value |
The categorical column (unquoted): a factor, character or logical. |
weights |
A |
n_perm |
Permutations of the labels for the p-values (default |
A tibble with one row per category: category, n (countries in
it), joins (pairs within it), expected and sd (under permutation),
z, and p_value (two-sided). Its "n_joins" attribute is the number
of joined pairs.
Cliff, A. D. & Ord, J. K. (1981). Spatial Processes: Models and Applications. Pion.
morans_i() for a numeric variable
snap <- countryatlas::world_snapshot$countries join_counts(snap, income, n_perm = 199)snap <- countryatlas::world_snapshot$countries join_counts(snap, income, n_perm = 199)
Auto-detects the country column, standardises it to ISO codes (via
standardize_country()), attaches geometry and returns a plot-ready frame –
the function that fulfils the package's promise for your own data. Pipe the
result straight into world_map().
join_world( data, country_col = NULL, origin = "country.name", geometry = c("polygon", "sf", "none"), scale = "small", region = NULL, projection = "equal_earth", recenter = NULL, warn = TRUE )join_world( data, country_col = NULL, origin = "country.name", geometry = c("polygon", "sf", "none"), scale = "small", region = NULL, projection = "equal_earth", recenter = NULL, warn = TRUE )
data |
A data frame keyed on country names or codes. |
country_col |
The country column (unquoted). If omitted, it is auto-detected. |
origin |
How to read |
geometry |
|
scale |
Natural Earth resolution for the |
region |
Optional region subset (see |
projection, recenter
|
Projection, and optional central meridian, for
the |
warn |
Whether to report unmatched countries (default |
A plot-ready frame: polygon tibble, sf object, or (for
geometry = "none") the standardised table.
rates <- data.frame(country = c("United States", "Brazil", "Kenya"), vaccination_pct = c(0.7, 0.8, 0.6)) joined <- join_world(rates, country)rates <- data.frame(country = c("United States", "Brazil", "Kenya"), vaccination_pct = c(0.7, 0.8, 0.6)) joined <- join_world(rates, country)
The two panel primitives everyone hand-rolls (and gets subtly wrong when the
frame isn't sorted): the value n years back, and the change since then –
grouped by iso3c, ordered by year, so country A's 1960 never leaks into
country B's first row.
lag_by_country(data, value, n = 1, suffix = NULL, by = c("year", "row")) diff_by_country(data, value, n = 1, suffix = NULL, by = c("year", "row"))lag_by_country(data, value, n = 1, suffix = NULL, by = c("year", "row")) diff_by_country(data, value, n = 1, suffix = NULL, by = c("year", "row"))
data |
A panel with |
value |
The value column (unquoted). |
n |
Number of years (or, with |
suffix |
Suffix for the new column. Defaults to |
by |
How "earlier" is found. |
data with the lagged / differenced column added. Rows come back
sorted by iso3c then year: the calculation reads each country's series
in time order, so a row-aligned vector held alongside data will no
longer line up.
df <- data.frame(iso3c = "USA", year = 2000:2003, gdp = c(100, 110, 121, 133)) lag_by_country(df, gdp) diff_by_country(df, gdp)df <- data.frame(iso3c = "USA", year = 2000:2003, gdp = c(100, 110, 121, 133)) lag_by_country(df, gdp) diff_by_country(df, gdp)
The map of local_morans(): countries coloured by cluster type, with
non-significant ones left neutral. Hot spots (High-High) and cold spots
(Low-Low) read immediately; the off-diagonal categories are the spatial
outliers.
lisa_map( data, value, weights = NULL, n_perm = 9999, alpha = 0.05, p_adjust = c("fdr", "bonferroni", "holm", "none"), ... )lisa_map( data, value, weights = NULL, n_perm = 9999, alpha = 0.05, p_adjust = c("fdr", "bonferroni", "holm", "none"), ... )
data |
A map-ready frame (polygon or |
value |
The value column (unquoted). |
weights |
A |
n_perm, alpha, p_adjust
|
Passed to |
... |
Passed to |
A ggplot object, with the local_morans() table attached as the
"countryatlas_lisa" attribute.
Either backend, as world_map(): the frame decides, and both draw in Equal
Earth unless projection is passed on through ....
snap <- countryatlas::world_snapshot$countries set.seed(1) attach_geometry(snap, geometry = "polygon") |> lisa_map(gdp_per_capita, weights = country_weights("knn", k = 5), n_perm = 99)snap <- countryatlas::world_snapshot$countries set.seed(1) attach_geometry(snap, geometry = "polygon") |> lisa_map(gdp_per_capita, weights = country_weights("knn", k = 5), n_perm = 99)
Local Indicators of Spatial Association (Anselin 1995): one Moran statistic
per country, plus the cluster type it belongs to. Where morans_i() answers
"is there clustering anywhere", this answers "where, and of what kind".
local_morans( data, value, weights = NULL, n_perm = 9999, alpha = 0.05, p_adjust = c("fdr", "bonferroni", "holm", "none") )local_morans( data, value, weights = NULL, n_perm = 9999, alpha = 0.05, p_adjust = c("fdr", "bonferroni", "holm", "none") )
data |
A country-level frame with |
value |
The value column (unquoted). |
weights |
A |
n_perm |
Permutations for the pseudo-p-value (default |
alpha |
Significance threshold for the |
p_adjust |
How to adjust the per-country p-values for testing about
190 countries at once: |
A tibble, one row per country: iso3c, value, lag (the
neighbour average), ii (the local statistic), p_value, p_adjusted
and cluster ("High-High", "Low-Low", "High-Low", "Low-High" or
"Not significant", judged on p_adjusted). The method is recorded as
the "countryatlas_p_adjust" attribute.
A local statistic runs one test per country, about 190 of them, so at
alpha = 0.05 roughly ten countries are expected to look significant when
nothing is going on at all. p_adjust = "fdr" controls the false discovery
rate instead – the share of flagged countries that are false alarms –
which is the standard remedy for exactly this setting (Caldas de Castro &
Singer 2006) and what GeoDa offers. "bonferroni" and "holm" control the
chance of any false alarm, which is stricter; "none" reproduces the
unadjusted labels of earlier versions. p_value itself is never adjusted,
so it still matches spdep.
Adjustment needs resolution. A permutation p-value cannot go below
, and Benjamini-Hochberg flags the most significant of
190 tests only when its p-value is below .
With 999 permutations the floor, 0.001, is above that, so nothing short of
several countries all reaching the floor can be flagged, and an empty map
would say "no clusters" when it means "not enough permutations". Hence the
default of 9,999, which takes about a second for the whole world.
p_value is a two-sided pseudo-p from conditional permutation:
, so it is never
exactly zero and its floor is – with the default 999
permutations, 0.001. Two-sided because a local statistic is interesting at
both ends: a country surrounded by unlike neighbours is as much a finding
as one surrounded by like ones. cluster is "Not significant" wherever
p_value > alpha, and everywhere when n_perm = 0 leaves it NA. Set a
seed beforehand for a reproducible p_value.
Anselin, L. (1995). Local Indicators of Spatial Association – LISA. Geographical Analysis 27(2), 93-115. doi:10.1111/j.1538-4632.1995.tb00338.x
Caldas de Castro, M. & Singer, B. H. (2006). Controlling the false discovery rate: a new application to account for multiple and dependent tests in local statistics of spatial association. Geographical Analysis 38(2), 180-208. doi:10.1111/j.0016-7363.2006.00682.x
lisa_map(), morans_i(), country_weights()
snap <- countryatlas::world_snapshot$countries set.seed(1) local_morans(snap, gdp_per_capita, weights = country_weights("knn", k = 5), n_perm = 99)snap <- countryatlas::world_snapshot$countries set.seed(1) local_morans(snap, gdp_per_capita, weights = country_weights("knn", k = 5), n_perm = 99)
Point-in-polygon lookup: given longitude / latitude vectors (or an sf POINT
object), return the iso3c of the country each point falls in – the bridge
for getting point data (events, stations, observations) onto the country
spine so it can be joined, aggregated and mapped like everything else.
locate_country( lon = NULL, lat = NULL, points = NULL, scale = "small", add = "country", tolerance_km = 25 )locate_country( lon = NULL, lat = NULL, points = NULL, scale = "small", add = "country", tolerance_km = 25 )
lon, lat
|
Equal-length numeric vectors of longitude / latitude, giving
one point per element (ignored if |
points |
Optional |
scale |
Natural Earth resolution for the lookup geometry. |
add |
Extra attributes to return alongside |
tolerance_km |
Snap an unmatched point to the nearest country when it
lies within this many kilometres of one (default |
A tibble with one row per point: iso3c plus any add columns
(NA for points that fall in no country, e.g. open ocean).
if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE)) { locate_country(lon = c(2.35, -74.0), lat = c(48.85, 40.7)) # Paris, NYC }if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE)) { locate_country(lon = c(2.35, -74.0), lat = c(48.85, 40.7)) # Paris, NYC }
A text description of a map drawn by the package, computed from the final
plot – its projection, its fill scale and the data it draws – so it stays
true when the plot is modified afterwards. Every map verb sets it as the
plot's alt text (labs(alt =)), which ggplot2::get_alt_text() returns and
which knitr, Quarto and Shiny can pass to a screen reader.
map_alt_text(p, level = 2)map_alt_text(p, level = 2)
p |
A map from one of the package's map verbs. |
level |
|
The levels follow Lundgard & Satyanarayan (2022). Level 1 describes the construction: the kind of map, the projection, what the fill shows and how it is classified, and how many countries have data. Level 2 adds statistics: the three highest and three lowest countries, the share missing, and the continent with the highest median. Levels 3 and 4 – trends, and what the map means – are the author's to write; add them to the text this returns.
knitr does not ask ggplot2 for alt text when a chunk sets none, so in
R Markdown set it on the chunk, from a plot built in an earlier chunk:
fig.alt = countryatlas::map_alt_text(p).
A single string.
Lundgard, A. & Satyanarayan, A. (2022). Accessible visualization via natural language descriptions: a four-level model of semantic content. IEEE Transactions on Visualization and Computer Graphics 28(1), 1073-1083. doi:10.1109/TVCG.2021.3114770
snap <- countryatlas::world_snapshot$countries p <- world_map(attach_geometry(snap), gdp_per_capita) map_alt_text(p) ggplot2::get_alt_text(p)snap <- countryatlas::world_snapshot$countries p <- world_map(attach_geometry(snap), gdp_per_capita) map_alt_text(p) ggplot2::get_alt_text(p)
The references for the sources and methods behind one map: the data, from
its source_info() record; the geometry; the projection; the
classification; and the method behind a value-suppressing palette, a
value-by-alpha map, a ternary map, historical borders or a
false-discovery-rate LISA map.
Cite these alongside the package itself (citation("countryatlas")).
map_citation(p, format = c("text", "bibtex"))map_citation(p, format = c("text", "bibtex"))
p |
A map from one of the package's map verbs. |
format |
|
For "text", a character vector, one reference per element; for
"bibtex", a Bibtex object for a .bib file.
map_provenance(), source_info()
snap <- countryatlas::world_snapshot$countries p <- world_map(attach_geometry(snap), gdp_per_capita) map_citation(p)snap <- countryatlas::world_snapshot$countries p <- world_map(attach_geometry(snap), gdp_per_capita) map_citation(p)
Report the provenance of a world_map() (or any plot the package's map verbs
produced): the package version, the geometry backend and projection, the
classification method and its breaks, the fill column, and how many countries
are shown versus missing. These are the questions a reviewer asks first, and
the answers are already known at plot time – this just makes them readable.
map_provenance(x, value = NULL)map_provenance(x, value = NULL)
x |
A plot returned by any of the package's map verbs –
|
value |
For a data frame, the column whose coverage to report (unquoted). Ignored for a plot, which already knows its own fill. |
A one-row tibble of provenance fields, invisibly printed in a
human-readable block. Fields: countryatlas, fill, backend,
projection, style, modified, n_bins, na_style, n_countries,
n_missing, n_total, uncertainty, disputes, dispute_policy,
worldview, n_imputed, breaks, missing_iso3c, snapshot_year, and
sources, the
source_info() record of the fill column when the data carried one –
which the print names: "World Bank WDI NY.GDP.PCAP.KD (constant 2015
US$), release 2026-07, fetched 2026-10-01".
projection and style describe the plot as it is now, read from its
coordinate system and fill scale: a map given + coord_sf(crs = 3035)
after the verb drew it reports "custom", and modified = TRUE says the
plot no longer matches what the verb recorded.
The three counts are: n_countries, the countries actually drawn with a
value; n_missing, those drawn without one; and n_total, the two added
together – every country the map covers. n_countries is the numerator,
not the denominator, which its name does not say on its own.
world_map()(footnote = "auto") prints the coverage line as a caption, and
classification_report = TRUE attaches the per-class counts. Together they
cover what a methods note needs:
p <- world_map(mapdf, gdp_per_capita, style = "quantile",
footnote = "auto", classification_report = TRUE)
map_provenance(p)
attr(p, "countryatlas_classification")
world_map(), audit_coverage(), coverage_map()
snap <- countryatlas::world_snapshot$countries p <- attach_geometry(snap, geometry = "polygon") |> world_map(gdp_per_capita, style = "quantile") map_provenance(p)snap <- countryatlas::world_snapshot$countries p <- attach_geometry(snap, geometry = "polygon") |> world_map(gdp_per_capita, style = "quantile") map_provenance(p)
Do neighbouring countries have similar values? Global Moran's I on the country
spine, with a permutation pseudo-p-value. No spdep required: at ~200
countries the dense arithmetic is trivial.
morans_i(data, value, scale = deprecated(), n_perm = 999, weights = NULL)morans_i(data, value, scale = deprecated(), n_perm = 999, weights = NULL)
data |
A country-level data frame with |
value |
The value column (unquoted). |
scale |
|
n_perm |
Number of permutations for the pseudo-p-value (default |
weights |
A |
A one-row tibble: i (observed Moran's I), expected
( under no autocorrelation), n (countries used),
n_excluded (countries with data that the weights could not reach),
n_links, p_value (one-sided, , computed as
, so never exactly
zero – the floor is ) and an excluded list-column of
the excluded iso3c codes. Set a seed beforehand for a reproducible
p_value.
The default weights are the five nearest neighbours, so every country with
data and a bundled centroid takes part. Land-border contiguity, the default
before 4.0.0, is still available and still systematic in what it drops: an
island has no land border, so any country with no land neighbour present
in data leaves the statistic – Japan, the United Kingdom, Australia,
Indonesia, Madagascar, New Zealand, the Philippines, Iceland, Cuba, Sri Lanka
and every small island state. On the bundled world_snapshot's GDP per
capita that changes the answer, not only the sample: see the numbers in the
example below. n_excluded and excluded report who is left out under
either scheme:
morans_i(snap, gdp_per_capita, weights = country_weights("contiguity"))
Moran, P. A. P. (1950). Notes on continuous stochastic phenomena. Biometrika 37(1/2), 17-23. doi:10.2307/2332142
country_weights(), local_morans(), gearys_c(), spatial_lag()
snap <- countryatlas::world_snapshot$countries set.seed(42) # every country included, no sf required morans_i(snap, gdp_per_capita, n_perm = 99, weights = country_weights("knn", k = 5))snap <- countryatlas::world_snapshot$countries set.seed(42) # every country included, no sf required morans_i(snap, gdp_per_capita, n_perm = 99, weights = country_weights("knn", k = 5))
Which countries border a given country (or countries) – a vectorised
lookup built on country_borders().
neighbors(x, origin = "country.name", scale = "small", warn = TRUE)neighbors(x, origin = "country.name", scale = "small", warn = TRUE)
x |
A vector of country names or codes. |
origin |
How to read |
scale |
Natural Earth resolution to compute adjacency from. |
warn |
Whether to report values that do not resolve to a country
(default |
A tibble with one row per (iso3c, neighbor) pair: the queried
country's iso3c, and each bordering country's iso3c and country
name (neighbor, neighbor_country). Countries with no land border
(islands, e.g. Japan, Madagascar) return zero rows.
Every call rebuilds the whole world's adjacency from polygon topology, so
asking about one country costs the same as asking about all of them. x is
vectorised, and adding countries to a single call only adds the filtering:
countryatlas::neighbors(c("FRA", "DEU", "ESP"), origin = "iso3c")
Looping instead pays that rebuild once per country – for every bordering
country in the world, roughly two orders of magnitude more work than one
vectorised call. The same applies to country_borders(), which does the work.
igraph also exports a neighbors(), and it takes a graph and a vertex
rather than country names. Whichever package is attached later wins, so if
you use both – which country_borders() suggests, for building a graph of
the adjacency – qualify this one as countryatlas::neighbors().
if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE)) { neighbors("France") neighbors(c("FRA", "JPN"), origin = "iso3c") # Japan has no land border }if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE)) { neighbors("France") neighbors(c("FRA", "JPN"), origin = "iso3c") # Japan has no land border }
European subnational boundaries from Eurostat's GISCO service via the
optional giscoR package. Nothing is bundled – the geometry is downloaded
and cached by giscoR itself.
nuts_geometry( level = 2, year = 2021, countries = NULL, resolution = "60", projection = "equal_earth" )nuts_geometry( level = 2, year = 2021, countries = NULL, resolution = "60", projection = "equal_earth" )
level |
NUTS level: |
year |
NUTS vintage: |
countries |
Optional |
resolution |
GISCO resolution: |
projection |
Projection for the result (see |
An sf frame with nuts_id, iso3c, name, level and geometry.
standardize_subnational(), subnational_map()
## Not run: nuts_geometry(level = 2, countries = c("DEU", "FRA")) ## End(Not run)## Not run: nuts_geometry(level = 2, countries = c("DEU", "FRA")) ## End(Not run)
One small map per origin, each showing where that origin's flow goes. The
answer to the arc map's central problem: past a few dozen flows,
flow_map() is a plate of spaghetti and an OD map is legible.
od_map( data, from, to, weight = NULL, origin = "country.name", origins = 6, direction = c("out", "in"), ... )od_map( data, from, to, weight = NULL, origin = "country.name", origins = 6, direction = c("out", "in"), ... )
data |
An OD table. |
from, to
|
The origin and destination country columns (unquoted). |
weight |
The flow column (unquoted). If omitted, every pair counts as 1. |
origin |
How to read |
origins |
Which origins to draw. A character vector of names or codes,
or an integer giving how many of the largest to take (default Note that this is not |
direction |
|
... |
Passed to |
A faceted ggplot object.
flow_map(), country_network(), facet_map()
od <- data.frame( from = rep(c("China", "Germany", "USA"), each = 3), to = c("USA", "Japan", "Brazil", "France", "Italy", "Poland", "Mexico", "Canada", "Japan"), value = c(500, 200, 90, 80, 70, 60, 300, 280, 120) ) od_map(od, from, to, value, origins = 3)od <- data.frame( from = rep(c("China", "Germany", "USA"), each = 3), to = c("USA", "Japan", "Brazil", "France", "Italy", "Poland", "Mexico", "Canada", "Japan"), value = c(500, 200, 90, 80, 70, 60, 300, 280, 120) ) od_map(od, from, to, value, origins = 3)
Removes the "is this map just a population map?" footgun by dividing a value
column by population. If no population column is supplied, SP.POP.TOTL is
pulled automatically for the relevant countries and years.
per_capita(data, value, pop = NULL, suffix = "_per_capita", cache = TRUE)per_capita(data, value, pop = NULL, suffix = "_per_capita", cache = TRUE)
data |
A country-level (or panel) data frame with |
value |
The value column to normalise (unquoted). |
pop |
Optional population column (unquoted). If absent, population is fetched from WDI. |
suffix |
Suffix for the new column (default |
cache |
Whether to use the WDI cache when fetching population. |
data with a new per-capita column.
df <- data.frame(iso3c = c("USA", "CHN"), year = 2020L, co2 = c(5e6, 1e7), pop = c(331e6, 1402e6)) per_capita(df, co2, pop)df <- data.frame(iso3c = c("USA", "CHN"), year = 2020L, co2 = c(5e6, 1e7), pop = c(331e6, 1402e6)) per_capita(df, co2, pop)
The transform the polygon backend uses when sf cannot be loaded: a
spherical Equal Earth (Savric, Patterson & Jenny 2019) on the authalic
sphere, written out in base R so the default map is equal-area on any
installation. Use it to place your own points, labels or paths on such a
map, which is drawn in metres rather than degrees. Where sf loads, maps
are drawn with ggplot2::coord_sf() instead and take layers in longitude
and latitude directly, so there is nothing to convert.
project_lonlat(lon, lat, projection = "equal_earth", recenter = NULL)project_lonlat(lon, lat, projection = "equal_earth", recenter = NULL)
lon, lat
|
Numeric vectors of longitude and latitude, in degrees. |
projection |
|
recenter |
Optional central meridian, as in |
A tibble of x and y, in metres, one row per point; NA where a
coordinate is missing.
Savric, B., Patterson, T. & Jenny, B. (2019). The Equal Earth map projection. International Journal of Geographical Information Science 33(3), 454-465. doi:10.1080/13658816.2018.1504949
project_lonlat(c(2.35, -74.0), c(48.85, 40.7)) # Paris, New Yorkproject_lonlat(c(2.35, -74.0), c(48.85, 40.7)) # Paris, New York
Small multiples of one choropleth, drawn once per projection, so the cost of a projection choice is visible rather than asserted. The data and the classification are held fixed; only the CRS varies.
projection_compare( data, fill, projections = c("equal_earth", "robinson", "winkel_tripel", "mercator"), ncol = NULL, labeller = c("name", "property"), ..., projection = NULL )projection_compare( data, fill, projections = c("equal_earth", "robinson", "winkel_tripel", "mercator"), ncol = NULL, labeller = c("name", "property"), ..., projection = NULL )
data |
An |
fill |
The fill column (unquoted). |
projections |
Projections to compare (default: Equal Earth, Robinson,
Winkel tripel and Mercator – an equal-area, two compromises and a
conformal). See |
ncol |
Number of facet columns. |
labeller |
|
... |
Passed to |
projection |
Not an argument of this function. It exists only to catch
the singular spelling, which R's partial matching would otherwise bind to
|
A faceted ggplot object.
projection_info(), tissot_map()
if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE)) { attach_geometry(countryatlas::world_snapshot$countries, geometry = "sf") |> projection_compare(gdp_per_capita, style = "quantile") }if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE)) { attach_geometry(countryatlas::world_snapshot$countries, geometry = "sf") |> projection_compare(gdp_per_capita, style = "quantile") }
The numeric companion to tissot_map(): distortion sampled on a grid, so it
can be summarised, compared or mapped rather than eyeballed. Computed by
projecting a small circle at each grid point and measuring what happens to
it, which is the definition rather than an approximation of it.
projection_distortion( projection = "equal_earth", measure = c("areal", "angular", "max_scale"), spacing = 10, max_lat = 85 )projection_distortion( projection = "equal_earth", measure = c("areal", "angular", "max_scale"), spacing = 10, max_lat = 85 )
projection |
Projection to measure (see |
measure |
|
spacing |
Grid spacing in degrees (default |
max_lat |
Absolute latitude limit (default |
A tibble of lon, lat and distortion, with the area-weighted
mean and the range attached as the "countryatlas_distortion" attribute.
An equal-area projection has "areal" distortion of 1 everywhere – that is
what equal-area means, and it is worth checking rather than trusting.
A conformal projection has "angular" distortion of 0 everywhere and
unbounded areal distortion. A compromise projection is bad at both by a
little, everywhere, which is the trade it makes.
Distortion is measured against the WGS84 ellipsoid, the datum every CRS the
package builds is defined on. Equal Earth, Gall-Peters and the three Lambert
azimuthal projections have ellipsoidal forms and read exactly 1. PROJ
implements Mollweide and Eckert IV with their spherical formulas, so on this
datum they are equal-area only to within about 0.7% ("areal" between
0.993 and 1.007): a property of the maps drawn here, and the kind of thing
this check exists to show.
tissot_map(), projection_info(), projection_compare()
if (requireNamespace("sf", quietly = TRUE)) { d <- projection_distortion("mercator", measure = "areal") attr(d, "countryatlas_distortion") }if (requireNamespace("sf", quietly = TRUE)) { d <- projection_distortion("mercator", measure = "areal") attr(d, "countryatlas_distortion") }
Look up the properties of the projections world_map() understands: the
construction family, whether it is equal-area (a choropleth's ink is
proportional to ground area) or conformal (shapes are locally right), and the
PROJ string the package builds. Called with no arguments it returns the whole
table, which is the quickest way to see what is available.
projection_info(projection = NULL)projection_info(projection = NULL)
projection |
A projection name, or |
A tibble with one row per projection: projection, family,
property, equal_area, conformal, note and proj4.
For a world choropleth the honest choice is equal-area, because the eye
reads coloured area as quantity: a projection that inflates Greenland makes
Greenland's value look more important than it is. Equal Earth is the
recommended default (and the package's own) – it is equal-area, close to
Robinson in appearance, and cheap to evaluate (Savric, Patterson & Jenny
2019). "mercator" is conformal, not equal-area, and should not be used for
choropleths. Use projection_compare() to see the difference on your own
data, and tissot_map() to see it on the graticule.
Savric, B., Patterson, T. & Jenny, B. (2019). The Equal Earth map projection. International Journal of Geographical Information Science 33(3), 454-465. doi:10.1080/13658816.2018.1504949
projection_compare(), tissot_map(), world_map()
projection_info() projection_info("mercator") # every equal-area projection the package can build subset(projection_info(), equal_area)$projectionprojection_info() projection_info("mercator") # every equal-area projection the package can build subset(projection_info(), equal_area)$projection
Adds rank, percentile and z_score for a value column, optionally within
a group (region, year, ...), for "top 10" tables and labelling.
rank_countries(data, value, within = NULL, desc = TRUE)rank_countries(data, value, within = NULL, desc = TRUE)
data |
A data frame. |
value |
The value column to rank (unquoted). |
within |
Optional grouping column(s) (unquoted or character) to rank within. |
desc |
Rank descending (largest = rank 1); default |
data with rank, percentile and z_score columns added.
With no within, every row is ranked against every other. On a panel that
pools the years: a country's 2020 and its 2000 compete for the same ranks,
and z_score carries the time trend. So a frame whose year holds more
than one value warns (class countryatlas_panel); within = year ranks
countries within each year, which is nearly always what is meant.
df <- data.frame(iso3c = c("USA", "CHN", "IND"), gdp = c(21, 17, 3)) rank_countries(df, gdp)df <- data.frame(iso3c = c("USA", "CHN", "IND"), gdp = c(21, 17, 3)) rank_countries(df, gdp)
Kendall's tau between the ranks of two cross-sections, and the share of
countries that moved at least k places: a summary of mobility that
complements transition_matrix().
rank_mobility(data, value, from, to, k = 5)rank_mobility(data, value, from, to, k = 5)
data |
A panel with |
value |
The column to rank (unquoted). |
from, to
|
The two years to compare. |
k |
The number of places that counts as moving (default |
A one-row tibble: n (countries with a value in both years),
tau (Kendall's tau-b of the two rankings: 1 for an unchanged order),
p_value (for tau = 0), and share_moved (the share of countries whose
rank changed by k places or more), with the per-country ranks as the
"ranks" attribute.
transition_matrix(), rank_countries()
set.seed(1) pan <- expand.grid(iso3c = c("FRA", "DEU", "BRA", "IND", "CHN", "NGA"), year = c(2000, 2010), stringsAsFactors = FALSE) pan$gdp <- exp(stats::rnorm(nrow(pan), 9, 1)) rank_mobility(pan, gdp, from = 2000, to = 2010, k = 1)set.seed(1) pan <- expand.grid(iso3c = c("FRA", "DEU", "BRA", "IND", "CHN", "NGA"), year = c(2000, 2010), stringsAsFactors = FALSE) pan$gdp <- exp(stats::rnorm(nrow(pan), 9, 1)) rank_mobility(pan, gdp, from = 2000, to = 2010, k = 1)
The small-number problem, named: a rate over a very small population is mostly noise, and on a map it shouts as loudly as a rate over a very large one. This reports which countries' rates you should not trust before you plot them.
rate_check(data, numerator, denominator, min_denominator = NULL, rate = NULL)rate_check(data, numerator, denominator, min_denominator = NULL, rate = NULL)
data |
A country-level frame. |
numerator, denominator
|
The count and the population it is over (unquoted). |
min_denominator |
Denominators below this are flagged. |
rate |
An existing rate column (unquoted), if you already computed it.
Otherwise the rate is |
A tibble of iso3c, numerator, denominator, rate,
expected_se (the Poisson standard error of the rate, ;
NA, with a warning, for a negative rate) and flagged, sorted with the
least reliable first.
"Least reliable" is ordered on the standard error a single event would
imply, , which is identical to expected_se for
every row with at least one event. Ordering on expected_se directly put
zero-count rows last, at the reliable end: it is , which
is exactly 0 when the count is 0, so one country with no events out of
251 people outranked another with one event out of the same 251. Observing
nothing is not evidence of precision. expected_se itself is still the
plain Poisson standard error, 0 and all.
flagged is TRUE for a
denominator below the threshold, FALSE above it or missing, and NA for
every row when no threshold could be computed at all – which is warned
about, and means sum(flagged) is NA rather than a misleading 0.
Three answers, in rough order of preference: smooth_rates() shrinks the
unreliable rates toward the global rate; value_by_alpha_map() leaves them
alone but fades them out; or drop them and say so in the caption. Plotting
them raw and unremarked is the one option that misleads.
Roth, R. E., Woodruff, A. W. & Johnson, Z. F. (2010). Value-by-alpha maps: an alternative technique to the cartogram. The Cartographic Journal 47(2), 130-140. doi:10.1179/000870409X12488753453372
smooth_rates(), per_capita(), value_by_alpha_map()
d <- data.frame( iso3c = c("CHN", "IND", "TUV", "NRU"), cases = c(50000, 42000, 3, 1), pop = c(1.41e9, 1.39e9, 11000, 12000) ) rate_check(d, cases, pop)d <- data.frame( iso3c = c("CHN", "IND", "TUV", "NRU"), cases = c(50000, 42000, 3, 1), pop = c(1.41e9, 1.39e9, 11000, 12000) ) rate_check(d, cases, pop)
Each country's rate against its denominator, inside control limits for the
rate a country of that size would show by chance alone. A small country's
extreme rate falls inside the wide mouth of the funnel; a large country
outside the narrow neck is a real outlier. It completes the rates set:
rate_check() flags the unreliable rates, smooth_rates() shrinks them,
rate_funnel() shows them, value_by_alpha_map() maps them.
rate_funnel( data, numerator, denominator, target = NULL, limits = c(0.95, 0.998), overdispersion = FALSE, label_outliers = TRUE )rate_funnel( data, numerator, denominator, target = NULL, limits = c(0.95, 0.998), overdispersion = FALSE, label_outliers = TRUE )
data |
A country-level frame with |
numerator, denominator
|
The counts and their population at risk (unquoted). |
target |
The rate the limits are drawn around; |
limits |
The coverage of the inner and outer limits (default
|
overdispersion |
If |
label_outliers |
Label the countries outside the outer limit with their
ISO code (default |
A ggplot, with the per-country table attached as the
"countryatlas_funnel" attribute: iso3c, the two columns, rate, z
(the standardised deviation) and flag ("within", "above 95%",
"above 99.8%", "below 95%" or "below 99.8%", named after limits).
For a denominator the expected count is , and the
limit at probability is the exact Poisson quantile, interpolated
so the funnel is smooth: with ,
, and the rate limit
is . With overdispersion = TRUE the limits are
, estimated from the
winsorised z-scores.
Spiegelhalter, D. J. (2005). Funnel plots for comparing institutional performance. Statistics in Medicine 24(8), 1185-1202. doi:10.1002/sim.1970
set.seed(1) d <- data.frame(iso3c = c("CHN", "IND", "FRA", "TUV", "NRU", "MLT"), pop = c(1.41e9, 1.39e9, 6.8e7, 1.1e4, 1.2e4, 5.3e5)) d$deaths <- stats::rpois(nrow(d), d$pop * 0.008) rate_funnel(d, deaths, pop)set.seed(1) d <- data.frame(iso3c = c("CHN", "IND", "FRA", "TUV", "NRU", "MLT"), pop = c(1.41e9, 1.39e9, 6.8e7, 1.1e4, 1.2e4, 5.3e5)) d$deaths <- stats::rpois(nrow(d), d$pop * 0.008) rate_funnel(d, deaths, pop)
Teach countryatlas where to get indicators from. A source is a name plus a
fetch function; once registered it works with fetch_indicator(),
add_indicator() and compare_sources() exactly like the built-in ones.
register_country_source( name, fetch, meta = NULL, citation = NULL, key_col = "iso3c", key_type = "iso3c", cache = TRUE )register_country_source( name, fetch, meta = NULL, citation = NULL, key_col = "iso3c", key_type = "iso3c", cache = TRUE )
name |
Short source name, used everywhere else (e.g. |
fetch |
A function |
meta |
Optional one-line description of the provider. |
citation |
Optional citation string; surfaced by |
key_col |
The country-key column |
key_type |
How to read that column: any |
cache |
Whether results should be memoised for the session
(default |
This is deliberately a registry rather than more Suggests. It means a
provider with no CRAN package – V-Dem, the IMF, the World Inequality
Database – is reachable without this package depending on anything, and it
means an internal or proprietary feed is a first-class citizen.
Invisibly, the source name.
fetch(indicator, countries, years) must return a data frame with:
a country key column (named key_col, holding key_type values –
normally an iso3c column of iso3c codes),
a year column (integer) if the data is a panel,
one column per requested indicator, named after the indicator, and
one row per key – iso3c for a cross-section, iso3c and year for a
panel.
The key has to be unique because add_indicator() joins on it: a repeated
key multiplies the caller's rows, so it is reported rather than joined in
silence. Aggregate before returning if the provider serves several rows per
country-year.
countries is a character vector of iso3c codes or NULL for all;
years is a numeric vector or NULL for the provider's default. Returning
extra columns is fine – they travel through. Anything the provider cannot
serve should come back as NA, not as a missing row, wherever that is
cheap to arrange.
country_sources(), fetch_indicator(), compare_sources()
# A trivial in-memory source register_country_source( "demo", fetch = function(indicator, countries = NULL, years = NULL) { data.frame(iso3c = c("USA", "FRA"), year = 2020L, demo_value = c(1, 2)) }, meta = "A toy source for the examples" ) country_sources() fetch_indicator("demo", "demo_value") # Registering is permanent for the session, so example() would leave "demo" # and "demo_named" in the registry and country_sources() would report # different rows afterwards. Clean up at the end -- see the last lines. # A source keyed on country names rather than codes: `key_col` says which # column holds the key, `key_type` says how to read it. register_country_source( "demo_named", fetch = function(indicator, countries = NULL, years = NULL) { data.frame(country = c("United States", "Japan"), year = 2020L, demo_value = c(3, 4)) }, key_col = "country", key_type = "country.name", meta = "A toy name-keyed source" ) fetch_indicator("demo_named", "demo_value") # Leave the registry as it was found. remove_country_source(c("demo", "demo_named"))# A trivial in-memory source register_country_source( "demo", fetch = function(indicator, countries = NULL, years = NULL) { data.frame(iso3c = c("USA", "FRA"), year = 2020L, demo_value = c(1, 2)) }, meta = "A toy source for the examples" ) country_sources() fetch_indicator("demo", "demo_value") # Registering is permanent for the session, so example() would leave "demo" # and "demo_named" in the registry and country_sources() would report # different rows afterwards. Clean up at the end -- see the last lines. # A source keyed on country names rather than codes: `key_col` says which # column holds the key, `key_type` says how to read it. register_country_source( "demo_named", fetch = function(indicator, countries = NULL, years = NULL) { data.frame(country = c("United States", "Japan"), year = 2020L, demo_value = c(3, 4)) }, key_col = "country", key_type = "country.name", meta = "A toy name-keyed source" ) fetch_indicator("demo_named", "demo_value") # Leave the registry as it was found. remove_country_source(c("demo", "demo_named"))
The counterpart to register_country_source(). Registering is permanent for
the session, so without this there was no way to undo one – which made any
code that registers a source (an example, a test, an exploratory script)
leave the registry permanently changed, and country_sources() report
different rows afterwards.
remove_country_source(source)remove_country_source(source)
source |
One or more source names, as given to
|
The five built-in sources cannot be removed: they are what the package
documents, and dropping one would make ?fetch_indicator wrong. Pass
cache = FALSE to a fetch instead if you want to bypass one.
The names actually removed, invisibly.
register_country_source(), country_sources()
register_country_source("scratch", function(indicator, ...) NULL) "scratch" %in% country_sources()$source remove_country_source("scratch") "scratch" %in% country_sources()$sourceregister_country_source("scratch", function(indicator, ...) NULL) "scratch" %in% country_sources()$source remove_country_source("scratch") "scratch" %in% country_sources()$source
The "act on it" companion to check_country_match(): replaces unmatched
country names with their closest known country name (by string distance), but
only when the match is confident enough, and reports what it changed. Pipe the
result into standardize_country() / join_world().
repair_country_names( x, threshold = 0.2, origin = "country.name", quiet = FALSE, verbose = deprecated() )repair_country_names( x, threshold = 0.2, origin = "country.name", quiet = FALSE, verbose = deprecated() )
x |
A vector of country names. |
threshold |
Maximum string distance to accept a repair (0 = identical,
1 = unrelated). Lower is stricter; default Install |
origin |
countrycode origin scheme (default |
quiet |
If |
verbose |
A character vector the same length as x, with confident misses
replaced by the closest known country name (others left unchanged). The
applied substitutions are attached as the attribute "repairs".
check_country_match() for the report this acts on, and
dissolve_country() for dissolved entities, which are deliberately not
repaired.
repair_country_names(c("United States", "Brzil", "Germny"))repair_country_names(c("United States", "Brzil", "Germny"))
Is the cross-country distribution actually narrowing? Reports the dispersion
of a (positive) indicator across countries for every year of a panel –
falling dispersion is sigma convergence. The natural companion to
beta_convergence(): beta convergence is necessary but not sufficient for
sigma convergence.
sigma_convergence(data, value, measure = c("sd_log", "cv"))sigma_convergence(data, value, measure = c("sd_log", "cv"))
data |
A panel with |
value |
The value column (unquoted). |
measure |
|
A tibble with one row per year: year, n (countries with
positive values) and sigma.
beta_convergence() for the growth-regression counterpart.
df <- data.frame( iso3c = rep(c("A", "B", "C"), 2), year = rep(c(2000L, 2010L), each = 3), gdp = c(1, 10, 100, 2, 11, 60) # dispersion falls ) sigma_convergence(df, gdp)df <- data.frame( iso3c = rep(c("A", "B", "C"), 2), year = rep(c(2000L, 2010L), each = 3), gdp = c(1, 10, 100, 2, 11, 60) # dispersion falls ) sigma_convergence(df, gdp)
Reduce the vertex count of an sf object via the optional rmapshaper
package (falling back to sf::st_simplify()), for fast web/plotting.
simplify_geometry(x, keep = 0.05, ...)simplify_geometry(x, keep = 0.05, ...)
x |
An |
keep |
Proportion of vertices to keep: greater than 0 and at most 1
( |
... |
Passed to the underlying simplifier. |
A simplified sf object.
if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE)) { world_geometry(geometry = "sf") |> simplify_geometry(keep = 0.1) }if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE)) { world_geometry(geometry = "sf") |> simplify_geometry(keep = 0.1) }
Empirical-Bayes smoothing: a rate computed over a small denominator is pulled
toward the overall rate in proportion to how little information stands behind
it, while a rate over a large denominator is left essentially alone. Standard
practice in disease mapping, and the statistical counterpart to
value_by_alpha_map()'s visual answer.
smooth_rates( data, numerator, denominator, method = c("eb", "local_eb", "none"), suffix = "_smoothed", weights = NULL )smooth_rates( data, numerator, denominator, method = c("eb", "local_eb", "none"), suffix = "_smoothed", weights = NULL )
data |
A country-level frame. |
numerator, denominator
|
The count and its denominator (unquoted). |
method |
|
suffix |
Suffix for the new columns (default |
weights |
For |
data with <numerator>_rate and <numerator>_smoothed columns
added, plus <numerator>_shrinkage – the weight given to the country's own
rate, between 0 (fully shrunk to the global rate) and 1 (untouched). A row
with no finite, positive denominator or with a negative count has no rate:
all three are NA there, with a warning.
A Poisson-gamma model: counts
with , whose mean and variance are estimated
from the data by the method of moments. The posterior mean is
with , so the
shrinkage weight is exactly the "how much do we believe this country"
quantity that rate_check() flags. Where the between-country variance is
estimated as non-positive (rates no more dispersed than Poisson noise alone),
every rate shrinks fully to the global mean, which is the right answer:
the data contain no evidence of real between-country variation.
On a panel the prior is estimated separately for each year, so every
rate is shrunk toward its own year's global rate and every row is kept.
"local_eb" estimates the same two moments in each country's neighbourhood
(itself and its neighbours): the local rate and the local
between-country variance , computed from the deviations of the
neighbourhood's rates from . It agrees with
spdep::EBlocal(geoda = TRUE) on the same neighbours.
Marshall, R. J. (1991). Mapping disease and mortality rates using empirical Bayes estimators. Journal of the Royal Statistical Society, Series C 40(2), 283-294. doi:10.2307/2347593
Anselin, L., Lozano, N. & Koschinsky, J. (2006). Rate transformations and smoothing. Spatial Analysis Laboratory, University of Illinois.
rate_check(), per_capita(), value_by_alpha_map()
d <- data.frame( iso3c = c("CHN", "IND", "TUV", "NRU"), cases = c(50000, 42000, 3, 1), pop = c(1.41e9, 1.39e9, 11000, 12000) ) smooth_rates(d, cases, pop)d <- data.frame( iso3c = c("CHN", "IND", "TUV", "NRU"), cases = c(50000, 42000, 3, 1), pop = c(1.41e9, 1.39e9, 11000, 12000) ) smooth_rates(d, cases, pop)
Thin wrappers that put a provider's data on the ISO spine. Our World in Data
and the OECD are read straight from their public APIs through the package's
own HTTP client, with a timeout and bounded retries; Eurostat and UN Comtrade
go through their own client packages, which are Suggests. All are
registered as sources, so the usual route is fetch_indicator()("owid", ...) rather than calling these directly; they are exported because calling
them directly is sometimes what you want.
fetch_owid(indicator, countries = NULL, years = NULL, ...) fetch_eurostat(indicator, countries = NULL, years = NULL, ...) fetch_oecd(indicator, countries = NULL, years = NULL, key = NULL, ...) fetch_comtrade(indicator, countries = NULL, years = NULL, ...)fetch_owid(indicator, countries = NULL, years = NULL, ...) fetch_eurostat(indicator, countries = NULL, years = NULL, ...) fetch_oecd(indicator, countries = NULL, years = NULL, key = NULL, ...) fetch_comtrade(indicator, countries = NULL, years = NULL, ...)
indicator |
Indicator code(s), optionally named to rename the output
columns. For |
countries |
Optional |
years |
Optional numeric year vector. |
... |
Passed to the underlying client ( |
key |
For |
A tibble on the ISO spine: iso3c, year and one column per
indicator.
| Adapter | Needs | Notes |
fetch_owid() |
nothing | OWID's Chart API; indicator is a grapher slug, keyed on OWID's ISO codes |
fetch_oecd() |
nothing | the OECD's SDMX service; indicator is a dataflow, key selects the series |
fetch_eurostat() |
eurostat |
European coverage only; geo codes are harmonised to iso3c |
fetch_comtrade() |
comtradr |
UN trade flows; needs an API token (see comtradr::set_primary_comtrade_key())
|
The IMF and the ILO are reachable as fetch_indicator("imf", flow, key = )
and fetch_indicator("ilo", flow, key = ), through fetch_sdmx().
A chart's data comes from https://ourworldindata.org/grapher/<slug>.csv
and its metadata – title, unit, citation, last update – from the matching
.metadata.json, which feeds source_info(). Rows are keyed on OWID's
code column, which is ISO 3166-1 alpha-3 for countries; OWID's own
aggregates (OWID_WRL, the continents, the income groups) are not
countries and are dropped, except OWID_KOS, which is Kosovo (XKX). A
chart with several value columns returns them all, named
<indicator>_<column> (or by OWID's short column names when indicator is
unnamed), unless "slug/column" picks one.
OECD.Stat, which the OECD package's client targets, went offline on
2024-07-01, so the 3.0.0 adapter could not return data. The OECD Data
Explorer's dataflow ids replace the old dataset codes; find a flow and its
key at https://data-explorer.oecd.org (its "Developer API" panel shows
both). The old codes do not exist anywhere any more.
fetch_indicator(), register_country_source(), compare_sources()
## Not run: fetch_owid("life-expectancy", years = 2020) fetch_eurostat("demo_pjan", years = 2020) ## End(Not run)## Not run: fetch_owid("life-expectancy", years = 2020) fetch_eurostat("demo_pjan", years = 2020) ## End(Not run)
Every fetch – fetch_indicator(), country_data(), world_data(),
add_indicator(), and the population, deflator and PPP series that
per_capita(), deflate() and to_ppp() fetch for themselves – records
which provider each value column came from, in what unit, from which
release and when. source_info() reads that record; source_info<- writes
one for your own data, so a map of it can say where it came from too.
source_info(x) source_info(x) <- valuesource_info(x) source_info(x) <- value
x |
A data frame. |
value |
A data frame with a |
source_info(): a tibble, one row per described column, with the
ten fields above (no rows when nothing is recorded). source_info<-: x
with the record attached.
The record is an attribute. dplyr's filter(), mutate(), arrange(),
select() and left_join() (on the left-hand side) keep a data frame's
attributes, and summarise() drops them; the package's own verbs carry the
record explicitly, including attach_geometry(), which builds its result
from the geometry. The map verbs copy it into map_provenance(), whose
print method names the source of the fill, and footnote = "auto" adds a
one-line source note to the caption.
map_provenance(), compare_sources(), compare_vintages()
d <- data.frame(iso3c = c("FRA", "DEU"), rate = c(7.3, 3.1)) source_info(d) <- data.frame(column = "rate", source = "My survey", unit = "%", fetched_at = "2026-10-01") source_info(d)d <- data.frame(iso3c = c("FRA", "DEU"), rate = c(7.3, 3.1)) source_info(d) <- data.frame(column = "rate", source = "My survey", unit = "%", fetched_at = "2026-10-01") source_info(d)
The spatially lagged value: for each country, the (weighted) mean of its neighbours. The building block behind every statistic here, and useful on its own – "what is happening around this country" as a regressor, a map layer or a scatter-plot axis against the country's own value (the Moran scatterplot).
spatial_lag(data, value, weights = NULL, suffix = "_lag")spatial_lag(data, value, weights = NULL, suffix = "_lag")
data |
A country-level frame with |
value |
The value column (unquoted). |
weights |
A |
suffix |
Suffix for the new column (default |
data with the lagged column added. Countries the weights cannot
reach get NA – and since that NA is indistinguishable from one caused
by a missing input value, the codes themselves are attached as the
"countryatlas_excluded" attribute, the frame-shaped counterpart to the
excluded column morans_i() returns.
country_weights(), local_morans()
snap <- countryatlas::world_snapshot$countries spatial_lag(snap, gdp_per_capita, weights = country_weights("knn", k = 5))snap <- countryatlas::world_snapshot$countries spatial_lag(snap, gdp_per_capita, weights = country_weights("knn", k = 5))
Rey's (2001) spatial Markov chain: one transition matrix for each class of a country's spatial lag – the average class of its neighbours – so you can ask whether a poor country among poor neighbours is less likely to move up than one among rich neighbours. A homogeneity test says whether the conditional matrices differ from the pooled one.
spatial_markov( data, value, weights = NULL, n_classes = 5, classes = c("quantile", "relative"), step = 1 )spatial_markov( data, value, weights = NULL, n_classes = 5, classes = c("quantile", "relative"), step = 1 )
data |
A panel with |
value |
The column to classify (unquoted), such as GDP per capita. |
weights |
A |
n_classes |
Number of classes (default |
classes |
|
step |
Years between the two observations of a transition (default
|
A tibble of lag_class, from, to, n and p, with the
attribute test: a tibble of the likelihood-ratio and chi-square
homogeneity statistics, their degrees of freedom and p-values
(Bickenbach & Bode 2003), where cells with no transitions do not count
toward the degrees of freedom (Kang & Rey 2018).
Rey, S. J. (2001). Spatial empirics for economic growth and convergence. Geographical Analysis 33(3), 195-214. doi:10.1111/j.1538-4632.2001.tb00444.x
Bickenbach, F. & Bode, E. (2003). Evaluating the Markov property in studies of economic convergence. International Regional Science Review 26(3), 363-392. doi:10.1177/0160017603253789
Kang, W. & Rey, S. J. (2018). Conditional and joint tests for spatial effects in discrete Markov chain models of regional income distribution dynamics. The Annals of Regional Science 61(1), 73-93. doi:10.1007/s00168-017-0859-9
transition_matrix(), spatial_lag()
set.seed(1) iso <- countryatlas::world_snapshot$countries$iso3c pan <- expand.grid(iso3c = iso, year = 2010:2015, stringsAsFactors = FALSE) pan$v <- exp(stats::rnorm(nrow(pan))) spatial_markov(pan, v, n_classes = 3)set.seed(1) iso <- countryatlas::world_snapshot$countries$iso3c pan <- expand.grid(iso3c = iso, year = 2010:2015, stringsAsFactors = FALSE) pan$v <- exp(stats::rnorm(nrow(pan))) spatial_markov(pan, v, n_classes = 3)
The classic "population spikes" display: a triangular spike at each country
centroid whose height encodes the value. Like bubble_map() it is the
honest idiom for totals, with a different visual trade-off: spikes
overplot less in dense regions (Europe, the Caribbean) because they only
grow upward. Uses the polygon backend, so it needs no extra package.
spike_map( data, height, max_height = 20, width = 1.6, color = "#B2182B", alpha = 0.65, projection = "equal_earth", footnote = "auto" )spike_map( data, height, max_height = 20, width = 1.6, color = "#B2182B", alpha = 0.65, projection = "equal_earth", footnote = "auto" )
data |
A country-level frame with |
height |
The column controlling spike height (unquoted). |
max_height |
Height of the tallest spike, in degrees of latitude
(default |
width |
Base width of each spike, in degrees of longitude at the
equator (default |
color |
Spike colour (default a warm red). |
alpha |
Spike fill transparency. |
projection |
Map projection, as in |
footnote |
The caption: |
A ggplot object.
spike_map(countryatlas::world_snapshot$countries, population)spike_map(countryatlas::world_snapshot$countries, population)
An animated GIF of the world rotating on its axis: a sequence of orthographic
globe_map() frames at evenly spaced central longitudes, assembled into a
looping animation with the optional gifski (preferred) or magick package.
Embeds directly in R Markdown / Quarto / a README.
spin_globe( data, fill, lat = 20, n_frames = 60, fps = 15, backend = c("polygon", "sf"), width = 480, height = 480, file = NULL, ... )spin_globe( data, fill, lat = 20, n_frames = 60, fps = 15, backend = c("polygon", "sf"), width = 480, height = 480, file = NULL, ... )
data |
A map-ready frame (see |
fill |
The fill column (unquoted). |
lat |
The latitude the globe is tilted toward (the viewer's eye line). |
n_frames |
Number of frames in one full 360 degrees rotation. |
fps |
Frames per second of the output animation. |
backend |
|
width, height
|
Pixel dimensions of the animation. |
file |
Optional output path ( |
... |
Passed to |
The path to the written GIF, invisibly.
# Six frames rather than the default 60, so this stays quick enough to be # checked: \dontrun{} meant the example was never executed by anything, and # an example nothing runs is an example free to rot. if (requireNamespace("mapproj", quietly = TRUE) && (requireNamespace("gifski", quietly = TRUE) || requireNamespace("magick", quietly = TRUE))) { # No sf required on the polygon backend. gif <- spin_globe(world_snapshot$countries, continent, backend = "polygon", style = "categorical", n_frames = 6, width = 200, height = 200) file.exists(gif) # written to a temporary file }# Six frames rather than the default 60, so this stays quick enough to be # checked: \dontrun{} meant the example was never executed by anything, and # an example nothing runs is an example free to rot. if (requireNamespace("mapproj", quietly = TRUE) && (requireNamespace("gifski", quietly = TRUE) || requireNamespace("magick", quietly = TRUE))) { # No sf required on the polygon backend. gif <- spin_globe(world_snapshot$countries, continent, backend = "polygon", style = "categorical", n_frames = 6, width = 200, height = 200) file.exists(gif) # written to a temporary file }
The package's mission, exposed for your data: take a data frame keyed on
messy country names (or codes) and attach standardised ISO codes plus useful
classifications, reconciling spellings via countrycode::countrycode() and
the curated country_overrides() table. The result joins cleanly to anything
else keyed on iso3c.
standardize_country( data, country_col, origin = "country.name", add = c("iso3c", "iso2c", "continent", "region"), custom_match = country_overrides(), warn = TRUE )standardize_country( data, country_col, origin = "country.name", add = c("iso3c", "iso2c", "continent", "region"), custom_match = country_overrides(), warn = TRUE )
data |
A data frame / tibble. |
country_col |
The column holding country names or codes (unquoted, tidy-eval). |
origin |
How to read |
add |
Character vector of attributes to add. Defaults to
|
custom_match |
A named character vector of name -> iso3c overrides;
defaults to |
warn |
Whether to warn about unmatched countries (default |
data with the requested columns added (and existing same-named
columns overwritten).
df <- data.frame(nation = c("U.S.", "S. Korea", "Czechia"), value = 1:3) standardize_country(df, nation)df <- data.frame(nation = c("U.S.", "S. Korea", "Czechia"), value = 1:3) standardize_country(df, nation)
The subnational counterpart to standardize_country(): resolve messy region
names within a country to ISO 3166-2 codes, so subnational data can be joined
on a real key instead of on spelling.
standardize_subnational( data, region, country, origin = "country.name", warn = TRUE )standardize_subnational( data, region, country, origin = "country.name", warn = TRUE )
data |
A data frame with a region column. |
region |
The region-name column (unquoted). |
country |
The country column (unquoted), or a single country name/code applying to every row. ISO 3166-2 codes are only unique within a country, so this is required. |
origin |
How to read |
warn |
Warn about regions that do not resolve (default |
data with iso3c and iso_3166_2 columns added. Unresolved
regions get NA, never a guess.
Two things resolve, and it is worth being blunt about how little that is.
A region value that is already an ISO 3166-2 code ("DE-BY", "US-CA")
passes through, upper-cased and trimmed, provided its country prefix
matches the country the row gives – these codes are unique only within a
country, which is why country is required. A mismatch is reported and
left as NA.
A region name resolves only through the optional regions package's
crosswalk, and only when the installed version exposes a name-to-code pair
this function recognises. As of regions 0.1.8 none of them do:
nuts_lau_2019 offers lau_name_national / lau_name_latin and
all_valid_nuts_codes has no name column, so no region name resolves at
all, anywhere – the function says so once per session. The package
carries no ISO 3166-2 name table of its own, deliberately: the datasets that
do pair names with codes key on NUTS codes (DE2) rather than ISO 3166-2
(DE-BY), and filling iso_3166_2 from those would put a different code
system in the column.
So: pass codes if you have them. If you have names, expect NA until
regions ships a usable crosswalk. This function returns NA rather than a
plausible-looking wrong code, and audit_coverage() on the result is the
right next step.
subnational_map(), nuts_geometry(), standardize_country()
d <- data.frame(region = c("Bavaria", "Hesse", "Nowhere"), value = 1:3) if (requireNamespace("regions", quietly = TRUE)) { standardize_subnational(d, region, country = "Germany") }d <- data.frame(region = c("Bavaria", "Hesse", "Nowhere"), value = 1:3) if (requireNamespace("regions", quietly = TRUE)) { standardize_subnational(d, region, country = "Germany") }
A choropleth below the country level, joining your data to NUTS geometry on
the region code. The subnational counterpart to world_map(), scoped to
where a maintained code system and free geometry actually exist.
subnational_map( data, fill, by = "nuts_id", level = 2, year = 2021, countries = NULL, resolution = "60", ... )subnational_map( data, fill, by = "nuts_id", level = 2, year = 2021, countries = NULL, resolution = "60", ... )
data |
A frame with a NUTS/ISO 3166-2 code column. |
fill |
The fill column (unquoted). |
by |
The code column in |
level, year, countries, resolution
|
Passed to |
... |
Passed to |
A ggplot object.
nuts_geometry(), standardize_subnational(), world_map()
## Not run: d <- data.frame(nuts_id = c("DE21", "DE22"), value = c(1, 2)) subnational_map(d, value, level = 2, countries = "DEU") ## End(Not run)## Not run: d <- data.frame(nuts_id = c("DE21", "DE22"), value = c(1, 2)) subnational_map(d, value, level = 2, countries = "DEU") ## End(Not run)
Colours each country by the balance of three parts of a whole: the agriculture, industry and services shares of GDP, or the young, working-age and old shares of a population. Each part has a primary colour, and a country's colour mixes the three in proportion to its shares, so a country dominated by one part takes that part's colour and an even mix is grey (the ternary balance scheme).
ternary_map( data, x, y, z, centre = TRUE, projection = "equal_earth", recenter = NULL, hue = 80, key = TRUE, title = NULL, legend = NULL, footnote = "auto" )ternary_map( data, x, y, z, centre = TRUE, projection = "equal_earth", recenter = NULL, hue = 80, key = TRUE, title = NULL, legend = NULL, footnote = "auto" )
data |
A map-ready frame from |
x, y, z
|
The three parts (unquoted), each non-negative. They are closed to shares, so counts and percentages work as well as proportions. A country missing any part, or with all three zero, is drawn as no data. |
centre |
|
projection, recenter
|
As in |
hue |
The hue of |
key |
Draw the colour triangle in the legend area? Needs ggplot2 3.5.0 or later. |
title, legend
|
Optional plot title and key title. |
footnote |
The caption, as in |
Countries' compositions usually sit close together, and on the plain scheme
they come out in near-identical colours. centre = TRUE (the default)
centres the scheme on the average composition (Schoeley 2021): grey is the
average country, and a colour says which part a country has more of than
the average. The key is the colour triangle with every country on it as a
ring, and the caption states the average composition the colours are
relative to.
A ggplot object. map_provenance() records the centre.
Schoeley, J. (2021). The centered ternary balance scheme: a technique to visualize surfaces of unbalanced three-part compositions. Demographic Research 44(19), 443-458. doi:10.4054/DemRes.2021.44.19
# A made-up composition: three parts that add up to each country's total. snap <- countryatlas::world_snapshot$countries set.seed(1) snap$a <- stats::runif(nrow(snap), 1, 4) snap$b <- stats::runif(nrow(snap), 2, 6) snap$c <- stats::runif(nrow(snap), 4, 9) ternary_map(attach_geometry(snap), a, b, c)# A made-up composition: three parts that add up to each country's total. snap <- countryatlas::world_snapshot$countries set.seed(1) snap$a <- stats::runif(nrow(snap), 1, 4) snap$b <- stats::runif(nrow(snap), 2, 6) snap$c <- stats::runif(nrow(snap), 4, 9) ternary_map(attach_geometry(snap), a, b, c)
The Theil inequality indices – less famous than Gini, but they decompose
exactly into a between-group and a within-group component, answering "how
much of world inequality is between continents vs within them?" in one
call. Weight by population to describe inequality between people rather
than between country units. type = "T" (default) is Theil's T, weighted
by income shares; type = "L" is Theil's L, the mean log deviation,
weighted by population shares, whose decomposition is path-independent.
theil(x, weights = NULL, groups = NULL, na.rm = TRUE, type = c("T", "L"))theil(x, weights = NULL, groups = NULL, na.rm = TRUE, type = c("T", "L"))
x |
A positive numeric vector (log scale; zero/negative values are dropped with a warning). |
weights |
Optional non-negative weights (e.g. population), either the
same length as |
groups |
Optional grouping vector (e.g. continent), the same length as
|
na.rm |
Whether to drop |
type |
|
Without groups: a single non-negative number (0 = perfect
equality). With groups: a tibble with components "total",
"between" and "within" (total = between + within) and each
component's share of the total (NA when the total is 0, i.e.
perfect equality, and the shares are undefined).
When there is nothing to compute (no values left after na.rm, a zero
total weight, an infinity in x or weights, or, with na.rm = FALSE, a
missing value or group), the result is a single NA whatever groups
says, so reach for the components only after checking is.data.frame().
gini() for the more familiar single-number summary, which does not
decompose.
snap <- countryatlas::world_snapshot$countries theil(snap$gdp_per_capita, weights = snap$population) theil(snap$gdp_per_capita, weights = snap$population, groups = snap$continent)snap <- countryatlas::world_snapshot$countries theil(snap$gdp_per_capita, weights = snap$population) theil(snap$gdp_per_capita, weights = snap$population, groups = snap$continent)
Strips axes, panel grid and background so the map is the focus. Applied by
every plotting function in the package except bivariate_map(), which uses
biscale::bi_theme() so the map matches its own legend, and exported here for
reuse on plots you build yourself.
theme_world_map(base_size = 12, base_family = "")theme_world_map(base_size = 12, base_family = "")
base_size |
Base font size. |
base_family |
Base font family. |
A ggplot2 theme object.
library(ggplot2) ggplot() + theme_world_map()library(ggplot2) ggplot() + theme_world_map()
A statebins-style equal-area tile grid of the world (one square per country)
so tiny states are actually visible. Uses the bundled world_tiles layout.
For small multiples of a tile grid, facet the result as you would any other
ggplot (or see facet_map() for the choropleth equivalent).
tile_map(data, fill, label = TRUE, footnote = "auto")tile_map(data, fill, label = TRUE, footnote = "auto")
data |
A country-level frame with |
fill |
The fill column (unquoted). |
label |
Whether to draw ISO codes on the tiles (default |
footnote |
The caption, as in |
Every tile in the layout is drawn, taking the scale's na.value fill where
data has no row for it. The converse also holds and is quieter: data rows
keyed on one of the 3 countries with no tile are dropped without a warning
(see world_tiles for which).
A ggplot object.
Drawn on the bundled equal-area tile grid (world_tiles), one square per country; no geometry backend is involved.
tile_map(countryatlas::world_snapshot$countries, gdp_per_capita)tile_map(countryatlas::world_snapshot$countries, gdp_per_capita)
Every country's trajectory in a panel at once: a sparkline per country, placed on the bundled equal-area world_tiles grid, so Europe and the Caribbean are as readable as Russia. It is the glyph-map idea (Wickham, Hofmann, Wickham & Cook 2012) on a grid of equal cells, which removes the overlap glyphs at country centroids suffer where countries are small and close together.
tile_trend_map( data, value, years = NULL, label = TRUE, scales = c("free_y", "fixed"), footnote = "auto" )tile_trend_map( data, value, years = NULL, label = TRUE, scales = c("free_y", "fixed"), footnote = "auto" )
data |
A panel with |
value |
The column to draw (unquoted). |
years |
Optional years to draw: a range |
label |
Whether to print each tile's ISO code (default |
scales |
|
footnote |
The caption, as in |
A ggplot object. A country needs two years with values to have a
line; one with fewer, or with no tile (see world_tiles), counts as
missing in the caption and in map_provenance().
Drawn on the bundled equal-area tile grid (world_tiles), one square per country; no geometry backend is involved.
Wickham, H., Hofmann, H., Wickham, C. & Cook, D. (2012). Glyph-maps for visually exploring temporal patterns in climate data and models. Environmetrics 23(5), 382-393. doi:10.1002/env.2152
tile_map() for one value per country, facet_map()
set.seed(1) pan <- expand.grid(iso3c = countryatlas::world_tiles$iso3c, year = 2000:2020) pan$v <- stats::ave(stats::rnorm(nrow(pan)), pan$iso3c, FUN = cumsum) tile_trend_map(pan, v)set.seed(1) pan <- expand.grid(iso3c = countryatlas::world_tiles$iso3c, year = 2000:2020) pan$v <- stats::ave(stats::rnorm(nrow(pan)), pan$iso3c, FUN = cumsum) tile_trend_map(pan, v)
Draws Tissot indicatrices – small circles of equal ground radius, placed on a graticule and projected with everything else. On an equal-area projection every ellipse encloses the same area (though shapes shear); on a conformal projection every ellipse stays circular but sizes explode. It is the standard cartographic device for showing what a projection costs, and it makes the package's "honest maps" claim visible instead of asserted.
tissot_map( projection = "equal_earth", spacing = 30, radius_km = 500, max_lat = 75, fill = "#B2182B", color = "grey20" )tissot_map( projection = "equal_earth", spacing = 30, radius_km = 500, max_lat = 75, fill = "#B2182B", color = "grey20" )
projection |
Projection to illustrate (see |
spacing |
Degrees between indicatrices (default |
radius_km |
Ground radius of each circle in kilometres (default |
max_lat |
Absolute latitude limit for the circle centres (default |
fill, color
|
Fill and outline colour for the ellipses. |
A ggplot object: the world outline with indicatrices on top.
projection_info(), projection_compare()
if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE)) { tissot_map("mercator") # circles stay round, and grow enormously tissot_map("equal_earth") # equal areas, sheared shapes }if (requireNamespace("sf", quietly = TRUE) && requireNamespace("rnaturalearth", quietly = TRUE)) { tissot_map("mercator") # circles stay round, and grow enormously tissot_map("equal_earth") # equal areas, sheared shapes }
Market exchange rates do not equalise what money buys. to_ppp() divides a
local-currency series by the PPP conversion factor, putting every country on
comparable international dollars – the correction that makes a cross-country
level comparison meaningful.
to_ppp(data, value, factor = NULL, suffix = "_ppp")to_ppp(data, value, factor = NULL, suffix = "_ppp")
data |
A panel with |
value |
The local-currency value column (unquoted). |
factor |
Either a column holding the PPP conversion factor (unquoted),
or |
suffix |
Suffix for the new column (default |
data with the PPP-converted column added.
d <- data.frame(iso3c = c("IND", "USA"), year = 2020L, gdp_lcu = c(1e5, 1e4), ppp = c(21.9, 1)) to_ppp(d, gdp_lcu, factor = ppp)d <- data.frame(iso3c = c("IND", "USA"), year = 2020L, gdp_lcu = c(1e5, 1e4), ppp = c(21.9, 1)) to_ppp(d, gdp_lcu, factor = ppp)
How countries move between classes of a variable from one year to the next: the discrete Markov chain of Quah's distribution dynamics. Class 1 is the lowest.
transition_matrix( data, value, n_classes = 5, classes = c("quantile", "relative"), step = 1 )transition_matrix( data, value, n_classes = 5, classes = c("quantile", "relative"), step = 1 )
data |
A panel with |
value |
The column to classify (unquoted), such as GDP per capita. |
n_classes |
Number of classes (default |
classes |
|
step |
Years between the two observations of a transition (default
|
A tibble of from, to, n (transitions observed) and p (the
probability of moving from from to to, NA for a class nothing left),
with attributes ergodic (the steady-state share of countries in each
class, if the chain ran on) and first_passage (the matrix of mean
first-passage times, in steps).
Quah, D. (1993). Empirical cross-section dynamics in economic growth. European Economic Review 37(2-3), 426-434. doi:10.1016/0014-2921(93)90031-5
Quah, D. T. (1996). Twin peaks: growth and convergence in models of distribution dynamics. The Economic Journal 106(437), 1045-1055. doi:10.2307/2235377
spatial_markov(), rank_mobility(), beta_convergence(),
sigma_convergence(), convergence_club()
set.seed(1) pan <- expand.grid(iso3c = c("FRA", "DEU", "BRA", "IND", "CHN", "NGA"), year = 2000:2010, stringsAsFactors = FALSE) pan$gdp <- exp(stats::rnorm(nrow(pan), 9, 1)) transition_matrix(pan, gdp, n_classes = 3)set.seed(1) pan <- expand.grid(iso3c = c("FRA", "DEU", "BRA", "IND", "CHN", "NGA"), year = 2000:2010, stringsAsFactors = FALSE) pan$gdp <- exp(stats::rnorm(nrow(pan), 9, 1)) transition_matrix(pan, gdp, n_classes = 3)
A choropleth where colour carries the value and opacity carries an equalising variable (usually population), over a neutral background. It is the answer to the small-number problem – a rate computed over eleven thousand people shouts as loudly as one computed over a billion – and unlike a cartogram it solves it without distorting geometry, which is the main objection to cartograms. Roth, Woodruff & Johnson (2010) introduced it for exactly this purpose.
value_by_alpha_map( data, value, equalize, style = c("quantile", "continuous", "binned", "equal", "jenks", "fisher", "headtails", "sd", "fixed"), palette = NULL, n_bins = 5, breaks = NULL, midpoint = NULL, alpha_range = c(0.15, 1), transform = c("rank", "log10", "identity"), background = "grey20", title = NULL, legend = NULL, projection = "equal_earth", footnote = "auto", small_states = c("auto", "none", "dots"), small_area_km2 = 1000 )value_by_alpha_map( data, value, equalize, style = c("quantile", "continuous", "binned", "equal", "jenks", "fisher", "headtails", "sd", "fixed"), palette = NULL, n_bins = 5, breaks = NULL, midpoint = NULL, alpha_range = c(0.15, 1), transform = c("rank", "log10", "identity"), background = "grey20", title = NULL, legend = NULL, projection = "equal_earth", footnote = "auto", small_states = c("auto", "none", "dots"), small_area_km2 = 1000 )
data |
A map-ready frame (polygon or |
value |
The value column, carried by colour (unquoted). |
equalize |
The equalising column, carried by opacity (unquoted) – population, total counts, or whatever denominator the rate was built on. |
style |
Classification for the colour channel, as in |
palette, breaks, midpoint
|
As in |
n_bins |
Number of colour bins for the binned styles. |
alpha_range |
Minimum and maximum opacity (default |
transform |
Transform applied to |
background |
Colour behind the countries, which shows through where opacity is low (default a dark neutral). |
title, legend
|
Optional plot title and legend title. |
projection |
Projection, as in |
footnote, small_states, small_area_km2
|
As in |
A ggplot object.
Either backend, from the frame: an sf frame draws through coord_sf(), a
polygon frame on the polygon backend, both in projection (Equal Earth by
default).
Roth, R. E., Woodruff, A. W. & Johnson, Z. F. (2010). Value-by-alpha maps: an alternative technique to the cartogram. The Cartographic Journal 47(2), 130-140. doi:10.1179/000870409X12488753453372
cartogram_map() and dorling_map() (the geometry-distorting
answers to the same problem), world_map()
snap <- countryatlas::world_snapshot$countries attach_geometry(snap, geometry = "polygon") |> value_by_alpha_map(gdp_per_capita, population)snap <- countryatlas::world_snapshot$countries attach_geometry(snap, geometry = "polygon") |> value_by_alpha_map(gdp_per_capita, population)
A tidy, pipeable wrapper on WDI::WDIsearch() for discovering indicator
codes.
wdi_search(pattern, field = c("name", "indicator"), cache = NULL)wdi_search(pattern, field = c("name", "indicator"), cache = NULL)
pattern |
A regular expression to search indicator names/codes for. |
field |
Which field to search: |
cache |
Optional cached |
A tibble of matching indicator codes and names.
# Searches WDI's bundled indicator list, so this needs no connection. wdi_search("CO2 emissions")# Searches WDI's bundled indicator list, so this needs no connection. wdi_search("CO2 emissions")
Every release in the World Bank's WDI Database Archives (API source 57),
oldest first, so a figure can be pinned to the release it came from with
vintage = in fetch_indicator(), country_data() and world_data().
The list is fetched once a day per session.
wdi_vintages()wdi_vintages()
A tibble of vintage ("2024-07"), id (the API's "202407") and
label (the API's own, "2024 Jul"). On a failed request, a warning and
no rows, unless options(countryatlas.strict = TRUE).
tail(wdi_vintages())tail(wdi_vintages())
The package's headline function, generalised but backward-compatible. Returns
a tibble that already stitches together map geometry, World Bank indicators
and the countrycode crosswalk, keyed on the ISO spine – ready to pipe into
world_map() or ggplot2.
world_data( year, indicator = c(gdp_per_capita = "NY.GDP.PCAP.KD"), geometry = c("polygon", "sf", "none"), scale = c("small", "medium", "large"), region = NULL, classify = c("income", "continent", "region"), projection = "equal_earth", recenter = NULL, latest = FALSE, cache = TRUE, language = "en", parallel = TRUE, overrides = country_overrides(), vintage = NULL )world_data( year, indicator = c(gdp_per_capita = "NY.GDP.PCAP.KD"), geometry = c("polygon", "sf", "none"), scale = c("small", "medium", "large"), region = NULL, classify = c("income", "continent", "region"), projection = "equal_earth", recenter = NULL, latest = FALSE, cache = TRUE, language = "en", parallel = TRUE, overrides = country_overrides(), vintage = NULL )
year |
A single year or a range (e.g. |
indicator |
A named character vector of WDI codes. Names drive column
names, e.g. |
geometry |
|
scale |
Natural Earth resolution for the |
region |
Optional subset: a continent, group name, |
classify |
Which classifications to add (any of |
projection, recenter
|
Projection, and optional central meridian, for
the |
latest |
For a single-year request: |
cache |
Whether to use the memoised / on-disk WDI cache. |
language |
WDI language code (default |
parallel |
Whether to fetch multiple indicators in parallel. Ignored
when the cache is memory-only (an unwritable |
overrides |
Name -> iso3c overrides for geometry matching (default
|
vintage |
Which release of the World Development Indicators to read:
|
world_data(2020) keeps its original behaviour (polygon backend, GDP per
capita). Everything else is opt-in: any indicator(s), a span of years (a
panel), an sf backend with real projections, and region subsetting.
A tibble (polygon backend), sf object (sf backend) or country-level
tibble (geometry = "none").
iso3c is the stable key; country is a label and its spelling depends on
where the row came from. A successful fetch carries the World Bank's names
("Korea, Rep.", "Congo, Dem. Rep."), while the country spine used when the
fetch returns nothing carries the countrycode names ("South Korea",
"Congo - Kinshasa") – as do convert_country(), standardize_country()
and the rest of the package. Match on iso3c, and relabel with
convert_country(iso3c, to = "country") if you need one consistent set.
# geometry = "polygon", the default, is bundled: nothing to install. world_data(2020) # geometry = "none" returns the country table alone. world_data(2020, indicator = c(life_exp = "SP.DYN.LE00.IN"), geometry = "none")# geometry = "polygon", the default, is bundled: nothing to install. world_data(2020) # geometry = "none" returns the country table alone. world_data(2020, indicator = c(life_exp = "SP.DYN.LE00.IN"), geometry = "none")
Sometimes you just want the canvas: country polygons, label-ready centroids, coastlines, internal borders, a graticule or an ocean rectangle – already projected, region-subset and antimeridian-safe. This is the building block the plotting functions sit on, exposed for power users.
world_geometry( what = c("countries", "centroids", "coastline", "borders", "graticule", "ocean"), geometry = c("polygon", "sf"), scale = "small", region = NULL, projection = "equal_earth", recenter = NULL, year = NULL, worldview = NULL )world_geometry( what = c("countries", "centroids", "coastline", "borders", "graticule", "ocean"), geometry = c("polygon", "sf"), scale = "small", region = NULL, projection = "equal_earth", recenter = NULL, year = NULL, worldview = NULL )
what |
What to return: |
geometry |
|
scale |
Natural Earth resolution for the |
region |
Optional subset: a continent, a group name, a vector of |
projection |
Projection for the |
recenter |
Optional central meridian (e.g. |
year |
Draw the world as it was in this year, via |
worldview |
Draw the boundaries as one country's government draws
them: the viewing country's ISO alpha-3 code ( |
A tibble (polygon backend) or sf object (sf backend), with columns
depending on what:
"countries"polygon: long, lat, group, order, region,
subregion, iso3c, iso2c. sf: iso3c, iso2c, name_long.
"centroids"the same identifier columns plus centroid_lon and
centroid_lat.
"coastline", "borders", "ocean", "graticule"
sf only.
The centroid columns are in the coordinate system of the object
returned, so on the sf backend they are projected metres, not degrees –
centroid_lon for France is 174097, not 2.1. For centroids in degrees
use country_meta$centroid_lon / $centroid_lat, which is also what the
polygon backend returns.
A few Natural Earth features have no ISO code and so come back with iso3c
NA – Somaliland at every scale, plus the Indian Ocean Territories and
Ashmore and Cartier Islands from "medium" on. They are kept so the land is
still drawn; drop or country_overrides() them if you group by iso3c.
"orthographic" is the one genuinely hemispheric projection: the countries
on the far side have no image and come back as empty geometries, and the
ones on the horizon are cut there (correctly, but sf::st_coordinates()
cannot read a column that mixes empty and non-empty – drop them first).
The other three azimuthal projections
("azimuthal_equal_area", "north_polar", "south_polar") are Lambert
equal-area and draw the whole globe, the far side stretched around the
rim rather than dropped, so pass region if you want a polar view of the
northern countries alone. "north_polar" leaves out what lies wholly
south of 60 degrees S (Antarctica), whose pole is its antipode: an empty
geometry on the sf backend, no rows on the polygon backend.
"ocean" is a whole-globe background rectangle. It is unavailable in all
four azimuthal projections – "orthographic" has no image for it, and the
Lambert three cut the globe at the antipode, which collapses the rectangle's
outline – and it cannot be recentred; both cases error rather than
returning an invisible layer.
head(world_geometry("countries", geometry = "polygon"))head(world_geometry("countries", geometry = "polygon"))
Encapsulates the choropleth boilerplate and goes beyond a single style.
Auto-detects the polygon vs sf backend, applies theme_world_map(), and
projects the map (Equal Earth by default). Classes are the default because
a continuous fill on a skewed indicator hides almost all the variation;
binning is the honest default for choropleths, and quantiles the safe
choice for a general audience.
world_map( data, fill, style = c("quantile", "continuous", "binned", "equal", "jenks", "fisher", "headtails", "sd", "fixed", "categorical"), projection = "equal_earth", palette = NULL, n_bins = 5, breaks = NULL, midpoint = NULL, borders = TRUE, title = NULL, legend = NULL, na_label = "No data", recenter = NULL, na_style = c("grey", "hatched", "outline", "omit"), footnote = "auto", classification_report = FALSE, uncertainty = NULL, n_uncertainty = 3, disputes = c("ignore", "mark"), small_states = c("auto", "none", "dots"), small_area_km2 = 1000, engine = c("ggplot2", "tmap") )world_map( data, fill, style = c("quantile", "continuous", "binned", "equal", "jenks", "fisher", "headtails", "sd", "fixed", "categorical"), projection = "equal_earth", palette = NULL, n_bins = 5, breaks = NULL, midpoint = NULL, borders = TRUE, title = NULL, legend = NULL, na_label = "No data", recenter = NULL, na_style = c("grey", "hatched", "outline", "omit"), footnote = "auto", classification_report = FALSE, uncertainty = NULL, n_uncertainty = 3, disputes = c("ignore", "mark"), small_states = c("auto", "none", "dots"), small_area_km2 = 1000, engine = c("ggplot2", "tmap") )
data |
A map-ready frame from |
fill |
The fill column (unquoted). |
style |
How the fill is classified: |
projection |
Any of the projections in |
palette |
Optional palette: a viridis option ( |
n_bins |
Number of classes for the classed styles. |
breaks |
Fixed class boundaries: a sorted, unique numeric vector of at
least two values, for thresholds that mean something (the World Bank's
income thresholds) and for maps that must be comparable across years and
publications. Implies |
midpoint |
A value to centre a diverging palette on: zero growth, a
target, a threshold. On a colourbar the scale is rescaled so |
borders |
Draw country borders (default |
title, legend
|
Optional plot title and legend title. |
na_label |
Legend key label for missing data, used by the styles with
a discrete legend ( |
recenter |
Optional central meridian (e.g. |
na_style |
How to draw countries with no data: |
footnote |
The caption. |
classification_report |
If |
uncertainty |
Optional uncertainty column (unquoted) – a standard error, a confidence half-width, anything where larger means less certain. Supplying it switches the fill to a value-suppressing uncertainty palette (Correll, Moritz & Heer 2018): the value range contracts as uncertainty rises, so an uncertain estimate cannot claim an extreme colour, and the legend becomes the value x uncertainty grid. |
n_uncertainty |
Number of uncertainty levels for the VSUP (default |
disputes |
|
small_states |
What to do with countries too small to see, or missing
from the basemap: |
small_area_km2 |
The area under which |
engine |
|
A ggplot object.
The default grey reads as "low" to many people, which is exactly wrong for
"unknown". na_style = "hatched" draws diagonal hatching instead –
unambiguous, and it survives greyscale printing. "omit" leaves a hole,
which is honest but can be mistaken for ocean. Whichever you pick,
footnote = "auto" states the count in words:
world_map(mapdf, gdp_per_capita, na_style = "hatched", footnote = "auto")
coverage_map() goes further and maps availability itself.
The polygon backend (the default of attach_geometry(), world_data() and
join_world()) keeps its frame in longitude and latitude and is projected
when it is drawn. Where sf can be loaded that is
ggplot2::coord_sf() with default_crs = sf::st_crs(4326), so any layer
you add in longitude and latitude is projected with the map, point by
point: the map's own outlines are dense, so a segment is drawn straight
between its two projected ends rather than re-interpolated, and a long line
of your own needs points along it to follow the projection. Where it
cannot, Equal Earth is computed by the package itself, on the sphere, and
the vertices are drawn in metres under ggplot2::coord_fixed(); place your
own layers on that map with project_lonlat(), and zoom with zoom_map(),
which keeps the projection where coord_quickmap(xlim, ylim) would replace
it. Another projection without sf falls back to that Equal Earth with a
warning (class countryatlas_projection_fallback). "orthographic" draws
through ggplot2::coord_map() and needs mapproj. map_provenance()
records which of these drew the map: "equal_earth", "equal_earth (spherical, built-in)" or "none".
The classification changes what readers conclude, and not by a little.
Brewer & Pickle's 56-subject study over nine map series found quantiles
among the best methods for general choropleth reading, and natural breaks
(Jenks) below 70% as accurate – the opposite of the common GIS default.
style = "quantile" is therefore the safe choice for a general audience.
Jenks earns its place on strongly clustered distributions, where quantiles
would split a natural group across two colours. Use classify_compare() to
see the difference on your own data before committing.
Brewer, C. A. & Pickle, L. (2002). Evaluation of methods for classifying epidemiological data on choropleth maps in series. Annals of the Association of American Geographers 92(4), 662-681. doi:10.1111/1467-8306.00310
Correll, M., Moritz, D. & Heer, J. (2018). Value-suppressing uncertainty palettes. Proceedings of the 2018 CHI Conference on Human Factors in Computing Systems, 1-11. doi:10.1145/3173574.3174216
classify_compare(), coverage_map(), projection_compare(),
map_provenance(), dispute_policy()
snap <- countryatlas::world_snapshot$countries mapdf <- attach_geometry(snap, geometry = "polygon") world_map(mapdf, gdp_per_capita, style = "quantile")snap <- countryatlas::world_snapshot$countries mapdf <- attach_geometry(snap, geometry = "polygon") world_map(mapdf, gdp_per_capita, style = "quantile")
Build a ggsql query string that draws a choropleth from
a registered countryatlas source – the same idea as world_map(), but the
map is rendered in the database (DuckDB) and returned as a web-ready
Vega-Lite widget, so the geometry never has to come back into R. Pure string
builder with no dependencies; pair it with as_ggsql_source() +
ggsql::ggsql_execute(), or drop the string into a {ggsql} chunk.
world_query( fill, source = "countryatlas_world", projection = "equal_earth", palette = "viridis", transform = NULL, title = NULL, draw = "spatial", layer = c("choropleth", "bubble", "binned"), facet = NULL, size = NULL, n_bins = NULL )world_query( fill, source = "countryatlas_world", projection = "equal_earth", palette = "viridis", transform = NULL, title = NULL, draw = "spatial", layer = c("choropleth", "bubble", "binned"), facet = NULL, size = NULL, n_bins = NULL )
fill |
The fill column (unquoted or a string). |
source |
The table/source name registered with ggsql (default
|
projection |
A projection: one of the package's own names (see
|
palette |
A scale ggsql's |
transform |
Optional scale transform for |
title |
Optional plot title ( |
draw |
The spatial layer (default |
layer |
|
facet |
Optional column to facet the query by, e.g. |
size |
Column driving symbol size for |
n_bins |
Number of classes for |
A ggsql_query string (prints as the formatted query).
Building the string needs nothing installed. Running it needs
ggsql >= 0.4.1, the version that added the DRAW spatial clause and map
projections; older ggsql releases parse the query and reject that clause.
Projections arrived in the engine at different versions – equal_earth,
the default here, only in 0.5.0 – so the query records the version it
needs as its "countryatlas_ggsql_version" attribute, and
interactive_map()(engine = "ggsql") refuses an installed ggsql older
than that, naming the projection. As of October 2026 the ggsql engine is
at 0.5.2 while the ggsql R package on CRAN is still 0.3.3, so execution
waits for the bindings to catch up. PROJECT TO additionally needs a
spatial backend – for DuckDB, its spatial extension.
world_query(gdp_per_capita, projection = "equal_earth", palette = "magma", transform = "log10", title = "GDP per capita")world_query(gdp_per_capita, projection = "equal_earth", palette = "magma", transform = "log10", title = "GDP per capita")
A small, lazy-loaded, one-row-per-country snapshot of a curated indicator set for one recent year. It lets every example, test and vignette run offline and deterministically, without the World Bank API.
world_snapshotworld_snapshot
A list with three elements:
A tibble, one row per country, with iso3c, iso2c,
country, the classifications continent, region and income, and
the curated indicators gdp_per_capita, population,
life_expectancy and co2_per_capita.
NULL in the released package – geometry is not bundled twice.
Attach it on demand with attach_geometry():
attach_geometry(world_snapshot$countries, geometry = "sf") pulls the
same Natural Earth 110m polygons from rnaturalearth.
The reference year.
country carries the World Bank's own names, which differ from the
countrycode names used by country_meta for 39 countries.
income and region are the World Bank's classifications in force on
1 January of the snapshot year (fiscal year 2024), from
country_classifications: the regions are therefore the ones before the
July 2025 change, with Afghanistan and Pakistan in South Asia. Data
fetched with world_data() carries the current fiscal year's instead, and
source_info() says which each frame holds.
The "provenance" attribute records the World Development Indicators
release the values come from, the build date, the package versions used,
and why the snapshot is on its year: it moves to a newer year only when
every indicator's coverage there is within five percentage points of the
current year's. The indicator columns carry a source_info() record.
World Bank World Development Indicators, release 2026-07 (CC BY 4.0), and the World Bank's classifications. Snapshot year: 2024.
The tabular counterpart to world_map(): take the same curated frame and
produce a ranked, formatted table instead of a picture. Uses gt when it is
installed and a plain tibble otherwise, so it never becomes a hard dependency.
world_table( data, value = NULL, top_n = 20, desc = TRUE, columns = NULL, engine = c("gt", "tibble"), title = NULL, subtitle = NULL )world_table( data, value = NULL, top_n = 20, desc = TRUE, columns = NULL, engine = c("gt", "tibble"), title = NULL, subtitle = NULL )
data |
A country-level or map-ready frame. |
value |
The column to rank on (unquoted). Tied values share a rank, as
in |
top_n |
How many rows (default |
desc |
Sort descending (default |
columns |
Extra columns to keep, beyond |
engine |
|
title, subtitle
|
Optional table title and subtitle ( |
A gt table, or a tibble when gt is unavailable or
engine = "tibble".
rank_countries(), country_factsheet(), world_map()
world_table(countryatlas::world_snapshot$countries, gdp_per_capita, top_n = 5, engine = "tibble")world_table(countryatlas::world_snapshot$countries, gdp_per_capita, top_n = 5, engine = "tibble")
A statebins-style equal-area tile layout: one square per country, positioned
on a row/col grid derived from country centroids. Used by tile_map().
world_tilesworld_tiles
A tibble with columns iso3c, country, row, col; one row per
country, with row/col unique across the grid.
The grid holds one row for each of the 247 countries in country_meta that
has a bundled centroid; the 3 without one (BVT, GIB, UMI – see
country_meta) have no tile and so cannot be drawn by tile_map().
Derived from Natural Earth country centroids.
Show part of a map by longitude and latitude limits while keeping the
projection it was drawn in. The 3.0.0 documentation zoomed with
coord_quickmap(xlim, ylim), which on a projected map silently replaces
the projection with an unprojected one.
zoom_map(p, xlim, ylim)zoom_map(p, xlim, ylim)
p |
A map from one of the package's map verbs. |
xlim, ylim
|
Longitude and latitude limits, in degrees, each a pair
|
p with the view limited, in the same projection.
snap <- countryatlas::world_snapshot$countries europe <- attach_geometry(snap, geometry = "polygon") |> world_map(gdp_per_capita) zoom_map(europe, xlim = c(-25, 45), ylim = c(34, 72))snap <- countryatlas::world_snapshot$countries europe <- attach_geometry(snap, geometry = "polygon") |> world_map(gdp_per_capita) zoom_map(europe, xlim = c(-25, 45), ylim = c(34, 72))