1 Libraries:

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   4.0.0     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.2.0     
## ── 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(viridis)
## Loading required package: viridisLite
library(plotly)
## 
## Attaching package: 'plotly'
## 
## The following object is masked from 'package:ggplot2':
## 
##     last_plot
## 
## The following object is masked from 'package:stats':
## 
##     filter
## 
## The following object is masked from 'package:graphics':
## 
##     layout
library(gganimate)

2 Data:

gapminder.coltypes <- cols(
  country = col_factor(),
  continent = col_factor(levels = c("Africa", "Americas", "Asia", "Europe", "Oceania")),
  year = col_integer(),
  lifeExp = col_double(),
  pop = col_integer(),
  gdpPercap = col_double()
)

gapminder <- read_csv("gapminder.csv", col_types = gapminder.coltypes)

gapminder |> head()
## # A tibble: 6 × 6
##   country     continent  year lifeExp      pop gdpPercap
##   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
## 1 Afghanistan Asia       1952    28.8  8425333      779.
## 2 Afghanistan Asia       1957    30.3  9240934      821.
## 3 Afghanistan Asia       1962    32.0 10267083      853.
## 4 Afghanistan Asia       1967    34.0 11537966      836.
## 5 Afghanistan Asia       1972    36.1 13079460      740.
## 6 Afghanistan Asia       1977    38.4 14880372      786.

These are the values used for year:

gapminder |>
  distinct(year) |>
  arrange(year) |>
  pull(year)
##  [1] 1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 2002 2007

These are the values used for continent:

gapminder |>
  pull(continent) |>
  levels()
## [1] "Africa"   "Americas" "Asia"     "Europe"   "Oceania"

3 2d Plots

2d plot ignoring year:
This is a little weird, because each country appears several times.

gapminder |>
  ggplot() +
  geom_point(aes(x = gdpPercap, y = lifeExp, fill = continent, size = pop), shape = 21, alpha = 0.8, color = "black") + # shapes 21 - 25 support fill and color
  scale_fill_manual(
    "Continent",
    values = viridis(7)[c(1, 3, 4, 6, 7)],
    guide = guide_legend(override.aes = list(shape = 21, alpha = 1, size = 5, color = "black")) # ensures the legend key shows the fill color, only some shapes support this
  ) +
  scale_size_continuous(
    "Population",
    breaks = c(5e7, 1e8, 5e8, 1e9, 2e9),
    # labels = scales::comma,
    labels = scales::label_number(scale = 1e-6, suffix = "M"),
    range = c(0.1, 15)
  ) +
  labs(x = "GDP per Capita", y = "Life Expectancy") +
  theme(
    legend.text.align = 1
  )

2d plot for one year:
Better, but now we plot year 2007 only.

gapminder |>
  filter(year == 2007) |>
  ggplot() +
  geom_point(aes(x = gdpPercap, y = lifeExp, fill = continent, size = pop), shape = 21, alpha = 0.8, color = "black") + # shapes 21 - 25 support fill and color
  scale_fill_manual(
    "Continent",
    values = viridis(7)[c(1, 3, 4, 6, 7)],
    guide = guide_legend(override.aes = list(shape = 21, alpha = 1, size = 5, color = "black")) # ensures the legend key shows the fill color, only some shapes support this
  ) +
  scale_size_continuous(
    "Population",
    breaks = c(5e7, 1e8, 5e8, 1e9, 2e9),
    # labels = scales::comma,
    labels = scales::label_number(scale = 1e-6, suffix = "M"),
    range = c(0.1, 15)
  ) +
  labs(x = "GDP per Capita", y = "Life Expectancy") +
  theme(
    legend.text.align = 1
  )

2d plot:
We can use shape to distinguish more than one year, but it gets a bit crowded.

gapminder |>
  filter(year %in% c(1997, 2002, 2007)) |>
  ggplot() +
  geom_point(aes(x = gdpPercap, y = lifeExp, fill = continent, shape = factor(year), size = pop), alpha = 0.8, color = "black") + # need factor(year) and not year
  scale_shape_manual(
    "Year",
    values = c(21, 22, 23), # shapes 21–25 support both fill and border color
    guide = guide_legend(override.aes = list(size = 5))
  ) +
  scale_fill_manual(
    "Continent",
    values = viridis(7)[c(1, 3, 4, 6, 7)],
    guide = guide_legend(override.aes = list(shape = 21, alpha = 1, size = 5, color = "black")) # ensures the legend key shows the fill color, only some shapes support this
  ) +
  scale_size_continuous(
    "Population",
    breaks = c(5e7, 1e8, 5e8, 1e9, 2e9),
    # labels = scales::comma,
    labels = scales::label_number(scale = 1e-6, suffix = "M"),
    range = c(0.1, 15)
  ) +
  labs(x = "GDP per Capita", y = "Life Expectancy") +
  theme(
    legend.text.align = 1
  )

Can you find India, China, and the USA in the plot above?

Maybe it is okay to look at fewer countries and plot all years using fill

gapminder |>
  filter(country %in% c("China", "India", "United States", "Germany", "Austria")) |>
  ggplot() +
  geom_point(aes(x = gdpPercap, y = lifeExp, fill = year, shape = country, size = pop), alpha = 0.8, color = "black") + # use year and not factor(year) here
  scale_shape_manual(
    "Country",
    values = c(21, 22, 23, 24, 25), # shapes 21 - 25 support fill and color
    guide = guide_legend(override.aes = list(size = 5))
  ) +
  scale_fill_viridis_c("Year") +
  scale_size_continuous(
    "Population",
    breaks = c(5e7, 1e8, 5e8, 1e9, 2e9),
    # labels = scales::comma,
    labels = scales::label_number(scale = 1e-6, suffix = "M"),
    range = c(0.1, 15)
  ) +
  labs(x = "GDP per Capita", y = "Life Expectancy") +
  theme(
    legend.text.align = 1
  )

4 3d Plot

In rare cases, it makes sense to build 3d plots.

plot_ly() |>
  add_markers(data = gapminder, x = ~gdpPercap, y = ~year, z = ~lifeExp)

5 Animation

A better alternative is to use time directly and create an animation:

p <- ggplot(gapminder) +
  geom_point(aes(x = gdpPercap, y = lifeExp, fill = continent, size = pop), shape = 21, alpha = 0.8, color = "black") + # shapes 21 - 25 support fill and color
  scale_fill_manual(
    "Continent",
    values = viridis(7)[c(1, 3, 4, 6, 7)],
    guide = guide_legend(override.aes = list(shape = 21, alpha = 1, size = 5, color = "black")) # ensures the legend key shows the fill color, only some shapes support this
  ) +
  scale_size_continuous(
    "Population",
    breaks = c(5e7, 1e8, 5e8, 1e9, 2e9),
    # labels = scales::comma,
    labels = scales::label_number(scale = 1e-6, suffix = "M"),
    range = c(0.1, 15)
  ) +
  labs(x = "GDP per Capita", y = "Life Expectancy") +
  theme(
    legend.text.align = 1
  ) +
  transition_time(year) +
  ease_aes("linear")

n <- gapminder |>
  distinct(year) |>
  pull(year) |>
  length()

animate(p, nframes = n, fps = 1) # we only plot our 12 time steps. Increasing means interpolating, one frame per second

Which country is on the top right?

gapminder |>
  filter(gdpPercap > 70000)
## # A tibble: 5 × 6
##   country continent  year lifeExp    pop gdpPercap
##   <fct>   <fct>     <int>   <dbl>  <int>     <dbl>
## 1 Kuwait  Asia       1952    55.6 160000   108382.
## 2 Kuwait  Asia       1957    58.0 212846   113523.
## 3 Kuwait  Asia       1962    60.5 358266    95458.
## 4 Kuwait  Asia       1967    64.6 575003    80895.
## 5 Kuwait  Asia       1972    67.7 841934   109348.