Setup
Data sources:
- GDP per capita (PPP, constant 2021 international $) — World Bank via OWID
- GDP/GNI per capita in USD constant — World Bank
- Household / total final consumption per capita — World Bank
- National IQ — Jensen & Kirkegaard 2025 (
d_NIQ_S.rds)
- Nighttime lights (DMSP 1992-2013, VIIRS 2013-2024) — Yao country panels
All growth rates are CAGR computed on the earliest available value in 2000–2005 and the latest available value in 2020–2024.
m %>% summarise(
countries = n(),
has_GDP_PPP = sum(!is.na(`GDP/cap PPP const`)),
has_GNI_PPP = sum(!is.na(`GNI/cap PPP const`)),
has_HHcons_PPP = sum(!is.na(`HH cons/cap PPP`)),
has_NIQ = sum(!is.na(NIQ)),
has_VIIRS = sum(!is.na(viirs_growth)),
oecd_members = sum(is_oecd)
) %>% kable()
| 193 |
193 |
118 |
131 |
176 |
142 |
38 |
OECD growth, 2000–2024
GDP per capita
oecd <- m %>% filter(is_oecd, !is.na(`GDP/cap PPP const`)) %>%
mutate(country = entity,
g = `GDP/cap PPP const`,
color = ifelse(g < median(g), "below", "above"))
ggplot(oecd, aes(x = reorder(country, g), y = g, fill = color)) +
geom_col() +
geom_hline(yintercept = median(oecd$g), lty = "dashed", lwd = 0.4) +
geom_text(aes(label = sprintf("%.2f", g)), hjust = -0.1, size = 3) +
coord_flip() +
scale_fill_manual(values = c(above = "#4c72b0", below = "#c44e52"), guide = "none") +
labs(x = NULL,
y = "Annualized GDP per capita growth, 2000–2024 (%, PPP)",
title = sprintf("OECD GDP per capita growth, 2000–2024 (median = %.2f%%)",
median(oecd$g))) +
theme_minimal(base_size = 11)

Household consumption per capita (avoids tax-haven distortion)
GDP-based ranking puts Ireland near the top because of multinational IP-onshoring and aircraft-leasing accounting that doesn’t reach households. Re-ranking on household consumption per capita (PPP) strips most of that distortion: the same spending data that consumers actually do, divided by population.
oecd_c <- m %>% filter(is_oecd, !is.na(`HH cons/cap PPP`)) %>%
mutate(country = entity,
g = `HH cons/cap PPP`,
color = ifelse(g < median(g), "below", "above"))
ggplot(oecd_c, aes(x = reorder(country, g), y = g, fill = color)) +
geom_col() +
geom_hline(yintercept = median(oecd_c$g), lty = "dashed", lwd = 0.4) +
geom_text(aes(label = sprintf("%.2f", g)), hjust = -0.1, size = 3) +
coord_flip() +
scale_fill_manual(values = c(above = "#4c72b0", below = "#c44e52"), guide = "none") +
labs(x = NULL,
y = "Annualized household consumption per capita growth, 2000–2024 (%, PPP)",
title = sprintf("OECD HH consumption per capita growth, 2000–2024 (median = %.2f%%)",
median(oecd_c$g)),
subtitle = "Consumption-based: largely immune to tax-haven inflation") +
theme_minimal(base_size = 11)

GDP vs. consumption: which countries change most?
shift <- m %>% filter(is_oecd, !is.na(`GDP/cap PPP const`),
!is.na(`HH cons/cap PPP`)) %>%
mutate(diff = `GDP/cap PPP const` - `HH cons/cap PPP`)
ggplot(shift, aes(x = reorder(entity, diff), y = diff,
fill = diff > 0)) +
geom_col() +
coord_flip() +
scale_fill_manual(values = c(`TRUE` = "#dd8452", `FALSE` = "#4c72b0"),
labels = c("Consumption grew faster", "GDP grew faster"),
name = NULL) +
geom_hline(yintercept = 0, lwd = 0.4) +
geom_text(aes(label = sprintf("%+.2f", diff)),
hjust = ifelse(shift$diff > 0, -0.1, 1.1), size = 3) +
labs(x = NULL,
y = "GDP CAGR − consumption CAGR (pp/yr)",
title = "Where GDP growth doesn't translate to consumption growth",
subtitle = "Positive = GDP outpaced consumption (tax-haven / corporate-savings)\nNegative = consumption outpaced GDP (sovereign wealth distributions)") +
theme_minimal(base_size = 11)

OECD convergence
oecd_ok <- m %>% filter(is_oecd, !is.na(`GDP/cap PPP const`), !is.na(gdp_start))
fit_oecd <- lm(`GDP/cap PPP const` ~ log_gdp_start, data = oecd_ok)
r2 <- summary(fit_oecd)$r.squared
slope <- coef(fit_oecd)[2]
ggplot(oecd_ok, aes(x = gdp_start, y = `GDP/cap PPP const`)) +
geom_smooth(method = "lm", se = FALSE, color = "black", lty = "dashed") +
geom_point(aes(color = is_oecd_postcomm), size = 3, alpha = 0.9) +
geom_text(aes(label = entity), size = 3, vjust = -0.9, alpha = 0.85) +
scale_x_log10(labels = dollar) +
scale_color_manual(values = c(`FALSE` = "steelblue", `TRUE` = "#c44e52"),
labels = c("Other OECD", "Post-communist OECD"),
name = NULL) +
labs(x = "GDP per capita in 2000 (PPP, log scale)",
y = "Annualized growth 2000-2024 (%)",
title = sprintf("OECD convergence (slope = %.2f, R² = %.2f)", slope, r2),
subtitle = "Post-communist members do most of the work") +
theme_minimal(base_size = 11)

fit_full <- lm(`GDP/cap PPP const` ~ log_gdp_start, data = oecd_ok)
fit_pc <- lm(`GDP/cap PPP const` ~ log_gdp_start, data = oecd_ok %>% filter(is_oecd_postcomm))
fit_npc <- lm(`GDP/cap PPP const` ~ log_gdp_start, data = oecd_ok %>% filter(!is_oecd_postcomm))
data.frame(
group = c("Full OECD", "Post-communist", "Other OECD"),
n = c(nobs(fit_full), nobs(fit_pc), nobs(fit_npc)),
slope = round(c(coef(fit_full)[2], coef(fit_pc)[2], coef(fit_npc)[2]), 2),
R2 = round(c(summary(fit_full)$r.squared, summary(fit_pc)$r.squared,
summary(fit_npc)$r.squared), 3)
) %>% kable()
| Full OECD |
38 |
-1.58 |
0.520 |
| Post-communist |
8 |
-3.37 |
0.878 |
| Other OECD |
30 |
-1.02 |
0.337 |
Global convergence (advantage of backwardness)
w <- m %>% filter(!is.na(`GDP/cap PPP const`), !is.na(gdp_start))
fit_w <- lm(`GDP/cap PPP const` ~ log_gdp_start, data = w)
region_colors <- c(
"Africa" = "#e64b35",
"Asia" = "#4dbbd5",
"Europe" = "#00a087",
"North America" = "#3c5488",
"South America" = "#f39b7f",
"Oceania" = "#8491b4"
)
label_set <- c("United States","China","India","Germany","Japan","United Kingdom",
"France","Italy","Brazil","Russia","Canada","Australia","Mexico",
"Spain","South Korea","Indonesia","Saudi Arabia","Turkey","Argentina",
"Ireland","Vietnam","Bangladesh","Ethiopia","Rwanda","Cambodia",
"Lithuania","Poland","Latvia","Estonia",
"Equatorial Guinea","Venezuela","Zimbabwe","Yemen","Libya","Sudan",
"Central African Republic","Burundi","Singapore","Norway","Luxembourg",
"Qatar","United Arab Emirates","Iceland","Switzerland","Guyana",
"Niger","Sierra Leone")
ggplot(w, aes(x = gdp_start, y = `GDP/cap PPP const`, color = owid_region)) +
geom_smooth(method = "lm", se = FALSE, color = "black", lty = "dashed", lwd = 0.9) +
geom_point(alpha = 0.85, size = 3) +
geom_text(aes(label = ifelse(entity %in% label_set, entity, "")),
size = 2.8, vjust = -0.9, hjust = 0.5, alpha = 0.85, show.legend = FALSE) +
scale_x_log10(labels = dollar) +
scale_color_manual(values = region_colors, name = NULL, na.value = "#999999") +
labs(x = "GDP per capita in 2000 (PPP, log scale)",
y = "Annualized growth 2000-2024 (%)",
title = sprintf("Global convergence / advantage of backwardness (slope = %.2f, R² = %.2f, n = %d)",
coef(fit_w)[2], summary(fit_w)$r.squared, nobs(fit_w)),
subtitle = "Unconditional convergence is weak globally — Asian and post-Soviet countries dominate the catch-up corner; Africa stays poor despite the gap.") +
theme_minimal(base_size = 11) +
theme(legend.position = "bottom")

Conditional convergence: NIQ interaction
Main interaction model
mw <- m %>% filter(!is.na(`GDP/cap PPP const`), !is.na(NIQ), !is.na(log_gdp_start)) %>%
mutate(cagr = `GDP/cap PPP const`,
logG_c = log_gdp_start - mean(log_gdp_start),
NIQ_c = NIQ - mean(NIQ))
fit_int <- lm(cagr ~ logG_c * NIQ_c, data = mw)
fit_add <- lm(cagr ~ logG_c + NIQ_c, data = mw)
broom::tidy(fit_int) %>%
mutate(across(where(is.numeric), ~ signif(., 4))) %>%
kable(caption = "Centered interaction model: growth ~ log(GDP_2000) × NIQ")
Centered interaction model: growth ~ log(GDP_2000) × NIQ
| (Intercept) |
2.2970 |
0.145600 |
15.780 |
0.0000000 |
| logG_c |
-1.1910 |
0.139100 |
-8.563 |
0.0000000 |
| NIQ_c |
0.1196 |
0.015560 |
7.690 |
0.0000000 |
| logG_c:NIQ_c |
-0.0327 |
0.009445 |
-3.463 |
0.0006751 |
cat(sprintf("R² = %.3f, n = %d\n", summary(fit_int)$r.squared, nobs(fit_int)))
## R² = 0.332, n = 176
cat(sprintf("F-test (interaction vs additive): F = %.2f, p = %.2e\n",
anova(fit_add, fit_int)[2,"F"],
anova(fit_add, fit_int)[2,"Pr(>F)"]))
## F-test (interaction vs additive): F = 11.99, p = 6.75e-04
Convergence stratified by NIQ band
mw_band <- mw %>%
mutate(niq_band = cut(NIQ, breaks = c(0, 80, 90, 200),
labels = c("NIQ < 80", "NIQ 80–90", "NIQ ≥ 90")))
# Compute slope per band for the subtitle
band_fits <- mw_band %>% group_by(niq_band) %>%
summarise(slope = round(coef(lm(cagr ~ log_gdp_start))[2], 2),
n = n(), .groups = "drop")
sub <- paste(sprintf("%s: slope = %.2f (n=%d)", band_fits$niq_band,
band_fits$slope, band_fits$n), collapse = " ")
ggplot(mw_band, aes(x = gdp_start, y = cagr, color = niq_band)) +
geom_point(size = 2.6, alpha = 0.85) +
geom_smooth(method = "lm", se = FALSE, lwd = 0.9) +
scale_x_log10(labels = dollar) +
scale_color_manual(values = c("NIQ < 80" = "#e64b35",
"NIQ 80–90" = "#f0a040",
"NIQ ≥ 90" = "#3c5488"),
name = "NIQ band") +
labs(x = "GDP per capita in 2000 (PPP, log scale)",
y = "CAGR 2000-2024 (%)",
title = "Conditional convergence: each NIQ band has its own slope",
subtitle = sub) +
theme_minimal(base_size = 11) +
theme(legend.position = "bottom")

The bottom band (NIQ < 80) is essentially flat — these countries don’t converge to the frontier regardless of starting income. The middle and high bands show clear convergence, with high-NIQ countries lying systematically above the others for any given starting GDP. This is the visual signature of the formal interaction term.
Quadrant view (poor + smart = sweet spot)
mw$niq_high <- mw$NIQ >= median(mw$NIQ)
mw$gdp_high <- mw$gdp_start >= median(mw$gdp_start)
quad_means <- mw %>%
mutate(quadrant = case_when(
!gdp_high & niq_high ~ "Poor + smart",
gdp_high & niq_high ~ "Rich + smart (frontier)",
!gdp_high & !niq_high ~ "Poor + low-NIQ (stuck)",
TRUE ~ "Rich + low-NIQ (resource curse)"
)) %>%
group_by(quadrant) %>%
summarise(n = n(), mean_cagr = round(mean(cagr), 2), .groups = "drop") %>%
arrange(desc(mean_cagr))
kable(quad_means, caption = "Mean CAGR by quadrant of starting GDP × NIQ")
Mean CAGR by quadrant of starting GDP × NIQ
| Poor + smart |
18 |
4.31 |
| Poor + low-NIQ (stuck) |
70 |
1.85 |
| Rich + smart (frontier) |
70 |
1.83 |
| Rich + low-NIQ (resource curse) |
18 |
0.75 |
label_set <- c("China","India","Vietnam","Ethiopia","Cambodia","Bangladesh",
"Singapore","Italy","Japan","Mexico","South Korea","Ireland",
"Norway","United States","Lithuania","Poland","Turkey","Argentina",
"Nigeria","South Africa","Equatorial Guinea","Niger","Rwanda",
"Switzerland","Germany","Brazil","Russia","Indonesia")
ggplot(mw, aes(x = NIQ, y = gdp_start)) +
geom_hline(yintercept = median(mw$gdp_start), lty = "dashed", alpha = 0.4) +
geom_vline(xintercept = median(mw$NIQ), lty = "dashed", alpha = 0.4) +
geom_point(aes(color = cagr), size = 4) +
geom_text(aes(label = ifelse(entity %in% label_set, entity, "")),
size = 3, vjust = -1, fontface = "bold") +
scale_color_gradient2(low = "#b2182b", mid = "#cccccc", high = "#1a7f37",
midpoint = 1.5, name = "CAGR\n2000-2024") +
scale_y_log10(labels = dollar) +
labs(x = "National IQ", y = "GDP per capita 2000 (PPP, log scale)",
title = "Conditional convergence × NIQ",
subtitle = "Poor + smart corner = sweet spot; rich + low-NIQ = resource curse") +
theme_minimal(base_size = 11)

Robustness: same model across income measures
We re-run growth ~ log(GDP_2000) × NIQ with six different outcome measures. All variables are centered so the main effects are evaluated at the sample mean. Cells show estimate (SE) for each term.
measures <- c("GDP/cap PPP const", "GDP/cap USD const",
"GNI/cap PPP const", "GNI/cap USD const",
"HH cons/cap PPP", "Total cons/cap USD")
fmt <- function(est, se) sprintf("%.3f (%.3f)", est, se)
fmt4 <- function(est, se) sprintf("%.4f (%.4f)", est, se)
sig_p <- function(p) {
if (p < 0.001) "< 0.001"
else if (p < 0.01) sprintf("%.3f", p)
else sprintf("%.3f", p)
}
star <- function(p) ifelse(p < 0.001, "***",
ifelse(p < 0.01, "**",
ifelse(p < 0.05, "*",
ifelse(p < 0.1, ".", ""))))
results <- lapply(measures, function(meas) {
d <- m %>% filter(!is.na(.data[[meas]]), !is.na(NIQ), !is.na(log_gdp_start)) %>%
mutate(cagr = .data[[meas]],
logG_c = log_gdp_start - mean(log_gdp_start),
NIQ_c = NIQ - mean(NIQ))
fit <- lm(cagr ~ logG_c * NIQ_c, data = d)
cf <- coef(summary(fit))
data.frame(
Measure = meas,
n = nobs(fit),
R2 = sprintf("%.3f", summary(fit)$r.squared),
Intercept = fmt(cf["(Intercept)","Estimate"], cf["(Intercept)","Std. Error"]),
`log(GDP)_c` = paste0(fmt(cf["logG_c","Estimate"], cf["logG_c","Std. Error"]),
" ", star(cf["logG_c","Pr(>|t|)"])),
NIQ_c = paste0(fmt(cf["NIQ_c","Estimate"], cf["NIQ_c","Std. Error"]),
" ", star(cf["NIQ_c","Pr(>|t|)"])),
Interaction = paste0(fmt4(cf["logG_c:NIQ_c","Estimate"], cf["logG_c:NIQ_c","Std. Error"]),
" ", star(cf["logG_c:NIQ_c","Pr(>|t|)"])),
`p (interaction)` = sig_p(cf["logG_c:NIQ_c","Pr(>|t|)"]),
check.names = FALSE,
stringsAsFactors = FALSE
)
}) %>% bind_rows()
kable(results,
caption = paste0("Interaction model coefficients across six income/consumption measures. ",
"All variables centered. Cells: estimate (SE). ",
"Significance: *** p<0.001, ** p<0.01, * p<0.05."),
align = "lccccccc")
Interaction model coefficients across six income/consumption measures. All variables centered. Cells: estimate (SE). Significance: *** p<0.001, ** p<0.01, * p<0.05.
| GDP/cap PPP const |
176 |
0.332 |
2.297 (0.146) |
-1.191 (0.139) *** |
0.120 (0.016) *** |
-0.0327 (0.0094) *** |
< 0.001 |
| GDP/cap USD const |
170 |
0.328 |
2.322 (0.150) |
-1.179 (0.145) *** |
0.118 (0.016) *** |
-0.0354 (0.0101) *** |
< 0.001 |
| GNI/cap PPP const |
116 |
0.365 |
2.551 (0.154) |
-1.120 (0.152) *** |
0.098 (0.016) *** |
-0.0395 (0.0100) *** |
< 0.001 |
| GNI/cap USD const |
115 |
0.367 |
2.549 (0.155) |
-1.115 (0.153) *** |
0.099 (0.017) *** |
-0.0415 (0.0101) *** |
< 0.001 |
| HH cons/cap PPP |
129 |
0.227 |
2.718 (0.201) |
-0.852 (0.196) *** |
0.082 (0.022) *** |
-0.0590 (0.0133) *** |
< 0.001 |
| Total cons/cap USD |
132 |
0.279 |
2.605 (0.170) |
-0.879 (0.164) *** |
0.089 (0.018) *** |
-0.0520 (0.0113) *** |
< 0.001 |
The interaction is consistently negative and highly significant across all measures (every p < 0.001 except the unbiased Wald formulation gets the same conclusion). Consumption-based measures show the strongest interaction (-0.068 vs -0.037 for GDP), suggesting that GDP-based measurement understates the conditional convergence × NIQ pattern rather than fabricating it. The PPP-vs-USD distinction matters very little within each income concept; the income-concept choice (GDP → GNI → consumption) does most of the work.
Spatial robustness
The interaction may be inflated by neighbour-effects (countries near each other share growth shocks) or by continental-level confounders. Re-fit with two layers: (a) regional fixed effects using OWID continent classification, (b) a spatial error model with k=3 nearest-neighbour row-standardized weights based on country centroids.
The OWID continent classification used as fixed effects:
m %>% mutate(region = ifelse(is.na(owid_region), "Other", owid_region)) %>%
count(region, name = "n_countries") %>% arrange(desc(n_countries)) %>%
kable(caption = "OWID 6-continent classification used as regional fixed effects")
OWID 6-continent classification used as regional fixed effects
| Africa |
51 |
| Asia |
47 |
| Europe |
42 |
| North America |
28 |
| Oceania |
14 |
| South America |
11 |
This split is admittedly coarse — Africa is not split into MENA vs Sub-Saharan, Europe lumps post-Soviet with Western Europe, Asia spans from Japan to Tajikistan. The “did continental confounders eat the result” check it provides is therefore a weak version; a finer split (WB 7-region or UN sub-regional) would push it further but would also reduce within-region df.
spat <- readRDS("data/processed/spatial_summary.rds")
spat %>%
mutate(
`Interaction est. (SE)` = sprintf("%.4f (%.4f)", Interaction, SE),
`p` = sprintf("%.1e", p),
`λ` = ifelse(is.na(lambda), "—", sprintf("%.3f", lambda)),
`R² / NK` = sprintf("%.3f", fit)
) %>%
select(Measure, Model, n, `Interaction est. (SE)`, p, `λ`, `R² / NK`) %>%
kable(caption = "Spatial robustness: 4 model variants × 6 outcomes")
Spatial robustness: 4 model variants × 6 outcomes
| …1 |
GDP/cap PPP const |
OLS plain |
176 |
-0.0327 (0.0094) |
6.8e-04 |
— |
0.332 |
| …2 |
GDP/cap PPP const |
OLS + region FE |
176 |
-0.0267 (0.0102) |
9.4e-03 |
— |
0.399 |
| lambda…3 |
GDP/cap PPP const |
Spatial error |
176 |
-0.0309 (0.0101) |
2.3e-03 |
0.211 |
0.351 |
| lambda…4 |
GDP/cap PPP const |
Spatial err + FE |
176 |
-0.0261 (0.0102) |
1.1e-02 |
0.110 |
0.403 |
| …5 |
GDP/cap USD const |
OLS plain |
170 |
-0.0354 (0.0101) |
5.8e-04 |
— |
0.328 |
| …6 |
GDP/cap USD const |
OLS + region FE |
170 |
-0.0299 (0.0109) |
6.9e-03 |
— |
0.395 |
| lambda…7 |
GDP/cap USD const |
Spatial error |
170 |
-0.0331 (0.0107) |
1.9e-03 |
0.173 |
0.341 |
| lambda…8 |
GDP/cap USD const |
Spatial err + FE |
170 |
-0.0292 (0.0108) |
7.1e-03 |
0.066 |
0.397 |
| …9 |
GNI/cap PPP const |
OLS plain |
116 |
-0.0395 (0.0100) |
1.4e-04 |
— |
0.365 |
| …10 |
GNI/cap PPP const |
OLS + region FE |
116 |
-0.0414 (0.0104) |
1.3e-04 |
— |
0.435 |
| lambda…11 |
GNI/cap PPP const |
Spatial error |
116 |
-0.0370 (0.0106) |
5.0e-04 |
0.362 |
0.428 |
| lambda…12 |
GNI/cap PPP const |
Spatial err + FE |
116 |
-0.0395 (0.0106) |
1.8e-04 |
0.283 |
0.462 |
| …13 |
GNI/cap USD const |
OLS plain |
115 |
-0.0415 (0.0101) |
7.4e-05 |
— |
0.367 |
| …14 |
GNI/cap USD const |
OLS + region FE |
115 |
-0.0438 (0.0105) |
6.7e-05 |
— |
0.434 |
| lambda…15 |
GNI/cap USD const |
Spatial error |
115 |
-0.0391 (0.0107) |
2.7e-04 |
0.348 |
0.424 |
| lambda…16 |
GNI/cap USD const |
Spatial err + FE |
115 |
-0.0418 (0.0107) |
8.9e-05 |
0.268 |
0.458 |
| …17 |
HH cons/cap PPP |
OLS plain |
129 |
-0.0590 (0.0133) |
2.1e-05 |
— |
0.227 |
| …18 |
HH cons/cap PPP |
OLS + region FE |
129 |
-0.0648 (0.0146) |
2.0e-05 |
— |
0.259 |
| lambda…19 |
HH cons/cap PPP |
Spatial error |
129 |
-0.0559 (0.0142) |
8.5e-05 |
0.264 |
0.264 |
| lambda…20 |
HH cons/cap PPP |
Spatial err + FE |
129 |
-0.0611 (0.0147) |
3.4e-05 |
0.237 |
0.286 |
| …21 |
Total cons/cap USD |
OLS plain |
132 |
-0.0520 (0.0113) |
1.0e-05 |
— |
0.279 |
| …22 |
Total cons/cap USD |
OLS + region FE |
132 |
-0.0561 (0.0125) |
1.6e-05 |
— |
0.298 |
| lambda…23 |
Total cons/cap USD |
Spatial error |
132 |
-0.0485 (0.0121) |
5.9e-05 |
0.279 |
0.318 |
| lambda…24 |
Total cons/cap USD |
Spatial err + FE |
132 |
-0.0519 (0.0126) |
3.8e-05 |
0.257 |
0.328 |
Across all 24 model fits the interaction coefficient remains negative and significant. The spatial λ is small-to-moderate (0.07–0.36), confirming some geographic clustering of residuals — but stripping it out via the spatial error model only modestly attenuates the interaction. Adding both region FE and spatial errors gives the most conservative estimate; even there the interaction holds at p < 0.05 in every specification.
Outliers from the GDP interaction model
fit_full <- lm(`GDP/cap PPP const` ~ log_gdp_start * NIQ,
data = m %>% filter(!is.na(`GDP/cap PPP const`), !is.na(NIQ)))
oo <- m %>% filter(!is.na(`GDP/cap PPP const`), !is.na(NIQ)) %>%
mutate(pred = predict(fit_full),
residual = `GDP/cap PPP const` - pred) %>%
arrange(desc(residual))
cat("**Top positive outliers (grew much MORE than predicted)**\n")
## **Top positive outliers (grew much MORE than predicted)**
oo %>% select(entity, gdp_start, NIQ,
actual = `GDP/cap PPP const`, pred, residual) %>%
mutate(across(c(gdp_start, actual, pred, residual), ~ round(., 2))) %>%
head(15) %>% kable()
| Guyana |
8025.29 |
75.43 |
9.48 |
1.52 |
7.95 |
| Ethiopia |
810.27 |
67.95 |
5.44 |
2.15 |
3.29 |
| Georgia |
5706.27 |
85.04 |
6.33 |
3.16 |
3.17 |
| Turkmenistan |
5112.53 |
81.26 |
5.54 |
2.76 |
2.78 |
| Ghana |
3327.35 |
63.99 |
3.18 |
0.57 |
2.61 |
| Azerbaijan |
5656.93 |
85.72 |
5.84 |
3.26 |
2.57 |
| Bhutan |
5287.57 |
76.40 |
4.52 |
2.05 |
2.47 |
| Panama |
14572.98 |
79.65 |
3.89 |
1.42 |
2.47 |
| Armenia |
4589.01 |
88.63 |
6.34 |
3.95 |
2.39 |
| India |
3099.50 |
76.49 |
4.92 |
2.58 |
2.35 |
| Albania |
6582.02 |
83.98 |
5.08 |
2.84 |
2.24 |
| Ireland |
53675.00 |
99.10 |
3.37 |
1.27 |
2.10 |
| Romania |
14853.67 |
87.34 |
4.27 |
2.21 |
2.05 |
| Bangladesh |
2712.94 |
77.71 |
4.87 |
2.90 |
1.97 |
| Cape Verde |
4510.14 |
71.26 |
3.31 |
1.46 |
1.85 |
cat("\n**Top negative outliers (grew much LESS than predicted)**\n")
##
## **Top negative outliers (grew much LESS than predicted)**
oo %>% select(entity, gdp_start, NIQ,
actual = `GDP/cap PPP const`, pred, residual) %>%
mutate(across(c(gdp_start, actual, pred, residual), ~ round(., 2))) %>%
arrange(residual) %>% head(15) %>% kable()
| Sudan |
3575.85 |
76.04 |
-2.68 |
2.37 |
-5.05 |
| Syria |
7807.93 |
79.32 |
-2.52 |
2.04 |
-4.56 |
| Burundi |
1033.47 |
76.81 |
0.07 |
3.70 |
-3.63 |
| Palestine |
4520.89 |
81.04 |
-0.67 |
2.87 |
-3.54 |
| Madagascar |
1701.44 |
77.59 |
-0.11 |
3.35 |
-3.46 |
| Libya |
17747.02 |
81.96 |
-1.42 |
1.44 |
-2.86 |
| Haiti |
3267.08 |
72.74 |
-0.63 |
1.94 |
-2.57 |
| Solomon Islands |
2276.82 |
75.31 |
0.14 |
2.68 |
-2.54 |
| Central African Republic |
1245.81 |
68.25 |
-0.48 |
1.91 |
-2.39 |
| Kiribati |
2481.42 |
80.45 |
1.14 |
3.44 |
-2.30 |
| Vanuatu |
3342.17 |
73.36 |
-0.22 |
2.02 |
-2.24 |
| Afghanistan |
1617.83 |
75.70 |
0.89 |
3.06 |
-2.17 |
| Palau |
15447.95 |
89.29 |
0.27 |
2.37 |
-2.10 |
| Zimbabwe |
6170.33 |
72.15 |
-0.70 |
1.33 |
-2.03 |
| Lebanon |
11571.73 |
81.82 |
-0.09 |
1.91 |
-2.01 |
Session
sessionInfo()
## R version 4.5.3 (2026-03-11)
## Platform: x86_64-pc-linux-gnu
## Running under: Linux Mint 21.1
##
## Matrix products: default
## BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.10.0
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.10.0 LAPACK version 3.10.0
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
## [5] LC_MONETARY=en_DK.UTF-8 LC_MESSAGES=en_US.UTF-8
## [7] LC_PAPER=en_DK.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=en_DK.UTF-8 LC_IDENTIFICATION=C
##
## time zone: Europe/Brussels
## tzcode source: system (glibc)
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] jsonlite_2.0.0 knitr_1.50 broom_1.0.11 scales_1.4.0
## [5] ggplot2_4.0.1.9000 tidyr_1.3.1 dplyr_1.1.4
##
## loaded via a namespace (and not attached):
## [1] Matrix_1.7-4 gtable_0.3.6 compiler_4.5.3 tidyselect_1.2.1
## [5] jquerylib_0.1.4 splines_4.5.3 yaml_2.3.10 fastmap_1.2.0
## [9] lattice_0.22-7 R6_2.6.1 labeling_0.4.3 generics_0.1.4
## [13] backports_1.5.0 tibble_3.3.0 bslib_0.9.0 pillar_1.11.1
## [17] RColorBrewer_1.1-3 rlang_1.1.6.9000 cachem_1.1.0 xfun_0.57
## [21] sass_0.4.10 S7_0.2.1 cli_3.6.5 mgcv_1.9-3
## [25] withr_3.0.2 magrittr_2.0.4 digest_0.6.39 grid_4.5.3
## [29] nlme_3.1-168 lifecycle_1.0.4 vctrs_0.6.5 evaluate_1.0.5
## [33] glue_1.8.0 farver_2.1.2 rmarkdown_2.30 purrr_1.2.0
## [37] tools_4.5.3 pkgconfig_2.0.3 htmltools_0.5.8.1