I was recently working on a fun project using gganimate
and had one of those
breakthroughs that made me think “This is what it feels like to steal fire from
the gods!”1 I thought I would share the epiphany as a blog post for anyone
else who would find it helpful.
An introduction to frame variables
One of gganimate
’s cool
features is frame variables. They allow your labels to include data about the
animation using glue
-style syntax. For example:
library(tidyverse)
library(cowplot) # for theme_minimal_hgrid()
library(gganimate)
diamond_summary <- diamonds %>%
group_by(color, cut) %>%
summarise(ave_price = mean(price), max_price = max(price))
ggplot(diamond_summary, aes(color, ave_price)) +
geom_col() +
theme_minimal_hgrid() +
transition_states(cut, transition_length = 3, state_length = 1) +
ggtitle("Going from {previous_state} to {next_state}")
We’re using the frame variable "{previous_state}"
to show that last value of
cut
we stopped at and "{next_state}"
to show where we’re going2.
But what if we wanted a title like “Max price for [{next_state}] is [price] with
color: [color]”? This is the part where we get to play Prometheus. Thankfully,
"{next_state}"
is a string we can pass to functions:
ggplot(diamond_summary, aes(color, ave_price)) +
geom_col() +
theme_minimal_hgrid() +
transition_states(cut, transition_length = 3, state_length = 1) +
# cut is factor in diamonds, but is being coerced into a character
ggtitle("next_state is a string: {is.character(next_state)}",
subtitle = "next_state is a factor: {is.factor(next_state)}")
Note: Depending on the transition, type might be preserved instead of being coerced to character. Experiment to figure out how the transition you’re using handles the data before writing any functions.
Helper functions for frame variables
So now we just need a function to find which color has the highest price for a
given next_state
.
create_max_price_title <- function(frame_var){
max_price_row <- diamond_summary %>%
filter(cut == frame_var) %>%
group_by(cut) %>%
filter(max_price == max(max_price))
max_price <- pull(max_price_row, max_price)
max_color <- pull(max_price_row, color)
paste( "Max price for", frame_var,"is", max_price, "with color:", max_color)
}
Now we’re good to go.
ggplot(diamond_summary, aes(color, ave_price)) +
geom_col() +
theme_minimal_hgrid() +
transition_states(cut, transition_length = 3, state_length = 1) +
ggtitle("{create_max_price_title(next_state)}")
We did it!
A couple of notes about transition_reveal()
and transition_time()
First, unlike transition_state()
, both transition_reveal()
and
transition_time()
preserve the type of their frame variables. If your
helper function relies on the fact that you are transitioning over a Date
,
for example, you don’t have to convert it back from a character.
On the other hand, the frame variables for transition_reveal()
and
transition_time()
often won’t correspond to an actual value in your data the
same way next_state
does. gganimate
creates artificial times evenly spaced
across your data set and interpolates the appropriate values for your other
variables3. The frame variables are those artificial times. As result,
your helper function will need to replace filter(transition_col == frame_var)
with a step like filter(transition_col <= frame_var)
. Depending on the
helper function, you may want to use slice()
or additional filter()
calls to
get just the most “recent” data.