Search code examples
rggplot2tweengganimate

gganimate animate based on both ID and time


I'm quite new to gganimate, and I'm struggling with it quite a bit.

Here is my current animation; each set of lines that appears corresponds to a new participant: enter image description here

As you may notice, my x axis represents two points in time (Before and After an event). I would therefore like to animate each line, so that a datapoint first appears on "before", and then traces a path/line to reach the "after" value. All that, while keeping the participant by participant display that I currently have. Is there any way to achieve this ?

Here is my code for the current animation :

library('ggplot2')
library('gifski')
library('gganimate')
library('transformr')

anim <- df %>% ggplot(aes(x = factor(prepost, levels = c("Before", "After")), y = value)) +
  geom_point() +
  geom_line(aes(group = participant), size = 0.8) +
  scale_y_continuous(breaks = seq(10, 50, 5)) +
  scale_color_identity() +
  facet_wrap(~factor(group, levels = c("Positive", "Negative")), ncol = 2) +
  transition_time(participant) +
  shadow_mark(past = T, future = F, size = 0.2, alpha = 0.1) 
animate(anim, fps = 2)

I have tried using transition_reveal() and transition_states(), but with no result, I tried doing the transition using my "prepost" variable, or even an artifically created "time" variable, but everything failed.

Here is a nugget of my dataset to reproduce what I did (it's from a dataframe in long format) :

df <- structure(list(participant = c(1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 
3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 
8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 
13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 
17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 
21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 
25, 25, 25, 25), variable = c("POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post", "POS_pre", "NEG_pre", 
"POS_post", "NEG_post"), value = c(24, 23, 26, 13, 
22, 11, 38, 17, 28, 15, 27, 16, 31, 16, 40, 10, 30, 20, 40, 12, 
23, 11, 26, 10, 36, 19, 37, 15, 34, 10, 43, 14, 26, 10, 26, 10, 
32, 10, 38, 17, 35, 10, 33, 11, 24, 10, 17, 15, 33, 12, 34, 30, 
18, 10, 30, 13, 27, 13, 28, 13, 23, 10, 34, 19, 28, 15, 32, 23, 
26, 25, 36, 20, 27, 11, 39, 15, 29, 10, 35, 10, 18, 10, 22, 13, 
30, 12, 23, 11, 24, 12, 22, 11, 28, 10, 24, 13, 29, 16, 31, 27
), group = c("Positive", "Negative", "Positive", "Negative", 
"Positive", "Negative", "Positive", "Negative", "Positive", "Negative", 
"Positive", "Negative", "Positive", "Negative", "Positive", "Negative", 
"Positive", "Negative", "Positive", "Negative", "Positive", "Negative", 
"Positive", "Negative", "Positive", "Negative", "Positive", "Negative", 
"Positive", "Negative", "Positive", "Negative", "Positive", "Negative", 
"Positive", "Negative", "Positive", "Negative", "Positive", "Negative", 
"Positive", "Negative", "Positive", "Negative", "Positive", "Negative", 
"Positive", "Negative", "Positive", "Negative", "Positive", "Negative", 
"Positive", "Negative", "Positive", "Negative", "Positive", "Negative", 
"Positive", "Negative", "Positive", "Negative", "Positive", "Negative", 
"Positive", "Negative", "Positive", "Negative", "Positive", "Negative", 
"Positive", "Negative", "Positive", "Negative", "Positive", "Negative", 
"Positive", "Negative", "Positive", "Negative", "Positive", "Negative", 
"Positive", "Negative", "Positive", "Negative", "Positive", "Negative", 
"Positive", "Negative", "Positive", "Negative", "Positive", "Negative", 
"Positive", "Negative", "Positive", "Negative", "Positive", "Negative"
), prepost = structure(c(1L, 1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 
1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 
1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 
1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 
1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 
1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 
1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 2L), levels = c("Before", 
"After"), class = "factor")), row.names = c(NA, -100L), class = "data.frame")

Thanks a lot for your time and attention !


Solution

  • I think you need a bit of data manipulation beforehand to get the animation you are looking for. The time variable should be continuous between 'Before' and 'After', then increment for each ID. You could use geom_segment to show the growing lines. Remember that since you are using shadow mark, lines that expand across the screen will leave heavier traces on the left than the right, causing the shadow marks to appear to 'fade out' across the panels.

    library(ggplot2)
    library(gifski)
    library(gganimate)
    library(transformr)
    library(tidyverse)
    
    anim_df <- df %>% 
      mutate(prepost = factor(prepost, levels = c("Before", "After"))) %>%
      group_by(participant, group) %>%
      arrange(prepost, .by_group = TRUE) %>%
      reframe(value = seq(first(value), last(value), length = 30),
              prepost = seq(0, 1, length = 30)) %>%
      group_by(participant, group) %>%
      mutate(start_x = first(prepost), start_y = first(value)) %>%
      mutate(grad = last(value) - start_y) %>%
      ungroup() %>%
      mutate(frame_var = prepost + participant)
    
    anim <- anim_df %>%
      ggplot(aes(x = start_x, y = start_y, color = grad)) +
      geom_segment(aes(xend = prepost, yend = value, group = participant), 
                   linewidth = 0.8) +
      scale_y_continuous('value', breaks = seq(10, 50, 5)) +
      scale_x_continuous('PrePost', breaks = 0:1, labels = c('Before', 'After'),
                         expand = expansion(0, 0.6)) +
      scale_color_gradientn(colors = rep(c('deepskyblue3', 'orange'), each = 2),
                            values = c(0, 0.45, 0.55, 1), guide = 'none') +
      facet_wrap(~factor(group, levels = c("Positive", "Negative")), ncol = 2) +
      transition_time(frame_var) +
      theme_minimal() +
      labs(title = 'Participant: {anim_df %>% filter(group == "Positive") %>%
                                  pull(participant) %>% .[frame]}') +
      shadow_mark(past = TRUE, future = FALSE, linewidth = 0.2, alpha = 0.05) 
    

    Now we have

    animate(anim, nframes = 30*25, fps = 30)
    

    enter image description here