This post is the fifth of a series of seven on population issues in the Pacific, regenerating the graphs I used in a keynote speech before the November 2025 meeting of the Pacific Heads of Planning and Statistics in Wellington, New Zealand. The seven pieces of the puzzle are:
Today’s post is about population pyramids and is familiar territory for any regular readers of the blog. The code is basically an adaptation of the code used to create these animated population pyramidsadjusted to create still images I needed to make the point in my talk.
Kiribati and Marshall Islands
Initially, that meant this image, which contrasts the demographic shape and growth of two coral atoll countries, Kiribati and the Marshall Islands:
Kiribati today has about four times the population of the Marshall Islands, but in 1980 that was only about double. The most important thing here is the wasp waist of the Marshall Islands pyramid in 2025 – while it had a similar shape to Kiribati in 1980. People of prime working and reproductive age are literally absent from today’s Marshall Islands – in this case especially in the US, which automatically gives residency rights to the Compact of Free Association Countries (Marshall Islands, Palau and Federated States of Micronesia).
The result of this is that the Marshall Islands not only benefits from individuals having greater freedom of movement and opportunity, and sending money back; but also with a pressure valve for what would otherwise be a rapidly (too rapidly?) growing population. To put it bluntly, Kiribati has a problem of too many people (especially in the busy south of Tarawa); The Marshall Islands, if they have a population problem, are one of the few. The contrast between the busy, relatively poor Tarawa and the less busy, relatively affluent Majuro is clear and stark to anyone who travels to both in quick succession.
That first graph tries to show both absolute size and shape at the same time. An alternative presentation leaves the x-axis free, losing size comparability but making changes in shape more visible. There are pros and cons to each, but the free-axis version certainly dramatically shows the change in shape of the Marshall Islands in particular:
Here’s the code to download the data from the Pacific Data Hub and draw those graphs:
# this script draws population pyramids for 1980 and 2025, firstly
# for Marshall Islands and Kiribati together for comparison
# purposes, and then for each of the 21 PICTs (exlcuding Pitcairn)
# so we can pick and choose which ones
#
# Peter Ellis November 2025
library(tidyverse)
library(janitor)
library(rsdmx)
library(ISOcodes)
library(glue)
# see https://blog.datawrapper.de/gendercolor/
pal <- c("#D4855A", "#C5CB81")
names(pal) <- c("Female", "Male")
# Download all population data needed
if(!exists("pop2picts")){
pop2picts <- readSDMX("https://stats-sdmx-disseminate.pacificdata.org/rest/data/SPC,DF_POP_PROJ,3.0/A.AS+CK+FJ+PF+GU+KI+MH+FM+NR+NC+NU+MP+PW+PG+WS+SB+TK+TO+TV+VU+WF.MIDYEARPOPEST.F+M.Y00T04+Y05T09+Y10T14+Y15T19+Y20T24+Y25T29+Y30T34+Y35T39+Y40T44+Y45T49+Y50T54+Y55T59+Y60T64+Y65T69+Y70T999?startPeriod=1980&endPeriod=2025&dimensionAtObservation=AllDimensions") |>
as_tibble() |>
clean_names()
}
# sort out the from and to ages, rename sex, and add country labels
d <- pop2picts |>
mutate(age = gsub("^Y", "", age)) |>
separate(age, into = c("from", "to"), sep = "T", remove = FALSE) |>
mutate(age = gsub("T", "-", age),
age = gsub("-999", "+", age, fixed = TRUE),
sex = case_when(
sex == "M" ~ "Male",
sex == "F" ~ "Female"
)) |>
mutate(age = factor(age)) |>
left_join(ISO_3166_1, by = c("geo_pict" = "Alpha_2")) |>
rename(pict = Name) |>
filter(time_period %in% c(1980, 2025))
#----------Marshalls and Kiribati-------------
# subset data to these two countries:
d1 <- d |>
filter(pict %in% c("Kiribati", "Marshall Islands"))
# breaks in axis for Marshall and Kiribati chart:
x_breaks <- c(-6000, - 4000, -2000, 0, 2000, 4000, 6000)
# draw chart:
pyramid_km <- d1 |>
# according to Wikipedia males are usually on the left and females on the right
filter(sex == "Female") |>
ggplot(aes(y = age)) +
facet_grid(pict ~ time_period) +
geom_col(aes(x = obs_value), fill = pal['Female']) +
geom_col(data = filter(d1, sex == "Male"), aes(x = -obs_value), fill = pal['Male']) +
labs(x = "", y = "Age group") +
scale_x_continuous(breaks = x_breaks, labels = c("6,000", "4,000", "2,000\n(male)", 0 ,
"2,000\n(female)", "4,000", "6,000")) +
theme(panel.grid.minor = element_blank(),
strip.text = element_text(size = 14, face = "bold"))
print(pyramid_km)
pyramid_km_fr <- pyramid_km +
facet_wrap(pict ~ time_period, scales = "free_x")
print(pyramid_km_fr)All the island states in the Pacific Ocean, one by one
I used the same diagram to generate a PNG image of each Pacific island one by one. During the actual lecture I pulled a few into the PowerPoint to engage the audience and contrast different shapes. These plots are all sized to fit in one frame in the PowerPoint template I used.
For example, here is Tuvalu:
Until recently, it was relatively difficult to migrate from Tuvalu. As a result, we see a more or less regular population pyramid for a country that is in a late stage of demographic transition.
In contrast, here is the French territory of Wallis and Futuna:
![]()
The inhabitants of Wallis and Futuna can move freely to other French territories, such as New Caledonia, and have done so in significant numbers. We therefore see a shortage in the age category 25-39 years.
Here’s the code to produce those pyramids for individual countries, and store them neatly in a folder for future use. Yes, I always use loops for this kind of thing, because I find them easy to write as well as read (and saying that loops are never good in R is just an outdated prejudice):
#--------------population pyramid individual image for each pict-----------
# This section draws one chart and saves as an image for each PICT
dir.create("pic-pyramids", showWarnings = FALSE)
all_picts <- unique(d$pict)
for(this_pict in all_picts){
this_d <- d |>
filter(pict == this_pict)
this_pyramid <- this_d |>
filter(sex == "Female") |>
ggplot(aes(y = age)) +
facet_grid(pict ~ time_period) +
geom_col(aes(x = obs_value), fill = pal['Female']) +
geom_col(data = filter(this_d, sex == "Male"), aes(x = -obs_value), fill = pal['Male']) +
labs(x = "", y = "Age group") +
theme(panel.grid.minor = element_blank(),
strip.text = element_text(size = 14, face = "bold"))
png(glue("pic-pyramids/pyramid-{this_pict}.png"), width = 5000, height = 2800,
res = 600, type = "cairo-png")
print(this_pyramid)
dev.off()
}That’s all for today. The final post in the series will say more about the implications of all this in the context of the other bits of analysis.
Related
#Pacific #island #population #pyramids #ellis2013nz #bloggers


