Introduction

First I download the libraries I expect to use.

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.2.1     ✔ readr     2.2.0
## ✔ forcats   1.0.1     ✔ stringr   1.6.0
## ✔ ggplot2   4.0.3     ✔ tibble    3.3.1
## ✔ lubridate 1.9.5     ✔ tidyr     1.3.2
## ✔ purrr     1.2.2     
## ── 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(dplyr)
library(ggplot2)
library(gt)

Data

For this exercise I work with data about Life Expectancy from the World Bank. The data are in uploaded in GitHub. I retrieve them from there.

url <- "https://raw.githubusercontent.com/Enrique01234/607-Fall-2026/refs/heads/main/API_SP.DYN.LE00.IN_DS2_en_excel_v2_461099%20without%20metadata.csv"

Given the original data has three lines which are not needed for the exercise (e.g.the date of the latest update by the World Bank), they are skipped

lifex <- read_csv(file= url, show_col_types = FALSE, progress = FALSE, skip = 3)
lifex
## # A tibble: 265 × 70
##    `Country Name` `Country Code` `Indicator Name` `Indicator Code` `1960` `1961`
##    <chr>          <chr>          <chr>            <chr>             <dbl>  <dbl>
##  1 Aruba          ABW            Life expectancy… SP.DYN.LE00.IN     64.0   64.2
##  2 Africa Easter… AFE            Life expectancy… SP.DYN.LE00.IN     44.2   44.5
##  3 Afghanistan    AFG            Life expectancy… SP.DYN.LE00.IN     32.8   33.3
##  4 Africa Wester… AFW            Life expectancy… SP.DYN.LE00.IN     37.8   38.1
##  5 Angola         AGO            Life expectancy… SP.DYN.LE00.IN     37.9   36.9
##  6 Albania        ALB            Life expectancy… SP.DYN.LE00.IN     56.4   57.5
##  7 Andorra        AND            Life expectancy… SP.DYN.LE00.IN     72.1   72.6
##  8 Arab World     ARB            Life expectancy… SP.DYN.LE00.IN     44.8   45.5
##  9 United Arab E… ARE            Life expectancy… SP.DYN.LE00.IN     50.7   51.6
## 10 Argentina      ARG            Life expectancy… SP.DYN.LE00.IN     64.2   64.6
## # ℹ 255 more rows
## # ℹ 64 more variables: `1962` <dbl>, `1963` <dbl>, `1964` <dbl>, `1965` <dbl>,
## #   `1966` <dbl>, `1967` <dbl>, `1968` <dbl>, `1969` <dbl>, `1970` <dbl>,
## #   `1971` <dbl>, `1972` <dbl>, `1973` <dbl>, `1974` <dbl>, `1975` <dbl>,
## #   `1976` <dbl>, `1977` <dbl>, `1978` <dbl>, `1979` <dbl>, `1980` <dbl>,
## #   `1981` <dbl>, `1982` <dbl>, `1983` <dbl>, `1984` <dbl>, `1985` <dbl>,
## #   `1986` <dbl>, `1987` <dbl>, `1988` <dbl>, `1989` <dbl>, `1990` <dbl>, …

There are two columns about the indicator (the name and the code) which are the same for all rows (countries). In order to save space and facilitate the manipulation of the file, I delete them and create a new data frame (lifex2). I use the column numbers because R was not recognizing the columns by their name.

lifex2<- lifex[-c(3,4)]
lifex2
## # A tibble: 265 × 68
##    `Country Name`       `Country Code` `1960` `1961` `1962` `1963` `1964` `1965`
##    <chr>                <chr>           <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
##  1 Aruba                ABW              64.0   64.2   64.6   64.9   65.3   65.6
##  2 Africa Eastern and … AFE              44.2   44.5   44.9   45.2   45.5   45.8
##  3 Afghanistan          AFG              32.8   33.3   33.8   34.2   34.7   35.1
##  4 Africa Western and … AFW              37.8   38.1   38.7   38.9   39.2   39.5
##  5 Angola               AGO              37.9   36.9   37.2   37.4   37.7   38.0
##  6 Albania              ALB              56.4   57.5   58.5   59.5   60.4   61.3
##  7 Andorra              AND              72.1   72.6   73.0   73.3   73.6   73.9
##  8 Arab World           ARB              44.8   45.5   46.0   47.0   47.6   48.1
##  9 United Arab Emirates ARE              50.7   51.6   52.5   53.5   54.5   55.5
## 10 Argentina            ARG              64.2   64.6   64.6   64.9   64.8   65.1
## # ℹ 255 more rows
## # ℹ 60 more variables: `1966` <dbl>, `1967` <dbl>, `1968` <dbl>, `1969` <dbl>,
## #   `1970` <dbl>, `1971` <dbl>, `1972` <dbl>, `1973` <dbl>, `1974` <dbl>,
## #   `1975` <dbl>, `1976` <dbl>, `1977` <dbl>, `1978` <dbl>, `1979` <dbl>,
## #   `1980` <dbl>, `1981` <dbl>, `1982` <dbl>, `1983` <dbl>, `1984` <dbl>,
## #   `1985` <dbl>, `1986` <dbl>, `1987` <dbl>, `1988` <dbl>, `1989` <dbl>,
## #   `1990` <dbl>, `1991` <dbl>, `1992` <dbl>, `1993` <dbl>, `1994` <dbl>, …

Towards a tidy data frame

In the first two rows we can see that the column “Country Names” includes country groupings (e.g. Africa, Eastern and Southern). As they are not countries, I do not want to include them in the data frame, nor do I want to delete them by hand.

Fortunately, the World Bank provides the metadata. In that worksheet (I have saved them separately in GitHub) the country groupings have no information about the region. Some of them (like in the example in the previous paragraph) are sub-regions. However, in other cases (such as low income countries) they span more than one region.

I get the data from GitHub and create a new data frame (lifexMETAD).

url<- "https://raw.githubusercontent.com/Enrique01234/607-Fall-2026/refs/heads/main/Lifex%20Metadata.csv"
lifexMETAD<- read_csv(file= url, show_col_types = FALSE, progress = FALSE)
lifexMETAD
## # A tibble: 264 × 2
##    `Country Code` Region                    
##    <chr>          <chr>                     
##  1 ABW            Latin America & Caribbean 
##  2 AFE            <NA>                      
##  3 AFG            Middle East & North Africa
##  4 AFW            <NA>                      
##  5 AGO            Sub-Saharan Africa        
##  6 ALB            Europe & Central Asia     
##  7 AND            Europe & Central Asia     
##  8 ARB            <NA>                      
##  9 ARE            Middle East & North Africa
## 10 ARG            Latin America & Caribbean 
## # ℹ 254 more rows

Clearly, the rows which are not countries have “NA” in the Region column.

I (left)merge the two files because I plan to use these “NAs” to filter out the rows which are not countries. The merged data frame is called “lifex3”

lifex3<- lifex2|>
  left_join(lifexMETAD)
## Joining with `by = join_by(`Country Code`)`

R automatically selected “Country Code” as the key variable to merge the files.

lifex3
## # A tibble: 265 × 69
##    `Country Name`       `Country Code` `1960` `1961` `1962` `1963` `1964` `1965`
##    <chr>                <chr>           <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
##  1 Aruba                ABW              64.0   64.2   64.6   64.9   65.3   65.6
##  2 Africa Eastern and … AFE              44.2   44.5   44.9   45.2   45.5   45.8
##  3 Afghanistan          AFG              32.8   33.3   33.8   34.2   34.7   35.1
##  4 Africa Western and … AFW              37.8   38.1   38.7   38.9   39.2   39.5
##  5 Angola               AGO              37.9   36.9   37.2   37.4   37.7   38.0
##  6 Albania              ALB              56.4   57.5   58.5   59.5   60.4   61.3
##  7 Andorra              AND              72.1   72.6   73.0   73.3   73.6   73.9
##  8 Arab World           ARB              44.8   45.5   46.0   47.0   47.6   48.1
##  9 United Arab Emirates ARE              50.7   51.6   52.5   53.5   54.5   55.5
## 10 Argentina            ARG              64.2   64.6   64.6   64.9   64.8   65.1
## # ℹ 255 more rows
## # ℹ 61 more variables: `1966` <dbl>, `1967` <dbl>, `1968` <dbl>, `1969` <dbl>,
## #   `1970` <dbl>, `1971` <dbl>, `1972` <dbl>, `1973` <dbl>, `1974` <dbl>,
## #   `1975` <dbl>, `1976` <dbl>, `1977` <dbl>, `1978` <dbl>, `1979` <dbl>,
## #   `1980` <dbl>, `1981` <dbl>, `1982` <dbl>, `1983` <dbl>, `1984` <dbl>,
## #   `1985` <dbl>, `1986` <dbl>, `1987` <dbl>, `1988` <dbl>, `1989` <dbl>,
## #   `1990` <dbl>, `1991` <dbl>, `1992` <dbl>, `1993` <dbl>, `1994` <dbl>, …

Comparing with the lifex 2 tibble, it can be seen a new column has been added (from 68 to 69 columns).

In order to make sure I can filter and keep the countries, I explore which rows are the ones which have “NAs” in the Region column. I use a new data frame: lifextry.

lifextry<-lifex3|>
     filter(is.na(Region))
lifextry
## # A tibble: 48 × 69
##    `Country Name`       `Country Code` `1960` `1961` `1962` `1963` `1964` `1965`
##    <chr>                <chr>           <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
##  1 Africa Eastern and … AFE              44.2   44.5   44.9   45.2   45.5   45.8
##  2 Africa Western and … AFW              37.8   38.1   38.7   38.9   39.2   39.5
##  3 Arab World           ARB              44.8   45.5   46.0   47.0   47.6   48.1
##  4 Central Europe and … CEB              67.9   68.2   67.7   68.7   69.2   69.4
##  5 Caribbean small sta… CSS              60.3   60.7   61.4   61.7   62.1   62.4
##  6 East Asia & Pacific… EAP              37.8   43.1   51.2   51.8   52.4   52.3
##  7 Early-demographic d… EAR              46.9   47.4   47.9   48.3   48.7   47.8
##  8 East Asia & Pacific  EAS              41.7   46.3   53.3   53.9   54.5   54.4
##  9 Europe & Central As… ECA              60.9   61.4   61.8   62.2   62.5   62.8
## 10 Europe & Central As… ECS              67.4   67.7   67.7   68.0   68.5   68.6
## # ℹ 38 more rows
## # ℹ 61 more variables: `1966` <dbl>, `1967` <dbl>, `1968` <dbl>, `1969` <dbl>,
## #   `1970` <dbl>, `1971` <dbl>, `1972` <dbl>, `1973` <dbl>, `1974` <dbl>,
## #   `1975` <dbl>, `1976` <dbl>, `1977` <dbl>, `1978` <dbl>, `1979` <dbl>,
## #   `1980` <dbl>, `1981` <dbl>, `1982` <dbl>, `1983` <dbl>, `1984` <dbl>,
## #   `1985` <dbl>, `1986` <dbl>, `1987` <dbl>, `1988` <dbl>, `1989` <dbl>,
## #   `1990` <dbl>, `1991` <dbl>, `1992` <dbl>, `1993` <dbl>, `1994` <dbl>, …

As I cannot see all of the rows, I add the instruction to see all 48 of them.

print (lifextry, n = 48)
## # A tibble: 48 × 69
##    `Country Name`       `Country Code` `1960` `1961` `1962` `1963` `1964` `1965`
##    <chr>                <chr>           <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
##  1 Africa Eastern and … AFE              44.2   44.5   44.9   45.2   45.5   45.8
##  2 Africa Western and … AFW              37.8   38.1   38.7   38.9   39.2   39.5
##  3 Arab World           ARB              44.8   45.5   46.0   47.0   47.6   48.1
##  4 Central Europe and … CEB              67.9   68.2   67.7   68.7   69.2   69.4
##  5 Caribbean small sta… CSS              60.3   60.7   61.4   61.7   62.1   62.4
##  6 East Asia & Pacific… EAP              37.8   43.1   51.2   51.8   52.4   52.3
##  7 Early-demographic d… EAR              46.9   47.4   47.9   48.3   48.7   47.8
##  8 East Asia & Pacific  EAS              41.7   46.3   53.3   53.9   54.5   54.4
##  9 Europe & Central As… ECA              60.9   61.4   61.8   62.2   62.5   62.8
## 10 Europe & Central As… ECS              67.4   67.7   67.7   68.0   68.5   68.6
## 11 Euro area            EMU              69.3   69.7   69.6   69.8   70.4   70.5
## 12 European Union       EUU              69.1   69.4   69.3   69.6   70.2   70.3
## 13 High income          HIC              68.3   68.7   68.7   68.9   69.4   69.4
## 14 Heavily indebted po… HPC              40.8   41.1   41.7   41.9   42.3   42.6
## 15 IBRD only            IBD              45.9   48.6   52.5   53.0   53.5   53.2
## 16 IDA & IBRD total     IBT              45.3   47.7   51.0   51.5   52.0   51.8
## 17 IDA total            IDA              42.6   43.1   43.7   44.0   44.6   44.9
## 18 IDA blend            IDB              42.5   43.1   44.0   44.6   45.2   45.7
## 19 IDA only             IDX              42.6   43.1   43.6   43.8   44.4   44.5
## 20 Not classified       INX              NA     NA     NA     NA     NA     NA  
## 21 Latin America & Car… LAC              54.5   55.1   55.5   55.9   56.4   56.8
## 22 Latin America & Car… LCN              55.1   55.6   56.0   56.4   56.8   57.3
## 23 Least developed cou… LDC              41.2   41.6   42.1   42.2   42.8   42.9
## 24 Low income           LIC              41.3   41.6   42.0   42.2   42.7   42.9
## 25 Lower middle income  LMC              44.7   45.1   45.6   46.0   46.4   45.9
## 26 Low & middle income  LMY              43.5   46.0   49.6   50.1   50.6   50.4
## 27 Late-demographic di… LTE              43.9   48.3   54.9   55.4   56.0   56.4
## 28 Middle East, North … MEA              43.5   44.7   45.2   45.8   46.6   47.2
## 29 Middle income        MIC              43.6   46.3   50.1   50.6   51.1   50.9
## 30 Middle East, North … MNA              43.4   44.2   44.7   45.7   46.5   47.1
## 31 North America        NAC              69.9   70.4   70.2   70.0   70.3   70.4
## 32 OECD members         OED              67.2   67.6   67.6   67.8   68.2   68.4
## 33 Other small states   OSS              57.3   57.8   58.2   58.1   58.6   59.3
## 34 Pre-demographic div… PRE              39.9   40.2   40.7   41.1   41.4   41.7
## 35 Pacific island smal… PSS              54.9   55.5   56.1   56.6   56.8   57.9
## 36 Post-demographic di… PST              68.8   69.3   69.3   69.4   69.9   70.0
## 37 South Asia           SAS              45.6   45.9   46.3   46.5   46.9   45.8
## 38 Sub-Saharan Africa … SSA              41.4   41.7   42.2   42.5   42.8   43.1
## 39 Sub-Saharan Africa   SSF              41.4   41.7   42.2   42.5   42.9   43.1
## 40 Small states         SST              57.9   58.4   58.9   59.0   59.4   60.1
## 41 East Asia & Pacific… TEA              37.6   42.9   51.1   51.7   52.3   52.2
## 42 Europe & Central As… TEC              64.7   65.0   65.1   65.7   66.1   66.2
## 43 Latin America & the… TLA              54.6   55.1   55.6   56.0   56.4   56.9
## 44 Middle East, North … TMN              43.4   44.2   44.7   45.7   46.5   47.1
## 45 South Asia (IDA & I… TSA              45.6   45.9   46.3   46.5   46.9   45.8
## 46 Sub-Saharan Africa … TSS              41.4   41.7   42.2   42.5   42.9   43.1
## 47 Upper middle income  UMC              42.9   47.0   53.0   53.6   54.1   54.1
## 48 World                WLD              50.9   52.8   55.3   55.7   56.1   55.9
## # ℹ 61 more variables: `1966` <dbl>, `1967` <dbl>, `1968` <dbl>, `1969` <dbl>,
## #   `1970` <dbl>, `1971` <dbl>, `1972` <dbl>, `1973` <dbl>, `1974` <dbl>,
## #   `1975` <dbl>, `1976` <dbl>, `1977` <dbl>, `1978` <dbl>, `1979` <dbl>,
## #   `1980` <dbl>, `1981` <dbl>, `1982` <dbl>, `1983` <dbl>, `1984` <dbl>,
## #   `1985` <dbl>, `1986` <dbl>, `1987` <dbl>, `1988` <dbl>, `1989` <dbl>,
## #   `1990` <dbl>, `1991` <dbl>, `1992` <dbl>, `1993` <dbl>, `1994` <dbl>,
## #   `1995` <dbl>, `1996` <dbl>, `1997` <dbl>, `1998` <dbl>, `1999` <dbl>, …

None of the rows are a country. Then, I proceed to filter them out and create a new data frame (lifex4).

lifex4<-lifex3|>
 filter_out(is.na(Region))
lifex4
## # A tibble: 217 × 69
##    `Country Name`       `Country Code` `1960` `1961` `1962` `1963` `1964` `1965`
##    <chr>                <chr>           <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
##  1 Aruba                ABW              64.0   64.2   64.6   64.9   65.3   65.6
##  2 Afghanistan          AFG              32.8   33.3   33.8   34.2   34.7   35.1
##  3 Angola               AGO              37.9   36.9   37.2   37.4   37.7   38.0
##  4 Albania              ALB              56.4   57.5   58.5   59.5   60.4   61.3
##  5 Andorra              AND              72.1   72.6   73.0   73.3   73.6   73.9
##  6 United Arab Emirates ARE              50.7   51.6   52.5   53.5   54.5   55.5
##  7 Argentina            ARG              64.2   64.6   64.6   64.9   64.8   65.1
##  8 Armenia              ARM              59.1   59.5   59.9   60.3   60.7   61.1
##  9 American Samoa       ASM              65.1   65.6   66.0   66.2   66.1   66.0
## 10 Antigua and Barbuda  ATG              62.6   63.3   64.0   64.7   65.4   66.2
## # ℹ 207 more rows
## # ℹ 61 more variables: `1966` <dbl>, `1967` <dbl>, `1968` <dbl>, `1969` <dbl>,
## #   `1970` <dbl>, `1971` <dbl>, `1972` <dbl>, `1973` <dbl>, `1974` <dbl>,
## #   `1975` <dbl>, `1976` <dbl>, `1977` <dbl>, `1978` <dbl>, `1979` <dbl>,
## #   `1980` <dbl>, `1981` <dbl>, `1982` <dbl>, `1983` <dbl>, `1984` <dbl>,
## #   `1985` <dbl>, `1986` <dbl>, `1987` <dbl>, `1988` <dbl>, `1989` <dbl>,
## #   `1990` <dbl>, `1991` <dbl>, `1992` <dbl>, `1993` <dbl>, `1994` <dbl>, …

Lifex4 has 217 and lifex 3 has 265. The difference of 48 is the number of rows in lifextry. I have laso inspected visually the 217 rows but it is too long to print here. They are all countries or semi-autonomus areas (like Aruba).

Lifex4 is the data frame to tidy. A new data frame is created with long-pivot and called “pivlifex”.

pivlifex<- lifex4|>
pivot_longer (!c(1,2, Region), names_to = "Year", values_to = "Life Expectancy")
pivlifex
## # A tibble: 14,322 × 5
##    `Country Name` `Country Code` Region                  Year  `Life Expectancy`
##    <chr>          <chr>          <chr>                   <chr>             <dbl>
##  1 Aruba          ABW            Latin America & Caribb… 1960               64.0
##  2 Aruba          ABW            Latin America & Caribb… 1961               64.2
##  3 Aruba          ABW            Latin America & Caribb… 1962               64.6
##  4 Aruba          ABW            Latin America & Caribb… 1963               64.9
##  5 Aruba          ABW            Latin America & Caribb… 1964               65.3
##  6 Aruba          ABW            Latin America & Caribb… 1965               65.6
##  7 Aruba          ABW            Latin America & Caribb… 1966               66.1
##  8 Aruba          ABW            Latin America & Caribb… 1967               66.4
##  9 Aruba          ABW            Latin America & Caribb… 1968               66.7
## 10 Aruba          ABW            Latin America & Caribb… 1969               67.1
## # ℹ 14,312 more rows

Now pivlifex is a tidy data frame. Each column is a variable and each variable is a column. Also, each observation is a row and each row is an observation while each value is a cell and each cell is a single value.

Working with the tidy data frame

Given the Region category is included, I explore the top performer (highest life expectancy) per region. I do this irrespective of the year, assuming the most recent year with data would show the highest value.

pivlifex|>
 group_by(Region)|>
 top_n(1)
## Selecting by Life Expectancy
## # A tibble: 7 × 5
## # Groups:   Region [7]
##   `Country Name`       `Country Code` Region             Year  `Life Expectancy`
##   <chr>                <chr>          <chr>              <chr>             <dbl>
## 1 Bermuda              BMU            North America      2024               82.5
## 2 Hong Kong SAR, China HKG            East Asia & Pacif… 2021               85.5
## 3 Kuwait               KWT            Middle East & Nor… 2024               84.6
## 4 Monaco               MCO            Europe & Central … 2024               86.5
## 5 Maldives             MDV            South Asia         2024               81.3
## 6 Puerto Rico (US)     PRI            Latin America & C… 2024               81.9
## 7 Seychelles           SYC            Sub-Saharan Africa 2020               77.2

Then, I do the same for the countries with the lowest life expectancy. I expect these worst performances to be the ones in 1960.

pivlifex|>
 group_by(Region)|>
 top_n(-1)
## Selecting by Life Expectancy
## # A tibble: 7 × 5
## # Groups:   Region [7]
##   `Country Name` `Country Code` Region                   Year  `Life Expectancy`
##   <chr>          <chr>          <chr>                    <chr>             <dbl>
## 1 Afghanistan    AFG            Middle East & North Afr… 1984               31.5
## 2 Bangladesh     BGD            South Asia               1971               26.5
## 3 Bermuda        BMU            North America            1960               66.5
## 4 Haiti          HTI            Latin America & Caribbe… 1963               42.7
## 5 Cambodia       KHM            East Asia & Pacific      1977               11.3
## 6 South Sudan    SSD            Sub-Saharan Africa       1988               11.0
## 7 Kosovo         XKX            Europe & Central Asia    1962               48.4

The highest life expectancy is actually observed in the most recent years, as expected. However, some of the lowest ones are recorded in the 1970s and 1980s. This may be the result of internal wars or long periods of economic decline.