Project 2 — Data Tidying: GDP per Capita

Author

Esra Dogan

Published

October 11, 2026

Introduction

This is one of the three datasets for Project 2. In this file, I will tidy my own GDP per capita dataset, which I shared in the 5A discussion posts. I will decide the tidying steps myself, based on what makes the data messy, and then use the analysis ideas I suggested in my post.

Source and Requested Analysis

This dataset is the GDP per capita (current US$) data from the World Bank (https://data.worldbank.org/indicator/NY.GDP.PCAP.CD), which I downloaded as a CSV file. It shows GDP per capita for countries and regions from 1960 to 2025. I suggested comparing how GDP per capita has changed over time in different countries and regions and finding which countries had the fastest growth in recent years.

Data Structure

The data is wide because each year from 1960 to 2025 has its own column. In the tidy version, each row will show one country in one year, with country, year, and GDP per capita.

Planned Approach

First, I will upload the World Bank CSV file to GitHub as it is, in its original wide format, and read it into R from there with skip = 4. Next, I will remove the empty column at the end, move the rows for regions and groups into a separate table, and use one pivot_longer() to put all the years into a single year column. I will also rename the columns in one consistent style and change the year to a number.

Once the data is tidy, I will compare how GDP per capita changed over time in different countries, calculate which countries grew the fastest in recent years, and compare a few regions and income groups. I will use summary tables and ggplot2 charts with clear titles and labels, and end with a conclusion that refers to specific values.

Anticipated Challenges

The file has metadata lines at the top, so reading it without skip = 4 gives an error, and there is an empty column at the end. The rows mix countries with regions and groups like “Euro area” or “World”, so I will move these into a separate table and keep only countries in the main table. Many countries also have missing values in the early years; for example, Aruba has no values before 1986. I will keep these as NA instead of replacing them with 0, and to compare growth fairly, I will use a time range where most countries have data, even though this means leaving out some early years.

Tidying the Data

Loading the packages

Code
library(tidyverse)

Reading CSV

Code
gdp_raw <- read.csv(
  "https://raw.githubusercontent.com/esradogan3/Project_2/refs/heads/main/API_NY.GDP.PCAP.CD_DS2_en_csv_v2_429627.csv",
  skip = 4
)

# Structure before tidying
dim(gdp_raw)                 # rows and columns
[1] 265  71
Code
names(gdp_raw)[1:8]          # first column names
[1] "Country.Name"   "Country.Code"   "Indicator.Name" "Indicator.Code"
[5] "X1960"          "X1961"          "X1962"          "X1963"         
Code
tail(names(gdp_raw), 3)      # last column names
[1] "X2024" "X2025" "X"    
Code
head(gdp_raw[, 1:8])         # representative sample
                 Country.Name Country.Code               Indicator.Name
1                       Aruba          ABW GDP per capita (current US$)
2 Africa Eastern and Southern          AFE GDP per capita (current US$)
3                 Afghanistan          AFG GDP per capita (current US$)
4  Africa Western and Central          AFW GDP per capita (current US$)
5                      Angola          AGO GDP per capita (current US$)
6                     Albania          ALB GDP per capita (current US$)
  Indicator.Code    X1960    X1961    X1962    X1963
1 NY.GDP.PCAP.CD       NA       NA       NA       NA
2 NY.GDP.PCAP.CD 186.0895 186.9094 197.3679 225.4005
3 NY.GDP.PCAP.CD       NA       NA       NA       NA
4 NY.GDP.PCAP.CD 121.9368 127.4510 133.8238 139.0050
5 NY.GDP.PCAP.CD       NA       NA       NA       NA
6 NY.GDP.PCAP.CD       NA       NA       NA       NA

I read the raw CSV file from my GitHub repository. The first four lines of the file are metadata (the data source and the update date), so I used skip = 4 to start reading at the real header. The raw data has 265 rows and 71 columns. Because the year columns start with numbers, read.csv() added an “X” in front of them (X1960, X1961, and so on), and the empty column at the end of every line became a column called X.

Separating regions and groups

The World Bank file mixes 217 countries with 48 regions and groups, such as “World”, “Euro area”, and “High income”. These groups are sums or averages of countries, so if I kept them together with the countries, the same people would be counted twice. Instead of deleting them, I will move them into a separate table so I can still compare them. To make this list, I used an LLM (Claude) to go through all 265 names in the file and pick the ones that are regions or groups instead of countries. I then checked the list myself: it has 48 codes, and the remaining 217 rows are all countries.

Code
aggregate_codes <- c(
  "AFE", "AFW", "ARB", "CEB", "CSS", "EAP", "EAR", "EAS", "ECA", "ECS",
  "EMU", "EUU", "HIC", "HPC", "IBD", "IBT", "IDA", "IDB", "IDX", "INX",
  "LAC", "LCN", "LDC", "LIC", "LMC", "LMY", "LTE", "MEA", "MIC", "MNA",
  "NAC", "OED", "OSS", "PRE", "PSS", "PST", "SAS", "SSA", "SSF", "SST",
  "TEA", "TEC", "TLA", "TMN", "TSA", "TSS", "UMC", "WLD"
)

# Check: these are the rows I will move to the regions table
gdp_raw |>
  filter(Country.Code %in% aggregate_codes) |>
  select(Country.Code, Country.Name) |>
  head(10)
   Country.Code                                  Country.Name
1           AFE                   Africa Eastern and Southern
2           AFW                    Africa Western and Central
3           ARB                                    Arab World
4           CEB                Central Europe and the Baltics
5           CSS                        Caribbean small states
6           EAP   East Asia & Pacific (excluding high income)
7           EAR                    Early-demographic dividend
8           EAS                           East Asia & Pacific
9           ECA Europe & Central Asia (excluding high income)
10          ECS                         Europe & Central Asia

Tidy Format

Code
gdp_long <- gdp_raw |>
  # Remove the empty last column and the two columns that are the same in every row
  select(-X, -Indicator.Name, -Indicator.Code) |>
  # Rename the columns in one consistent lowercase style
  rename(
    country = Country.Name,
    country_code = Country.Code
  ) |>
  # Put all the year columns into one year column
  pivot_longer(
    cols = starts_with("X"),
    names_to = "year",
    names_prefix = "X",
    values_to = "gdp_per_capita"
  ) |>
  # Change year to a number
  mutate(year = as.integer(year))

# Split into countries and regions/groups
gdp_tidy <- gdp_long |>
  filter(!country_code %in% aggregate_codes)

gdp_regions <- gdp_long |>
  filter(country_code %in% aggregate_codes) |>
  rename(region = country, region_code = country_code)

gdp_tidy
# A tibble: 14,322 × 4
   country country_code  year gdp_per_capita
   <chr>   <chr>        <int>          <dbl>
 1 Aruba   ABW           1960             NA
 2 Aruba   ABW           1961             NA
 3 Aruba   ABW           1962             NA
 4 Aruba   ABW           1963             NA
 5 Aruba   ABW           1964             NA
 6 Aruba   ABW           1965             NA
 7 Aruba   ABW           1966             NA
 8 Aruba   ABW           1967             NA
 9 Aruba   ABW           1968             NA
10 Aruba   ABW           1969             NA
# ℹ 14,312 more rows
Code
gdp_regions
# A tibble: 3,168 × 4
   region                      region_code  year gdp_per_capita
   <chr>                       <chr>       <int>          <dbl>
 1 Africa Eastern and Southern AFE          1960           186.
 2 Africa Eastern and Southern AFE          1961           187.
 3 Africa Eastern and Southern AFE          1962           197.
 4 Africa Eastern and Southern AFE          1963           225.
 5 Africa Eastern and Southern AFE          1964           209.
 6 Africa Eastern and Southern AFE          1965           227.
 7 Africa Eastern and Southern AFE          1966           241.
 8 Africa Eastern and Southern AFE          1967           244.
 9 Africa Eastern and Southern AFE          1968           257.
10 Africa Eastern and Southern AFE          1969           282.
# ℹ 3,158 more rows

I removed the empty column at the end, and also the Indicator.Name and Indicator.Code columns, because they have the same value (“GDP per capita (current US$)”) in every row. I renamed the columns to country and country_code so that all names follow the same lowercase style, and moved the 48 regions and groups into a separate table called gdp_regions. Since each year column holds only one piece of information, the year, one pivot_longer() was enough. I used names_prefix = “X” to remove the “X” that read.csv() added, and then changed year to an integer.

Checking missing and inconsistent values

Code
# Size of the tidy data: 217 countries x 66 years
n_distinct(gdp_tidy$country)
[1] 217
Code
nrow(gdp_tidy)
[1] 14322
Code
# Total number of missing values
sum(is.na(gdp_tidy$gdp_per_capita))
[1] 2558
Code
# Number of countries with data in selected years
gdp_tidy |>
  filter(year %in% c(1960, 1990, 2015, 2024, 2025)) |>
  group_by(year) |>
  summarise(countries_with_data = sum(!is.na(gdp_per_capita)))
# A tibble: 5 × 2
   year countries_with_data
  <int>               <int>
1  1960                 111
2  1990                 192
3  2015                 212
4  2024                 200
5  2025                 186
Code
# Example: the first year with data for Aruba
gdp_tidy |>
  filter(country == "Aruba", !is.na(gdp_per_capita)) |>
  slice_min(year)
# A tibble: 1 × 4
  country country_code  year gdp_per_capita
  <chr>   <chr>        <int>          <dbl>
1 Aruba   ABW           1986          6768.

There are 2,558 missing values out of 14,322 rows. Most of them are in the early years: only 111 countries have data in 1960, but 212 have data in 2015. Aruba, for example, has no values before 1986. I kept these as NA instead of replacing them with 0, because 0 would mean a country had no income at all, which is not true. The year 2025 also has fewer countries (186) than 2024 (200), because some countries have not reported yet.

Code
# One country name is written in a different language
gdp_tidy |>
  filter(country_code == "NRU") |>
  distinct(country, country_code)
# A tibble: 1 × 2
  country country_code
  <chr>   <chr>       
1 Naoero  NRU         
Code
gdp_tidy <- gdp_tidy |>
  mutate(country = recode(country, "Naoero" = "Nauru"))

I also found one inconsistent country name: Nauru is written as “Naoero”, its name in the Nauruan language, while all other countries use English names. I changed it to “Nauru”.

Analysis

GDP per capita over time in selected countries

Code
selected <- c("United States", "Germany", "Japan", "Turkiye", "China", "Brazil", "India")

gdp_tidy |>
  filter(country %in% selected, !is.na(gdp_per_capita)) |>
  ggplot(aes(x = year, y = gdp_per_capita, color = country)) +
  geom_line() +
  scale_y_log10(labels = scales::comma) +
  labs(
    title = "GDP per Capita in Selected Countries (1960–2025)",
    x = "Year",
    y = "GDP per capita (current US$, log scale)",
    color = "Country"
  )

I chose seven large economies from different parts of the world. I used a log scale on the y axis because the values range from under 100 USD to over 90,000 USD; on a normal scale, China and India would look like flat lines at the bottom.

Code
gdp_tidy |>
  filter(country %in% selected, year %in% c(1960, 2000, 2015, 2024)) |>
  select(country, year, gdp_per_capita) |>
  mutate(gdp_per_capita = round(gdp_per_capita)) |>
  pivot_wider(names_from = year, values_from = gdp_per_capita, names_prefix = "usd_") |>
  arrange(desc(usd_2024))
# A tibble: 7 × 5
  country       usd_1960 usd_2000 usd_2015 usd_2024
  <chr>            <dbl>    <dbl>    <dbl>    <dbl>
1 United States     3000    36330    56849    86170
2 Germany           1162    23926    41930    56104
3 Japan              519    39753    35665    33797
4 Turkiye            267     4199    11065    15893
5 China               90      969     8175    13293
6 Brazil             235     3767     8936    10311
7 India               85      443     1584     2592

This table shows the GDP per capita (in current USD) of the selected countries in four years.

Fastest growth in recent years (2015–2024)

Code
gdp_growth <- gdp_tidy |>
  filter(year %in% c(2015, 2024)) |>
  select(country, year, gdp_per_capita) |>
  pivot_wider(names_from = year, values_from = gdp_per_capita, names_prefix = "usd_") |>
  # Keep only countries with data in both years
  drop_na() |>
  mutate(growth_pct = round((usd_2024 - usd_2015) / usd_2015 * 100, 1))

# Number of countries in the comparison
nrow(gdp_growth)
[1] 200
Code
# Top 10 fastest-growing countries
top_10 <- gdp_growth |>
  slice_max(growth_pct, n = 10)

top_10
# A tibble: 10 × 4
   country               usd_2015 usd_2024 growth_pct
   <chr>                    <dbl>    <dbl>      <dbl>
 1 Guyana                   5640.   29675.       426.
 2 Moldova                  2750.    7579.       176.
 3 Albania                  4200.   11374.       171.
 4 Sao Tome and Principe    1298.    3503.       170.
 5 Ukraine                  2094.    5392.       157.
 6 Armenia                  3512.    8556.       144.
 7 Bulgaria                 7269.   17597.       142.
 8 Serbia                   5820.   13678.       135 
 9 Kyrgyz Republic          1106.    2515.       128.
10 Guinea                    747.    1695.       127.
Code
# 5 countries with the largest decline
gdp_growth |>
  slice_min(growth_pct, n = 5)
# A tibble: 5 × 4
  country     usd_2015 usd_2024 growth_pct
  <chr>          <dbl>    <dbl>      <dbl>
1 Nigeria        2586.    1084.      -58.1
2 Lebanon        7714.    4473.      -42  
3 Afghanistan     566.     417.      -26.3
4 Angola         3642.    2721.      -25.3
5 Sudan          1292.     985.      -23.8
Code
ggplot(top_10, aes(x = reorder(country, growth_pct), y = growth_pct)) +
  geom_col(fill = "steelblue") +
  coord_flip() +
  labs(
    title = "Top 10 Countries by GDP per Capita Growth (2015–2024)",
    x = "Country",
    y = "Growth in GDP per capita (%)"
  )

I compared 2015 and 2024 because these are recent years where most countries have data. I did not use 2025, because 14 fewer countries have data for it. I kept only countries with values in both years (200 countries), and calculated the percent change for each one.

Comparing regions and groups

Code
gdp_regions |>
  filter(region %in% c("World", "Euro area", "High income", "Low income", "Sub-Saharan Africa"),
         !is.na(gdp_per_capita)) |>
  ggplot(aes(x = year, y = gdp_per_capita, color = region)) +
  geom_line() +
  scale_y_log10(labels = scales::comma) +
  labs(
    title = "GDP per Capita in Selected Regions and Groups (1960-2025)",
    x = "Year",
    y = "GDP per capita (current USD, log scale)",
    color = "Region or group"
  )

Code
gdp_regions |>
  filter(region %in% c("World", "Euro area", "High income", "Low income", "Sub-Saharan Africa"),
         year %in% c(2000, 2024)) |>
  select(region, year, gdp_per_capita) |>
  mutate(gdp_per_capita = round(gdp_per_capita)) |>
  pivot_wider(names_from = year, values_from = gdp_per_capita, names_prefix = "usd_")
# A tibble: 5 × 3
  region             usd_2000 usd_2024
  <chr>                 <dbl>    <dbl>
1 Euro area             19563    46424
2 High income           22428    50907
3 Low income              317      809
4 Sub-Saharan Africa      630     1543
5 World                  5513    13717

I compared the world average with a few regions and income groups. In 2024, GDP per capita was 13,717 USD for the world, 46,424 USD for the Euro area, and 50,907 USD for high-income countries, but only 809 USD for low-income countries. This means the average person in a high-income country produced about 63 times more than in a low-income country.

Conclusion

All seven selected countries had higher GDP per capita in 2024 than in 1960. The United States grew from 3,000 USD to 86,170 USD, and China from 90 USD to 13,293 USD. Japan was the exception in recent years: it dropped from 39,753 USD in 2000 to 33,797 USD in 2024.

From 2015 to 2024, Guyana grew the fastest (426.1%), and Nigeria had the biggest drop (-58.1%). These changes have different causes. Guyana started producing oil at the end of 2019, and its economy grew very quickly after that. Nigeria’s drop does not mean its economy became half as big. In June 2023, Nigeria let its currency, the naira, float freely, and the naira lost a large part of its value against the dollar. Because this data is in current US dollars, a weaker currency makes GDP per capita look much smaller, even if the economy itself did not shrink that much.

The region comparison shows a large gap between rich and poor countries: in 2024, GDP per capita in high-income countries (50,907 USD) was about 63 times higher than in low-income countries (809 USD).

The values are not adjusted for inflation, and many countries have missing data in the early years, so I used 2015-2024 for the growth comparison.

Use of LLM

I used Claude (Anthropic) to help me write and debug the R code for this file and to help draft some of the written explanations. I ran all code myself, and the numbers in this file come from my own output.

References

Anthropic. (2026). Claude (Opus 5.5 version) [Large language model]. https://claude.ai

World Bank. (2026). GDP per capita (current US$) [Data set]. World Development Indicators. https://data.worldbank.org/indicator/NY.GDP.PCAP.CD

U.S. Energy Information Administration. (2024). Country analysis brief: Guyana. https://www.eia.gov/international/content/analysis/countries_long/Guyana/pdf/Guyana%202024%20CAB.pdf

Nairametrics. (2023, June 14). Exchange rate closes at N646. https://nairametrics.com/2023/06/14/exchange-rate-closes-at-n646