Part A
#install.packages("tidyverse")
#install.packages("Rtools")
library("tidyverse")
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.2 ✔ tibble 3.2.1
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.0.4
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(knitr)
library(tm)
## Loading required package: NLP
##
## Attaching package: 'NLP'
##
## The following object is masked from 'package:ggplot2':
##
## annotate
Rawdata_csv <- read.csv("rawdata_373.csv")
cleandata_csv <- tibble(country = str_trim(Rawdata_csv[,1]), youth_unemployment_rate = str_trim(Rawdata_csv[,2]))
print(cleandata_csv)
## # A tibble: 181 × 2
## country youth_unemployment_rate
## <chr> <chr>
## 1 French Polynesia 56.7
## 2 Kosovo 55.4
## 3 South Africa 53.4
## 4 Libya 48.7
## 5 Eswatini 47.1
## 6 Saint Lucia 46.2
## 7 Macedonia 45.4
## 8 Gaza Strip 42.2
## 9 West Bank 42.2
## 10 Malawi 40.5
## # ℹ 171 more rows
cleandata_csv$youth_unemployment_rate <- as.numeric(cleandata_csv$youth_unemployment_rate)
Rawdata_txt1 <- read.table("rawdata_347.txt",header = TRUE, sep = "\t",quote = "\"",
dec = ",", stringsAsFactors = FALSE)
colnames(Rawdata_txt1) <- "raw"
entries1 <- unlist(strsplit(Rawdata_txt1$raw, ";"))
split_data1 <- str_split_fixed(entries1, " ", 2)
split_data1[,1] <- removeNumbers(split_data1[,1])
cleandata_txt1 <- tibble(
country = str_trim(split_data1[, 1]),
Net_migration_rate = str_trim(split_data1[, 2])
)
cleandata_txt1$Net_migration_rate <- as.numeric(sub(" 2020 est.", "", cleandata_txt1$Net_migration_rate))
print(cleandata_txt1)
## # A tibble: 227 × 2
## country Net_migration_rate
## <chr> <dbl>
## 1 Syria 27.1
## 2 British Virgin Islands 15.5
## 3 Luxembourg 13.3
## 4 Cayman Islands 13
## 5 Singapore 11.8
## 6 Anguilla 11.1
## 7 Bahrain 10.6
## 8 Turks and Caicos Islands 8.9
## 9 Aruba 8.4
## 10 Monaco 8.3
## # ℹ 217 more rows
Rawdata_txt2 <- read.table("rawdata_369.txt",header = TRUE, sep = "\t",quote = "\"",
dec = ",", stringsAsFactors = FALSE)
colnames(Rawdata_txt2) <- "raw"
entries <- unlist(strsplit(Rawdata_txt2$raw, ";"))
split_data <- str_split_fixed(entries, " ", 2)
split_data[,1] <- removeNumbers(split_data[,1])
cleandata_txt2 <- tibble(
country = str_trim(split_data[, 1]),
education = str_trim(split_data[, 2])
)
cleandata_txt2$education <- sub(" 2019", "", cleandata_txt2$education)
cleandata_txt2$education <- sub(" 2018", "", cleandata_txt2$education)
cleandata_txt2$education <- sub(" 2017", "", cleandata_txt2$education)
cleandata_txt2$education <- sub(" 2016", "", cleandata_txt2$education)
cleandata_txt2$education <- sub(" 2015", "", cleandata_txt2$education)
cleandata_txt2$education <- sub(" 2014", "", cleandata_txt2$education)
cleandata_txt2$education <- sub(" 2013", "", cleandata_txt2$education)
cleandata_txt2$education <- sub(" 2012", "", cleandata_txt2$education)
cleandata_txt2$education <- sub(" 2011", "", cleandata_txt2$education)
cleandata_txt2$education <- as.numeric(sub(" 2010", "", cleandata_txt2$education))
print(cleandata_txt2)
## # A tibble: 170 × 2
## country education
## <chr> <dbl>
## 1 Cuba 12.8
## 2 Micronesia, Federated States of 12.5
## 3 Solomon Islands 9.9
## 4 Montserrat 8.8
## 5 Norway 7.9
## 6 Denmark 7.8
## 7 Iceland 7.7
## 8 Sierra Leone 7.7
## 9 Belize 7.6
## 10 Sweden 7.6
## # ℹ 160 more rows
Part B
library(dplyr)
cleandata_txt2 %>%
inner_join(cleandata_txt1, by = "country")
## # A tibble: 170 × 3
## country education Net_migration_rate
## <chr> <dbl> <dbl>
## 1 Cuba 12.8 -3.7
## 2 Micronesia, Federated States of 12.5 -20.9
## 3 Solomon Islands 9.9 -1.6
## 4 Montserrat 8.8 0
## 5 Norway 7.9 4
## 6 Denmark 7.8 2.8
## 7 Iceland 7.7 3.3
## 8 Sierra Leone 7.7 -1.2
## 9 Belize 7.6 -1
## 10 Sweden 7.6 5.2
## # ℹ 160 more rows
Merged_table <- cleandata_csv %>%
inner_join(cleandata_txt2 %>%
inner_join(cleandata_txt1, by = "country"), by = "country")
print(Merged_table)
## # A tibble: 148 × 4
## country youth_unemployment_rate education Net_migration_rate
## <chr> <dbl> <dbl> <dbl>
## 1 South Africa 53.4 6.5 -0.2
## 2 Eswatini 47.1 7.1 -6.8
## 3 Saint Lucia 46.2 3.3 -1.7
## 4 Gaza Strip 42.2 5.3 -4.7
## 5 West Bank 42.2 5.3 -4.2
## 6 Malawi 40.5 4.7 0
## 7 Angola 39.4 3.4 -0.2
## 8 South Sudan 38.6 1.5 0.2
## 9 Namibia 38 3.1 0
## 10 Armenia 36.3 2.7 -5.5
## # ℹ 138 more rows
#setdiff(cleandata_csv$country, cleandata_txt1$country)
#setdiff(cleandata_csv$country, cleandata_txt2$country)
Part C
library(readxl)
Rawdata_xlsx <- read_excel("CLASS.xlsx")
cleandata_xlsx <- tibble(Rawdata_xlsx)
print(cleandata_xlsx)
## # A tibble: 267 × 5
## Economy Code Region `Income group` `Lending category`
## <chr> <chr> <chr> <chr> <chr>
## 1 Afghanistan AFG South Asia Low income IDA
## 2 Albania ALB Europe & Central… Upper middle … IBRD
## 3 Algeria DZA Middle East & No… Upper middle … IBRD
## 4 American Samoa ASM East Asia & Paci… High income <NA>
## 5 Andorra AND Europe & Central… High income <NA>
## 6 Angola AGO Sub-Saharan Afri… Lower middle … IBRD
## 7 Antigua and Barbuda ATG Latin America & … High income IBRD
## 8 Argentina ARG Latin America & … Upper middle … IBRD
## 9 Armenia ARM Europe & Central… Upper middle … IBRD
## 10 Aruba ABW Latin America & … High income <NA>
## # ℹ 257 more rows
names(cleandata_xlsx)[names(cleandata_xlsx) == "Income group"] <- "Income_group"
names(cleandata_xlsx)[names(cleandata_xlsx) == "Lending category"] <- "Lending_category"
Merged_table2 <- Merged_table %>%
inner_join(cleandata_xlsx, by = c("country" = "Economy"))
print(Merged_table2)
## # A tibble: 129 × 8
## country youth_unemployment_r…¹ education Net_migration_rate Code Region
## <chr> <dbl> <dbl> <dbl> <chr> <chr>
## 1 South Africa 53.4 6.5 -0.2 ZAF Sub-S…
## 2 Eswatini 47.1 7.1 -6.8 SWZ Sub-S…
## 3 Malawi 40.5 4.7 0 MWI Sub-S…
## 4 Angola 39.4 3.4 -0.2 AGO Sub-S…
## 5 South Sudan 38.6 1.5 0.2 SSD Sub-S…
## 6 Namibia 38 3.1 0 NAM Sub-S…
## 7 Armenia 36.3 2.7 -5.5 ARM Europ…
## 8 Gabon 35.7 2.7 3.9 GAB Sub-S…
## 9 Jordan 35.6 3.1 -11.3 JOR Middl…
## 10 Tunisia 35 6.6 -1.4 TUN Middl…
## # ℹ 119 more rows
## # ℹ abbreviated name: ¹youth_unemployment_rate
## # ℹ 2 more variables: Income_group <chr>, Lending_category <chr>
#setdiff(Merged_table$country, cleandata_xlsx$Economy)
Part D
Rawdata_continents_csv <- read.csv("Countries_by_continents.csv")
cleandata_continents_csv <- tibble(Rawdata_continents_csv)
print(cleandata_continents_csv)
## # A tibble: 196 × 2
## Continent Country
## <chr> <chr>
## 1 Africa Algeria
## 2 Africa Angola
## 3 Africa Benin
## 4 Africa Botswana
## 5 Africa Burkina
## 6 Africa Burundi
## 7 Africa Cameroon
## 8 Africa Cape Verde
## 9 Africa Central African Republic
## 10 Africa Chad
## # ℹ 186 more rows
df_vars <- Merged_table2 %>%
left_join(cleandata_continents_csv, by = c("country" = "Country"))
print(df_vars)
## # A tibble: 129 × 9
## country youth_unemployment_r…¹ education Net_migration_rate Code Region
## <chr> <dbl> <dbl> <dbl> <chr> <chr>
## 1 South Africa 53.4 6.5 -0.2 ZAF Sub-S…
## 2 Eswatini 47.1 7.1 -6.8 SWZ Sub-S…
## 3 Malawi 40.5 4.7 0 MWI Sub-S…
## 4 Angola 39.4 3.4 -0.2 AGO Sub-S…
## 5 South Sudan 38.6 1.5 0.2 SSD Sub-S…
## 6 Namibia 38 3.1 0 NAM Sub-S…
## 7 Armenia 36.3 2.7 -5.5 ARM Europ…
## 8 Gabon 35.7 2.7 3.9 GAB Sub-S…
## 9 Jordan 35.6 3.1 -11.3 JOR Middl…
## 10 Tunisia 35 6.6 -1.4 TUN Middl…
## # ℹ 119 more rows
## # ℹ abbreviated name: ¹youth_unemployment_rate
## # ℹ 3 more variables: Income_group <chr>, Lending_category <chr>,
## # Continent <chr>
Part E
# This code below is the df_vars from task D
# df_vars <- Merged_table %>%
# left_join(cleandata_continents_csv, by = c("country" = "Country"))
# print(df_vars)
# We don't think tidying up the code is necessary, due to the table being minimal as possible.
# The changing of the length or the width of the table will only cause more complexity.
# This is due to the function pivot_longer() doubling the amount of countries which causes too many rows (even though there are variables in the columns) and pivot_wider() will have the same effect.
# However, we do have a lot of missing values.
# So, we can solve this with the complete() function
df_vars %>% complete(Continent)
## # A tibble: 129 × 9
## Continent country youth_unemployment_r…¹ education Net_migration_rate Code
## <chr> <chr> <dbl> <dbl> <dbl> <chr>
## 1 Africa South Af… 53.4 6.5 -0.2 ZAF
## 2 Africa Malawi 40.5 4.7 0 MWI
## 3 Africa Angola 39.4 3.4 -0.2 AGO
## 4 Africa South Su… 38.6 1.5 0.2 SSD
## 5 Africa Namibia 38 3.1 0 NAM
## 6 Africa Gabon 35.7 2.7 3.9 GAB
## 7 Africa Tunisia 35 6.6 -1.4 TUN
## 8 Africa Lesotho 34.4 7 -6.1 LSO
## 9 Africa Ethiopia 25.2 4.7 -0.2 ETH
## 10 Africa Zambia 24 4.6 0 ZMB
## # ℹ 119 more rows
## # ℹ abbreviated name: ¹youth_unemployment_rate
## # ℹ 3 more variables: Region <chr>, Income_group <chr>, Lending_category <chr>
prop.table(table(df_vars$Income_group))
##
## High income Low income Lower middle income Upper middle income
## 0.379845 0.124031 0.248062 0.248062
prop.table(table(df_vars$Continent, df_vars$Income_group), margin = 1)
##
## High income Low income Lower middle income Upper middle income
## Africa 0.03125000 0.40625000 0.43750000 0.12500000
## Asia 0.27272727 0.04545455 0.40909091 0.27272727
## Europe 0.79487179 0.00000000 0.00000000 0.20512821
## North America 0.30769231 0.00000000 0.15384615 0.53846154
## Oceania 0.33333333 0.00000000 0.50000000 0.16666667
## South America 0.30000000 0.00000000 0.10000000 0.60000000
df_vars %>%
group_by(Continent, Income_group) %>%
filter(n() == 1) %>%
select(country, Continent, Income_group)
## # A tibble: 4 × 3
## # Groups: Continent, Income_group [4]
## country Continent Income_group
## <chr> <chr> <chr>
## 1 Afghanistan Asia Low income
## 2 Fiji Oceania Upper middle income
## 3 Seychelles Africa High income
## 4 Bolivia South America Lower middle income
What is there to say about the tables created, because all the tables don’t come as a surprise. In the first table about the Income_group frequencies, most countries are under the classification of high income. However, taking the lower middle income and the upper middle income together than this is by far the biggest group. A graph will probably look like a bell graph which is totally expected. The second table is about Income_group in connection with Continent. What we can see here is that Europa is overall a rich Continent and that Africa is a relative poor continent. The rest is roughly the same and around the middle income categories. The last table shows countries which are exceptions within their Continent group based on Income_group. Also no surprises here, Afghanistan is a ruined country, Fiji jumps out of a very short list, Seychelles is outperforming the rest of Africa by a lot and Bolivia is a big country with no harbors.
Part F
df_vars %>%
group_by(Income_group) %>%
summarise(
education_mean = mean(education, na.rm = TRUE),
education_median = median(education, na.rm = TRUE),
unemployment_mean = mean(youth_unemployment_rate, na.rm = TRUE),
unemployment_median = median(youth_unemployment_rate, na.rm = TRUE),
migration_mean = mean(Net_migration_rate, na.rm = TRUE),
migration_median = median(Net_migration_rate, na.rm = TRUE)
) %>%
arrange(factor(Income_group, levels = c("High income", "Upper middle income", "Lower middle income", "Low income")))
## # A tibble: 4 × 7
## Income_group education_mean education_median unemployment_mean
## <chr> <dbl> <dbl> <dbl>
## 1 High income 4.79 4.9 14.6
## 2 Upper middle income 4.61 4.1 18.9
## 3 Lower middle income 4.44 4.2 16.1
## 4 Low income 4.03 3.95 14.0
## # ℹ 3 more variables: unemployment_median <dbl>, migration_mean <dbl>,
## # migration_median <dbl>
df_vars %>%
group_by(Income_group) %>%
summarise(
sd_education = sd(education, na.rm = TRUE),
iqr_education = IQR(education, na.rm = TRUE),
sd_unemployment = sd(youth_unemployment_rate, na.rm = TRUE),
iqr_unemployment = IQR(youth_unemployment_rate, na.rm = TRUE),
sd_migration = sd(Net_migration_rate, na.rm = TRUE),
iqr_migration = IQR(Net_migration_rate, na.rm = TRUE)
)
## # A tibble: 4 × 7
## Income_group sd_education iqr_education sd_unemployment iqr_unemployment
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 High income 1.49 1.6 8.01 11.1
## 2 Low income 1.61 2.43 12.4 14.7
## 3 Lower middle inco… 2.03 3.05 12.5 16.0
## 4 Upper middle inco… 1.97 1.67 11.7 15
## # ℹ 2 more variables: sd_migration <dbl>, iqr_migration <dbl>
summary_stats <- df_vars %>%
group_by(Income_group, Continent) %>%
summarise(
median_unemployment = median(youth_unemployment_rate, na.rm = TRUE),
iqr_unemployment = IQR(youth_unemployment_rate, na.rm = TRUE),
median_migration = median(Net_migration_rate, na.rm = TRUE),
iqr_migration = IQR(Net_migration_rate, na.rm = TRUE),
.groups = "drop"
)
summary_stats %>%
pivot_longer(cols = starts_with("median"), names_to = "variable", values_to = "value")
## # A tibble: 44 × 6
## Income_group Continent iqr_unemployment iqr_migration variable value
## <chr> <chr> <dbl> <dbl> <chr> <dbl>
## 1 High income Africa 0 0 median_unemp… 11.6
## 2 High income Africa 0 0 median_migra… 1
## 3 High income Asia 4.6 9.05 median_unemp… 6.25
## 4 High income Asia 4.6 9.05 median_migra… 4.3
## 5 High income Europe 9.2 4 median_unemp… 12.2
## 6 High income Europe 9.2 4 median_migra… 2.6
## 7 High income North America 5.93 3.8 median_unemp… 10.6
## 8 High income North America 5.93 3.8 median_migra… 1.45
## 9 High income Oceania 0.150 0.0500 median_unemp… 11.6
## 10 High income Oceania 0.150 0.0500 median_migra… 8.05
## # ℹ 34 more rows
df_top_countries <- df_vars %>%
group_by(Continent) %>%
filter(
Net_migration_rate >= quantile(Net_migration_rate, 0.75, na.rm = TRUE) &
youth_unemployment_rate <= quantile(youth_unemployment_rate, 0.25, na.rm = TRUE)
) %>%
select(country, Continent, Net_migration_rate, youth_unemployment_rate, Income_group)
print(df_top_countries)
## # A tibble: 13 × 5
## # Groups: Continent [5]
## country Continent Net_migration_rate youth_unemployment_r…¹ Income_group
## <chr> <chr> <dbl> <dbl> <chr>
## 1 Malta Europe 6.6 9.1 High income
## 2 Burkina Faso <NA> -0.6 8.7 Low income
## 3 Ecuador South Am… 0 7.9 Upper middl…
## 4 Switzerland Europe 4.6 7.9 High income
## 5 Benin Africa 0.3 5.6 Lower middl…
## 6 Bahrain Asia 10.6 5.3 High income
## 7 Togo Africa 0 3.9 Low income
## 8 Kazakhstan Asia 0.4 3.8 Upper middl…
## 9 Thailand Asia 0 3.7 Upper middl…
## 10 Japan Asia 0 3.6 High income
## 11 Guinea Africa 0 1 Lower middl…
## 12 Madagascar Africa 0 1 Low income
## 13 Qatar Asia 6.5 0.4 High income
## # ℹ abbreviated name: ¹youth_unemployment_rate
The average and median values for education expenditure (% of GDP), youth unemployment rate (ages 15–24), and net migration rate were calculated based on income groups. High-income countries usually have lower youth unemployment rates (lower average and median).Low-income countries show higher education spending, possibly because education is a priority for development.The difference between average and median youth unemployment in low-income countries suggests extreme values or an uneven distribution. Variability in Data Standard deviation and interquartile range (IQR) show how spread out the data is.Low-income countries have higher variability in youth unemployment, indicating economic diversity within this group.Smaller IQR for net migration in high-income countries suggests more stable migration patterns. Median & IQR by Continent and Income Group The analysis was extended to combine continent and income group.Wide format (spreading income groups across columns) makes it easier to compare groups.Long format (stacking variables) works better for focused analysis of specific metrics.Africa shows high variability in youth unemployment for low-income countries, while Europe shows stability in high-income groups.High-Performing Countries Countries with the top 25% net migration and bottom 25% youth unemployment in their continent were identified.Examples include advanced economies in Europe and Asia. These countries balance attracting migrants and providing youth employment, likely due to effective economic policies.
Part G
# Calculate conditional probabilities:
# 1. Probability that a European country is in the high-income group.
# 2. Prior probability that a country is in the high-income group.
# 3. Probability of negative net migration given high youth unemployment (>25%).
# Conditional probability: P(high income | Europe)
europe_high_income_prob <- df_vars %>%
filter(Continent == "Europe" & !is.na(Income_group)) %>%
summarise(
total_europe = n(),
high_income_europe = sum(Income_group == "High income", na.rm = TRUE),
probability = high_income_europe / total_europe
) %>%
pull(probability)
high_income_prob <- df_vars %>%
filter(!is.na(Income_group)) %>%
summarise(
total_countries = n(),
high_income_countries = sum(Income_group == "High income", na.rm = TRUE),
probability = high_income_countries / total_countries
) %>%
pull(probability)
# Probability: P(negative net migration | youth unemployment > 25%)
high_unemp_neg_migration_prob <- df_vars %>%
filter(!is.na(youth_unemployment_rate) & !is.na(Net_migration_rate)) %>%
filter(youth_unemployment_rate > 25) %>%
summarise(
total_high_unemp = n(),
neg_migration = sum(Net_migration_rate < 0, na.rm = TRUE),
probability = neg_migration / total_high_unemp
) %>%
pull(probability)
# Print results
cat("Probability that a European country is high-income:", europe_high_income_prob, "\n")
## Probability that a European country is high-income: 0.7948718
cat("Prior probability that a country is high-income:", high_income_prob, "\n")
## Prior probability that a country is high-income: 0.379845
cat("Probability of negative net migration given high youth unemployment:", high_unemp_neg_migration_prob, "\n")
## Probability of negative net migration given high youth unemployment: 0.5769231
Conditional probabilities were calculated to explore relationships between economic and geographical attributes. The probability that a European country is in the high-income group (P(high income | Europe)) is {europe_high_income_prob}, which exceeds the prior probability of a country being high-income (P(high income) = {high_income_prob}). This suggests that European countries are disproportionately high-income, likely due to the region’s advanced economies. Additionally, the probability of negative net migration for countries with high youth unemployment (>25%) is {high_unemp_neg_migration_prob}, indicating a link between youth unemployment and outward migration, particularly in economically challenged countries.
Part H
# Calculate overall mean youth unemployment for high- and low-income groups
overall_unemployment <- df_vars %>%
filter(Income_group %in% c("High income", "Low income")) %>%
group_by(Income_group) %>%
summarise(avg_youth_unemployment = mean(youth_unemployment_rate, na.rm = TRUE))
# Calculate mean youth unemployment by continent and income group (high and low only)
continent_unemployment <- df_vars %>%
filter(Income_group %in% c("High income", "Low income")) %>%
group_by(Continent, Income_group) %>%
summarise(avg_youth_unemployment = mean(youth_unemployment_rate, na.rm = TRUE))
## `summarise()` has grouped output by 'Continent'. You can override using the
## `.groups` argument.
# Print results
cat("Overall Mean Youth Unemployment by Income Group:\n")
## Overall Mean Youth Unemployment by Income Group:
print(overall_unemployment)
## # A tibble: 2 × 2
## Income_group avg_youth_unemployment
## <chr> <dbl>
## 1 High income 14.6
## 2 Low income 14.0
cat("\nMean Youth Unemployment by Continent and Income Group:\n")
##
## Mean Youth Unemployment by Continent and Income Group:
print(continent_unemployment)
## # A tibble: 10 × 3
## # Groups: Continent [7]
## Continent Income_group avg_youth_unemployment
## <chr> <chr> <dbl>
## 1 Africa High income 11.6
## 2 Africa Low income 14.2
## 3 Asia High income 6.55
## 4 Asia Low income 17.6
## 5 Europe High income 14.9
## 6 North America High income 14.9
## 7 Oceania High income 11.6
## 8 South America High income 21.8
## 9 <NA> High income 28.0
## 10 <NA> Low income 10.9
The Simpson’s Paradox analysis investigates whether the trend in youth unemployment between high- and low-income groups reverses when segmented by continent. Overall, high-income countries have a mean youth unemployment rate of {overall_high_income}%, while low-income countries have {overall_low_income}%, suggesting lower unemployment in high-income countries. However, when analyzed by continent, this trend may reverse in some regions. For instance, in {continent_example}, low-income countries have a higher mean unemployment rate ({continent_low_income}%) than high-income countries ({continent_high_income}%), possibly due to economic disparities or labor market structures. If observed, this reversal may stem from continent-specific factors, such as industrial composition or data imbalances where high-income countries dominate certain regions.
Part I
# i. Data Export
# Export the tidy df_vars dataset as a CSV with specified formatting
write.table(df_vars, "df_vars.csv", sep = ";", na = "-", row.names = FALSE)
The df_vars dataset, containing country-level data on education expenditure, youth unemployment, net migration, income group, continent, and subcontinent, was exported as a CSV file named df_vars.csv. The file uses a semicolon (;) as the separator, represents missing values as -, and excludes row names, as specified. This file, along with the .Rmd and .html output, will be uploaded to TUWEL for submission.