The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Country data onto honest maps: joined on ISO codes, never on country names.
Join the World Bank’s life-expectancy table to
map_data("world") by country name and 37 of 210
countries silently vanish: nobody spells Czechia, Côte d’Ivoire
or "Congo, Dem. Rep." the same way twice.
countryatlas makes the ISO code the join key, so nothing
goes missing, then draws the map.

install.packages("countryatlas") # CRAN
pak::pak("PursuitOfDataScience/countryatlas") # developmentThe base install is light. Every heavy spatial dependency
(sf, cartogram, leaflet, …) is a
Suggests you only need for the feature that uses it.
world_data() fetches the indicator, attaches the
geometry and keys the whole thing on iso3c: three worlds
(ggplot2 maps, WDI, countrycode)
stitched together in one line.
world_map(world_data(2020), gdp_per_capita, style = "quantile")
world_data(2020, c(life_exp = "SP.DYN.LE00.IN")) |>
glimpse()
#> Rows: 99,338
#> Columns: 12
#> $ long <dbl> -69.89912, -69.89571, -69.94219, -70.00415, -70.06612, -70.0…
#> $ lat <dbl> 12.45200, 12.42300, 12.43853, 12.50049, 12.54697, 12.59707, …
#> $ group <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
#> $ order <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18, 1…
#> $ subregion <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
#> $ iso3c <chr> "ABW", "ABW", "ABW", "ABW", "ABW", "ABW", "ABW", "ABW", "ABW…
#> $ iso2c <chr> "AW", "AW", "AW", "AW", "AW", "AW", "AW", "AW", "AW", "AW", …
#> $ country <chr> "Aruba", "Aruba", "Aruba", "Aruba", "Aruba", "Aruba", "Aruba…
#> $ continent <chr> "Americas", "Americas", "Americas", "Americas", "Americas", …
#> $ region <chr> "Latin America & Caribbean", "Latin America & Caribbean", "L…
#> $ income <fct> High income, High income, High income, High income, High inc…
#> $ life_exp <dbl> 75.406, 75.406, 75.406, 75.406, 75.406, 75.406, 75.406, 75.4…Geometry, codes, classifications and the indicator, in one frame,
ready for world_map().
Drop geometry for a plain country table
(country_data()), ask for a year range to get a panel, or
pass several indicators at once. wdi_search() finds codes
offline; common_indicators keeps the 22 you actually
use.
Point join_world() at whatever column holds the country
and it standardises, joins and attaches geometry in one go.
tibble(
nation = c("U.S.", "S. Korea", "Czechia", "Kosovo", "Cote d'Ivoire", "Burma"),
score = c(10, 8, 6, 4, 7, 5)
) |>
join_world(nation, warn = FALSE) |>
world_map(score, title = "Six countries, six spellings, one map")
Two messy tables reconcile against each other the same way:
a <- tibble(country = c("Czechia", "South Korea"), gdp = c(1, 2))
b <- tibble(nation = c("Czech Republic", "Korea, Rep."), pop = c(10, 51))
country_join(a, b, country, nation)
#> # A tibble: 2 × 5
#> country gdp iso3c nation pop
#> <chr> <dbl> <chr> <chr> <dbl>
#> 1 Czechia 1 CZE Czech Republic 10
#> 2 South Korea 2 KOR Korea, Rep. 51check_country_match() reports before you join, including
the entities countrycode resolves wrongly rather
than not at all.
check_country_match(c("USA", "Cote d'Ivoire", "USSR", "Wakanda"))
#> # A tibble: 4 × 5
#> input iso3c matched historical suggestion
#> <chr> <chr> <lgl> <lgl> <chr>
#> 1 USA USA TRUE FALSE <NA>
#> 2 Cote d'Ivoire CIV TRUE FALSE <NA>
#> 3 USSR RUS TRUE TRUE <NA>
#> 4 Wakanda <NA> FALSE FALSE Canadarepair_country_names() fixes typos,
audit_coverage() grades a finished join, and
dissolve_country() expands a dead state into its
successors.
dissolve_country("Yugoslavia")
#> # A tibble: 7 × 5
#> input historical dissolved iso3c country
#> <chr> <chr> <int> <chr> <chr>
#> 1 Yugoslavia Yugoslavia 1992 BIH Bosnia & Herzegovina
#> 2 Yugoslavia Yugoslavia 1992 HRV Croatia
#> 3 Yugoslavia Yugoslavia 1992 MKD North Macedonia
#> 4 Yugoslavia Yugoslavia 1992 MNE Montenegro
#> 5 Yugoslavia Yugoslavia 1992 SRB Serbia
#> 6 Yugoslavia Yugoslavia 1992 SVN Slovenia
#> 7 Yugoslavia Yugoslavia 1992 XKX KosovoEvery map below is one function call on the bundled offline snapshot.
![]() world_map(d,
gdp_per_capita, style = “quantile”)
|
![]() world_map(d,
continent, style = “categorical”)
|
![]() bubble_map(d,
population)
|
![]() spike_map(d,
population)
|
![]() cartogram_map(d,
population)
|
![]() dorling_map(d,
population)
|
![]() bivariate_map(d,
gdp_per_capita, life_expectancy)
|
![]() flow_map(corridors,
from, to, people)
|
![]() globe_map(d,
income, style = “categorical”)
|
![]() spin_globe(d,
continent, style = “categorical”)
|
![]() tile_map(d,
gdp_per_capita): one square per country, so microstates are as
visible as Russia
|
|
![]() facet_map(panel,
life_exp, year, style = “quantile”), or
animate_world(panel, life_exp) for the moving version
|
|
interactive_map() hands the same frame to
plotly, ggiraph,
leaflet or ggsql for a web-ready
widget. With as_ggsql_source() and
world_query() the drawing happens inside DuckDB:
countryatlas reconciles the countries and ggsql renders them without ggplot2 or
sf at runtime.
“Honest maps” is in the package description, so the package has to earn it. Four ways a world map misleads, the verb for each, and one more that makes the map admit what it did.
Your classification is doing the talking. Equal-interval breaks put 92% of countries in one class here; quantiles spread them evenly. Same data, same palette, opposite conclusions.

p <- classify_compare(poly, gdp_per_capita)
attr(p, "countryatlas_classification") |> filter(method %in% c("quantile", "equal"))
#> # A tibble: 10 × 4
#> method class n share
#> <chr> <chr> <int> <dbl>
#> 1 quantile [268.7,1662] 38 0.201
#> 2 quantile (1662,4594] 38 0.201
#> 3 quantile (4594,1.029e+04] 37 0.196
#> 4 quantile (1.029e+04,2.937e+04] 38 0.201
#> 5 quantile (2.937e+04,2.472e+05] 38 0.201
#> 6 equal [268.7,4.965e+04] 173 0.915
#> 7 equal (4.965e+04,9.903e+04] 13 0.0688
#> 8 equal (9.903e+04,1.484e+05] 2 0.0106
#> 9 equal (1.484e+05,1.978e+05] 0 0
#> 10 equal (1.978e+05,2.472e+05] 1 0.00529Your projection is doing the talking too. Tissot’s indicatrix puts circles of equal ground radius on the map: whatever the projection does to them, it is doing to your data.
![]() tissot_map(“mercator”):
shapes right, areas wildly wrong
|
![]() tissot_map(“equal_earth”):
areas right, shapes sheared
|
![]() projection_compare(d,
gdp_per_capita), and projection_info() for which of
the thirteen are equal-area
|
|
Grey means “no data”, but it reads as “low”. Hatch the gaps so nobody mistakes them for a value, or map availability itself.
![]() world_map(d,
co2_per_capita, na_style = “hatched”)
|
![]() coverage_map(d,
co2_per_capita)
|
A rate over eleven thousand people should not shout as loudly as one over a billion. Value-by-alpha spends opacity on the denominator, so small-population countries recede: the cartogram’s answer to the same problem, without distorting the geometry.
value_by_alpha_map(d, gdp_per_capita, population)And the map should say what it is.
footnote = "auto" writes the coverage line;
map_provenance() answers the questions a reviewer asks
first.
world_map(poly, gdp_per_capita, style = "quantile",
na_style = "hatched", footnote = "auto") |>
map_provenance()
#>
#> ── countryatlas map provenance
#> package: countryatlas 3.0.0 (snapshot 2024)
#> fill: gdp_per_capita
#> geometry: polygon backend, coord_quickmap
#> classification: quantile, 5 bins
#> missing data: hatched
#> coverage: 189 countries shown, 51 missing
#> breaks: 268.7 | 1662 | 4594 | 10290 | 29370 | 247200The ISO spine is not only the World Bank’s, and not only 2024’s.
![]() attach_geometry(d,
year = 1950): 1950 borders, via CShapes. Africa is nearly empty
because in 1950 almost none of it was sovereign; pass dependencies
= TRUE for the colonies.
|
![]() lisa_map(d,
gdp_per_capita, weights = country_weights(“knn”))
|
![]() gridded_cartogram(d,
population): one cell per N people, countable
|
![]() value_by_alpha_map(d,
gdp_per_capita, population)
|
![]() od_map(flows,
from, to, value): where each origin sends, when
flow_map() would be spaghetti
|
|
Membership is a function of time, and a snapshot quietly misstates any panel that spans an accession:
c(`2016` = in_group("United Kingdom", "EU", as_of = 2016),
`2021` = in_group("United Kingdom", "EU", as_of = 2021))
#> 2016 2021
#> TRUE FALSEIslands have no land border, so the default
contiguity weights drop a quarter of the world from a “global” Moran’s
I. country_weights() fixes it, and the result says how many
it dropped either way:
snap <- world_snapshot$countries
cols <- c("i", "n", "n_excluded")
rbind(
cbind(scheme = "contiguity",
morans_i(snap, gdp_per_capita, n_perm = 0)[cols]),
cbind(scheme = "knn",
morans_i(snap, gdp_per_capita, n_perm = 0,
weights = country_weights("knn", k = 5))[cols])
)
#> scheme i n n_excluded
#> 1 contiguity 0.6073182 142 49
#> 2 knn 0.4720522 189 2Any provider, one shape.
register_country_source() takes a fetch function and a
name; fetch_indicator() and compare_sources()
do the rest, including telling you where two providers disagree.
country_sources()[, c("source", "meta")]
#> # A tibble: 5 × 2
#> source meta
#> <chr> <chr>
#> 1 comtrade UN Comtrade bilateral trade (via comtradr); needs an API token
#> 2 eurostat Eurostat (via eurostat); European coverage only
#> 3 oecd OECD statistics (via OECD)
#> 4 owid Our World in Data (via owidR)
#> 5 wdi World Bank World Development Indicators (via WDI)snap <- world_snapshot$countries
# inequality between people, not between country units
gini(snap$gdp_per_capita, weights = snap$population)
#> [1] 0.6094909
# how much of it sits between continents rather than within them
theil(snap$gdp_per_capita, weights = snap$population, groups = snap$continent)
#> # A tibble: 3 × 3
#> component value share
#> <chr> <dbl> <dbl>
#> 1 total 0.678 1
#> 2 between 0.310 0.458
#> 3 within 0.368 0.542
# who borders whom, and how far apart they are -- no sf required
distance_between("France", "Germany")
#> [1] 802.3524
convert_country(c("Japan", "Brazil"), to = "flag")
#> [1] "🇯🇵" "🇧🇷"| Assemble | world_data() country_data()
world_geometry() attach_geometry()
clear_country_cache() |
| Other sources | register_country_source()
remove_country_source() country_sources()
fetch_indicator() add_indicator()
compare_sources() fetch_owid()
fetch_eurostat() fetch_oecd()
fetch_comtrade() |
| Join | standardize_country() join_world()
country_join() country_join_all()
dissolve_country()
standardize_subnational() |
| Diagnose | check_country_match()
repair_country_names() audit_coverage()
audit_time_coverage() rate_check()
check_dispute_coverage()
country_overrides() |
| Look up | convert_country() country_codes()
country_groups() in_group()
wdi_search() |
| Compute | per_capita() share_of_world()
growth_rate() index_to()
rank_countries() aggregate_regions()
complete_years() lag_by_country()
diff_by_country() correlate_indicators()
deflate() to_ppp() smooth_rates()
interpolate_missing() |
| Measure spread | gini() theil()
beta_convergence() sigma_convergence()
convergence_club() |
| Spatial statistics | country_weights() morans_i()
local_morans() lisa_map()
gearys_c() getis_ord()
spatial_lag() |
| Locate | locate_country() neighbors()
country_borders() distance_between()
simplify_geometry() nuts_geometry() |
| Travel in time | historical_geometry() country_timeline()
country_groups(as_of=) in_group(as_of=) |
| Relate | flow_matrix() country_network()
od_map() |
| Draw | world_map() globe_map()
spin_globe() facet_map()
bubble_map() spike_map()
bivariate_map() cartogram_map()
dorling_map() value_by_alpha_map()
tile_map() flow_map()
animate_world() interactive_map()
gridded_cartogram() subnational_map()
geom_country_labels() theme_world_map() |
| Keep honest | classify_compare() coverage_map()
projection_info() projection_compare()
projection_distortion() tissot_map()
cartogram_diagnostics() map_provenance()
dispute_policy() |
| Report | country_factsheet() world_table() |
| Push to the database | as_ggsql_source() world_query() |
| Bundled data | world_snapshot country_meta
common_indicators country_groups_tbl
country_groups_history disputed_territories
world_tiles historical_codes |
world_snapshot ships a curated indicator set for one
recent year, so every example, test and vignette in the package runs
with the network unplugged. Live world_data() calls are
memoised on disk between sessions.
| Needs | For |
|---|---|
maps |
the polygon backend: world_map(),
bubble_map(), spike_map(),
flow_map(), globe_map(backend = "polygon")
(with mapproj) |
sf + rnaturalearth +
rnaturalearthdata |
real geometry: world_map(sf),
world_geometry(sf), locate_country(),
country_borders(), neighbors(),
morans_i() |
cartogram + sf |
cartogram_map(), dorling_map() |
biscale + sf |
bivariate_map() |
gganimate + gifski or
magick |
animate_world(), spin_globe() |
cartogramR |
cartogram_map(type = "flow"), the fast
Gastner-Seguy-More algorithm |
cshapes |
historical_geometry() and
attach_geometry(year=) |
owidR / eurostat / OECD /
comtradr |
the four built-in fetch_*() source adapters |
mapgl |
interactive_map(engine = "mapgl"),
globe_map(interactive = TRUE) |
tmap |
world_map(engine = "tmap") |
giscoR / regions |
nuts_geometry(),
standardize_subnational() |
gt |
world_table() |
ggpattern |
world_map(na_style = "hatched") |
plotly / ggiraph / leaflet /
ggsql |
the four interactive_map() engines |
duckdb + DBI, or
nanoarrow |
as_ggsql_source() |
stringdist |
fuzzy matching in repair_country_names() and
check_country_match() |
rmapshaper |
the better simplifier behind simplify_geometry() |
classInt |
style = "jenks" |
Getting started · Joining your own data · Maps with sf & projections · Beyond the choropleth · countryatlas and ggsql · Full reference · Changelog
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.