Animating Plots

TLDR

There are many instances where it may be useful to animate graphical representations of data, perhaps to add an additional dimension to a plot. The below example builds a cumulative map of car accidents in the UK using some of the functionality of the gganimate package.


Making Moving Plots with gganimate

Graphics made using the ggplot2 package are already extremely customisable. They can be further enhanced using some of the extensions that have been developed. These include providing access to new themes, as well as entirely new functionality.

gganimate allows for the animation of existing ggplot graphics. Once installed, we can load both packages (ggplot2 is included as part of the tidyverse):

library(gganimate); library(tidyverse)

The example uses a car accident dataset that I found on Kaggle. Here are the first few rows of the variables that we’re interested in:

head(Accidents_Dec2015 %>% 
       dplyr::select(Date, Longitude, Latitude, Number_of_Casualties, Accident_Severity))
##         Date Longitude Latitude Number_of_Casualties Accident_Severity
## 1 2015-12-01 -0.155880 51.48959                    1            Slight
## 2 2015-12-01 -0.200271 51.49262                    1            Slight
## 3 2015-12-03 -0.210643 51.49997                    2            Slight
## 4 2015-12-03 -0.156754 51.49293                    1            Slight
## 5 2015-12-03 -0.159124 51.50205                    1            Slight
## 6 2015-12-04 -0.197452 51.49104                    1            Slight

We can plot the coordinates using a map of the UK included in ggplot2.

UK_coords <- ggplot2::map_data(map = 'world', region = 'UK')

Before animating we need to create a ggplot that we will work from.

accidents_plot <- ggplot(data = UK_coords)+
  geom_polygon(mapping = aes(x = long, y = lat, group = group), col = 'black', fill = NA)+
  theme_void(base_size = 12, base_family = 'Bahnschrift')+
  geom_point(data = Accidents_Dec2015, 
             mapping = aes(x = Longitude, y = Latitude, col = as.factor(Accident_Severity), 
                           alpha = as.factor(Accident_Severity), size = Number_of_Casualties,
             group = seq_len(length.out = nrow(Accidents_Dec2015))))+
  theme(legend.position = 'right')+
  scale_size_continuous(breaks = c(1, 3, 9))+
  scale_color_manual(values = c('firebrick', 'forestgreen', 'steelblue'))+
  scale_alpha_manual(values = c(0.4, 0.2, 0.1), guide = 'none')+
  guides(col = guide_legend(title = element_blank(), ncol = 1),
         size = guide_legend(title = element_text('Casualties', size = 10), ncol = 1, alpha = 0.4))

accidents_plot

We can now add some functions from gganimate, which will describe how and saving as a new variable:

library(gganimate)

accidents_plot <- accidents_plot +
  transition_time(time = Date)+
  enter_grow()+
  shadow_mark()+
  ggtitle(label = 'UK Car Accidents in December 2015', subtitle = 'Date : {frame_time}')+
  labs(caption = 'Data from Kaggle: https://www.kaggle.com/silicon99/dft-accident-data/data |  @Domenic_DF')

transition_time() requires specification of a time variable that the plot will display sequentially. As shown above, this animation will transition through values of Date. There are many more options that allow for animation across different data types in different ways.

shadow_mark() has been added to keep accidents from previous dates. Again, there are various methods of showing data from previous states.

enter_grow() means that when new data first appear on the plot, they will emerge by growing into their final size.

Some of these additional options are detailed here.

In this case, transition_length and state_length describe the relative amount of time spent displaying the current state, and transitioning to the next state.

Regardless of the transition function selected, the best way to create the moving plot is to use the animate function:

animate(plot = accidents_plot, fps = 20, duration = 30, end_pause = 100)

The animate() function requires us to specify the plot (to be animated), but includes many additional arguments not all of which are detailed above.

fps is the frames per second, duration is the length of the animation (in seconds), and end_pause is the length of time that the final frame is held for (in number of frames.)

Final Animation

I tried a few alternatives here. In this case each state has quite a few points and so I wanted it to be held for a reasonable amount of time. The trade-off here is the number of frames (and associated processing time and file size). The below allocates approximately 1 second per day and results in a total of 600 frames for the animation. On my (ageing) laptop this required approximately 4 minutes to render.

Animated Map

Animated Map

Thoughts on the Animation

What does the animation tell us that the stationary plot doesn’t?

The final frame is pretty much identical, but the transitions will show when the accidents occurred. From viewing the animation there doesn’t appear to be a clear time when accidents were more frequent. We can check this with an additional plot:

Accidents_Dec2015 %>% 
     dplyr::select(Date, Number_of_Casualties, Accident_Severity) %>% 
     ggplot(mapping = aes(x = Date))+
        geom_bar(stat = 'count', fill = 'grey80')+
        facet_wrap(facets = ~ Accident_Severity, scales = 'free')+
        theme_ddf_light()+
        coord_flip()+
        theme(axis.title = element_blank())

What isn’t shown in the data is the number of cars that were on the road at the time, so it could be that there were a higher proportion of accidents between the 24th and 31st December - but that will have to remain speculation for now.

One tip that I picked up from the package developer was the need to group data in the geom to avoid new points travelling from the location of other points. Given the context of the plot, that could have been interpreted as the same vehicles having accidents all over the UK. I’m glad that I was able to avoid this ambiguity in the plot.

In conclusion, I think the animated plot looks cool, but it is perhaps a little gimmicky for this particular application. The same information is contained in the two static plots in this post. However, I hope this has content serves as a helpful introduction to how the gganimate package can automate animated graphics.

Additional Resources

This example is certainly not exhaustive and there are many additional tweaks available to further customise an animation. I have personally found the below resources to be particularly helpful.

CEng, PhD Candidate

My research interests include multi-level Bayesian modelling (for partial pooling of information) and it’s decision theoretic applications, such as quantification of the expected value of information. I also work in football (soccer) analytics.

Related