Animations, plotly, colors, and themes


The graphs below don’t have proper titles, axis labels, legends, etc. Please take care to do this on your own graphs.


Use gganimate to add animations

By far, the simplest way to create visualizations with animations is to use the gganimate package. This effectively works as an extension to ggplot figures but with the inclusion of various transition_* functions

When should we animate plots?

First, let’s think about when you should NOT animate a plot. We first create a visualization of penguins data from the palmerpenguins package, of bill length on the y-axis against the body mass on the x-axis colored by species:

library(tidyverse)
library(palmerpenguins)

penguins %>% 
  ggplot(aes(x = body_mass_g, y = bill_length_mm, color = species)) +
  geom_point(alpha = 0.5, size = 2) +
  labs(x = "Body Mass (g)", y = "Bill Length (mm)") +
  theme_bw()
## Warning: Removed 2 rows containing missing values (geom_point).

Now, we could do the following: use the gganimate package to only display one species at a time with the transition_states() function:

library(gganimate)
penguins %>% 
  ggplot(aes(x = body_mass_g, y = bill_length_mm, color = species)) +
  geom_point(alpha = 0.5, size = 2) +
  labs(x = "Body Mass (g)", y = "Bill Length (mm)") +
  theme_bw() +
  transition_states(species,
                    transition_length = 0.5,
                    state_length = 1)
## 
Rendering [>---------------------------------------------------] at 1.3 fps ~ eta:  1m
Rendering [>-----------------------------------------------------] at 2 fps ~ eta: 49s
Rendering [=>--------------------------------------------------] at 2.5 fps ~ eta: 39s
Rendering [=>--------------------------------------------------] at 2.8 fps ~ eta: 34s
Rendering [==>-------------------------------------------------] at 3.1 fps ~ eta: 30s
Rendering [==>-------------------------------------------------] at 3.4 fps ~ eta: 28s
Rendering [===>------------------------------------------------] at 3.6 fps ~ eta: 26s
Rendering [===>------------------------------------------------] at 3.7 fps ~ eta: 25s
Rendering [====>-----------------------------------------------] at 3.9 fps ~ eta: 24s
Rendering [====>-------------------------------------------------] at 4 fps ~ eta: 23s
Rendering [=====>----------------------------------------------] at 4.1 fps ~ eta: 22s
Rendering [=====>----------------------------------------------] at 4.2 fps ~ eta: 21s
Rendering [======>---------------------------------------------] at 4.3 fps ~ eta: 20s
Rendering [=======>--------------------------------------------] at 4.4 fps ~ eta: 19s
Rendering [=======>--------------------------------------------] at 4.1 fps ~ eta: 20s
Rendering [========>-------------------------------------------] at 4.2 fps ~ eta: 20s
Rendering [========>-------------------------------------------] at 4.2 fps ~ eta: 19s
Rendering [=========>------------------------------------------] at 4.3 fps ~ eta: 19s
Rendering [=========>------------------------------------------] at 4.4 fps ~ eta: 18s
Rendering [==========>-----------------------------------------] at 4.4 fps ~ eta: 18s
Rendering [===========>----------------------------------------] at 4.5 fps ~ eta: 17s
Rendering [============>---------------------------------------] at 4.6 fps ~ eta: 16s
Rendering [=============>--------------------------------------] at 4.6 fps ~ eta: 16s
Rendering [==============>-------------------------------------] at 4.7 fps ~ eta: 15s
Rendering [===============>------------------------------------] at 4.7 fps ~ eta: 15s
Rendering [===============>------------------------------------] at 4.8 fps ~ eta: 14s
Rendering [================>-----------------------------------] at 4.8 fps ~ eta: 14s
Rendering [=================>----------------------------------] at 4.8 fps ~ eta: 14s
Rendering [=================>----------------------------------] at 4.9 fps ~ eta: 13s
Rendering [==================>---------------------------------] at 4.9 fps ~ eta: 13s
Rendering [===================>--------------------------------] at 4.9 fps ~ eta: 13s
Rendering [===================>--------------------------------] at 4.9 fps ~ eta: 12s
Rendering [=====================>--------------------------------] at 5 fps ~ eta: 12s
Rendering [======================>-------------------------------] at 5 fps ~ eta: 12s
Rendering [======================>-------------------------------] at 5 fps ~ eta: 11s
Rendering [======================>-----------------------------] at 5.1 fps ~ eta: 11s
Rendering [=======================>----------------------------] at 5.1 fps ~ eta: 11s
Rendering [=======================>----------------------------] at 5.1 fps ~ eta: 10s
Rendering [========================>---------------------------] at 5.1 fps ~ eta: 10s
Rendering [=========================>--------------------------] at 5.1 fps ~ eta: 10s
Rendering [==========================>-------------------------] at 5.1 fps ~ eta: 10s
Rendering [==========================>-------------------------] at 5.2 fps ~ eta:  9s
Rendering [===========================>------------------------] at 5.2 fps ~ eta:  9s
Rendering [============================>-----------------------] at 5.2 fps ~ eta:  9s
Rendering [============================>-----------------------] at 5.2 fps ~ eta:  8s
Rendering [=============================>----------------------] at 5.2 fps ~ eta:  8s
Rendering [==============================>---------------------] at 5.2 fps ~ eta:  8s
Rendering [===============================>--------------------] at 5.2 fps ~ eta:  7s
Rendering [===============================>--------------------] at 5.3 fps ~ eta:  7s
Rendering [================================>-------------------] at 5.3 fps ~ eta:  7s
Rendering [=================================>------------------] at 5.3 fps ~ eta:  7s
Rendering [=================================>------------------] at 5.3 fps ~ eta:  6s
Rendering [==================================>-----------------] at 5.3 fps ~ eta:  6s
Rendering [===================================>----------------] at 5.3 fps ~ eta:  6s
Rendering [====================================>---------------] at 5.3 fps ~ eta:  5s
Rendering [=====================================>--------------] at 5.3 fps ~ eta:  5s
Rendering [=====================================>--------------] at 5.4 fps ~ eta:  5s
Rendering [======================================>-------------] at 5.4 fps ~ eta:  5s
Rendering [=======================================>------------] at 5.4 fps ~ eta:  4s
Rendering [========================================>-----------] at 5.4 fps ~ eta:  4s
Rendering [=========================================>----------] at 5.4 fps ~ eta:  4s
Rendering [==========================================>---------] at 5.4 fps ~ eta:  3s
Rendering [===========================================>--------] at 5.4 fps ~ eta:  3s
Rendering [============================================>-------] at 5.4 fps ~ eta:  3s
Rendering [============================================>-------] at 5.4 fps ~ eta:  2s
Rendering [=============================================>------] at 5.4 fps ~ eta:  2s
Rendering [==============================================>-----] at 5.4 fps ~ eta:  2s
Rendering [===============================================>----] at 5.4 fps ~ eta:  1s
Rendering [===============================================>----] at 5.5 fps ~ eta:  1s
Rendering [================================================>---] at 5.5 fps ~ eta:  1s
Rendering [=================================================>--] at 5.5 fps ~ eta:  1s
Rendering [==================================================>-] at 5.5 fps ~ eta:  0s
Rendering [====================================================] at 5.5 fps ~ eta:  0s
                                                                                      
## 
Frame 1 (1%)
Frame 2 (2%)
Frame 3 (3%)
Frame 4 (4%)
Frame 5 (5%)
Frame 6 (6%)
Frame 7 (7%)
Frame 8 (8%)
Frame 9 (9%)
Frame 10 (10%)
Frame 11 (11%)
Frame 12 (12%)
Frame 13 (13%)
Frame 14 (14%)
Frame 15 (15%)
Frame 16 (16%)
Frame 17 (17%)
Frame 18 (18%)
Frame 19 (19%)
Frame 20 (20%)
Frame 21 (21%)
Frame 22 (22%)
Frame 23 (23%)
Frame 24 (24%)
Frame 25 (25%)
Frame 26 (26%)
Frame 27 (27%)
Frame 28 (28%)
Frame 29 (29%)
Frame 30 (30%)
Frame 31 (31%)
Frame 32 (32%)
Frame 33 (33%)
Frame 34 (34%)
Frame 35 (35%)
Frame 36 (36%)
Frame 37 (37%)
Frame 38 (38%)
Frame 39 (39%)
Frame 40 (40%)
Frame 41 (41%)
Frame 42 (42%)
Frame 43 (43%)
Frame 44 (44%)
Frame 45 (45%)
Frame 46 (46%)
Frame 47 (47%)
Frame 48 (48%)
Frame 49 (49%)
Frame 50 (50%)
Frame 51 (51%)
Frame 52 (52%)
Frame 53 (53%)
Frame 54 (54%)
Frame 55 (55%)
Frame 56 (56%)
Frame 57 (57%)
Frame 58 (58%)
Frame 59 (59%)
Frame 60 (60%)
Frame 61 (61%)
Frame 62 (62%)
Frame 63 (63%)
Frame 64 (64%)
Frame 65 (65%)
Frame 66 (66%)
Frame 67 (67%)
Frame 68 (68%)
Frame 69 (69%)
Frame 70 (70%)
Frame 71 (71%)
Frame 72 (72%)
Frame 73 (73%)
Frame 74 (74%)
Frame 75 (75%)
Frame 76 (76%)
Frame 77 (77%)
Frame 78 (78%)
Frame 79 (79%)
Frame 80 (80%)
Frame 81 (81%)
Frame 82 (82%)
Frame 83 (83%)
Frame 84 (84%)
Frame 85 (85%)
Frame 86 (86%)
Frame 87 (87%)
Frame 88 (88%)
Frame 89 (89%)
Frame 90 (90%)
Frame 91 (91%)
Frame 92 (92%)
Frame 93 (93%)
Frame 94 (94%)
Frame 95 (95%)
Frame 96 (96%)
Frame 97 (97%)
Frame 98 (98%)
Frame 99 (99%)
Frame 100 (100%)
## Finalizing encoding... done!

The use of transition_length and state_length indicate how much relative time should take place when transitioning between states and the pause at each state, respectively. But the above use of animation is useless!

So when should you consider using animation?

One appropriate usage is in the context of storytelling with data, to emphasize some aspect of your visual display. For instance, we’ll borrow this F1 racing dataset from Meghan Hall’s iteration of 36-315 to compare the performance of three racing teams:

# First load the data from Meghan's github
f1_data_ex <- read_csv('https://raw.githubusercontent.com/meghall06/CMU-36-315-site/main/data/constructor_pts.csv') %>%
  filter(name %in% c("McLaren", "Renault", "Racing Point"),
         year == 2020)
## 
## ── Column specification ────────────────────────────────────────────────────────────────
## cols(
##   constructorStandingsId = col_double(),
##   raceId = col_double(),
##   constructorId = col_double(),
##   points = col_double(),
##   position = col_double(),
##   positionText = col_double(),
##   wins = col_double(),
##   name = col_character(),
##   year = col_double(),
##   round = col_double(),
##   date = col_date(format = ""),
##   race_name = col_character()
## )
# Now display the results across the rounds:
f1_data_ex %>%
  ggplot(aes(x = round, y = points, group = name, color = name)) +
  geom_line(size = 2) +
  scale_x_continuous(breaks = seq(1, 17, 1)) +
  labs(title = "The race for third place in the 2020 F1 season",
       y = "Accumulated points", x = NULL) +
  theme_bw()

From above we can see the accumulated points increasing over time for each team, with McLaren finishing better than both, Racing Point and Renault, at the end. But we could incrementally reveal the results at each stage emphasize the story of progression. We’re not adding another dimension to the display, but we emphasize the intermediate results through animation with the transition_reveal() function:

f1_data_ex %>%
  ggplot(aes(x = round, y = points, group = name, color = name)) +
  geom_line(size = 2) +
  scale_x_continuous(breaks = seq(1, 17, 1)) +
  labs(title = "The race for third place in the 2020 F1 season",
       y = "Accumulated points", x = NULL) +
  theme_bw() +
  # Reveal the results by round
  transition_reveal(round)
## 
Rendering [>---------------------------------------------------] at 2.6 fps ~ eta: 38s
Rendering [>---------------------------------------------------] at 2.4 fps ~ eta: 41s
Rendering [=>--------------------------------------------------] at 2.9 fps ~ eta: 34s
Rendering [=>--------------------------------------------------] at 3.1 fps ~ eta: 31s
Rendering [==>-------------------------------------------------] at 3.3 fps ~ eta: 29s
Rendering [==>-------------------------------------------------] at 3.5 fps ~ eta: 27s
Rendering [===>------------------------------------------------] at 3.6 fps ~ eta: 26s
Rendering [===>------------------------------------------------] at 3.7 fps ~ eta: 25s
Rendering [====>-----------------------------------------------] at 3.7 fps ~ eta: 24s
Rendering [====>-----------------------------------------------] at 3.8 fps ~ eta: 24s
Rendering [=====>----------------------------------------------] at 3.9 fps ~ eta: 23s
Rendering [=====>----------------------------------------------] at 3.9 fps ~ eta: 22s
Rendering [======>-----------------------------------------------] at 4 fps ~ eta: 22s
Rendering [=======>----------------------------------------------] at 4 fps ~ eta: 21s
Rendering [=======>--------------------------------------------] at 4.1 fps ~ eta: 21s
Rendering [=======>--------------------------------------------] at 4.1 fps ~ eta: 20s
Rendering [========>-------------------------------------------] at 4.1 fps ~ eta: 20s
Rendering [========>-------------------------------------------] at 4.2 fps ~ eta: 20s
Rendering [=========>------------------------------------------] at 4.2 fps ~ eta: 19s
Rendering [==========>-----------------------------------------] at 4.3 fps ~ eta: 19s
Rendering [==========>-----------------------------------------] at 4.3 fps ~ eta: 18s
Rendering [===========>----------------------------------------] at 4.3 fps ~ eta: 18s
Rendering [============>---------------------------------------] at 4.3 fps ~ eta: 17s
Rendering [=============>--------------------------------------] at 4.4 fps ~ eta: 17s
Rendering [==============>-------------------------------------] at 4.4 fps ~ eta: 16s
Rendering [===============>------------------------------------] at 4.4 fps ~ eta: 16s
Rendering [================>-----------------------------------] at 4.4 fps ~ eta: 15s
Rendering [================>-----------------------------------] at 4.5 fps ~ eta: 15s
Rendering [=================>----------------------------------] at 4.5 fps ~ eta: 15s
Rendering [==================>---------------------------------] at 4.5 fps ~ eta: 14s
Rendering [===================>--------------------------------] at 4.5 fps ~ eta: 14s
Rendering [====================>-------------------------------] at 4.5 fps ~ eta: 13s
Rendering [=====================>------------------------------] at 4.5 fps ~ eta: 13s
Rendering [======================>-----------------------------] at 4.5 fps ~ eta: 12s
Rendering [======================>-----------------------------] at 4.6 fps ~ eta: 12s
Rendering [=======================>----------------------------] at 4.6 fps ~ eta: 12s
Rendering [========================>---------------------------] at 4.6 fps ~ eta: 11s
Rendering [=========================>--------------------------] at 4.6 fps ~ eta: 11s
Rendering [==========================>-------------------------] at 4.6 fps ~ eta: 11s
Rendering [==========================>-------------------------] at 4.6 fps ~ eta: 10s
Rendering [===========================>------------------------] at 4.6 fps ~ eta: 10s
Rendering [============================>-----------------------] at 4.6 fps ~ eta: 10s
Rendering [=============================>----------------------] at 4.6 fps ~ eta:  9s
Rendering [==============================>---------------------] at 4.6 fps ~ eta:  9s
Rendering [===============================>--------------------] at 4.6 fps ~ eta:  8s
Rendering [================================>-------------------] at 4.6 fps ~ eta:  8s
Rendering [=================================>------------------] at 4.6 fps ~ eta:  8s
Rendering [=================================>------------------] at 4.6 fps ~ eta:  7s
Rendering [==================================>-----------------] at 4.6 fps ~ eta:  7s
Rendering [===================================>----------------] at 4.6 fps ~ eta:  7s
Rendering [===================================>----------------] at 4.7 fps ~ eta:  6s
Rendering [====================================>---------------] at 4.7 fps ~ eta:  6s
Rendering [=====================================>--------------] at 4.7 fps ~ eta:  6s
Rendering [======================================>-------------] at 4.7 fps ~ eta:  5s
Rendering [=======================================>------------] at 4.7 fps ~ eta:  5s
Rendering [========================================>-----------] at 4.7 fps ~ eta:  5s
Rendering [=========================================>----------] at 4.7 fps ~ eta:  4s
Rendering [==========================================>---------] at 4.7 fps ~ eta:  4s
Rendering [===========================================>--------] at 4.7 fps ~ eta:  3s
Rendering [============================================>-------] at 4.7 fps ~ eta:  3s
Rendering [=============================================>------] at 4.7 fps ~ eta:  3s
Rendering [=============================================>------] at 4.7 fps ~ eta:  2s
Rendering [==============================================>-----] at 4.7 fps ~ eta:  2s
Rendering [===============================================>----] at 4.7 fps ~ eta:  2s
Rendering [================================================>---] at 4.6 fps ~ eta:  1s
Rendering [================================================>---] at 4.7 fps ~ eta:  1s
Rendering [=================================================>--] at 4.6 fps ~ eta:  1s
Rendering [==================================================>-] at 4.6 fps ~ eta:  0s
Rendering [====================================================] at 4.6 fps ~ eta:  0s
                                                                                      
## 
Frame 1 (1%)
Frame 2 (2%)
Frame 3 (3%)
Frame 4 (4%)
Frame 5 (5%)
Frame 6 (6%)
Frame 7 (7%)
Frame 8 (8%)
Frame 9 (9%)
Frame 10 (10%)
Frame 11 (11%)
Frame 12 (12%)
Frame 13 (13%)
Frame 14 (14%)
Frame 15 (15%)
Frame 16 (16%)
Frame 17 (17%)
Frame 18 (18%)
Frame 19 (19%)
Frame 20 (20%)
Frame 21 (21%)
Frame 22 (22%)
Frame 23 (23%)
Frame 24 (24%)
Frame 25 (25%)
Frame 26 (26%)
Frame 27 (27%)
Frame 28 (28%)
Frame 29 (29%)
Frame 30 (30%)
Frame 31 (31%)
Frame 32 (32%)
Frame 33 (33%)
Frame 34 (34%)
Frame 35 (35%)
Frame 36 (36%)
Frame 37 (37%)
Frame 38 (38%)
Frame 39 (39%)
Frame 40 (40%)
Frame 41 (41%)
Frame 42 (42%)
Frame 43 (43%)
Frame 44 (44%)
Frame 45 (45%)
Frame 46 (46%)
Frame 47 (47%)
Frame 48 (48%)
Frame 49 (49%)
Frame 50 (50%)
Frame 51 (51%)
Frame 52 (52%)
Frame 53 (53%)
Frame 54 (54%)
Frame 55 (55%)
Frame 56 (56%)
Frame 57 (57%)
Frame 58 (58%)
Frame 59 (59%)
Frame 60 (60%)
Frame 61 (61%)
Frame 62 (62%)
Frame 63 (63%)
Frame 64 (64%)
Frame 65 (65%)
Frame 66 (66%)
Frame 67 (67%)
Frame 68 (68%)
Frame 69 (69%)
Frame 70 (70%)
Frame 71 (71%)
Frame 72 (72%)
Frame 73 (73%)
Frame 74 (74%)
Frame 75 (75%)
Frame 76 (76%)
Frame 77 (77%)
Frame 78 (78%)
Frame 79 (79%)
Frame 80 (80%)
Frame 81 (81%)
Frame 82 (82%)
Frame 83 (83%)
Frame 84 (84%)
Frame 85 (85%)
Frame 86 (86%)
Frame 87 (87%)
Frame 88 (88%)
Frame 89 (89%)
Frame 90 (90%)
Frame 91 (91%)
Frame 92 (92%)
Frame 93 (93%)
Frame 94 (94%)
Frame 95 (95%)
Frame 96 (96%)
Frame 97 (97%)
Frame 98 (98%)
Frame 99 (99%)
Frame 100 (100%)
## Finalizing encoding... done!

The most effective use of animation is when it adds another dimension to your visualization, typically in the form of time. The previous visualization only animated across the x-axis - it did NOT add another variable in our data. However, animation can let us bring in another dimension so that we can see differences between relationships of variables in various ways. You should watch Hans Rosling’s 200 Countries, 200 Years, 4 Minutes to see one example in action. We can make similar visualizations with gganimate.

In the code chunk below, we’re going to display yearly summaries about housing sales in TX (dataset comes loaded with ggplot2). We’re going to plot the average number of active listings and average median sale price for each city-year combination in the data. For context, we’re going to highlight the data for Houston in red with a larger point size:

# Load the scales package for better labeling of the axes
txhousing %>% 
  group_by(city, year) %>% 
  summarize(median = mean(median, na.rm = TRUE),
            listings = mean(listings, na.rm = TRUE)) %>% 
  ggplot(aes(x = median, y = listings, 
             color = (city == "Houston"),
             size = (city == "Houston"))) +
  # Hide the legend for the point layer
  geom_point(alpha = 0.5, show.legend = FALSE) +
  # Manual color label
  scale_color_manual(values = c("black", "darkred")) +
  # Manual size adjustment
  scale_size_manual(values = c(2, 4)) +
  scale_x_continuous(labels = scales::dollar, name = "Median Price") +
  scale_y_continuous(labels = scales::label_number_si()) +
  theme_bw() +
  labs(x = "Median Price", y = "Avg. of Monthly Listings",
       subtitle = "Houston in red")
## `summarise()` has grouped output by 'city'. You can override using the `.groups` argument.
## Warning: Removed 68 rows containing missing values (geom_point).

In the figure above we do not have year included in any way. But we can use the transition_time() function to animate the visual over time, while also updating the plot title to include the displayed year:

# Load the scales package for better labeling of the axes
txhousing %>% 
  group_by(city, year) %>% 
  summarize(median = mean(median, na.rm = TRUE),
            listings = mean(listings, na.rm = TRUE)) %>% 
  ggplot(aes(x = median, y = listings, 
             color = (city == "Houston"),
             size = (city == "Houston"))) +
  # Hide the legend for the point layer
  geom_point(alpha = 0.5, show.legend = FALSE) +
  # Manual color label
  scale_color_manual(values = c("black", "darkred")) +
  # Manual size adjustment
  scale_size_manual(values = c(2, 4)) +
  scale_x_continuous(labels = scales::dollar, name = "Median Price") +
  scale_y_continuous(labels = scales::label_number_si()) +
  theme_bw() +
  labs(x = "Median Price", y = "Avg. of Monthly Listings",
       subtitle = "Houston in red", 
       title = "Year: {frame_time}") +
  transition_time(year)
## `summarise()` has grouped output by 'city'. You can override using the `.groups` argument.
## 
Rendering [>----------------------------------------------------] at 10 fps ~ eta: 10s
Rendering [=>--------------------------------------------------] at 7.7 fps ~ eta: 13s
Rendering [=>--------------------------------------------------] at 4.7 fps ~ eta: 20s
Rendering [==>-------------------------------------------------] at 5.2 fps ~ eta: 18s
Rendering [==>-------------------------------------------------] at 5.6 fps ~ eta: 17s
Rendering [===>------------------------------------------------] at 5.8 fps ~ eta: 16s
Rendering [===>------------------------------------------------] at 6.1 fps ~ eta: 15s
Rendering [====>-----------------------------------------------] at 6.2 fps ~ eta: 15s
Rendering [====>-----------------------------------------------] at 6.4 fps ~ eta: 14s
Rendering [=====>----------------------------------------------] at 6.7 fps ~ eta: 13s
Rendering [=====>----------------------------------------------] at 6.8 fps ~ eta: 13s
Rendering [======>---------------------------------------------] at 6.9 fps ~ eta: 13s
Rendering [======>---------------------------------------------] at 7.1 fps ~ eta: 12s
Rendering [=======>--------------------------------------------] at 7.2 fps ~ eta: 12s
Rendering [========>-------------------------------------------] at 7.3 fps ~ eta: 11s
Rendering [========>-------------------------------------------] at 7.4 fps ~ eta: 11s
Rendering [=========>------------------------------------------] at 7.5 fps ~ eta: 11s
Rendering [=========>------------------------------------------] at 7.6 fps ~ eta: 10s
Rendering [==========>-----------------------------------------] at 7.7 fps ~ eta: 10s
Rendering [===========>----------------------------------------] at 7.7 fps ~ eta: 10s
Rendering [===========>----------------------------------------] at 7.8 fps ~ eta: 10s
Rendering [============>---------------------------------------] at 7.8 fps ~ eta: 10s
Rendering [=============>--------------------------------------] at 7.9 fps ~ eta:  9s
Rendering [==============>---------------------------------------] at 8 fps ~ eta:  9s
Rendering [===============>--------------------------------------] at 8 fps ~ eta:  9s
Rendering [===============>------------------------------------] at 8.1 fps ~ eta:  9s
Rendering [===============>------------------------------------] at 8.1 fps ~ eta:  8s
Rendering [================>-----------------------------------] at 8.2 fps ~ eta:  8s
Rendering [=================>----------------------------------] at 8.3 fps ~ eta:  8s
Rendering [=================>----------------------------------] at 8.2 fps ~ eta:  8s
Rendering [==================>---------------------------------] at 8.3 fps ~ eta:  8s
Rendering [===================>--------------------------------] at 8.3 fps ~ eta:  7s
Rendering [===================>--------------------------------] at 8.4 fps ~ eta:  7s
Rendering [====================>-------------------------------] at 8.4 fps ~ eta:  7s
Rendering [=====================>------------------------------] at 8.4 fps ~ eta:  7s
Rendering [======================>-----------------------------] at 8.5 fps ~ eta:  7s
Rendering [======================>-----------------------------] at 8.5 fps ~ eta:  6s
Rendering [=======================>----------------------------] at 8.5 fps ~ eta:  6s
Rendering [=======================>----------------------------] at 8.6 fps ~ eta:  6s
Rendering [========================>---------------------------] at 8.6 fps ~ eta:  6s
Rendering [=========================>--------------------------] at 8.6 fps ~ eta:  6s
Rendering [==========================>-------------------------] at 8.6 fps ~ eta:  6s
Rendering [==========================>-------------------------] at 8.7 fps ~ eta:  6s
Rendering [===========================>------------------------] at 8.7 fps ~ eta:  5s
Rendering [============================>-----------------------] at 8.7 fps ~ eta:  5s
Rendering [=============================>----------------------] at 8.7 fps ~ eta:  5s
Rendering [==============================>---------------------] at 8.7 fps ~ eta:  5s
Rendering [==============================>---------------------] at 8.8 fps ~ eta:  5s
Rendering [===============================>--------------------] at 8.7 fps ~ eta:  4s
Rendering [===============================>--------------------] at 8.8 fps ~ eta:  4s
Rendering [================================>-------------------] at 8.8 fps ~ eta:  4s
Rendering [=================================>------------------] at 8.8 fps ~ eta:  4s
Rendering [==================================>-----------------] at 8.8 fps ~ eta:  4s
Rendering [===================================>----------------] at 8.8 fps ~ eta:  4s
Rendering [===================================>----------------] at 8.8 fps ~ eta:  3s
Rendering [====================================>---------------] at 8.8 fps ~ eta:  3s
Rendering [=====================================>--------------] at 8.8 fps ~ eta:  3s
Rendering [======================================>-------------] at 8.8 fps ~ eta:  3s
Rendering [=======================================>------------] at 8.9 fps ~ eta:  3s
Rendering [========================================>-----------] at 8.9 fps ~ eta:  2s
Rendering [=========================================>----------] at 8.9 fps ~ eta:  2s
Rendering [==========================================>---------] at 8.9 fps ~ eta:  2s
Rendering [===========================================>--------] at 8.9 fps ~ eta:  2s
Rendering [============================================>-------] at 8.9 fps ~ eta:  2s
Rendering [============================================>-------] at 8.9 fps ~ eta:  1s
Rendering [=============================================>------] at 8.9 fps ~ eta:  1s
Rendering [==============================================>-----] at 8.9 fps ~ eta:  1s
Rendering [===============================================>----] at 8.9 fps ~ eta:  1s
Rendering [==================================================>---] at 9 fps ~ eta:  1s
Rendering [===================================================>--] at 9 fps ~ eta:  0s
Rendering [==================================================>-] at 8.9 fps ~ eta:  0s
Rendering [====================================================] at 8.9 fps ~ eta:  0s
                                                                                      
## 
Frame 1 (1%)
Frame 2 (2%)
Frame 3 (3%)
Frame 4 (4%)
Frame 5 (5%)
Frame 6 (6%)
Frame 7 (7%)
Frame 8 (8%)
Frame 9 (9%)
Frame 10 (10%)
Frame 11 (11%)
Frame 12 (12%)
Frame 13 (13%)
Frame 14 (14%)
Frame 15 (15%)
Frame 16 (16%)
Frame 17 (17%)
Frame 18 (18%)
Frame 19 (19%)
Frame 20 (20%)
Frame 21 (21%)
Frame 22 (22%)
Frame 23 (23%)
Frame 24 (24%)
Frame 25 (25%)
Frame 26 (26%)
Frame 27 (27%)
Frame 28 (28%)
Frame 29 (29%)
Frame 30 (30%)
Frame 31 (31%)
Frame 32 (32%)
Frame 33 (33%)
Frame 34 (34%)
Frame 35 (35%)
Frame 36 (36%)
Frame 37 (37%)
Frame 38 (38%)
Frame 39 (39%)
Frame 40 (40%)
Frame 41 (41%)
Frame 42 (42%)
Frame 43 (43%)
Frame 44 (44%)
Frame 45 (45%)
Frame 46 (46%)
Frame 47 (47%)
Frame 48 (48%)
Frame 49 (49%)
Frame 50 (50%)
Frame 51 (51%)
Frame 52 (52%)
Frame 53 (53%)
Frame 54 (54%)
Frame 55 (55%)
Frame 56 (56%)
Frame 57 (57%)
Frame 58 (58%)
Frame 59 (59%)
Frame 60 (60%)
Frame 61 (61%)
Frame 62 (62%)
Frame 63 (63%)
Frame 64 (64%)
Frame 65 (65%)
Frame 66 (66%)
Frame 67 (67%)
Frame 68 (68%)
Frame 69 (69%)
Frame 70 (70%)
Frame 71 (71%)
Frame 72 (72%)
Frame 73 (73%)
Frame 74 (74%)
Frame 75 (75%)
Frame 76 (76%)
Frame 77 (77%)
Frame 78 (78%)
Frame 79 (79%)
Frame 80 (80%)
Frame 81 (81%)
Frame 82 (82%)
Frame 83 (83%)
Frame 84 (84%)
Frame 85 (85%)
Frame 86 (86%)
Frame 87 (87%)
Frame 88 (88%)
Frame 89 (89%)
Frame 90 (90%)
Frame 91 (91%)
Frame 92 (92%)
Frame 93 (93%)
Frame 94 (94%)
Frame 95 (95%)
Frame 96 (96%)
Frame 97 (97%)
Frame 98 (98%)
Frame 99 (99%)
Frame 100 (100%)
## Finalizing encoding... done!

From viewing the above visual, you can see how animation makes changes appear more dramatic between years - versus plotting each year separately with facets. We can then save the above animation as a GIF with the anim_save("INSERT/FILEPATH") function, which will save the last animation you made by default.

anim_save("examples/txhousing.gif")

Some key points to think about before adding animation to a visualization:

  1. Always make and describe the original / base graphic first that does NOT include animation.

  2. Before adding animation to the graph, ask yourself: How would animation give you additional insights about the data that you would otherwise not be able to?

  3. Never add animation just because it’s cool!

  4. When presenting, make sure you explain exactly what is being displayed with animation and what within the animation you want to emphasize. This will help you determine if animation is actually worth including.

Use plotly to make visuals interactive

Rather than spending a lot of time to make a fully interactive app, plotly makes it incredibly easy to take an existing ggplot object, then use the ggplotly function to make it interactive. For instance, let’s take our penguins plot from before and assign it to an object named scatter_plain:

scatter_plain <- penguins %>% 
  ggplot(aes(x = body_mass_g, y = bill_length_mm, color = species)) +
  geom_point(alpha = 0.5, size = 2) +
  labs(x = "Body Mass (g)", y = "Bill Length (mm)") +
  theme_bw()
scatter_plain
## Warning: Removed 2 rows containing missing values (geom_point).

Now, we’re going to load the plotly package and use ggplotly to make this plot interactive:

library(plotly)
ggplotly(scatter_plain)

Notice that there are several ways you can interactive with the plot, you can filter, zoom, and get additional information by hovering over the points with the tooltip. You can also customize what’s displayed in the tooltip in various ways. For instance, I can update the above plot to include text denoting the penguin’s sex. Then when we call ggplotly we can specify what is included in the tooltip when hovering over the points:

scatter_upd <- penguins %>% 
  ggplot(aes(x = body_mass_g, y = bill_length_mm, color = species,
             text = paste("sex:", sex))) +
  geom_point(alpha = 0.5, size = 2) +
  labs(x = "Body Mass (g)", y = "Bill Length (mm)") +
  theme_bw()
# Display the text with the sex variable and the penguins species with the tooltip
ggplotly(scatter_upd, tooltip = c("text", "species"))

Notes on colors in plots

Three types of color scales to work with:

  1. Qualitative: distinguishing discrete items that don’t have an order (nominal categorical). Colors should be distinct and equal with none standing out unless otherwise desired for emphasis.
  • Do NOT use a discrete scale on a continuous variable
  1. Sequential: when data values are mapped to one shade, e.g., in a choropleth, for an ordered categorical variable or low to high continuous variable
  • Do NOT use a sequential scale on an unordered variable
  1. Divergent: think of it as two sequential scales with a natural midpoint midpoint could represent 0 (assuming +/- values) or 50% if your data spans the full scale
  • Do NOT use a divergent scale on data without natural midpoint

Options for ggplot2 colors

The default color scheme is pretty bad to put it bluntly, but ggplot2 has ColorBrewer built in which makes it easy to customize your color scales. For instance, we change the palette for the species plot from before.

penguins %>% 
  ggplot(aes(x = body_mass_g, y = bill_length_mm, color = species)) +
  geom_point(alpha = 0.5, size = 2) +
  scale_color_brewer(palette = "Set2") +
  labs(x = "Body Mass (g)", y = "Bill Length (mm)") +
  theme_bw()
## Warning: Removed 2 rows containing missing values (geom_point).

Something you should keep in mind is to pick a color-blind friendly palette. One simple way to do this is by using the ggthemes package which has color-blind friendly palettes included:

penguins %>% 
  ggplot(aes(x = body_mass_g, y = bill_length_mm, color = species)) +
  geom_point(alpha = 0.5, size = 2) +
  ggthemes::scale_color_colorblind() +
  labs(x = "Body Mass (g)", y = "Bill Length (mm)") +
  theme_bw()
## Warning: Removed 2 rows containing missing values (geom_point).

In terms of displaying color from low to high, the viridis scales are excellent choices (and are also color-blind friendly!).

penguins %>% 
  ggplot(aes(x = body_mass_g, y = bill_length_mm, 
             color = flipper_length_mm)) +
  geom_point(alpha = 0.5, size = 2) +
  scale_color_viridis_c() +
  labs(x = "Body Mass (g)", y = "Bill Length (mm)",
       color = "Flipper Length (mm)") +
  theme_bw()
## Warning: Removed 2 rows containing missing values (geom_point).

Notes on themes

We have not explicitly talked about this throughout the summer, but you have seen various changes to the theme of plots for customization. You will constantly be changing the theme of your plots to optimize the display. Fortunately, there are a number of built-in themes you can use to start with rather than the default theme_gray():

penguins %>% 
  ggplot(aes(x = body_mass_g, y = bill_length_mm, color = species)) +
  geom_point(alpha = 0.5, size = 2) +
  ggthemes::scale_color_colorblind() +
  labs(x = "Body Mass (g)", y = "Bill Length (mm)") +
  theme_gray()
## Warning: Removed 2 rows containing missing values (geom_point).

For instance, you have seen me use theme_bw() many times:

penguins %>% 
  ggplot(aes(x = body_mass_g, y = bill_length_mm, color = species)) +
  geom_point(alpha = 0.5, size = 2) +
  ggthemes::scale_color_colorblind() +
  labs(x = "Body Mass (g)", y = "Bill Length (mm)") +
  theme_bw()
## Warning: Removed 2 rows containing missing values (geom_point).

There are options such as theme_minimal():

penguins %>% 
  ggplot(aes(x = body_mass_g, y = bill_length_mm, color = species)) +
  geom_point(alpha = 0.5, size = 2) +
  ggthemes::scale_color_colorblind() +
  labs(x = "Body Mass (g)", y = "Bill Length (mm)") +
  theme_minimal()
## Warning: Removed 2 rows containing missing values (geom_point).

or theme_classic():

penguins %>% 
  ggplot(aes(x = body_mass_g, y = bill_length_mm, color = species)) +
  geom_point(alpha = 0.5, size = 2) +
  ggthemes::scale_color_colorblind() +
  labs(x = "Body Mass (g)", y = "Bill Length (mm)") +
  theme_classic()
## Warning: Removed 2 rows containing missing values (geom_point).

There are also packages with popular, such as the ggthemes package which includes, for example, theme_economist():

library(ggthemes)
penguins %>% 
  ggplot(aes(x = body_mass_g, y = bill_length_mm, color = species)) +
  geom_point(alpha = 0.5, size = 2) +
  ggthemes::scale_color_colorblind() +
  labs(x = "Body Mass (g)", y = "Bill Length (mm)") +
  theme_economist()
## Warning: Removed 2 rows containing missing values (geom_point).

and theme_fivethirtyeight() to name a couple:

penguins %>% 
  ggplot(aes(x = body_mass_g, y = bill_length_mm, color = species)) +
  geom_point(alpha = 0.5, size = 2) +
  ggthemes::scale_color_colorblind() +
  labs(x = "Body Mass (g)", y = "Bill Length (mm)") +
  theme_fivethirtyeight()
## Warning: Removed 2 rows containing missing values (geom_point).

With any theme you have picked, you can then modify specific components directly using the theme() layer. There are many aspects of the plot’s theme to modify, such as my decision to move the legend to the bottom of the figure, drop the legend title, and increase the font size for the y-axis:

penguins %>% 
  ggplot(aes(x = body_mass_g, y = bill_length_mm, color = species)) +
  geom_point(alpha = 0.5, size = 2) +
  ggthemes::scale_color_colorblind() +
  labs(x = "Body Mass (g)", y = "Bill Length (mm)",
       title = "Larger penguins tend to have larger bills",
       subtitle = "Positive relationship between mass and length is consistent across species") +
  theme_bw() +
  theme(legend.position = "bottom",
        legend.title = element_blank(),
        axis.text.y = element_text(size = 14),
        axis.text.x = element_text(size = 6))
## Warning: Removed 2 rows containing missing values (geom_point).

If you’re tired of explicitly customizing every plot in the same way all the time, then you should make a custom theme. It’s quite easy to make a custom theme for ggplot2 and of course there are an incredible number of ways to customize your theme. In the code chunk, I modify the theme_bw() theme using the %+replace% argument to make my new theme named my_theme() - which is stored as a function:

my_theme <- function () {
  # Start with the base font size
  theme_bw(base_size = 10) %+replace%
    theme(
      panel.background  = element_blank(),
      plot.background = element_rect(fill = "transparent", color = NA), 
      legend.position = "bottom",
      legend.background = element_rect(fill = "transparent", color = NA),
      legend.key = element_rect(fill = "transparent", color = NA),
      axis.ticks = element_blank(),
      panel.grid.major = element_line(color = "grey90", size = 0.3), 
      panel.grid.minor = element_blank(),
      plot.title = element_text(size = 18, 
                                hjust = 0, vjust = 0.5, 
                                face = "bold", 
                                margin = margin(b = 0.2, unit = "cm")),
      plot.subtitle = element_text(size = 12, hjust = 0, 
                                   vjust = 0.5, 
                                   margin = margin(b = 0.2, unit = "cm")),
      plot.caption = element_text(size = 7, hjust = 1,
                                  face = "italic", 
                                  margin = margin(t = 0.1, unit = "cm")),
      axis.text.x = element_text(size = 13),
      axis.text.y = element_text(size = 13)
    )
}

Now I can go ahead and my plot from before with this theme:

penguins %>% 
  ggplot(aes(x = body_mass_g, y = bill_length_mm, color = species)) +
  geom_point(alpha = 0.5, size = 2) +
  ggthemes::scale_color_colorblind() +
  labs(x = "Body Mass (g)", y = "Bill Length (mm)",
       title = "Larger penguins tend to have larger bills",
       subtitle = "Positive relationship between mass and length is consistent across species") +
  my_theme()
## Warning: Removed 2 rows containing missing values (geom_point).