Introduction

This code through explores how to use the gganimate package in R to create animated data visualizations. While a traditional graph shows us what the data looks like at one point or across an entire data set, an animated graph can help us see how the data changes over time.

To demonstrate this concept, we will create a fictional 30-game volleyball season involving six teams. We will use team hitting percentage and winning percentage to watch how each team’s performance changes throughout the season.


Content Overview

Specifically, we’ll explain and demonstrate how to take a basic ggplot2 visualization and turn it into an animation using gganimate. We will creat fictional volleyball data, explore the data, create a static graph, and then add animation to. show the season progressing from Game 1 through Game 30.


Why You Should Care

This topic is valuable because data often changes over time, and those changes can be difficult to see in a single static graph. For example, two volleyball teams may finish a season with similar records but take very different paths to get there. Animation allows us to see those changes as they happen.

The same technique can also be applied to areas such as education, public health, human services, business, and other field where outcomes are measured over time.


Learning Objectives

Specifically, you’ll learn how to:

  • Create and organize a simple dataset in R
  • Create a visualization using ggplot2
  • Use transition_time() to animate changes over time
  • Use shadow_wake() to show a team’s recent movement
  • Interpret an animated visualization and identify patterns over time



Animating a Volleyball Season with R

Here, we will create a fictional volleyball dataset and use it to demonstrate how gganimate can help us visualize changes throughout a season.


Further Exposition

gganimate extends ggplot2 by adding movement to data visualizations. Instead of showing every observation at once, we can tell R how the graph should change as time passes.

For this example, each team will have 30 observations representing Games 1 through 30. We will use game as our time variable. This allows gganimate to show us how team performance changes throughout the season.

The basic process will be: Create the data -> Make a static graph -> Add animation -> Interpret the results


Basic Example

A basic example shows how to create a fictional volleyball dataset. We will use six teams with different performance patterns to demonstrate how their seasons might unfold.

# Make the fictional data reproducible
set.seed(123)

# Create the teams
teams <- c("Falcons", "Panthers", "Wolves", "Tigers", "Hawks", "Lions")

# Create 30 games for each team
volleyball_data <- expand.grid(
  game = 1:30,
  team = teams
) %>%
  arrange(team, game) %>%
  mutate(
    # Add small random variations 
    hitting_noise = rnorm(n(), 0, 0.008), 
    winning_noise = rnorm(n(), 0, 0.035), 
    # Create different performance patterns 
    hitting_pct = case_when( 
      team == "Falcons" ~ 0.145 + game * 0.004, 
      team == "Panthers" ~ 0.245, 
      team == "Wolves" ~ 0.275, 
      team == "Tigers" ~ 0.120 + game * 0.005, 
      team == "Hawks" ~ 0.195, 
      team == "Lions" ~ 0.110 
      ) + hitting_noise, 
    
    win_pct = case_when( 
      team == "Falcons" ~ 0.250 + game * 0.018, 
      team == "Panthers" ~ 0.700, 
      team == "Wolves" ~ 0.400 + sin(game * 1.2) * 0.150,
      team == "Tigers" ~ 0.100 + game * 0.018, 
      team == "Hawks" ~ 0.480, 
      team == "Lions" ~ 0.180 
      ) + winning_noise 
    ) %>% 
  mutate( 
    hitting_pct = pmin(pmax(hitting_pct, 0.05), 0.40),
    win_pct = pmin(pmax(win_pct, 0.05), 0.95)
  )

# Display the first six rows
head(volleyball_data)

The expand.grid() function creates every combination of the six teams and 30 games, givign us 180 observations.

The mutate() function creates new variables, while case_when() allows us to assign different performance patterns to each team. For example, the Falcons’ hitting percentage increases throughout the season, while the Panthers’ hitting percentage remains relatively stable.

The set.seed() function makes the random variations reproducible so that the same fictional data can be generated again.

We can check the size of our dataset with :

# Check the number of rows and columns
dim(volleyball_data)
## [1] 180   6

The result should be 180 rows and 6 columns. Each row represents one team’s performance during one game.


Advanced Examples

More specifically, we can use ggplot2 to create a static graph comparing hitting percentage and winning percentage.

# Create a static scatterplot 
ggplot( 
  volleyball_data, 
  aes( 
    x = hitting_pct, 
    y = win_pct, 
    color = team, 
    group = team 
    ) 
  ) + 
  geom_point(size = 3) + 
  labs( 
    title = "Volleyball Team Performance", 
    x = "Hitting Percentage", 
    y = "Winning Percentage", 
    color = "Team" 
    ) + 
  theme_minimal()

The ggplot() function identifies the dataset and the variables used in the graph. The aes() function determines where the variables appear: hitting percentage is on the x-axis, winning percentage is on the y-axis, and color distinguishes the teams. The geom_point() function displays the observations as points.

This static graph allows us to compare team performance, but it does not clearly show how each team’s performance changes from game to game.


What’s more, gganimate allows us to add movement to the graph using transition_time().

# Animate performance throughout the season
volleyball_animation <- ggplot( 
  volleyball_data, 
  aes( 
    x = hitting_pct, 
    y = win_pct, 
    color = team, 
    group = team 
    ) 
  ) + 
  geom_point(size = 4) + 
  labs( 
    title = "Volleyball Season: Game {frame_time}", 
    x = "Hitting Percentage", 
    y = "Winning Percentage", 
    color = "Team" 
    ) + 
  theme_minimal() + 
  transition_time(game)

animate(
  volleyball_animation,
  renderer = gifski_renderer()
)

# Display the animation
volleyball_animation

The important addition is transition_time(game). This tells gganimate to use the game number as the time dimension, allowing the points to move as the season progresses.

The title also includes {frame_time}, which updates to display the current game number.


Most notably, we can use shadow_wake() to show a trail behind each team’s current position.

# Add a trail to show recent movement
volleyball_animation + 
  shadow_wake(wake_length = 0.10)

The shadow_wake() function displays recent frames behind the current frame. This creates a trail that helps us follow each team’s movement throughout the animation.

For example, the Falcons should generally move toward higher hitting and winning percentages as the season progresses. The Panthers should remain relatively stable, while the Wolves should show more variation in winning percentage.

These patterns demonstrate how animation can help us see changes that might be less obvious in a static graph.


Interpreting the Results

The animation allows us to explore several questions:

  • Which teams improve? The Falcons and Tigers are designed to show improvement throughout the season.
  • Which team remains consistent? The Panthers should maintain relatively strong performance.
  • Which team has inconsistent results? The Wolves have strong hitting percentages but more variation in winning percentage.
  • Which team struggles? The Lions are designed to remain toward the lower end of the graph.

Because the dataset is fictional, these patterns demonstrate how the technique works rather than providing evidence about actual volleyball teams.

The example also illustrates an important data science principle: a relationship between two variables does not necessarily mean that one causes the other. Additional data and analysis would be needed to explain why teams perform differently.



Further Resources

Learn more about gganimate and data visualization with the following:




Works Cited

This code through references and cites the following sources:


  • Pedersen, T. L., Robinson, D., & Posit, PBC. (n.d). gganimate: A Grammar of Animated Graphics. Hyperlink Text

  • Pedersen, T. L., Robinson, D., & Posit, PBC. (n.d.). gganimate Reference Documentation. Hyperlink Text

  • Wickham, H., Chang, W., Henry, L., Pedersen, T. L., Takahashi, K., Wilke, C., Woo, K., Dunnington, D., & Yutani, H. (n.d.). ggplot2: Create Elegant Data Visualisations Using the Grammar of Graphics. Hyperlink Text