Introduction

This post walks through how to create an animated graph using the gganimate and gifski packages. With these R packages you can create a plot like this:


Animated plot depicting NBA Top 10 Scoring Players from 1950-2022.
Animated plot depicting NBA Top 10 Scoring Players from 1950-2022.


Load Libraries

library(gifski) # save gif file
library(dplyr) # subsetting data
library(ggplot2) # plotting data
library(gganimate) # animating data
library(tidyverse) # cleaning data

Loading the NBA Data, Source:NBA Advanced Stats Site.

nba_df <- read_csv("nba-top-10-career-scoring-leaders-yearly.csv")

Exploratory Data Analysis

Selecting from a range can be done either by filtering and using the “>” and “=” operator, or by nesting the filter and between functions:

# filtering by range range between 1960 and 1969
nba_df_1960s_random_10 <- nba_df %>% 
  filter(YearEnd >= 1960 & YearEnd <= 1969) %>% 
  sample_n(10)
nba_df_1960s_random_10
Player YearEnd CareerPts Rank
Dolph Schayes 1967 18438 3
Bob Cousy 1961 14777 2
Jack Twyman 1965 15297 6
Clyde Lovellette 1962 11251 7
Larry Foust 1961 10645 7
Hal Greer 1968 15247 10
Vern Mikkelsen 1962 10063 10
Elgin Baylor 1964 12892 7
Dolph Schayes 1969 18438 5
Bob Pettit 1969 20880 3
# or: same as above but with between() method
nba_df_1960s_random_10 <- nba_df %>% 
  filter(between(YearEnd,1960,1969)) %>% 
  sample_n(10)
Player YearEnd CareerPts Rank
Dolph Schayes 1966 18438 3
Wilt Chamberlain 1967 23442 1
Richie Guerin 1966 13426 10
Oscar Robertson 1966 13998 8
Bob Pettit 1966 20880 2
Clyde Lovellette 1963 11646 8
Bob Cousy 1966 16955 4
Larry Foust 1960 10093 7
Bob Cousy 1962 15952 3
Cliff Hagan 1965 12433 9

The filter function can also be used on strings, for example, the code below filters for Lebron James’ stats:

# filter() for just LeBron
lebron <- nba_df %>% 
  filter(Player=="LeBron James")
Player YearEnd CareerPts Rank
LeBron James 2017 28787 7
LeBron James 2018 31038 7
LeBron James 2019 32543 4
LeBron James 2020 34241 3
LeBron James 2021 37062 2
LeBron James 2022 37311 2
nba_grouped <- nba_df %>% 
  # group by player
  group_by(Player) %>% 
  # calc # of years top player & create col name Top_10_Yrs
  summarize(Top_10_Yrs=n()) %>% 
  # arrange by # of years
  arrange(desc(Top_10_Yrs)) 

# get sum total of Top 10 points by year
nba_df_sum <- nba_df %>% 
  group_by(YearEnd) %>% 
  summarize(TotPts = sum(CareerPts)) %>% 
  arrange(desc(TotPts)) 

Plotting the Data

# saving animated plot as obj
anim <- nba_df |> 
  # setting coordinates and filling by player
  ggplot(aes(x = -Rank, y = CareerPts, fill = Player)) + 
  # placing player name inside the bars
  geom_tile(aes(y = CareerPts / 2, height = CareerPts), 
            width = 0.9) + 
  # setting text inside bars to be white
  geom_text(aes(label = Player), col = "white", 
            hjust = "right", nudge_y = -1000) +
  # geom_text(aes(label = comma(CareerPts, accuracy = 1)),
  #           hjust = "left", nudge_y = 1000) +
  geom_text(aes(label = as.character(CareerPts)),
            hjust = "left", nudge_y = 1000) +
  # flipping coordinates
  coord_flip(clip = "off", expand = FALSE) + 
  # setting y-axis (appears as x-axis b/c coord flip)
  ylab("Career Points") + 
  # setting plot title
  ggtitle("NBA All-Time Scoring Leaders") + 
  scale_x_discrete("") +
  scale_y_continuous(limits = c(-1000, 49000),
                     labels = scales::comma) + 
  theme_minimal() +
  # setting locations of titles
  theme(plot.title = element_text(hjust = 0.5, size = 20),
        legend.position = "none",
        panel.grid.minor = element_line(linetype = "dashed"),
        panel.grid.major = element_line(linetype = "dashed")) + 
  # setting transition time
  transition_time(YearEnd) 
  # changing title by year appearing
  labs(subtitle = "Top 10 Scorers as of {round(frame_time, 0)}") + 
  # setting plot title alignment & font size
  theme(plot.subtitle = element_text(hjust = 0.5, size = 12))

# animating the plot
animate(anim,
        end_pause = 50, 
        nframes = 12*(2020-1950), 
        fps = 12,
        width = 8, 
        height = 4.5, 
        res = 150, 
        units="in",
        device = "ragg_png",
        renderer = gifski_renderer()
        )