An animated map showing the countries not represented in Grand Slam tennis (1990-2019)
By: Dr. Chris Martin
Tools used: R, R Markdown, ggplot2,
gganimate.
Techniques used: animation, mapping.
This notebook produces an animated map which features in my data storytelling project: The Changing Fortunes of the Richest Countries in Grand Slam Tennis. You can read the full story on my website.
To produce the chart, I needed data on the women’s and men’s singles entrants for each Grand Slam tournament since 1990. This came from the excellent Tennis Abstract.
Setting up the notebook
library(tidyverse)
# for mapping
library(sf)
library(rmapshaper)
# for animating ggplot2 charts
library(gganimate)
library(transformr)
# for using custom fonts
library(showtext)
# for table formatting
library(knitr)Creating the animated map
# -----------------------------------------------------------------------------
# enable custom fonts
# -----------------------------------------------------------------------------
font_add_google("Lato", family = "lato")
showtext_auto()
# -----------------------------------------------------------------------------
# Read in tennis data from csv (produced by data_clean.Rmd)
# -----------------------------------------------------------------------------
gs_first_round_gdp <- read_csv("../data/results_gdp.csv")
# output to check columns are as expected
gs_first_round_gdp %>%
head() %>%
kable()| year | tourney_name | tour | name | id | ioc | country | gdp_per_capita | iso |
|---|---|---|---|---|---|---|---|---|
| 1990 | Australian Open | atp | Jim Pugh | 101004 | USA | United States | 40436.94 | USA |
| 1990 | Australian Open | atp | Ivan Lendl | 100656 | USA | United States | 40436.94 | USA |
| 1990 | Australian Open | atp | Cyril Suk | 101327 | CZE | Czechia | 23585.18 | CZE |
| 1990 | Australian Open | atp | Tomas Carbonell | 101507 | ESP | Spain | 27543.92 | ESP |
| 1990 | Australian Open | atp | Michael Brown B395 | 101895 | AUS | Australia | 31016.42 | AUS |
| 1990 | Australian Open | atp | Karel Novacek | 101120 | CZE | Czechia | 23585.18 | CZE |
# -----------------------------------------------------------------------------
# Read in spatial data for creating a world map
# -----------------------------------------------------------------------------
countries <- read_sf("../data/ne_110m_admin_0_countries/ne_110m_admin_0_countries.shp") %>%
rmapshaper::ms_simplify(keep = 0.5) %>%
st_transform(crs = "+proj=eqearth")
# -----------------------------------------------------------------------------
# Clean the data
# -----------------------------------------------------------------------------
# create a grid with all combinations of years and countries
# so we know where data is implicitly missing
fake_data <- expand_grid(year = 1990:2020,
ISO_A3_EH = unique(countries$ISO_A3_EH))
# join the tennis data with the spatial data
countries_all_years <- countries %>%
select(ISO_A3_EH) %>%
filter(ISO_A3_EH != 'ATA') %>%
inner_join(fake_data) %>%
rename(iso = ISO_A3_EH) %>%
left_join(gs_first_round_gdp %>%
count(year, country, iso)) %>%
mutate(n = if_else(is.na(n), 0L, n),
at_gs = if_else(n > 0, TRUE, FALSE))
# -----------------------------------------------------------------------------
# Create the map
# -----------------------------------------------------------------------------
plot <- countries_all_years %>%
ggplot() +
geom_sf(aes(fill = at_gs, colour = at_gs,
group = seq_along(iso)),
size = 0) +
scale_fill_manual(values = c("#C94A54", "#DADADA")) +
scale_colour_manual(values = c("#C94A54", "#DADADA")) +
# simplify plot background
theme_void() +
# position and style the text which shows the year advancing
theme(plot.background = element_rect(fill = "#E9E9E9", colour = NA),
legend.position = "none",
text = element_text(family = "lato"),
plot.title = element_text(face = "bold", size = 45, hjust=0.5,
margin = margin(0,0,5,0), colour = "#2C3130"),
plot.subtitle = element_text(colour = "#374E83", size = 14, hjust=0.5,
margin = margin(0,0,5,0)),
plot.margin=unit(c(50,30,30,30), 'pt')) +
# prepare plot object for animation
transition_time(year) +
ggtitle('{floor(frame_time)}')
# -----------------------------------------------------------------------------
# Animate the map
# -----------------------------------------------------------------------------
animate(plot,
fps = 5, duration = 30,
height = 1440, width = 2560, units = "px")anim_save("not_at_gs.gif")