library(tidyverse)
library(scales)
library(sf)
library(hrbrthemes)
data_dir <- file.path("data", "governance-climate-finance")
dir.create(data_dir, recursive = TRUE, showWarnings = FALSE)Classwork 4
Governance and Climate Finance in the Developing World
Build a careful descriptive profile of climate-finance records and governance indicators. Every table and figure should identify its sample, year or period, unit, and data scope.
Work with a partner. One person drives the code while the other checks row grain, join keys, labels, and units. Switch roles after Part 3.
By the end, you should be able to:
- read course CSV files directly from their published URLs with
read_csv(), - retrieve governance indicators from the official World Bank API,
- reshape, summarize, and join data with
tidyranddplyr, - build several complementary
ggplot2displays, and - describe patterns with the appropriate scope and measurement cautions.
🧰 Part 0: Read the course CSV files
The course publishes five source CSV files used in this classwork, plus a pre-joined 2024 snapshot for checking results. Each file has a direct URL:
- OECD annual climate-finance totals: https://bcdanl.github.io/399/data/governance-climate-finance/oecd_climate_finance_2013_2024.csv
- Paper-derived project allocations: https://bcdanl.github.io/399/data/governance-climate-finance/paper_project_allocations_2011_2020.csv
- World Bank country-year indicators: https://bcdanl.github.io/399/data/governance-climate-finance/world_bank_country_year_2009_latest.csv
- OECD Rio Marker recipient totals: https://bcdanl.github.io/399/data/governance-climate-finance/oecd_riomarkers_recipient_2020_2024.csv
- Natural Earth polygon vertices: https://bcdanl.github.io/399/data/governance-climate-finance/world_admin0_110m.csv
- Pre-joined 2024 governance-finance snapshot: https://bcdanl.github.io/399/data/governance-climate-finance/latest_governance_mitigation_oda_2024.csv
read_csv() can import each file directly from its URL. The pre-joined snapshot is available as an optional check on the join in Part 4.
0A. Load packages and prepare an API output folder
The folder below is used only if you save the World Bank API result in Part 1. The course CSV files themselves are read from their URLs.
0B. Read the CSV files from their URLs
oecd_total <- read_csv(
"https://bcdanl.github.io/399/data/governance-climate-finance/oecd_climate_finance_2013_2024.csv",
show_col_types = FALSE
)
paper_projects <- read_csv(
"https://bcdanl.github.io/399/data/governance-climate-finance/paper_project_allocations_2011_2020.csv",
show_col_types = FALSE
)
world_bank <- read_csv(
"https://bcdanl.github.io/399/data/governance-climate-finance/world_bank_country_year_2009_latest.csv",
show_col_types = FALSE
)
rio_recipient <- read_csv(
"https://bcdanl.github.io/399/data/governance-climate-finance/oecd_riomarkers_recipient_2020_2024.csv",
show_col_types = FALSE
)
world_polygons <- read_csv(
"https://bcdanl.github.io/399/data/governance-climate-finance/world_admin0_110m.csv",
show_col_types = FALSE
)
latest_matched <- read_csv(
"https://bcdanl.github.io/399/data/governance-climate-finance/latest_governance_mitigation_oda_2024.csv",
show_col_types = FALSE
)Use glimpse() and nrow() to answer:
- What does one row represent in each file?
- Which variables identify country, year, and money?
- Which file has one row for each polygon vertex rather than one row for each country-year?
0C. Plot settings
The figures use the pre-defined theme_ipsum() theme and ggplot2’s viridis scales directly. No custom theme or hand-built color palette is needed. If necessary, install the theme package once with install.packages("hrbrthemes").
options(scipen = 999)🌐 Part 1: Get governance indicators from the World Bank API
The World Bank Indicators API needs no key. The six Worldwide Governance Indicators (WGI) used here are scores from 0 to 100.
| Indicator code | WGI dimension |
|---|---|
GOV_WGI_VA.SC |
Voice and Accountability |
GOV_WGI_PV.SC |
Political Stability and Absence of Violence/Terrorism |
GOV_WGI_GE.SC |
Government Effectiveness |
GOV_WGI_RQ.SC |
Regulatory Quality |
GOV_WGI_RL.SC |
Rule of Law |
GOV_WGI_CC.SC |
Control of Corruption |
The live API chunk is not evaluated when this page renders. Complete it, run it interactively when internet is available, and save the result as a local CSV. The later exercises use the published course snapshots loaded in Part 0.
wgi_spec <- tribble(
~indicator_code, ~variable,
"GOV_WGI_VA.SC", "voice_accountability",
"GOV_WGI_PV.SC", "political_stability",
"GOV_WGI_GE.SC", "government_effectiveness",
"GOV_WGI_RQ.SC", "regulatory_quality",
"GOV_WGI_RL.SC", "rule_of_law",
"GOV_WGI_CC.SC", "control_of_corruption"
)
fetch_wgi <- function(indicator_code, variable) {
api_url <- paste0(
"https://api.worldbank.org/v2/country/all/indicator/",
___,
"?source=3&format=json&per_page=20000&date=2024:2024"
)
payload <- jsonlite::fromJSON(api_url, simplifyDataFrame = TRUE)
observations <- payload[[2]]
tibble(
iso3 = observations$countryiso3code,
country = observations$country$value,
year = as.integer(observations$date),
variable = variable,
value = as.numeric(observations$value)
) |>
filter(str_length(iso3) == 3)
}
wgi_api_long <- map2_dfr(
wgi_spec$indicator_code,
wgi_spec$variable,
fetch_wgi
)
wgi_api_2024 <- wgi_api_long |>
select(iso3, country, year, variable, value) |>
pivot_wider(names_from = ___, values_from = ___)
write_csv(
wgi_api_2024,
file.path(data_dir, "wgi_api_2024.csv"),
na = ""
)map2_dfr() does
map2_dfr(.x, .y, .f) moves through two vectors in parallel. At each position, it passes one value from .x and the matching value from .y to the function .f, then combines all returned data frames by rows. Here it sends each indicator code and its readable variable name to fetch_wgi() and stacks the six API results into one long table.
Questions:
- Which part of the URL changes when you request a different indicator?
- What does
pivot_wider()change about the row grain? - Compare the API result with the 2024 rows in
world_bank. Which identifiers and WGI values match?
📏 Part 2: Climate-finance accounting
The OECD total describes finance provided and mobilised by developed countries for developing countries. Its components have different accounting bases. The 2015 grand total is missing because the measurement method changed.
2A. Distance from the USD 100 billion goal
goal_comparison <- ___ |>
transmute(
___,
total_usd_billion = ___,
difference_from_100b = ___ - 100,
reached_100b = ___ >= 100
)
goal_comparisonWhy should the 2015 value remain missing rather than be replaced automatically with zero?
2B. Reshape and plot the components
oecd_components <- ___ |>
select(
year,
bilateral_public_usd_billion,
multilateral_public_attributed_usd_billion,
export_credits_usd_billion,
private_mobilised_usd_billion
) |>
pivot_longer(
-___,
names_to = ___,
values_to = ___
) |>
mutate(
component = component |>
str_remove("_usd_billion$") |>
str_replace_all("_", " ") |>
str_to_sentence()
)
ggplot(
filter(___, year >= 2016),
aes(___, ___, color = ___)
) +
geom_line(linewidth = 1) +
geom_point(size = 2.2) +
scale_color_viridis_d(option = "D", end = 0.9) +
scale_x_continuous(breaks = 2016:2024) +
scale_y_continuous(labels = label_dollar(suffix = "B")) +
labs(
title = "Components of climate finance provided and mobilised",
subtitle = "Developed-country finance for developing countries, 2016-2024",
x = NULL,
y = "Current USD billions",
color = NULL,
caption = "Source: OECD (2026). Components differ in accounting basis."
) +
theme_ipsum(base_family = "Arial", base_size = 14, grid = "Y") +
theme(legend.position = "bottom")Which component changed most from 2023 to 2024? State the direction and amount.
🔍 Part 3: Audit and summarize the project archive
The paper archive expands some projects across recipient countries and sectors. Keep the low-, lower-middle-, and upper-middle-income rows. Use country_sector_allocated_usd when adding expanded rows.
developing_income_groups <- c(
"Low income",
"Lower middle income",
"Upper middle income"
)
paper_analysis <- ___ |>
filter(
___ %in% developing_income_groups,
___
) |>
mutate(
allocated_positive_usd = pmax(___, 0),
adjustment_negative_usd = pmin(___, 0)
)
paper_analysis |>
summarise(
rows = n(),
projects = n_distinct(___),
countries = n_distinct(___),
sectors = n_distinct(___),
negative_rows = sum(___)
)What is the row grain? Why would summing project_contribution_usd over these rows count some projects more than once?
3B. Ranked sector bars
sector_summary <- ___ |>
group_by(___) |>
summarise(
projects = n_distinct(___),
positive_usd_billion = sum(___) / 1e9,
.groups = "drop"
) |>
slice_max(___, n = 10) |>
arrange(___)
ggplot(
___,
aes(___, fct_reorder(___, ___), fill = ___)
) +
geom_col(width = 0.72, show.legend = FALSE) +
scale_fill_viridis_c(option = "D", end = 0.9) +
scale_x_continuous(labels = label_dollar(suffix = "B")) +
labs(
title = "Largest sectors in the assembled project archive",
subtitle = "Low- and middle-income recipient rows, 2011-2019",
x = "Positive allocations, current USD billions",
y = NULL,
caption = "Source: Choe and Ore-Monago project archive. This is not a global total."
) +
theme_ipsum(base_family = "Arial", base_size = 14, grid = "X")Which title, subtitle, axis label, and caption phrases define the scope of the bars?
🔗 Part 4: Join governance and Rio Marker snapshots
Use a common 2024 cross-section. The Rio Marker table reports bilateral allocable ODA commitments where adaptation or mitigation is a principal or significant objective.
wb_2024 <- ___ |>
filter(
___ == 2024,
___ %in% c("LIC", "LMC", "UMC")
) |>
select(
iso3,
country,
region,
income_group_current,
population,
voice_accountability,
political_stability,
government_effectiveness,
regulatory_quality,
rule_of_law,
rule_of_law_lower,
rule_of_law_upper,
control_of_corruption
)
rio_2024 <- ___ |>
filter(___ == 2024) |>
select(
recipient_iso3,
adaptation_total_usd_millions,
mitigation_total_usd_millions
)
current <- ___ |>
left_join(
___,
by = c("iso3" = "recipient_iso3"),
relationship = "one-to-one"
) |>
mutate(
rio_recorded = !is.na(___),
across(
c(adaptation_total_usd_millions, mitigation_total_usd_millions),
~ replace_na(.x, 0)
),
mitigation_usd_per_person =
___ * 1e6 / ___
)
current |>
summarise(
rows = n(),
unique_iso3 = n_distinct(iso3),
rio_records = sum(rio_recorded),
missing_rule_of_law = sum(is.na(rule_of_law))
)Why is ISO3 safer than a country-name join? What does relationship = "one-to-one" check?
🎻 Part 5: Compare distributions by income group
Use a violin to show the overall shape, a narrow boxplot to show the median and quartiles, and lightly jittered points to show individual economies.
governance_distribution <- ___ |>
filter(!is.na(___)) |>
mutate(
income_group_current = fct_relevel(
___,
"Low income",
"Lower middle income",
"Upper middle income"
)
)
ggplot(
___,
aes(___, ___, fill = ___)
) +
geom_violin(alpha = 0.45, color = NA, trim = FALSE) +
geom_boxplot(width = 0.18, outlier.shape = NA, alpha = 0.8) +
geom_jitter(width = 0.08, alpha = 0.45, size = 1.4) +
scale_fill_viridis_d(option = "D", end = 0.9) +
scale_y_continuous(limits = c(0, 100)) +
labs(
title = "Rule of Law scores vary within every income group",
subtitle = "Low- and middle-income economies with 2024 WGI observations",
x = NULL,
y = "Rule of Law score, 0-100",
fill = NULL,
caption = "Source: World Bank WGI, 2025 revision."
) +
theme_ipsum(base_family = "Arial", base_size = 14, grid = "Y") +
theme(axis.text.x = element_text(angle = 12, hjust = 1))Compare the medians and the within-group spreads. Which feature is visible in the violin that is not visible in a table of group means?
🪜 Part 6: Build a ranked lollipop chart
Show the 15 economies with the largest positive mitigation-marked ODA amounts per person.
mitigation_rank <- ___ |>
filter(
!is.na(___),
___ > 0
) |>
slice_max(___, n = 15) |>
mutate(country = fct_reorder(___, ___))
ggplot(___, aes(___, ___)) +
geom_segment(
aes(x = 0, xend = ___, yend = ___),
color = "grey75",
linewidth = 0.8
) +
geom_point(aes(color = ___), size = 3) +
scale_color_viridis_c(option = "D", end = 0.9, guide = "none") +
scale_x_continuous(labels = label_dollar(accuracy = 1)) +
labs(
title = "Largest mitigation-marked ODA amounts per person",
subtitle = "Top 15 low- and middle-income economies in the 2024 snapshot",
x = "Current USD per person",
y = NULL,
caption = "Sources: OECD Rio Markers and World Bank population. Positive recorded amounts only."
) +
theme_ipsum(base_family = "Arial", base_size = 14, grid = "X")How would the ranking change if you used total USD millions instead? What does the population denominator change about the comparison?
🔵 Part 7: Make a descriptive scatterplot
Use a logarithmic vertical scale because positive per-person finance values are highly skewed. Add a smooth curve to summarize the middle of the point cloud.
scatter_data <- ___ |>
filter(
!is.na(___),
___ > 0
)
ggplot(
___,
aes(___, ___, color = ___)
) +
geom_point(size = 2.6, alpha = 0.72) +
geom_smooth(
aes(group = 1),
method = "loess",
formula = y ~ x,
se = FALSE,
color = "grey25",
linewidth = 0.9
) +
scale_color_viridis_d(option = "D", end = 0.9) +
scale_y_log10(labels = label_dollar(accuracy = 0.01)) +
labs(
title = "Rule of Law and mitigation-marked bilateral ODA",
subtitle = "Low- and middle-income economies with positive recorded amounts, 2024",
x = "Rule of Law score, 0-100",
y = "Current USD per person, log scale",
color = NULL,
caption = "The smooth curve summarizes the displayed observations. Sources: OECD and World Bank."
) +
theme_ipsum(base_family = "Arial", base_size = 14, grid = "Y") +
theme(legend.position = "bottom")Describe the direction, spread, and any unusual observations. How does the log scale change the spacing of the points?
🎯 Part 8: Display WGI uncertainty intervals
WGI provides 90% uncertainty intervals. Plot them for the ten economies with the largest 2024 mitigation-marked ODA totals in this sample.
uncertainty_data <- ___ |>
filter(
___ > 0,
!is.na(___),
!is.na(___),
!is.na(___)
) |>
slice_max(___, n = 10) |>
mutate(country = fct_reorder(___, ___))
ggplot(___, aes(___, ___)) +
geom_errorbarh(
aes(xmin = ___, xmax = ___),
height = 0.15,
color = "grey70",
linewidth = 0.8
) +
geom_point(aes(color = ___), size = 3) +
scale_color_viridis_c(limits = c(0, 100), option = "D", guide = "none") +
scale_x_continuous(limits = c(0, 100)) +
labs(
title = "Rule of Law estimates include uncertainty",
subtitle = "Ten largest mitigation-marked ODA recipients in the 2024 sample",
x = "Rule of Law score and 90% interval",
y = NULL,
caption = "Source: World Bank WGI, 2025 revision."
) +
theme_ipsum(base_family = "Arial", base_size = 14, grid = "X")Choose two economies with overlapping intervals. What would be hidden if the chart showed only point estimates?
🧩 Part 9: Compare all six WGI dimensions
Select eight economies from at least three regions. Reshape the six WGI dimensions and make a tile plot.
selected_iso3 <- c(___)
governance_profile <- ___ |>
filter(___ %in% selected_iso3) |>
select(
country,
voice_accountability,
political_stability,
government_effectiveness,
regulatory_quality,
rule_of_law,
control_of_corruption
) |>
pivot_longer(
-___,
names_to = ___,
values_to = ___
) |>
mutate(
dimension = str_replace_all(___, "_", " ") |>
str_to_title()
)
ggplot(___, aes(___, ___, fill = ___)) +
geom_tile(color = "white", linewidth = 0.7) +
geom_text(aes(label = number(score, accuracy = 1)), size = 3.2) +
scale_fill_viridis_c(limits = c(0, 100), option = "C") +
labs(
title = "Governance profiles differ across dimensions",
subtitle = "Selected low- and middle-income economies, 2024",
x = NULL,
y = NULL,
fill = "Score",
caption = "Source: World Bank WGI, 2025 revision."
) +
theme_ipsum(base_family = "Arial", base_size = 14, grid = FALSE) +
theme(axis.text.x = element_text(angle = 35, hjust = 1))Which economies have fairly even profiles? Which have large gaps across dimensions?
🗺️ Part 10: End with an Equal Earth world map
The polygon CSV comes from Natural Earth Admin 0 countries at 1:110m scale. Convert its ordered longitude-latitude vertices to sf polygons, join the 2024 Rule of Law score by ISO3, and let coord_sf() project the map. If needed, install sf once with install.packages("sf").
world_sf <- world_polygons |>
arrange(___, ___) |>
st_as_sf(
coords = c("longitude", "latitude"),
crs = ___
) |>
group_by(iso3, country, continent, piece) |>
summarise(do_union = FALSE, .groups = "drop") |>
st_cast(___)
map_values <- world_bank |>
filter(___ == 2024) |>
select(___, ___)
world_map <- world_sf |>
left_join(map_values, by = "iso3")
ggplot(world_map) +
geom_sf(aes(fill = ___), color = "white", linewidth = 0.08) +
coord_sf(crs = ___, datum = NA) +
scale_fill_viridis_c(
limits = c(0, 100),
na.value = "grey88",
option = "C"
) +
labs(
title = "Rule of Law scores around the world",
subtitle = "Latest common WGI year in the course snapshot, 2024",
fill = "Score",
caption = "Sources: World Bank WGI and Natural Earth 1:110m Admin 0 boundaries. Equal Earth projection."
) +
theme_ipsum(base_family = "Arial", base_size = 14, grid = FALSE) +
theme(
axis.text.x = element_blank(),
axis.text.y = element_blank(),
axis.title = element_blank(),
legend.position = "bottom"
)Identify one broad regional pattern and one within-region contrast. Which countries are grey, and what does grey mean in this map?
✅ Exit ticket
Submit:
- one revised figure from Parts 5–10,
- two sentences describing its most visible pattern,
- the sample, year, unit, and data sources in the subtitle or caption, and
- one measurement or coverage limitation visible from the exercise.
Sources
- Choe, Byeong-Hak, and Tilsa Ore-Monago. 2023. Governance and Climate Finance in the Developing World.
- OECD, Climate Finance Provided and Mobilised by Developed Countries in 2013-2024.
- World Bank Worldwide Governance Indicators.
- World Bank Indicators API documentation.
- Natural Earth 1:110m Admin 0 countries.