Research question: Do people in wealthier countries live longer than people in poorer countries? And does having more money still make a big difference once a country is already wealthy?
I chose this question because I want to see how a country’s economy affects people’s health and how long they live. My guess is that wealthier countries have higher life expectancy because they usually have better access to healthcare, clean water, and food. But once a country reaches a certain level of wealth, most of those basic needs are already covered, so I expect extra money to add fewer years of life. To answer this, I am using the World Development Indicators (WDI) from the World Bank, which is the World Bank’s main collection of country-level data on the economy, health, and living conditions (World Bank, 2026). I downloaded two indicators as CSV files. In each file, every row is a country (or a group of countries, like “World”), and every year from 1960 on has its own column. Each download also comes with a metadata file that lists each country’s region and income group.
I am only using data from 2022, because almost every country has values for both indicators in that year. These are the columns I use:
NY.GDP.PCAP.CD is GDP per capita (the
total value of everything a country produces in a year divided by its
population, in current U.S. dollars) and SP.DYN.LE00.IN is
life expectancy at birth (the average number of years a
newborn would live if current death rates stayed the same).First, I load the three files and look at their structure and missing values (exploratory data analysis). Then I clean the data: I combine the two indicators, keep only the 2022 column, remove rows that are groups of countries rather than real countries, and drop countries that are missing either value. Next, I make a summary table of GDP per capita and life expectancy for each income group. Then I make two scatter plots. The first one shows GDP per capita on a normal scale, which lets me see whether the relationship curves and levels off. The second one puts GDP on a log scale, where each step on the axis means income multiplied by 10. If gains really do shrink as countries get richer, the points should fall close to a straight line on the log scale. Finally, I fit a linear regression of life expectancy on log(GDP per capita) to measure how strong the relationship is, and I compare it to a regression that uses plain GDP to see which one fits better.
The World Bank CSV files start with four lines of notes before the column names, so I skip those lines when reading them.
library(readr)
library(dplyr)
library(tidyr)
library(ggplot2)
library(knitr)
# The downloaded file names end with a version number, so I find them by pattern.
# All three files must be in the same folder as this .Rmd file.
gdp_file <- list.files(pattern = "^API_NY\\.GDP\\.PCAP\\.CD.*\\.csv$")[1]
life_file <- list.files(pattern = "^API_SP\\.DYN\\.LE00\\.IN.*\\.csv$")[1]
meta_file <- list.files(pattern = "^Metadata_Country_API_NY\\.GDP.*\\.csv$")[1]
gdp_raw <- read_csv(gdp_file, skip = 4, show_col_types = FALSE)
life_raw <- read_csv(life_file, skip = 4, show_col_types = FALSE)
meta_raw <- read_csv(meta_file, show_col_types = FALSE)
# How big is each file, and what do the first columns look like?
dim(gdp_raw)
## [1] 266 71
dim(life_raw)
## [1] 266 71
glimpse(meta_raw)
## Rows: 265
## Columns: 6
## $ `Country Code` <chr> "ABW", "AFE", "AFG", "AFW", "AGO", "ALB", "AND", "ARB",…
## $ Region <chr> "Latin America & Caribbean", NA, "Middle East & North A…
## $ IncomeGroup <chr> "High income", NA, "Low income", NA, "Lower middle inco…
## $ SpecialNotes <chr> NA, "26 countries, stretching from the Red Sea in the N…
## $ TableName <chr> "Aruba", "Africa Eastern and Southern", "Afghanistan", …
## $ ...6 <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
# How many rows are missing a 2022 value in each file?
sum(is.na(gdp_raw$`2022`))
## [1] 9
sum(is.na(life_raw$`2022`))
## [1] 1
# How many rows are groups of countries (blank Region) instead of real countries?
sum(is.na(meta_raw$Region))
## [1] 48
# Stack the two indicator files, keep only the columns I need,
# and use Indicator Code to give each measure its own column.
wdi_2022 <- bind_rows(gdp_raw, life_raw) %>%
select(`Country Name`, `Country Code`, `Indicator Code`, `2022`) %>%
pivot_wider(names_from = `Indicator Code`, values_from = `2022`) %>%
rename(country = `Country Name`,
code = `Country Code`,
gdp_pc = NY.GDP.PCAP.CD,
life_exp = SP.DYN.LE00.IN)
# Add region and income group from the metadata file.
countries <- wdi_2022 %>%
left_join(select(meta_raw, `Country Code`, Region, IncomeGroup),
by = c("code" = "Country Code")) %>%
filter(!is.na(Region)) %>% # drop groups like "World"
filter(!is.na(gdp_pc), !is.na(life_exp)) %>% # need both values
mutate(Region = trimws(Region),
IncomeGroup = factor(IncomeGroup,
levels = c("Low income", "Lower middle income",
"Upper middle income", "High income")),
log_gdp = log(gdp_pc))
summary(countries$gdp_pc)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 301.8 2899.2 7655.6 21200.9 28360.3 226052.1
summary(countries$life_exp)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 18.82 67.71 74.21 73.11 78.53 85.75
# Look at the lowest life expectancies to check for data errors.
countries %>% arrange(life_exp) %>% select(country, life_exp) %>% head(5)
## # A tibble: 5 × 2
## country life_exp
## <chr> <dbl>
## 1 Central African Republic 18.8
## 2 Somalia, Fed. Rep. 53.9
## 3 Nigeria 54.1
## 4 Chad 54.5
## 5 Lesotho 56.8
# A life expectancy under 40 years is far below every other country in 2022,
# so I treat it as a data error and remove it.
removed <- countries %>% filter(life_exp < 40)
countries <- countries %>% filter(life_exp >= 40)
removed %>% select(country, life_exp)
## # A tibble: 1 × 2
## country life_exp
## <chr> <dbl>
## 1 Central African Republic 18.8
nrow(countries)
## [1] 208
After cleaning, the dataset has 208 countries with both values for 2022. I removed Central African Republic because its reported life expectancy (18.8 years) is far below every other country, which points to a data error rather than a real value.
income_table <- countries %>%
group_by(IncomeGroup) %>%
summarise(Countries = n(),
`Median GDP per capita ($)` = round(median(gdp_pc)),
`Mean life expectancy (years)` = round(mean(life_exp), 1),
`Lowest` = round(min(life_exp), 1),
`Highest` = round(max(life_exp), 1))
kable(income_table, format.args = list(big.mark = ","),
caption = "Table 1. GDP per capita and life expectancy by World Bank income group, 2022")
| IncomeGroup | Countries | Median GDP per capita ($) | Mean life expectancy (years) | Lowest | Highest |
|---|---|---|---|---|---|
| Low income | 20 | 832 | 63.2 | 53.9 | 72.8 |
| Lower middle income | 47 | 2,325 | 67.7 | 54.1 | 78.0 |
| Upper middle income | 58 | 6,653 | 73.3 | 63.4 | 80.8 |
| High income | 83 | 35,654 | 79.1 | 61.9 | 85.7 |
# Plot 1: GDP on a normal scale, with a smooth curve to show the overall shape
ggplot(countries, aes(x = gdp_pc, y = life_exp)) +
geom_point(aes(color = Region), alpha = 0.75, size = 2.3) +
geom_smooth(method = "loess", se = FALSE, color = "black", linewidth = 0.9) +
scale_x_continuous(labels = scales::dollar) +
labs(title = "Life expectancy rises fast at low incomes, then levels off",
subtitle = "Each point is one country, 2022. Black line = smoothed trend.",
x = "GDP per capita (current US$)",
y = "Life expectancy at birth (years)",
color = "Region",
caption = "Source: World Bank, World Development Indicators") +
theme_minimal(base_size = 12) +
theme(legend.position = "bottom") +
guides(color = guide_legend(nrow = 3))
# Plot 2: GDP on a log scale, with a straight regression line
ggplot(countries, aes(x = gdp_pc, y = life_exp)) +
geom_point(aes(color = Region), alpha = 0.75, size = 2.3) +
geom_smooth(method = "lm", se = TRUE, color = "black", linewidth = 0.9) +
scale_x_log10(labels = scales::dollar) +
labs(title = "On a log scale, the relationship is close to a straight line",
subtitle = "Each step on the x-axis is 10 times more GDP per capita. Black line = linear regression.",
x = "GDP per capita (current US$, log scale)",
y = "Life expectancy at birth (years)",
color = "Region",
caption = "Source: World Bank, World Development Indicators") +
theme_minimal(base_size = 12) +
theme(legend.position = "bottom") +
guides(color = guide_legend(nrow = 3))
# Model A: life expectancy explained by plain GDP per capita
model_linear <- lm(life_exp ~ gdp_pc, data = countries)
# Model B: life expectancy explained by log(GDP per capita)
model_log <- lm(life_exp ~ log_gdp, data = countries)
summary(model_log)
##
## Call:
## lm(formula = life_exp ~ log_gdp, data = countries)
##
## Residuals:
## Min 1Q Median 3Q Max
## -14.9880 -2.1718 0.4391 2.6976 8.0083
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 37.1209 1.6943 21.91 <2e-16 ***
## log_gdp 4.0072 0.1849 21.67 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 3.877 on 206 degrees of freedom
## Multiple R-squared: 0.695, Adjusted R-squared: 0.6936
## F-statistic: 469.5 on 1 and 206 DF, p-value: < 2.2e-16
# Which model fits better? Compare R-squared.
fit_compare <- tibble(
Model = c("Life expectancy ~ GDP per capita",
"Life expectancy ~ log(GDP per capita)"),
`R-squared` = round(c(summary(model_linear)$r.squared,
summary(model_log)$r.squared), 3))
kable(fit_compare, caption = "Table 2. How well each model fits the data")
| Model | R-squared |
|---|---|
| Life expectancy ~ GDP per capita | 0.389 |
| Life expectancy ~ log(GDP per capita) | 0.695 |
# Correlation between log GDP and life expectancy
cor(countries$log_gdp, countries$life_exp)
## [1] 0.833688
# Save the key numbers so they can be used in the conclusion text
slope <- coef(model_log)[["log_gdp"]]
gain_x2 <- slope * log(2) # years gained when GDP per capita doubles
r2_log <- summary(model_log)$r.squared
r2_lin <- summary(model_linear)$r.squared
p_value <- summary(model_log)$coefficients["log_gdp", "Pr(>|t|)"]
r_value <- cor(countries$log_gdp, countries$life_exp)
# Years gained for a $1,000 raise at a poor vs. a rich income level
gain_poor <- slope * log(2000 / 1000)
gain_rich <- slope * log(51000 / 50000)
low_mean <- income_table$`Mean life expectancy (years)`[income_table$IncomeGroup == "Low income"]
high_mean <- income_table$`Mean life expectancy (years)`[income_table$IncomeGroup == "High income"]
The results answer both parts of my question. First, people in wealthier countries do live longer. The correlation between log GDP per capita and life expectancy is 0.83, which is a strong positive relationship, and the regression slope is statistically significant (p < 0.001). On average, people in high-income countries are expected to live 79.1 years, compared with 63.2 years in low-income countries, which is a gap of about 15.9 years (Table 1). Second, extra money matters much less once a country is already wealthy. In the first plot, life expectancy climbs steeply among the poorest countries and then flattens out. The log model explains 70% of the differences in life expectancy between countries, while the model with plain GDP explains only 39% (Table 2). This means the relationship depends on how much income grows relative to where a country starts, not on adding a fixed number of dollars. According to the model, each time GDP per capita doubles, life expectancy goes up by about 2.8 years. So an extra $1,000 per person is linked to about 2.8 more years of life in a country going from $1,000 to $2,000, but only about 0.08 years in a country going from $50,000 to $51,000.
These results suggest that economic growth in the poorest countries could have a much bigger effect on health than growth in rich countries. This is likely because the first gains in income pay for basics like clean water, vaccines, and enough food. However, this analysis only shows a relationship, not cause and effect. GDP per capita also leaves a lot unexplained: some countries live much longer or shorter lives than their income would predict, which suggests that things like healthcare systems, inequality, and conflict matter too. For future research, I would add health spending, access to clean water, or income inequality to the model to see which of these explain the gap. I would also look at how both measures changed over time (for example, 2000 to 2022) to see whether countries that grew richer actually gained years of life, which would get closer to showing cause and effect.
World Bank. (2026). GDP per capita (current US$) (Indicator NY.GDP.PCAP.CD) [Data set]. World Development Indicators. https://data.worldbank.org/indicator/NY.GDP.PCAP.CD
World Bank. (2026). Life expectancy at birth, total (years) (Indicator SP.DYN.LE00.IN) [Data set]. World Development Indicators. https://data.worldbank.org/indicator/SP.DYN.LE00.IN
World Bank. (2026). World Development Indicators [Database]. https://databank.worldbank.org/source/world-development-indicators
Wickham, H., François, R., Henry, L., Müller, K., & Vaughan, D. (2023). dplyr: A grammar of data manipulation (R package). https://CRAN.R-project.org/package=dplyr
Wickham, H. (2016). ggplot2: Elegant graphics for data analysis. Springer-Verlag New York. https://ggplot2.tidyverse.org