Animation of plots in R can be fairly straightforward.
It can also be stunningly complex.
We’ll focus on the straightforward ones - you’ll have to wrap your head around some of the lingo, but once you get the drift it’s not that difficult.
We’ll be using gganimate to do our animating.
If you don’t already have this or gifski and
av as an installed library, you’ll want to do that (these
are what support the creation of GIF and movie files respectively. Yet
again, we’ll be using the gapminder dataset.
Load the necessary libraries:
# install.packages(c("gifski", "av", "gganimate"))
library(gapminder)
library(tidyverse)
library(gganimate)
library(scales)
gganimate is built on top of ggplot, so the
general approach is to first get a solid static ggplot visualization
that we can use as our base, and once we have that, we animate it. Let’s
begin with a robust, but completely standard ggplot call:
p1 <- ggplot(gapminder, aes(gdpPercap, lifeExp, size = pop, color = country)) +
geom_point(alpha = 0.7, show.legend = FALSE) +
scale_color_manual(values = country_colors) +
scale_size(range = c(2, 12)) +
scale_x_log10(labels = label_currency(prefix = "$")) +
facet_wrap(~continent) +
theme_bw() +
labs(title = "Year: 1952-2007", x = "GPD per capita", y = "Life Expectancy")
print(p1)
To turn it into an animation, we simply add a few functions:
labs function overwrites the previous one, so we
can dynamically display the changing years as the data
points move across the plot. Note the curly brackets enclosing the
variable frame_time that will allow the year to dynamically
display.transition_time function takes in the year variable
as an input and it allows the animated plot to transition frame by frame
as a function of the year variable.ease_aes function takes in linear as an input argument
and it defines how the transition occurs from frame to frame (in this
case, linear). More on this below…animate function.anim_save function allows the animated plot to be
rendered to a .GIF file.p2 <- p1 +
labs(title = "Year: {frame_time}", x = "GDP per capita", y = "Life Expectancy") +
transition_time(year) +
ease_aes('linear')
animate(p2)
anim_save("plots/gapminder1.gif")
The ease_aes function defines how a value changes to
another value during it’s animated transition from one state to another.
Will it progress linearly, or maybe start slowly and then build up
momentum? Your ease function will determine that. Here are the available
options and what they do:
Ok, you’re thinking…I have no idea what any of that actually means. Neither do I really. So just use this resource that can give you a sense of how each of these easing functions behave: https://easings.net/
There are also modifiers you can apply to these ease functions: -in The easing function is applied as-is -out The easing function is applied in reverse -in-out The first half of the transition it is applied as-is, while in the last half it is reversed
I wouldn’t overthink this - just choose something that looks good to you!
We can use shadow_wake() to draw a small wake after the
data by showing the latest frames up to the current. You can choose to
gradually diminish the size and/or opacity of the shadow. The length of
the wake is not given in absolute frames, it is given as a proportion of
the total length of the animation, so the example below gives us a wake
of points from the last 30% of frames. The alpha value is set here to
FALSE so that the shadows are not transparent, but you can either set
that to TRUE or a numeric (0-1) indicating what the alpha should be.
Notice that we are simply layering on a shadow_wake()
function call to the previously saved p2 object.
p3 <- p2 +
shadow_wake(wake_length = 0.3, alpha = FALSE)
animate(p3)
anim_save("plots/gapminder2.gif")
Alternatively we can use shadow_trail() to show the
original data as a trail. The parameter distance means the
animation will keep the points from 30% of the frames, spaced as evenly
as possible.
p4 <- p2 +
shadow_trail(distance = 0.3)
animate(p4)
anim_save("plots/gapminder3.gif")
We can also use gganimate to reveal data along a
specific dimension. This is most useful for time series data in which
you can show the change over time. Remember the NVDIA versus Intel line
graph from last week? Let’s recreate that. In the R code below, we are
using the tidyquant library, which we have used before, to
get the stock prices for NVDIA and INTC. We are doing some custom
theming to try to come close to the look of the original. And then we
are doing a basic ggplot line graph.
library(tidyquant)
stocks <- tq_get(c("NVDA", "INTC"), get = "stock.prices")
black_theme <- theme_minimal(base_family = "Helvetica") +
theme(plot.background = element_rect(fill = "black", color = NA),
panel.background = element_rect(fill = "black", color = NA),
legend.background = element_rect(fill = "black", color = NA),
text = element_text(color = "white"),
axis.text = element_text(color = "white"),
axis.title = element_text(color = "white"),
legend.text = element_text(color = "white"),
legend.title = element_text(color = "white"),
plot.title = element_text(color = "white", size = 30, face = "bold"),
plot.subtitle = element_text(color = "white", size = 12),
plot.caption = element_text(color = "white", size = 12),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank())
p5 <- ggplot(stocks, aes(date, close, color = symbol)) +
geom_line(size = 1) +
scale_y_continuous(labels = label_dollar()) +
scale_x_date() +
scale_color_manual(values = c("NVDA" = "white", "INTC" = "red")) +
labs(title = "NVIDIA versus Intel", color = "Ticker Symbol",
x = "Date", y = "Daily Close Price $") +
black_theme
p5
We can then call transition_reveal to let the data
gradually appear, day by day. The geom_point call means
that as it appears it shows a point. We add a custom subtitle here that
displays the date, by using the variable {frame_along}, and
then we make the display axes dynamic, moving and changing as the data
changes by adding the function view_follow(). For that last
function we could have fixed the X or Y axis as a parameter, but to
mimic the original we don’t fix either one.
The last thing we do is animate it. But notice the additional parameters. Remember how the original one took interminably long? We want to change that. Fortunately the animation function let’s us set a few key parameters:
There are many other useful parameters to this function, like a start pause or a rewind.
# install.packages("transformr")
p6 <- p5 +
transition_reveal(date) +
geom_point(size = 3, show.legend = FALSE) +
labs(subtitle = "{frame_along}") +
view_follow()
# Animate with specified fps for speed control
animate(p6, fps = 15, duration = 30, end_pause = 100)