Tweenr/gganimate with line plot

hi all,

first I created an animated plot for a very simple time series line plot... Some example data:

library(dplyr)
library(gganimate)
library(tweenr)

data <- tribble(
  ~year, ~num,
  1950, 56,
  1951, 59,
  1952, 64,
  1953, 67,
  1954, 69,
  1955, 74,
  1956, 78,
  1957, 83
)

dat_ani <- data %>% 
  ggplot(aes(x = year, y = num, frame = year, cumulative = TRUE)) +
  geom_path() +
  geom_point()

gganimate(dat_ani)

so, just using gganimate is fine but I want to go a bit further by using tweenr for smoother transitions.

Knowing how tweenr uses interpolation I want a data frame similar to above but looks closer to this (just looking at in between 1950 and 1951):

data2 <- tribble(
  ~year, ~num,
  1950.0, 56.0,
  1950.1, 56.3,
  1950.2, 56.6,
  1950.3, 56.9,
  1950.4, 57.2,
  1950.5, 57.5,
  1950.6, 57.8,
  1950.7, 58.1,
  1950.8, 58.4,
  1950.9, 58.7,
  1951.0, 59.0,
)

but with all the other interpolated years and data points. I tried another way with tween_elements() function but that didn't work (I think because x, time, and id were all the same variable...).

I am stumped on how to create a list of these data frames to use as the input for the tween_states() function.
I've tried:

df_fun <- function(i) {
  dat <- data %>% subset(data$year[i])
  return(dat)
}

df_list <- purrr::map(seq(1950, 1957, by = 0.2), df_fun)

tween_data <- tween_states(df_list, tweenlength = 2, statelength = 3, 
                                             ease = "linear", nframes = 200)

among other attempts but of course this doesn't work as there is no year == 1950.2, 1950.4, etc. in the original data frame...

any help would be appreciated!

3 Likes

Not a direct help, but http://lenkiefer.com/ has a tremendous set of posts using tweenr in imaginative ways.

3 Likes

yeah, i've been using looking at his blog posts, some of my "attempts" above were based off his code.

thanks though! :slight_smile:

EDIT:
some examples for anybody else interested:

I'm not sure I quite understand what you want to end up with. If you have a time series plot of a single variable, and you're using the cumulative option in gganimate to reveal it along the x-axis, what would there be to tween other than opacity? Are you able to give me maybe a sketch of what you want to achieve?

I think I've figured out what you're after:

library(tidyverse)

data <- tribble(
  ~year, ~num,
  1950, 56,
  1951, 59,
  1952, 64,
  1953, 67,
  1954, 69,
  1955, 74,
  1956, 78,
  1957, 83
)

dat_ani <- map(seq(nrow(data)), ~data[c(seq(.x), rep(.x, nrow(data) - .x)), ]) %>% 
    tweenr::tween_states(5, 2, 'cubic-in-out', 100) %>% 
    ggplot(aes(year, num, frame = .frame)) + 
    geom_path() + 
    geom_point()

animation::ani.options(interval = 0.05)    # speed it up
gganimate::gganimate(dat_ani, title_frame = FALSE)

dat

The map call makes a list with a data frame for each row, of the same number of observations as data, but with the rows yet to be displayed replaced with the last displayed row. Now tweenr::tween_states can track where each point is supposed to be moving, which lets it interpolate smoothly.

There may well be a more efficient way to do this; I'm far from Len Kiefer status when it comes to ggplot GIFs.

7 Likes

COOOOOOOOOOOOOOOOOOOOL

... I know what I meant :expressionless:

1 Like

great! yes, this was exactly what i was looking for, thanks!

Would using split be better?

dat_ani <- data %>%
 split(.$year) %>%  
 tweenr::tween_states(5, 2, 'cubic-in-out', 100) %>%  
 ggplot(aes(year, num, frame = .frame)) + 
  geom_path() + 
  geom_point()

That would only put one observation in each data frame, so it just moves a point between them without drawing a line (as with one observation, there aren't two points between which to draw a line):

tweenr2

It could be a useful approach for tracing a line or animating particles, but doesn't give a sense of the full line.

You could add cumulative = TRUE back, which adds the line back, but unfortunately also a lot of intermediate points:

tweenr3

If this is a relatively frequent approach, it wouldn't be that hard to wrap the splitting and tweening into a function. If well-abstracted and presented with a solid case for its existence, it could even make a nice PR.

1 Like

This seems to be having issue with ggnanimate old version. I could not reproduce the same. The animate command version of code would be better.

With the new gganimate, you can create a similar effect very quickly with transition_reveal:

library(ggplot2)
library(gganimate)

data <- tibble::tribble(
  ~year, ~num,
  1950, 56,
  1951, 59,
  1952, 64,
  1953, 67,
  1954, 69,
  1955, 74,
  1956, 78,
  1957, 83
)

p <- ggplot(data, aes(year, num)) + 
    geom_line() + 
    geom_point() + 
    transition_reveal(year) + 
    ease_aes('cubic-in-out')

animate(p, fps = 20)

animated line and point plot

It seems like shadow_mark should be sufficient to leave points behind, but it isn't. The whole thing could be reconstructed by reshaping the data and using transition_states as above, or using transition_manual. Both are a little more work.

3 Likes