Though not appropriate to every medium, dynamic plots can breathe life into a website or powerpoint. I have always wanted to play around with plot animation, but it has never been compatible with the medium I primarily use R for (reports and journal articles), so this post gave me a great excuse to learn! As is the beauty of R, there are many paths to achieve the same goal. In this post, I will explore two packages (gganimate and plotly), which can be used to animate plots created with ggplot2, one of the packages in the amazing tidyverse. If you are unfamiliar with ggplot2, I recommend you begin by checking out some of the resources linked at the bottom of the page!


Data Selection

With this exercise, I wanted to highlight how dynamic plots can emphasize data that changes over a series of “subsets.” The first use that jumps to mind is annual data, which is appropriate because it is ordinal—meaning it has an order that matters (e.g., 2001 is preceded by 2000 and succeeded by 2002)—and also discrete—meaning there is a logical way to subset the data. Animation can be a good way to emphasize how certain variables change over time.

Data: World Population and Life Expectancy by Country, 1952–2007.

Source: Free data from UN POP via GAPMINDER.ORG (License: CC-BY 4.0). Retrieved through the gapminder package in R.

Dataset description: gapminder is a helpful tool for teaching data wrangling and visualization with R. It includes an excerpt of the data available at Gapminder.org, specifically: life expectancy, GDP per capita, and population for 142 countries every five years (1952 to 2007).

Warning from the package author: “this package exists for the purpose of teaching and making code examples. It is an excerpt of data found in specific spreadsheets on Gapminder.org circa 2010. It is not a definitive source of socioeconomic data and I don’t update it. Use other data sources if it’s important to have the current best estimate of these statistics.”


Animation using gganimate

The gganimate package (developed by Thomas Lin Pedersen and David Robinson) is an extension to ggplot2 which adds new “grammar” (aka “arguments”) to allow for animation by defining what data will be shown in each frame of the animation and how the frames should transition.

Note: The original iteration of gganimate (versions up to and including v0.1.1) is now deprecated and code written for the old API will not work with the new iteration of gganimate (v1.0.0 and beyond). This post will use grammar intended to work with the newer iteration of gganimate.

Let’s start by creating a static plot showing the life expectancy of each country as a function of the population, with the countries grouped by continent:

library(ggplot2)
library(gapminder)

ggplot(gapminder, aes(pop, lifeExp, colour = country)) +
  geom_point(show.legend = FALSE, size = 1.5) +
  scale_colour_manual(values = country_colors) +
  scale_x_log10() +
  facet_wrap(~continent) +
  theme_bw()


Now we can add an animation with gganimate. Here, I define that the frames I want to cycle through are a time series by using the “transition_time” argument and my variable, which is “year”. This creates a new variable called “frame_time”, which indicates which set of data should be shown in each frame. We can use this variable to assign a label for the plot which will change to show which year’s data is shown in the current frame of the animation.

library(gganimate)

ggplot(gapminder, aes(pop, lifeExp, colour = country)) +
  geom_point(show.legend = FALSE, size = 1.5) +
  scale_colour_manual(values = country_colors) +
  scale_x_log10() +
  facet_wrap(~continent) +
  theme_bw() +
  labs(title = "Year: {frame_time}", x = "Population", y = "Life Expectancy") +
  transition_time(year) +
  ease_aes("cubic-in-out")


Alternatively, because the data set only includes data from every 5 years, I can define each year as a distinct “state” (using the “transition_states” argument), rather than a time series. This places more emphasis on each 5-year datapoint rather than creating what looks like a “smooth” timeline. The “transition_states” argument has more aesthetic options, the length of the transition, the length of time each state is shown, etc. There are also three options for the frame label: “previous_state,” “next_state,” or “closest_state.”

ggplot(gapminder, aes(pop, lifeExp, colour = country)) +
  geom_point(show.legend = FALSE, size = 1.5) +
  scale_colour_manual(values = country_colors) +
  scale_x_log10() +
  facet_wrap(~continent) +
  theme_bw() +
  labs(title = "Year: {closest_state}", x = "Population", y = "Life Expectancy") +
  transition_states(year, transition_length = 2, state_length = 4) +
  ease_aes("cubic-in-out") +
  exit_fade(alpha = 0)

For more customization options, a full list of gganimate arguments can be found here.


Animation using plotly

plotly is an R package for creating interactive web-based graphs via a JavaScript graphing library of the same name plotly.js. Because plotly makes ggplots interactive, it opens up some exciting functionality if you are creating visualizations for a website. This suite of interactive features includes modifying axes to zoom in or out on certain data points, saving a screenshot of the plot as a png, or comparing data points. Compared to gganimate for animating plots, plotly really shines by addition of an interactive slider alongside your plot, which allows the viewer to select and focus on a specific frame in the animation. Effectively, the reader can toggle between a static and animated view.

Unlike gganimate, which works by adding new grammar to ggplot, plotly is designed to work off of a ggplot which has already been created. We will use the same base ggplot code to create a static graph, with one addition: an aesthetic called “frame”, which defines the list of frames that are cycled through in the animation. “frame” will not be recognized by ggplot2, but it will be recognized by the subsequent plotly function “ggplotly”.


library(ggplot2)
library(gapminder)
library(plotly)

p <- ggplot(gapminder, aes(pop, lifeExp, colour = country)) +
  geom_point(aes(frame = year), size = 1.5) +
  # The "frame" aesthetic will not be recognized by ggplot, which will
  # trigger a warning message that the aesthetic is being ignored. This is OK.
  scale_colour_manual(values = country_colors) +
  scale_x_log10() +
  facet_wrap(~continent) +
  theme_bw()

ggplotly(p) %>%
  layout(showlegend = FALSE)
  # Some layout features from ggplot are overriden by ggplotly (i.e. legend modification).
  # As a result, changes should be made here and not in the ggplot syntax.



Let’s fine-tune the visualization by adjusting the button and slider options.

p <- ggplot(gapminder, aes(pop, lifeExp, colour = country)) +
  geom_point(aes(frame = year), size = 1.5) +
  scale_colour_manual(values = country_colors) +
  scale_x_log10() +
  facet_wrap(~continent) +
  theme_bw() +
  labs(x = "Population", y = "Life Expectancy")

# Pipes (%>%) are a dplyr function. They require the `dplyr` or `tidyverse` package.
ggplotly(p) %>%
  layout(showlegend = FALSE) %>%
  animation_slider(currentvalue = list(prefix = "Source: UN POP, Year: ")) %>%
  animation_button(x = 0.9, y = 0.4)



In-depth guides for plotly can be found here. Some useful starting guides which apply to either static or animated plots are: modifying legends and configuring the interactive features.


Summary

Animation is a simple yet effective way to improve viewer engagement and understanding when plots are presented in a digital medium. To create dynamic visualizations with R, there are two main packages which can be used to animate plots made with ggplot2. The gganimate package expands on ggplot2 grammar to introduce animation, which can be rendered as a gif or video. The plotly package can also be used to animate plots created with ggplot2, but the output is an HTML widget (which is interactive but only dynamic on a website).


Software Used

R version: 4.3.2

RStudio version: 2023.09.1+494

R Packages: gapminder (v1.0.0); ggplot2 (v3.4.4); gganimate (v1.0.8); gifski (v1.12.0-2); plotly (v4.10.3)

Note: When using gganimate, a renderer is required to combine the frames into an animation (without a renderer, the frames will generate as individual png files instead of an animation). I used the gifski package, which renders as a gif. If you prefer a video output, you can install an alternate renderer (such as the av package) and specify it in your code to override the default use of gifski. The arguments used to change the renderer for gganimate can be found here.



Learning Roundup

New Skills Refresher Skills
Animation with R Plotting with R
GitHub Pages Markdown
Visual Studio Code Creative writing

I love how much bang for your buck you get from animating plots. With only a few lines of code added to R, you can add a huge amount of value to visualizations. I also like that animation can streamline data, particularly when you have a third or fourth variable that you want to present without needing numerous panels or plots. Learning how to animate plots with R was relatively easy because I already knew how to navigate R and have a good handle on ggplot grammar. I would say it had a great payout for the amount of time invested into it, and I really appreciate that both gganimate and plotly build off of ggplot2, which is a powerful and flexible tool on its own for data viz, and a package that most R users are already familiar with. For someone with no foundational knowledge of R or ggplot, there would certainly be a steeper learning curve.

I did run into a pretty frustrating knowledge gap when I tried to integrate processes that I am very comfortable with (creating an R Markdown file in RStudio) into a new process (version control and hosting content on GitHub Pages). Implementing version control is possible directly from RStudio, and there are some great guides about this (e.g., How to Combine R-Markdown and GitHub). However, I am using Visual Studio Code (VS Code) for anything related to this site that doesn’t require R. I quickly realized that making local changes to the same GitHub repository from two different programs (RStudio and VS Code) was a recipe for disaster. I also didn’t want to only make this blog post using RStudio, because I wanted to use some helpful VS Code extensions such as “Git Graph” for source control, “Prettier” for code formatting, and the built-in option to preview markdown files. So, I installed the “R” and the “vscode-pandoc” extensions to be able to create, edit, and knit (render) R Markdown (rmd) files in VS Code. I also discovered and opted to use the “github_document” output option for rmd files, which creates GitHub-compatible markdown (md) files after knitting an rmd file. When I first tried to knit my rmd document from VS code, I received an error that pandoc was not present, even though pandoc was installed on my computer. I found a similar problem on Stack OVerflow, which suggested manually adding the path to the folder housing pandoc to the settings.json file on vscode. I went into RStudio to find the complete path to pandoc, then added the path as follows:

{"terminal.integrated.env.windows": { "RSTUDIO_PANDOC":"C:/Program Files/RStudio/resources/app/bin/quarto/bin/tools" }
}

The next hurdle was a conflict between the plotly-generated HTML widgets and my site (some posts online suggest it’s a problem with Github or Jekyll, specifically Ruby). It seems that, for security reasons, GitHub Pages does not like markdown files that have JavaScript elements. When I knit my rmd file as an HTML document and tried uploading that as a blog post (my site template, beautiful-jekyll, treats files in the “_blog” folder in a special way), my site would not even deploy, which makes me think that there is a conflict with that code and the script or Ruby that is foundational to beautiful-jekyll. To resolve this issue, I was inspired by this forum to save the plotly graphs as standalone HTML files and to upload those files to my repo instead of including the full HTML/js code in the markdown file. Using this guide to Saving and embedding HTML and this article on Export plotly Graph as PNG, JPEG & HTML File in R (Example), I was able to save the plots (which plotly generates as HTML widgets) as stand-alone HTML files, which I uploaded to my repository in a separate folder and linked to iframes in my markdown. The site deploys fine when the plotly HTML code is not in the “_blogs” folder, so I hope I won’t find any issues with the site as I go along! All in all, it’s not an ideal solution, because I ended up need to use RStudio to export the plots. However, the solution is functional and isn’t worth sinking more time into right now because I don’t know if I will ever need to add more plotly graphs to this site in the future.


Resources