library(tidyverse) # dplyr, tidyr, ggplot2, readr, ...
library(scales) # axis label formatting
library(corrplot) # correlation heatmap
library(broom) # tidy regression output
library(knitr) # tables
theme_set(theme_minimal(base_size = 12))
Two Kaggle datasets are used:
world_population.csv) — https://www.kaggle.com/datasets/iamsouravbanerjee/world-population-datasetCO2_emission.csv) — https://www.kaggle.com/datasets/koustavghosh149/co2-emission-around-the-worldpop_raw <- read_csv("world_population.csv", show_col_types = FALSE)
co2_raw <- read_csv("CO2_emission.csv", show_col_types = FALSE,
name_repair = "unique_quiet")
names(pop_raw)
## [1] "Rank" "CCA3"
## [3] "Country/Territory" "Capital"
## [5] "Continent" "2022 Population"
## [7] "2020 Population" "2015 Population"
## [9] "2010 Population" "2000 Population"
## [11] "1990 Population" "1980 Population"
## [13] "1970 Population" "Area (km²)"
## [15] "Density (per km²)" "Growth Rate"
## [17] "World Population Percentage"
head(pop_raw, 5)
| Rank | CCA3 | Country/Territory | Capital | Continent | 2022 Population | 2020 Population | 2015 Population | 2010 Population | 2000 Population | 1990 Population | 1980 Population | 1970 Population | Area (km²) | Density (per km²) | Growth Rate | World Population Percentage |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 36 | AFG | Afghanistan | Kabul | Asia | 41128771 | 38972230 | 33753499 | 28189672 | 19542982 | 10694796 | 12486631 | 10752971 | 652230 | 63.0587 | 1.0257 | 0.52 |
| 138 | ALB | Albania | Tirana | Europe | 2842321 | 2866849 | 2882481 | 2913399 | 3182021 | 3295066 | 2941651 | 2324731 | 28748 | 98.8702 | 0.9957 | 0.04 |
| 34 | DZA | Algeria | Algiers | Africa | 44903225 | 43451666 | 39543154 | 35856344 | 30774621 | 25518074 | 18739378 | 13795915 | 2381741 | 18.8531 | 1.0164 | 0.56 |
| 213 | ASM | American Samoa | Pago Pago | Oceania | 44273 | 46189 | 51368 | 54849 | 58230 | 47818 | 32886 | 27075 | 199 | 222.4774 | 0.9831 | 0.00 |
| 203 | AND | Andorra | Andorra la Vella | Europe | 79824 | 77700 | 71746 | 71519 | 66097 | 53569 | 35611 | 19860 | 468 | 170.5641 | 1.0100 | 0.00 |
tail(pop_raw, 10)
| Rank | CCA3 | Country/Territory | Capital | Continent | 2022 Population | 2020 Population | 2015 Population | 2010 Population | 2000 Population | 1990 Population | 1980 Population | 1970 Population | Area (km²) | Density (per km²) | Growth Rate | World Population Percentage |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 43 | UZB | Uzbekistan | Tashkent | Asia | 34627652 | 33526656 | 30949417 | 28614227 | 24925554 | 20579100 | 15947129 | 12011361 | 447400 | 77.3975 | 1.0160 | 0.43 |
| 181 | VUT | Vanuatu | Port-Vila | Oceania | 326740 | 311685 | 276438 | 245453 | 192074 | 150882 | 118156 | 87019 | 12189 | 26.8061 | 1.0238 | 0.00 |
| 234 | VAT | Vatican City | Vatican City | Europe | 510 | 520 | 564 | 596 | 651 | 700 | 733 | 752 | 1 | 510.0000 | 0.9980 | 0.00 |
| 51 | VEN | Venezuela | Caracas | South America | 28301696 | 28490453 | 30529716 | 28715022 | 24427729 | 19750579 | 15210443 | 11355475 | 916445 | 30.8820 | 1.0036 | 0.35 |
| 16 | VNM | Vietnam | Hanoi | Asia | 98186856 | 96648685 | 92191398 | 87411012 | 79001142 | 66912613 | 52968270 | 41928849 | 331212 | 296.4472 | 1.0074 | 1.23 |
| 226 | WLF | Wallis and Futuna | Mata-Utu | Oceania | 11572 | 11655 | 12182 | 13142 | 14723 | 13454 | 11315 | 9377 | 142 | 81.4930 | 0.9953 | 0.00 |
| 172 | ESH | Western Sahara | El Aaiún | Africa | 575986 | 556048 | 491824 | 413296 | 270375 | 178529 | 116775 | 76371 | 266000 | 2.1654 | 1.0184 | 0.01 |
| 46 | YEM | Yemen | Sanaa | Asia | 33696614 | 32284046 | 28516545 | 24743946 | 18628700 | 13375121 | 9204938 | 6843607 | 527968 | 63.8232 | 1.0217 | 0.42 |
| 63 | ZMB | Zambia | Lusaka | Africa | 20017675 | 18927715 | 16248230 | 13792086 | 9891136 | 7686401 | 5720438 | 4281671 | 752612 | 26.5976 | 1.0280 | 0.25 |
| 74 | ZWE | Zimbabwe | Harare | Africa | 16320537 | 15669666 | 14154937 | 12839771 | 11834676 | 10113893 | 7049926 | 5202918 | 390757 | 41.7665 | 1.0204 | 0.20 |
tibble(variable = names(pop_raw),
type = map_chr(pop_raw, ~ class(.x)[1]))
| variable | type |
|---|---|
| Rank | numeric |
| CCA3 | character |
| Country/Territory | character |
| Capital | character |
| Continent | character |
| 2022 Population | numeric |
| 2020 Population | numeric |
| 2015 Population | numeric |
| 2010 Population | numeric |
| 2000 Population | numeric |
| 1990 Population | numeric |
| 1980 Population | numeric |
| 1970 Population | numeric |
| Area (km²) | numeric |
| Density (per km²) | numeric |
| Growth Rate | numeric |
| World Population Percentage | numeric |
dim(pop_raw)
## [1] 234 17
cat("Rows:", nrow(pop_raw), " | Columns:", ncol(pop_raw), "\n")
## Rows: 234 | Columns: 17
n_dup <- sum(duplicated(pop_raw))
cat("Number of duplicated rows:", n_dup, "\n")
## Number of duplicated rows: 0
pop <- distinct(pop_raw) # drop duplicates (if any)
cat("Rows after dropping duplicates:", nrow(pop), "\n")
## Rows after dropping duplicates: 234
The dataset contains 0 duplicate rows, so all 234 rows are kept.
For easier coding, the columns are renamed to short
snake_case names:
pop <- pop %>%
rename(rank = Rank, cca3 = CCA3, country = `Country/Territory`,
capital = Capital, continent = Continent,
pop_2022 = `2022 Population`, pop_2020 = `2020 Population`,
pop_2015 = `2015 Population`, pop_2010 = `2010 Population`,
pop_2000 = `2000 Population`, pop_1990 = `1990 Population`,
pop_1980 = `1980 Population`, pop_1970 = `1970 Population`,
area = `Area (km²)`, density = `Density (per km²)`,
growth_rate = `Growth Rate`,
world_pop_pct = `World Population Percentage`)
glimpse(pop)
## Rows: 234
## Columns: 17
## $ rank <dbl> 36, 138, 34, 213, 203, 42, 224, 201, 33, 140, 198, 55, 9…
## $ cca3 <chr> "AFG", "ALB", "DZA", "ASM", "AND", "AGO", "AIA", "ATG", …
## $ country <chr> "Afghanistan", "Albania", "Algeria", "American Samoa", "…
## $ capital <chr> "Kabul", "Tirana", "Algiers", "Pago Pago", "Andorra la V…
## $ continent <chr> "Asia", "Europe", "Africa", "Oceania", "Europe", "Africa…
## $ pop_2022 <dbl> 41128771, 2842321, 44903225, 44273, 79824, 35588987, 158…
## $ pop_2020 <dbl> 38972230, 2866849, 43451666, 46189, 77700, 33428485, 155…
## $ pop_2015 <dbl> 33753499, 2882481, 39543154, 51368, 71746, 28127721, 145…
## $ pop_2010 <dbl> 28189672, 2913399, 35856344, 54849, 71519, 23364185, 131…
## $ pop_2000 <dbl> 19542982, 3182021, 30774621, 58230, 66097, 16394062, 110…
## $ pop_1990 <dbl> 10694796, 3295066, 25518074, 47818, 53569, 11828638, 831…
## $ pop_1980 <dbl> 12486631, 2941651, 18739378, 32886, 35611, 8330047, 6560…
## $ pop_1970 <dbl> 10752971, 2324731, 13795915, 27075, 19860, 6029700, 6283…
## $ area <dbl> 652230, 28748, 2381741, 199, 468, 1246700, 91, 442, 2780…
## $ density <dbl> 63.0587, 98.8702, 18.8531, 222.4774, 170.5641, 28.5466, …
## $ growth_rate <dbl> 1.0257, 0.9957, 1.0164, 0.9831, 1.0100, 1.0315, 1.0066, …
## $ world_pop_pct <dbl> 0.52, 0.04, 0.56, 0.00, 0.00, 0.45, 0.00, 0.00, 0.57, 0.…
missing_tbl <- tibble(variable = names(pop),
n_missing = colSums(is.na(pop)))
missing_tbl
| variable | n_missing |
|---|---|
| rank | 0 |
| cca3 | 0 |
| country | 0 |
| capital | 0 |
| continent | 0 |
| pop_2022 | 0 |
| pop_2020 | 0 |
| pop_2015 | 0 |
| pop_2010 | 0 |
| pop_2000 | 0 |
| pop_1990 | 0 |
| pop_1980 | 0 |
| pop_1970 | 0 |
| area | 0 |
| density | 0 |
| growth_rate | 0 |
| world_pop_pct | 0 |
cat("Total missing values:", sum(missing_tbl$n_missing), "\n")
## Total missing values: 0
There are no missing values in the World Population Dataset, so no imputation is required. (Missing values do exist in the CO2 dataset; they are handled in Section 5.)
quant_vars <- pop %>% select(where(is.numeric), -rank) %>% names()
pop %>%
select(all_of(quant_vars)) %>%
pivot_longer(everything(), names_to = "variable", values_to = "value") %>%
ggplot(aes(x = "", y = value)) +
geom_boxplot(fill = "#8ecae6", outlier.colour = "#d62828", outlier.alpha = 0.7) +
facet_wrap(~ variable, scales = "free_y", ncol = 5) +
scale_y_continuous(labels = label_number(scale_cut = cut_short_scale())) +
labs(title = "Box plots of the quantitative variables",
subtitle = "Red points are outliers (beyond 1.5 × IQR)", x = NULL, y = NULL)
Number of outliers per variable using the 1.5 × IQR rule:
iqr_outliers <- function(x) {
q <- quantile(x, c(.25, .75), na.rm = TRUE)
iqr <- diff(q)
sum(x < q[1] - 1.5 * iqr | x > q[2] + 1.5 * iqr, na.rm = TRUE)
}
outlier_tbl <- pop %>%
summarise(across(all_of(quant_vars), iqr_outliers)) %>%
pivot_longer(everything(), names_to = "variable", values_to = "n_outliers")
outlier_tbl
| variable | n_outliers |
|---|---|
| pop_2022 | 25 |
| pop_2020 | 26 |
| pop_2015 | 27 |
| pop_2010 | 26 |
| pop_2000 | 29 |
| pop_1990 | 31 |
| pop_1980 | 32 |
| pop_1970 | 29 |
| area | 28 |
| density | 22 |
| growth_rate | 3 |
| world_pop_pct | 25 |
Which countries are the outliers? For example, population and density:
pop %>% arrange(desc(pop_2022)) %>% select(country, pop_2022) %>% head(5)
| country | pop_2022 |
|---|---|
| China | 1425887337 |
| India | 1417173173 |
| United States | 338289857 |
| Indonesia | 275501339 |
| Pakistan | 235824862 |
pop %>% arrange(desc(density)) %>% select(country, density) %>% head(5)
| country | density |
|---|---|
| Macau | 23172.267 |
| Monaco | 18234.500 |
| Singapore | 8416.463 |
| Hong Kong | 6783.392 |
| Gibraltar | 5441.500 |
The box plots after a log10 transform show far fewer extreme points:
pop %>%
select(all_of(quant_vars), -growth_rate) %>%
pivot_longer(everything(), names_to = "variable", values_to = "value") %>%
filter(value > 0) %>%
ggplot(aes(x = "", y = log10(value))) +
geom_boxplot(fill = "#b7e4c7", outlier.colour = "#d62828") +
facet_wrap(~ variable, scales = "free_y", ncol = 5) +
labs(title = "Box plots after log10 transformation", x = NULL, y = "log10(value)")
The exponential growth model is
\[N_t = P\,e^{rt}\]
where \(P\) is the population at the end of 2022, \(r\) is the annual growth rate and \(t = 2030 - 2022 = 8\) years.
Important: in this dataset Growth Rate
is stored as a growth factor (e.g. 1.0257), not as a rate. The
annual rate is therefore \(r = \text{Growth
Rate} - 1\) (e.g. 0.0257 = 2.57 %).
t_years <- 2030 - 2022
pop <- pop %>%
mutate(r = growth_rate - 1,
pop_2030 = round(pop_2022 * exp(r * t_years)),
change_2022_2030 = pop_2030 - pop_2022)
pop %>%
arrange(desc(pop_2030)) %>%
select(country, pop_2022, r, pop_2030, change_2022_2030) %>%
head(15) %>%
kable(format.args = list(big.mark = ","), digits = 4,
caption = "Projected 2030 population (top 15 countries)")
| country | pop_2022 | r | pop_2030 | change_2022_2030 |
|---|---|---|---|---|
| India | 1,417,173,173 | 0.0068 | 1,496,402,904 | 79,229,731 |
| China | 1,425,887,337 | 0.0000 | 1,425,887,337 | 0 |
| United States | 338,289,857 | 0.0038 | 348,731,782 | 10,441,925 |
| Indonesia | 275,501,339 | 0.0064 | 289,974,355 | 14,473,016 |
| Pakistan | 235,824,862 | 0.0191 | 274,757,645 | 38,932,783 |
| Nigeria | 218,541,212 | 0.0241 | 265,011,868 | 46,470,656 |
| Brazil | 215,313,498 | 0.0046 | 223,384,633 | 8,071,135 |
| Bangladesh | 171,186,372 | 0.0108 | 186,634,630 | 15,448,258 |
| Ethiopia | 123,379,924 | 0.0257 | 151,542,848 | 28,162,924 |
| Russia | 144,713,314 | -0.0027 | 141,621,023 | -3,092,291 |
| Mexico | 127,504,125 | 0.0063 | 134,095,029 | 6,590,904 |
| Philippines | 115,559,009 | 0.0147 | 129,980,092 | 14,421,083 |
| DR Congo | 99,010,212 | 0.0325 | 128,409,323 | 29,399,111 |
| Egypt | 110,990,103 | 0.0158 | 125,944,462 | 14,954,359 |
| Japan | 123,951,692 | -0.0053 | 118,806,000 | -5,145,692 |
world_2022 <- sum(pop$pop_2022)
world_2030 <- sum(pop$pop_2030)
cat("World population 2022:", comma(world_2022), "\n")
## World population 2022: 7,973,413,042
cat("Projected world population 2030:", comma(world_2030), "\n")
## Projected world population 2030: 8,569,157,744
The model projects a world population of about 8.57 billion in 2030 (from 7.97 billion in 2022). India (1,496,402,904) overtakes China (1,425,887,337), because China’s growth rate is 0 (no growth), while India still grows at 0.68 % per year. Russia (r = -0.0027) is projected to shrink.
top10 <- pop %>% slice_max(pop_2022, n = 10) %>%
select(cca3, country, continent, pop_2022)
top10 %>% kable(format.args = list(big.mark = ","))
| cca3 | country | continent | pop_2022 |
|---|---|---|---|
| CHN | China | Asia | 1,425,887,337 |
| IND | India | Asia | 1,417,173,173 |
| USA | United States | North America | 338,289,857 |
| IDN | Indonesia | Asia | 275,501,339 |
| PAK | Pakistan | Asia | 235,824,862 |
| NGA | Nigeria | Africa | 218,541,212 |
| BRA | Brazil | South America | 215,313,498 |
| BGD | Bangladesh | Asia | 171,186,372 |
| RUS | Russia | Europe | 144,713,314 |
| MEX | Mexico | North America | 127,504,125 |
ggplot(top10, aes(x = reorder(country, pop_2022), y = pop_2022 / 1e6)) +
geom_col(fill = "#219ebc") +
geom_text(aes(label = comma(round(pop_2022 / 1e6, 1))), hjust = -0.1, size = 3.5) +
coord_flip() +
scale_y_continuous(expand = expansion(mult = c(0, 0.12))) +
labs(title = "Top 10 most populous countries in 2022",
x = NULL, y = "Population (millions)")
The dataset provides population for 1990, 2000, 2010, 2015, 2020 and 2022.
pop_trend <- pop %>%
filter(cca3 %in% top10$cca3) %>%
select(country, pop_1990, pop_2000, pop_2010, pop_2015, pop_2020, pop_2022) %>%
pivot_longer(-country, names_to = "year", values_to = "population",
names_prefix = "pop_", names_transform = as.integer)
ggplot(pop_trend, aes(year, population / 1e6, colour = reorder(country, -population))) +
geom_line(linewidth = 1) +
geom_point(size = 1.8) +
scale_y_log10(labels = comma) +
scale_x_continuous(breaks = c(1990, 2000, 2010, 2015, 2020, 2022)) +
labs(title = "Population trend of the 10 most populous countries (1990-2022)",
x = "Year", y = "Population (millions, log scale)", colour = "Country")
A log scale is used so that the smaller countries’ trends remain visible next to China and India. Growth since 1990:
pop %>%
filter(cca3 %in% top10$cca3) %>%
transmute(country, pop_1990, pop_2022,
growth_pct = round(100 * (pop_2022 / pop_1990 - 1), 1)) %>%
arrange(desc(growth_pct)) %>%
kable(format.args = list(big.mark = ","))
| country | pop_1990 | pop_2022 | growth_pct |
|---|---|---|---|
| Nigeria | 95,214,257 | 218,541,212 | 129.5 |
| Pakistan | 115,414,069 | 235,824,862 | 104.3 |
| India | 870,452,165 | 1,417,173,173 | 62.8 |
| Bangladesh | 107,147,651 | 171,186,372 | 59.8 |
| Mexico | 81,720,428 | 127,504,125 | 56.0 |
| Indonesia | 182,159,874 | 275,501,339 | 51.2 |
| Brazil | 150,706,446 | 215,313,498 | 42.9 |
| United States | 248,083,732 | 338,289,857 | 36.4 |
| China | 1,153,704,252 | 1,425,887,337 | 23.6 |
| Russia | 148,005,704 | 144,713,314 | -2.2 |
The CO2 dataset contains a duplicated 2019 column
(renamed 2019...35 on import), which is dropped. Values are
CO2 emissions in metric tons per capita.
co2 <- co2_raw %>%
select(-`2019...35`) %>%
rename(country_name = `Country Name`, region = Region, `2019` = `2019...34`) %>%
pivot_longer(`1990`:`2019`, names_to = "year", values_to = "co2_pc",
names_transform = as.integer)
cat("Missing CO2 values:", sum(is.na(co2$co2_pc)), "of", nrow(co2), "\n")
## Missing CO2 values: 746 of 6450
The ten countries are matched with the population data using their ISO3 code. Names differ between the two datasets (for example Russia vs Russian Federation), so codes are more reliable.
co2_top10 <- co2 %>%
filter(country_code %in% top10$cca3) %>%
left_join(select(top10, cca3, country), by = c("country_code" = "cca3"))
cat("Missing values for the top 10:", sum(is.na(co2_top10$co2_pc)), "\n")
## Missing values for the top 10: 0
ggplot(co2_top10, aes(year, co2_pc, colour = reorder(country, -co2_pc))) +
geom_line(linewidth = 1) +
scale_x_continuous(breaks = seq(1990, 2019, 5)) +
labs(title = "CO2 emissions of the 10 most populous countries (1990-2019)",
x = "Year", y = "CO2 emissions (metric tons per capita)", colour = "Country")
co2_top10 %>%
filter(year %in% c(1990, 2019)) %>%
select(country, year, co2_pc) %>%
pivot_wider(names_from = year, values_from = co2_pc, names_prefix = "co2_") %>%
mutate(change_pct = round(100 * (co2_2019 / co2_1990 - 1), 1)) %>%
arrange(desc(co2_2019)) %>%
kable(digits = 2)
| country | co2_1990 | co2_2019 | change_pct |
|---|---|---|---|
| United States | 19.41 | 14.67 | -24.4 |
| Russia | 14.62 | 11.80 | -19.3 |
| China | 1.91 | 7.61 | 297.3 |
| Mexico | 3.21 | 3.52 | 9.7 |
| Indonesia | 0.82 | 2.29 | 179.7 |
| Brazil | 1.33 | 2.06 | 54.9 |
| India | 0.65 | 1.80 | 178.5 |
| Pakistan | 0.55 | 0.88 | 60.5 |
| Nigeria | 0.76 | 0.57 | -24.9 |
| Bangladesh | 0.11 | 0.56 | 398.4 |
cor_vars <- pop %>%
select(Area = area, Density = density,
`Growth Rate` = growth_rate, `World Pop %` = world_pop_pct)
cor_pearson <- cor(cor_vars, method = "pearson")
cor_spearman <- cor(cor_vars, method = "spearman")
kable(round(cor_pearson, 3), caption = "Pearson correlation matrix")
| Area | Density | Growth Rate | World Pop % | |
|---|---|---|---|---|
| Area | 1.000 | -0.063 | -0.014 | 0.453 |
| Density | -0.063 | 1.000 | -0.070 | -0.028 |
| Growth Rate | -0.014 | -0.070 | 1.000 | -0.021 |
| World Pop % | 0.453 | -0.028 | -0.021 | 1.000 |
kable(round(cor_spearman, 3), caption = "Spearman (rank) correlation matrix")
| Area | Density | Growth Rate | World Pop % | |
|---|---|---|---|---|
| Area | 1.000 | -0.584 | 0.356 | 0.841 |
| Density | -0.584 | 1.000 | -0.222 | -0.154 |
| Growth Rate | 0.356 | -0.222 | 1.000 | 0.278 |
| World Pop % | 0.841 | -0.154 | 0.278 | 1.000 |
par(mfrow = c(1, 2))
corrplot(cor_pearson, method = "color", type = "upper", addCoef.col = "black",
tl.col = "black", tl.srt = 45, col = COL2("RdBu", 200),
title = "Pearson", mar = c(0, 0, 2, 0))
corrplot(cor_spearman, method = "color", type = "upper", addCoef.col = "black",
tl.col = "black", tl.srt = 45, col = COL2("RdBu", 200),
title = "Spearman", mar = c(0, 0, 2, 0))
par(mfrow = c(1, 1))
Significance test of each pair (Pearson):
combn(names(cor_vars), 2, simplify = FALSE) %>%
map_dfr(function(p) {
ct <- cor.test(cor_vars[[p[1]]], cor_vars[[p[2]]])
tibble(var1 = p[1], var2 = p[2], r = ct$estimate, p_value = ct$p.value)
}) %>%
kable(digits = 4)
| var1 | var2 | r | p_value |
|---|---|---|---|
| Area | Density | -0.0631 | 0.3363 |
| Area | Growth Rate | -0.0140 | 0.8317 |
| Area | World Pop % | 0.4533 | 0.0000 |
| Density | Growth Rate | -0.0698 | 0.2880 |
| Density | World Pop % | -0.0276 | 0.6740 |
| Growth Rate | World Pop % | -0.0209 | 0.7501 |
The task asks for a merge on country name. First, a merge on exact names:
co2_2019 <- co2 %>% filter(year == 2019) %>%
select(country_name, country_code, region, co2_2019 = co2_pc)
by_name <- inner_join(pop, co2_2019, by = c("country" = "country_name"))
by_code <- inner_join(pop, co2_2019, by = c("cca3" = "country_code"))
cat("Matched on exact country name:", nrow(by_name), "\n")
## Matched on exact country name: 186
cat("Matched on ISO3 country code :", nrow(by_code), "\n")
## Matched on ISO3 country code : 214
Some countries are lost because the two datasets name them differently:
by_code %>% filter(country != country_name) %>%
select(population_dataset = country, co2_dataset = country_name)
| population_dataset | co2_dataset |
|---|---|
| Bahamas | Bahamas, The |
| Brunei | Brunei Darussalam |
| Cape Verde | Cabo Verde |
| DR Congo | Congo, Dem. Rep. |
| Egypt | Egypt, Arab Rep. |
| Gambia | Gambia, The |
| Hong Kong | Hong Kong SAR, China |
| Iran | Iran, Islamic Rep. |
| Ivory Coast | Cote d’Ivoire |
| Kyrgyzstan | Kyrgyz Republic |
| Laos | Lao PDR |
| Macau | Macao SAR, China |
| Micronesia | Micronesia, Fed. Sts. |
| North Korea | Korea, Dem. People’s Rep. |
| Palestine | West Bank and Gaza |
| Republic of the Congo | Congo, Rep. |
| Russia | Russian Federation |
| Saint Kitts and Nevis | St. Kitts and Nevis |
| Saint Lucia | St. Lucia |
| Saint Martin | St. Martin (French part) |
| Saint Vincent and the Grenadines | St. Vincent and the Grenadines |
| Sint Maarten | Sint Maarten (Dutch part) |
| Slovakia | Slovak Republic |
| South Korea | Korea, Rep. |
| Syria | Syrian Arab Republic |
| Turkey | Turkiye |
| United States Virgin Islands | Virgin Islands (U.S.) |
| Venezuela | Venezuela, RB |
| Yemen | Yemen, Rep. |
To keep these countries, the final merge uses the ISO3 code, which identifies the same country in both datasets. Rows with no 2019 CO2 value are then removed. A second new variable is also created: total CO2 emissions = per-capita emission × population.
merged <- by_code %>%
select(cca3, country, continent, pop_2022, co2_2019) %>%
filter(!is.na(co2_2019)) %>%
mutate(co2_total_mt = co2_2019 * pop_2022 / 1e6) # million metric tons (approx.)
cat("Countries in merged dataset:", nrow(merged), "\n")
## Countries in merged dataset: 191
head(merged, 10)
| cca3 | country | continent | pop_2022 | co2_2019 | co2_total_mt |
|---|---|---|---|---|---|
| AFG | Afghanistan | Asia | 41128771 | 0.1598244 | 6.5733800 |
| ALB | Albania | Europe | 2842321 | 1.6922483 | 4.8099129 |
| DZA | Algeria | Africa | 44903225 | 3.9776505 | 178.6093335 |
| AND | Andorra | Europe | 79824 | 6.4812174 | 0.5173567 |
| AGO | Angola | Africa | 35588987 | 0.7921371 | 28.1913559 |
| ATG | Antigua and Barbuda | North America | 93763 | 5.3544765 | 0.5020518 |
| ARG | Argentina | South America | 45510318 | 3.7406503 | 170.2381843 |
| ARM | Armenia | Asia | 2780469 | 2.0860607 | 5.8002270 |
| AUS | Australia | Oceania | 26177413 | 15.2382672 | 398.8984126 |
| AUT | Austria | Europe | 8939617 | 7.2939843 | 65.2054256 |
ggplot(merged, aes(pop_2022, co2_2019)) +
geom_point(aes(colour = continent), alpha = 0.75, size = 2) +
geom_smooth(method = "lm", se = TRUE, colour = "black") +
geom_text(data = filter(merged, cca3 %in% top10$cca3), aes(label = country),
size = 3, vjust = -0.8, check_overlap = TRUE) +
scale_x_log10(labels = label_number(scale_cut = cut_short_scale())) +
labs(title = "2022 population vs 2019 CO2 emissions per capita",
x = "Population 2022 (log scale)", y = "CO2 (metric tons per capita)",
colour = "Continent")
ggplot(merged, aes(pop_2022, co2_total_mt)) +
geom_point(aes(colour = continent), alpha = 0.75, size = 2) +
geom_smooth(method = "lm", se = TRUE, colour = "black") +
geom_text(data = filter(merged, cca3 %in% top10$cca3), aes(label = country),
size = 3, vjust = -0.8, check_overlap = TRUE) +
scale_x_log10(labels = label_number(scale_cut = cut_short_scale())) +
scale_y_log10(labels = comma) +
labs(title = "2022 population vs total CO2 emissions (log-log)",
x = "Population 2022 (log scale)", y = "Total CO2 (million metric tons, log scale)",
colour = "Continent")
ct_pearson <- cor.test(merged$pop_2022, merged$co2_2019, method = "pearson")
ct_spearman <- cor.test(merged$pop_2022, merged$co2_2019, method = "spearman", exact = FALSE)
ct_total_log <- cor.test(log10(merged$pop_2022), log10(merged$co2_total_mt))
tibble(
relationship = c("Population vs CO2 per capita (Pearson)",
"Population vs CO2 per capita (Spearman)",
"log Population vs log Total CO2 (Pearson)"),
correlation = c(ct_pearson$estimate, ct_spearman$estimate, ct_total_log$estimate),
p_value = c(ct_pearson$p.value, ct_spearman$p.value, ct_total_log$p.value)
) %>% kable(digits = 4)
| relationship | correlation | p_value |
|---|---|---|
| Population vs CO2 per capita (Pearson) | 0.0145 | 0.8426 |
| Population vs CO2 per capita (Spearman) | -0.1103 | 0.1288 |
| log Population vs log Total CO2 (Pearson) | 0.8182 | 0.0000 |
m_pc <- lm(co2_2019 ~ log10(pop_2022), data = merged)
m_total <- lm(log10(co2_total_mt) ~ log10(pop_2022), data = merged)
tidy(m_pc) %>% kable(digits = 4, caption = "Model 1: CO2 per capita ~ log10(population)")
| term | estimate | std.error | statistic | p.value |
|---|---|---|---|---|
| (Intercept) | 5.7780 | 2.4914 | 2.3192 | 0.0215 |
| log10(pop_2022) | -0.2437 | 0.3616 | -0.6738 | 0.5013 |
glance(m_pc) %>% select(r.squared, adj.r.squared, p.value, nobs) %>% kable(digits = 4)
| r.squared | adj.r.squared | p.value | nobs |
|---|---|---|---|
| 0.0024 | -0.0029 | 0.5013 | 191 |
tidy(m_total) %>% kable(digits = 4, caption = "Model 2: log10(total CO2) ~ log10(population)")
| term | estimate | std.error | statistic | p.value |
|---|---|---|---|---|
| (Intercept) | -5.1042 | 0.3214 | -15.8795 | 0 |
| log10(pop_2022) | 0.9127 | 0.0467 | 19.5630 | 0 |
glance(m_total) %>% select(r.squared, adj.r.squared, p.value, nobs) %>% kable(digits = 4)
| r.squared | adj.r.squared | p.value | nobs |
|---|---|---|---|
| 0.6694 | 0.6677 | 0 | 191 |
Interpretation.
As required, the 2019 CO2 emissions (metric tons per capita) in the merged dataset are summed by continent. The mean per country and the population-weighted mean are shown too, because a simple sum also depends on how many countries each continent has.
continent_co2 <- merged %>%
group_by(continent) %>%
summarise(n_countries = n(),
total_co2_pc = sum(co2_2019),
mean_co2_pc = mean(co2_2019),
weighted_co2_pc = weighted.mean(co2_2019, pop_2022),
total_co2_mt = sum(co2_total_mt)) %>%
arrange(desc(total_co2_pc)) %>%
mutate(rank = row_number(), .before = 1)
continent_co2 %>% kable(digits = 2, format.args = list(big.mark = ","))
| rank | continent | n_countries | total_co2_pc | mean_co2_pc | weighted_co2_pc | total_co2_mt |
|---|---|---|---|---|---|---|
| 1 | Asia | 46 | 290.55 | 6.32 | 4.32 | 20,247.94 |
| 2 | Europe | 42 | 246.67 | 5.87 | 6.97 | 5,179.65 |
| 3 | North America | 23 | 96.90 | 4.21 | 10.35 | 6,164.84 |
| 4 | Africa | 54 | 63.73 | 1.18 | 1.04 | 1,486.85 |
| 5 | Oceania | 14 | 54.78 | 3.91 | 10.12 | 446.64 |
| 6 | South America | 12 | 33.36 | 2.78 | 2.40 | 1,046.78 |
ggplot(continent_co2, aes(reorder(continent, total_co2_pc), total_co2_pc, fill = continent)) +
geom_col(show.legend = FALSE) +
geom_text(aes(label = round(total_co2_pc, 2)), hjust = -0.1, size = 4) +
coord_flip() +
scale_y_continuous(expand = expansion(mult = c(0, 0.12))) +
labs(title = "Sum of 2019 CO2 emissions by continent",
x = NULL, y = "Sum of CO2 emissions (metric tons per capita)")
Answers (from the graph):
Note: summing per-capita values favours continents with many countries. The population-weighted mean column gives a fairer comparison of how much an average person emits on each continent.
sessionInfo()
## R version 4.6.1 (2026-06-24)
## Platform: aarch64-apple-darwin23
## Running under: macOS Tahoe 26.3.1
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: Africa/Kigali
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] knitr_1.52 broom_1.0.13 corrplot_0.95 scales_1.4.0
## [5] lubridate_1.9.5 forcats_1.0.1 stringr_1.6.0 dplyr_1.2.1
## [9] purrr_1.2.2 readr_2.2.0 tidyr_1.3.2 tibble_3.3.1
## [13] ggplot2_4.0.3 tidyverse_2.0.0
##
## loaded via a namespace (and not attached):
## [1] sass_0.4.10 generics_0.1.4 lattice_0.22-9 stringi_1.8.9
## [5] hms_1.1.4 digest_0.6.39 magrittr_2.0.5 evaluate_1.0.5
## [9] grid_4.6.1 timechange_0.4.0 RColorBrewer_1.1-3 fastmap_1.2.0
## [13] Matrix_1.7-5 jsonlite_2.0.0 backports_1.5.1 mgcv_1.9-4
## [17] jquerylib_0.1.4 cli_3.6.6 rlang_1.3.0 crayon_1.5.3
## [21] splines_4.6.1 bit64_4.8.6 withr_3.0.3 cachem_1.1.0
## [25] yaml_2.3.12 otel_0.2.0 tools_4.6.1 parallel_4.6.1
## [29] tzdb_0.5.0 vctrs_0.7.3 R6_2.6.1 stats4_4.6.1
## [33] lifecycle_1.0.5 bit_4.6.0 vroom_1.7.1 pkgconfig_2.0.3
## [37] pillar_1.11.1 bslib_0.12.0 gtable_0.3.6 glue_1.8.1
## [41] xfun_0.62 tidyselect_1.2.1 rstudioapi_0.19.0 farver_2.1.2
## [45] nlme_3.1-169 htmltools_0.5.9 rmarkdown_2.32 labeling_0.4.3
## [49] compiler_4.6.1 S7_0.2.2