gganimate: How to combine geom_smooth(method = "lm) with transition_layers()

How do I combine the geom_smooth(method = "lm) function with gganimate()'s transition_layers(), so that, as the individual bars drift/grow upwards, the linear line of geom_smooth() appears, like so: [Example of desired appearance of geom_smooth() line] The only difference is that in my case, instead of the points, the bars would drift upwards as the line appears.

The bars currently work well, appearing by drifting upwards, made possible by using the transition_layers() function of gganimate().

However, I can't figure out how to add the geom_smooth() line, so it appears as the bars grow upwards. Right now, the line appears just at the end, as seen below.

See below for the current look of the animation.

Here is a simple reprex of my problem:

#Df for reprex
library(ggplot2)
library(tidyverse)

year <- as.numeric(c(1996:2002,
1996:2002,
1996:2002))
c <- c(39, 40, 67, 80, 30, 140, 90, 23, 100, 123,
140, 1, 2, 1, 13, 3, 3, 30, 1, 3, 3)
df <- data.frame(year, c) %>%
select(year, c) %>%
arrange(year)

#Static plot
(static_plot <- ggplot(data = df) +
geom_bar(data = df %>% filter(year == 1996), stat="identity", position ="stack",
aes(x = year, y = c)) +
geom_bar(data = df %>% filter(year == 1997), stat="identity", position ="stack",
aes(x = year, y = c)) +
geom_bar(data = df %>% filter(year == 1998), stat="identity", position ="stack",
aes(x = year, y = c)) +
geom_bar(data = df %>% filter(year == 1999), stat="identity", position ="stack",
aes(x = year, y = c)) +
geom_bar(data = df %>% filter(year == 2000), stat="identity", position ="stack",
aes(x = year, y = c)) +
geom_bar(data = df %>% filter(year == 2001), stat="identity", position ="stack",
aes(x = year, y = c)) +
geom_bar(data = df %>% filter(year == 2002), stat="identity", position ="stack",
aes(x = year, y = c)) +
labs(y = "year",
x = "c",
title = "Reprex") +
geom_smooth(df, mapping = aes(x = year, y = c), method = "lm",
colour = "black", se = F)
)

#Animation
library(gganimate)
anim <- static_plot +
transition_layers(layer_length = 1, transition_length = 1) +
enter_drift(x_mod = 0, y_mod = -max(df$c))

animate(anim, fps = 10, duration = 10,
width = 600, height = 500, renderer =
gifski_renderer())

anim

Hi @willrm. Your question are little complicate. I found out a way to do it. First you need to get the linear model fitting value in your data frame, not calculate by geom_smooth. Then, fit in each frame of animation that you want to show one by one like what you have done. Hope it can help.

library(ggplot2)
library(tidyverse)
library(gganimate)

year <- as.numeric(c(1996:2002,
                     1996:2002,
                     1996:2002))
c <- c(39, 40, 67, 80, 30, 140, 90, 23, 100, 123,
       140, 1, 2, 1, 13, 3, 3, 30, 1, 3, 3)
df <- data.frame(year, c) %>%
  select(year, c) %>%
  arrange(year)

df <- df %>%
  group_by(year) %>%
  summarise(c = sum(c)) %>%
  {mutate(., lm = predict(lm(c ~ year, df), newdata = distinct(df, year)))}
  
anim <- ggplot(data = df) +
    geom_bar(data = df %>% filter(year == 1996), stat="identity", position ="stack",
             aes(x = year, y = c)) +
    geom_bar(data = df %>% filter(year == 1997), stat="identity", position ="stack",
             aes(x = year, y = c)) +
    geom_line(filter(df, year %in% c(1996, 1997)), mapping = aes(x = year, y = lm),
                colour = "black") +
    geom_bar(data = df %>% filter(year == 1998), stat="identity", position ="stack",
             aes(x = year, y = c)) +
    geom_line(filter(df, year %in% c(1997, 1998)), mapping = aes(x = year, y = lm),
              colour = "black") +
    geom_bar(data = df %>% filter(year == 1999), stat="identity", position ="stack",
             aes(x = year, y = c)) +
    geom_line(filter(df, year %in% c(1998, 1999)), mapping = aes(x = year, y = lm),
              colour = "black") +
    geom_bar(data = df %>% filter(year == 2000), stat="identity", position ="stack",
             aes(x = year, y = c)) +
    geom_line(filter(df, year %in% c(1999, 2000)), mapping = aes(x = year, y = lm),
              colour = "black") +
    geom_bar(data = df %>% filter(year == 2001), stat="identity", position ="stack",
             aes(x = year, y = c)) +
    geom_line(filter(df, year %in% c(2000, 2001)), mapping = aes(x = year, y = lm),
              colour = "black") +
    geom_bar(data = df %>% filter(year == 2002), stat="identity", position ="stack",
             aes(x = year, y = c)) +
    geom_line(filter(df, year %in% c(2001, 2002)), mapping = aes(x = year, y = lm),
              colour = "black") +
    labs(y = "year",
         x = "c",
         title = "Reprex") +
    transition_layers(layer_length = 1, transition_length = 1) +
    enter_drift(x_mod = 0, y_mod = -max(df$c))
  
  animate(anim, fps = 10, duration = 10,
          width = 600, height = 500, renderer =
            gifski_renderer())

Created on 2019-09-27 by the reprex package (v0.3.0)

2 Likes

Thanks a lot for your help! I really appreciate it. Do you know if it would be possible to make the regression line appear more smoothly, like the example I linked to in the question?

Below is another approach that shortens the code considerably by calling the geoms only once. The regression line is also smoothly drawn along with the bars. I've worked from @raytong's example.

Here are a couple of questions to make sure the animation is tailored to your needs:

  1. Do you actually want only the final regression line to be revealed in each step (which is how your example works), or do you want the regression line from the beginning of the series until the current year? Revealing a piece of the final regression line at each step is potentially confusing, since the regression line for the full series and the regression line from year=1996 to year = x (where x is some future year), will in general be quite different.
  2. Do you want the regression line to be based on the individual c values for each year, or the sum of the c values for each year? In the current version, the bars are the sum of the c values, but the regression line is calculated from the individual c values, which, it seems to me, is misleading. Either the c values should be plotted as points, rather than a single bar, or the regression line should be based on the sums (although, even there, points would probably be better than bars).

Also, can you provide the code used to create the example gif in your question? It would be helpful to see how that animation was created.

df <- df %>%
  group_by(year) %>%
  summarise(c = sum(c)) %>%
  mutate(lm = predict(lm(c ~ year, df), newdata = distinct(df, year)))

anim = ggplot(df, aes(year, c)) +
  geom_col(fill="grey70") +
  geom_segment(aes(x=year[1], xend=year, y=lm[1], yend=lm)) +
  theme_bw() +
  transition_states(year) +
  shadow_mark() 

animate(anim, fps = 10, duration = 10,
        width = 600, height = 500, renderer = gifski_renderer())

filec87f3b0ed641

3 Likes

This topic was automatically closed 21 days after the last reply. New replies are no longer allowed.