可以在 gganimate 中使用滚动 window 过滤器吗?

Can you have a rolling window filter in gganimate?

我希望散点图的每一帧都被另一个具有一定 bin 宽度的向量过滤,并让它在这些向量中滚动。例如,我可以通过以下方式做到这一点:

library(ggplot2)
library(gganimate)

#example data
iris <- datasets::iris

#plot x and y
g <- ggplot(iris) + geom_point(aes(x = Petal.Width,y = Petal.Length))

#filter x and y by a third value with a bin width of 2 steping through by 0.5
g + transition_filter(transition_length = 1,
                      filter_length = 1,
                      4 < Sepal.Length & Sepal.Length < 6,
                      4.5 < Sepal.Length & Sepal.Length < 6.5,
                      5 < Sepal.Length & Sepal.Length < 7,
                      5.5 < Sepal.Length & Sepal.Length < 7.5,
                      6 < Sepal.Length & Sepal.Length < 8)

但是 - 写出每个过滤条件是乏味的,我想用 ~20 binwidth 过滤不同的数据集,在 300 点范围内步进 1,所以写 100+ 过滤器是不切实际的。

还有其他方法吗?

不久前我想要这个确切的功能,但实际上在 gganimate 中没有看到任何东西可以实现,所以我写了一些可以完成工作的东西。以下是我想出的,所以我最终重建 gganimate 并包含此功能以避免使用 :::.

我不久前写了这篇文章,所以我不记得在写它时每个论点的确切意图(永远记得记录你的代码)。

这是我记得的

  • span : 可以在数据层内计算的表达式
  • size : 一次显示多少数据
  • enter_length/exit_length :不完全记得它是如何相互关联或 size/span
  • range : 子集范围
  • retain_data_order:合乎逻辑 - 不记得为什么会出现在这里(抱歉!)
library(gganimate)
#> Loading required package: ggplot2
library(rlang)
library(tweenr)
library(stringi)

get_row_event <- gganimate:::get_row_event
is_placeholder <- gganimate:::is_placeholder
recast_event_times <- gganimate:::recast_event_times
recast_times <- gganimate:::recast_times
TransitionSpan <- ggplot2::ggproto('TransitionSpan',
                                   TransitionEvents,
                                   finish_data = function (self, data, params)
                                   {
                                     lapply(data, function(d) {
                                       split_panel <- stri_match(d$group, regex = "^(.+)<(.*)>(.*)$")
                                       if (is.na(split_panel[1]))
                                         return(list(d))
                                       d$group <- match(d$group, unique(d$group))
                                       empty_d <- d[0, , drop = FALSE]
                                       d <- split(d, as.integer(split_panel[, 3]))
                                       frames <- rep(list(empty_d), params$nframes)
                                       frames[as.integer(names(d))] <- d
                                       frames
                                     })
                                   },
                                   setup_params = function(self, data, params) {
                                     # browser()
                                     params$start <- get_row_event(data, params$span_quo, "start")
                                     time_class <- if (is_placeholder(params$start))
                                       NULL
                                     else params$start$class
                                     end_quo <- expr(!!params$span_quo + diff(range(!!params$span_quo))*!!params$size_quo)
                                     params$end <- get_row_event(data, end_quo, "end",
                                                                 time_class)
                                     params$enter_length <- get_row_event(data, params$enter_length_quo,
                                                                          "enter_length", time_class)
                                     params$exit_length <- get_row_event(data, params$exit_length_quo,
                                                                         "exit_length", time_class)
                                     params$require_stat <- is_placeholder(params$start) || is_placeholder(params$end) ||
                                       is_placeholder(params$enter_length) || is_placeholder(params$exit_length)
                                     static = lengths(params$start$values) == 0
                                     params$row_id <- Map(function(st, end, en, ex, s) if (s)
                                       character(0)
                                       else paste(st, end, en, ex, sep = "_"), st = params$start$values,
                                       end = params$end$values, en = params$enter_length$values,
                                       ex = params$exit_length$values, s = static)
                                     params
                                   },
                                   setup_params2 = function(self, data, params, row_vars) {
                                     late_start <- FALSE
                                     if (is_placeholder(params$start)) {
                                       params$start <- get_row_event(data, params$start_quo, 'start', after = TRUE)
                                       late_start <- TRUE
                                     } else {
                                       params$start$values <- lapply(row_vars$start, as.numeric)
                                     }
                                     size <- expr(!!params$size_quo)
                                     
                                     time_class <- params$start$class
                                     if (is_placeholder(params$end)) {
                                       params$end <- get_row_event(data, params$end_quo, 'end', time_class, after = TRUE)
                                     } else {
                                       params$end$values <- lapply(row_vars$end, as.numeric)
                                     }
                                     if (is_placeholder(params$enter_length)) {
                                       params$enter_length <- get_row_event(data, params$enter_length_quo, 'enter_length', time_class, after = TRUE)
                                     } else {
                                       params$enter_length$values <- lapply(row_vars$enter_length, as.numeric)
                                     }
                                     if (is_placeholder(params$exit_length)) {
                                       params$exit_length <- get_row_event(data, params$exit_length_quo, 'exit_length', time_class, after = TRUE)
                                     } else {
                                       params$exit_length$values <- lapply(row_vars$exit_length, as.numeric)
                                     }
                                     times <- recast_event_times(params$start, params$end, params$enter_length, params$exit_length)
                                     params$span_size <- diff(times$start$range)*eval_tidy(size)
                                     
                                     
                                     range <- if (is.null(params$range)) {
                                       low <- min(unlist(Map(function(start, enter) {
                                         start - (if (length(enter) == 0) 0 else enter)
                                       }, start = times$start$values, enter = times$enter_length$values)))
                                       high <- max(unlist(Map(function(start, end, exit) {
                                         (if (length(end) == 0) start else end) + (if (length(exit) == 0) 0 else exit)
                                       }, start = times$start$values, end = times$end$values, exit = times$exit_length$values)))
                                       range  <- c(low, high)
                                     } else {
                                       if (!inherits(params$range, time_class)) {
                                         stop('range must be given in the same class as time', call. = FALSE)
                                       }
                                       as.numeric(params$range)
                                     }
                                     full_length <- diff(range)
                                     frame_time <- recast_times(
                                       seq(range[1], range[2], length.out = params$nframes),
                                       time_class
                                     )
                                     
                                     frame_length <- full_length / params$nframes
                                     rep_frame <- round(params$span_size/frame_length)
                                     lowerl <- c(rep(frame_time[1],rep_frame), frame_time[2:(params$nframes-rep_frame+1)])
                                     upperl <- c(frame_time[1:(params$nframes-rep_frame)], rep(frame_time[params$nframes-rep_frame+1], rep_frame))
                                     start <- lapply(times$start$values, function(x) {
                                       round((params$nframes - 1) * (x - range[1])/full_length) + 1
                                     })
                                     end <- lapply(times$end$values, function(x) {
                                       if (length(x) == 0) return(numeric())
                                       round((params$nframes - 1) * (x - range[1])/full_length) + 1
                                     })
                                     enter_length <- lapply(times$enter_length$values, function(x) {
                                       if (length(x) == 0) return(numeric())
                                       round(x / frame_length)
                                     })
                                     exit_length <- lapply(times$exit_length$values, function(x) {
                                       if (length(x) == 0) return(numeric())
                                       round(x / frame_length)
                                     })
                                     
                                     params$range <- range
                                     params$frame_time <- frame_time
                                     static = lengths(start) == 0
                                     params$row_id <- Map(function(st, end, en, ex, s) if (s) character(0) else paste(st, end, en, ex, sep = '_'),
                                                          st = start, end = end, en = enter_length, ex = exit_length, s = static)
                                     params$lowerl <- lowerl
                                     params$upperl <- upperl
                                     params$frame_span <- upperl - lowerl
                                     params$frame_info <- data.frame(
                                       frame_time = frame_time,
                                       lowerl = lowerl,
                                       upperl = upperl,
                                       frame_span = upperl - lowerl
                                     )
                                     params$nframes <- nrow(params$frame_info)
                                     params
                                   },
                                   expand_panel = function(self, data, type, id, match, ease, enter, exit, params, layer_index) {
                                     #browser()
                                     row_vars <- self$get_row_vars(data)
                                     if (is.null(row_vars))
                                       return(data)
                                     data$group <- paste0(row_vars$before, row_vars$after)
                                     start <- as.numeric(row_vars$start)
                                     end <- as.numeric(row_vars$end)
                                     if (is.na(end[1]))
                                       end <- NULL
                                     enter_length <- as.numeric(row_vars$enter_length)
                                     if (is.na(enter_length[1]))
                                       enter_length <- NULL
                                     exit_length <- as.numeric(row_vars$exit_length)
                                     if (is.na(exit_length[1]))
                                       exit_length <- NULL
                                     data$.start <- start
                                     all_frames <- tween_events(data, c(ease,"linear"),
                                                                params$nframes, !!start, !!end, c(1, params$nframes),
                                                                enter, exit, !!enter_length, !!exit_length)
                                     if(params$retain_data_order){
                                       all_frames <- all_frames[order(as.numeric(all_frames$.id)),]
                                     } else {
                                       all_frames <- all_frames[order(all_frames$.start, as.numeric(all_frames$.id)),]
                                     }
                                     all_frames$group <- paste0(all_frames$group, '<', all_frames$.frame, '>')
                                     all_frames$.frame <- NULL
                                     all_frames$.start <- NULL
                                     all_frames
                                   })
transition_span <- function(span, size = 0.5, enter_length = NULL, exit_length = NULL, range = NULL, retain_data_order = T){
  
  span_quo <- enquo(span)
  size_quo <- enquo(size)
  enter_length_quo <- enquo(enter_length)
  exit_length_quo <- enquo(exit_length)
  gganimate:::require_quo(span_quo, "span")
  ggproto(NULL, TransitionSpan,
          params = list(span_quo = span_quo,
                        size_quo = size_quo, range = range, enter_length_quo = enter_length_quo,
                        exit_length_quo = exit_length_quo,
                        retain_data_order = retain_data_order))
  
}
g <- ggplot(iris) + 
  geom_point(aes(x = Petal.Width,y = Petal.Length, color = Sepal.Length)) +
  viridis::scale_color_viridis()
a <- g + transition_span(Sepal.Length, .1, 1, 1)
animate(a, renderer = gganimate::gifski_renderer())

reprex package (v2.0.0)

于 2021-08-11 创建