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")