gganimate和(有时)空面的问题 [英] Issue with gganimate and (sometimes) empty facets

查看:36
本文介绍了gganimate和(有时)空面的问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我观察到我无法解释的 gganimate 中的某些行为,我想了解自己做错了什么(或者它是否是错误).

例如,这是一个非常简单的数据集及其图:

  library(dplyr)#dplyr_0.7.8库(tidyr)#tidyr_0.8.2交叉(p = 1:2,t = seq(0,1,len = 30),s = c(0,.5))%>%变异(x = t,y = t ^ p)%>%过滤器(t> s)->ž库(ggplot2)#ggplot2_3.1.0Z%>%ggplot(aes(x,y))+facet_wrap(〜s)+geom_point() 

正如预期的那样,第二个方面(s = 0.5)仅具有x> 0.5的数据,该数据(通过构造小节Z的方式)来自t> 0.5.

如果要对上述数据进行动画处理(使用 t 作为时间),我希望动画的前半部分的第二个小面为空,然后显示与第一个小面相同的内容下半年.但是:

 库(gganimate)#gganimate_1.0.2Z%>%ggplot(aes(x,y,group = interact(p,s)))+facet_wrap(〜s)+geom_point()+transition_time(t)+ggtitle('{frame_time}') 

上面的代码生成了一个动画(使用 gifski_0.8.6 ),该动画具有两个小平面,其中第二个小平面仅简短地显示了其点,并且还在错误的时间(即在开始时)显示了它们的点.动画).

我错过了什么吗,或者这是一个错误吗?

解决方案

这将是一个很长的3部分的答案.您可以从此处开始进行解释,或向下滚动以获取两个建议的解决方法.

说明

这似乎是 transition_time 的问题,当它以空面开头时,它的行为很奇怪.

在调试了基础代码之后,我发现问题出在

解决方法2

定义全新的ggproto对象可能会过大,坦率地说,我对gganimate软件包的了解还不够多,因此无法确定这样做没有破坏其他任何事情.

作为一种破坏性较小的替代方法,我们可以简单地对数据帧进行预处理,以为每个构面(以及其他任何感兴趣的分组变量)包含相同范围的时间值,并使新行不可见:

  Z%>%mutate(alpha = 1)%>%tidyr :: complete(t,s,p,fill = list(alpha = 0))%>%group_by(s,p)%&%安排(t)%tidyr :: fill(x,y,.direction ="up")%>%ungroup()%&%;%ggplot(aes(x,y,group =交互作用(p,s),alpha = alpha))+geom_point()+facet_wrap(〜s)+scale_alpha_identity()+transition_time(t)+ggtitle('{frame_time}') 

I'm observing some behavior in gganimate that I cannot explain, and I would like to understand what I am doing wrong (or whether it is a bug).

For example, here is a very simple dataset and a plot of it:

library(dplyr) # dplyr_0.7.8
library(tidyr) # tidyr_0.8.2 

crossing(p = 1:2, 
         t = seq(0, 1, len = 30),
         s = c(0, .5)) %>%
  mutate(x = t,
         y = t^p) %>%
  filter(t > s) ->
  Z

library(ggplot2) # ggplot2_3.1.0

Z %>%
  ggplot(aes(x,y)) +
  facet_wrap(~s) +
  geom_point()

As expected the second facet (s=0.5) only has data for x > 0.5, which (from how the tibble Z is constructed) comes from t > 0.5.

If one were to animate the above data (using t as time) I would expect the second facet to be empty for the first half of the animation, and then show the same as the first facet for the second half. However:

library(gganimate) # gganimate_1.0.2
Z %>%
  ggplot(aes(x, y, group = interaction(p,s))) +
  facet_wrap(~s) +
  geom_point() +
  transition_time(t) +
  ggtitle('{frame_time}')

The above code generates an animation (using gifski_0.8.6) with two facets where the second facet only shows its points briefly, and furthermore shows them at the wrong time (namely at the start of the animation).

Am I missing something, or is this a bug?

解决方案

This is going to be a rather long answer in 3 parts. You can start here for the explanation, or scroll down for two proposed workarounds.

Explanation

This does appear to be an issue with transition_time, which acts weird when it starts with an empty facet.

After debug through the underlying code, I figure the problem lies with the expand_panel function under TransitionTime. We can demonstrate this by running debug(environment(TransitionTime$expand_panel)) before plotting the animation in question. Look out for what happens before & after lines A-B in the debugged code below:

> TransitionTime$expand_panel
<ggproto method>
  <Wrapper function>
    function (...) 
f(..., self = self)

  <Inner function (f)>
    function (self, data, type, id, match, ease, enter, exit, params, 
    layer_index) 
{
    ... # omitted

    true_frame <- seq(times[1], times[length(times)])

    # line A
    all_frames <- all_frames[
      all_frames$.frame %in% which(true_frame > 0 & true_frame <= params$nframes), 
      , 
      drop = FALSE]

    # line B
    all_frames$.frame <- all_frames$.frame - min(all_frames$.frame) + 1

    ... # omitted
}

Within each facet panel, all_frames is a data frame that holds the raw data rows corresponding to that specific facet, as well as additional rows transiting between them. true_frame is a vector of integers for valid frames during which the data should show up.

For the first panel (i.e. where s = 0), this is what we have before line A:

> true_frame
  [1]   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22
 [23]  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44
 [45]  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66
 [67]  67  68  69  70  71  72  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88
 [89]  89  90  91  92  93  94  95  96  97  98  99 100

> head(all_frames)
            x           y group PANEL shape    colour size fill alpha stroke .id     .phase .frame
1  0.03448276 0.034482759     1     1    19     black  1.5   NA    NA    0.5   1        raw      1
45 0.03448276 0.001189061     2     1    19     black  1.5   NA    NA    0.5   2        raw      1
3  0.04310345 0.043103448     1     1    19 #000000FF  1.5   NA    NA    0.5   1 transition      2
4  0.04310345 0.002080856     2     1    19 #000000FF  1.5   NA    NA    0.5   2 transition      2
5  0.05172414 0.051724138     1     1    19 #000000FF  1.5   NA    NA    0.5   1 transition      3
6  0.05172414 0.002972652     2     1    19 #000000FF  1.5   NA    NA    0.5   2 transition      3

> tail(all_frames)
            x         y group PANEL shape    colour size fill alpha stroke .id     .phase .frame
530 0.9827586 0.9827586     1     1    19 #000000FF  1.5   NA    NA    0.5   1 transition     98
629 0.9827586 0.9661118     2     1    19 #000000FF  1.5   NA    NA    0.5   2 transition     98
716 0.9913793 0.9913793     1     1    19 #000000FF  1.5   NA    NA    0.5   1 transition     99
816 0.9913793 0.9830559     2     1    19 #000000FF  1.5   NA    NA    0.5   2 transition     99
434 1.0000000 1.0000000     1     1    19     black  1.5   NA    NA    0.5   1        raw    100
871 1.0000000 1.0000000     2     1    19     black  1.5   NA    NA    0.5   2        raw    100

all_frames is unchanged after lines A-B, so I won't repeat the console printouts again.

For the second panel (i.e. s = 0.5), on the other hand, lines A-B made a significant difference. Here's what we have before line A:

> true_frame
 [1]  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72  73
[25]  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94  95  96  97
[49]  98  99 100

> head(all_frames)
           x         y group PANEL shape    colour size fill alpha stroke .id     .phase .frame
16 0.5172414 0.5172414     3     2    19     black  1.5   NA    NA    0.5  NA        raw     49
60 0.5172414 0.2675386     4     2    19     black  1.5   NA    NA    0.5  NA        raw     49
3  0.5241379 0.5241379     3     2    19 #000000FF  1.5   NA    NA    0.5   3 transition     50
4  0.5241379 0.2749108     4     2    19 #000000FF  1.5   NA    NA    0.5   4 transition     50
5  0.5310345 0.5310345     3     2    19 #000000FF  1.5   NA    NA    0.5   3 transition     51
6  0.5310345 0.2822830     4     2    19 #000000FF  1.5   NA    NA    0.5   4 transition     51

> tail(all_frames)
            x         y group PANEL shape    colour size fill alpha stroke .id     .phase .frame
513 0.9827586 0.9827586     3     2    19 #000000FF  1.5   NA    NA    0.5   3 transition     98
617 0.9827586 0.9661118     4     2    19 #000000FF  1.5   NA    NA    0.5   4 transition     98
710 0.9913793 0.9913793     3     2    19 #000000FF  1.5   NA    NA    0.5   3 transition     99
87  0.9913793 0.9830559     4     2    19 #000000FF  1.5   NA    NA    0.5   4 transition     99
441 1.0000000 1.0000000     3     2    19     black  1.5   NA    NA    0.5   3        raw    100
88  1.0000000 1.0000000     4     2    19     black  1.5   NA    NA    0.5   4        raw    100

true_frames covers the range 50-100, while the frame numbers in all_frames start from 49. Fine, close enough, we can subset the data frame for frames that match those in true_frames & drop the rows with .frame < 50, but that isn't what happens in line A. Observe:

> true_frame > 0 & true_frame <= params$nframes # all TRUE
 [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
[20] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
[39] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE

> which(true_frame > 0 & true_frame <= params$nframes) 
# values start from 1, rather than 1st frame number
 [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
[33] 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

> all_frames$.frame %in% which(true_frame > 0 & true_frame <= params$nframes) 
# only the first few frames match the last few values!
  [1]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
 [16] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
 [31] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
 [46] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
 [61] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
 [76] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
 [91] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE

> all_frames
# consequently, only the first few frames are left after the subsetting
           x         y group PANEL shape    colour size fill alpha stroke .id     .phase .frame
16 0.5172414 0.5172414     3     2    19     black  1.5   NA    NA    0.5  NA        raw     49
60 0.5172414 0.2675386     4     2    19     black  1.5   NA    NA    0.5  NA        raw     49
3  0.5241379 0.5241379     3     2    19 #000000FF  1.5   NA    NA    0.5   3 transition     50
4  0.5241379 0.2749108     4     2    19 #000000FF  1.5   NA    NA    0.5   4 transition     50
5  0.5310345 0.5310345     3     2    19 #000000FF  1.5   NA    NA    0.5   3 transition     51
6  0.5310345 0.2822830     4     2    19 #000000FF  1.5   NA    NA    0.5   4 transition     51

We now come to line B (all_frames$.frame <- all_frames$.frame - min(all_frames$.frame) + 1), which essentially re-centers the frames to start from 1. As a result, this is what we get after line B:

> all_frames
           x         y group PANEL shape    colour size fill alpha stroke .id     .phase .frame
16 0.5172414 0.5172414     3     2    19     black  1.5   NA    NA    0.5  NA        raw      1
60 0.5172414 0.2675386     4     2    19     black  1.5   NA    NA    0.5  NA        raw      1
3  0.5241379 0.5241379     3     2    19 #000000FF  1.5   NA    NA    0.5   3 transition      2
4  0.5241379 0.2749108     4     2    19 #000000FF  1.5   NA    NA    0.5   4 transition      2
5  0.5310345 0.5310345     3     2    19 #000000FF  1.5   NA    NA    0.5   3 transition      3
6  0.5310345 0.2822830     4     2    19 #000000FF  1.5   NA    NA    0.5   4 transition      3

There you have it: due to lines A-B in expand_panel, we get the phenomenon described in the question: animation in the second panel starts from frame 1, & only lasts a measly 3 frames before disappearing all together.

Workaround 1

Since we know what's causing the problem, we can tweak the code for expand_panel, and define a slightly different version of transition_time that uses it instead:

library(tweenr)

TransitionTime2 <- ggproto(
  "TransitionTime2",
  TransitionTime,
  expand_panel = function (self, data, type, id, match, ease, enter, exit, params, 
                           layer_index) {
    row_time <- self$get_row_vars(data)
    if (is.null(row_time)) 
      return(data)
    data$group <- paste0(row_time$before, row_time$after)
    time <- as.integer(row_time$time)
    states <- split(data, time)
    times <- as.integer(names(states))
    nframes <- diff(times)
    nframes[1] <- nframes[1] + 1
    if (times[1] <= 1) {
      all_frames <- states[[1]]
      states <- states[-1]
    }
    else {
      all_frames <- data[0, , drop = FALSE]
      nframes <- c(times[1] - 1, nframes)
    }
    if (times[length(times)] < params$nframes) {
      states <- c(states, list(data[0, , drop = FALSE]))
      nframes <- c(nframes, params$nframes - times[length(times)])
    }
    for (i in seq_along(states)) {
      all_frames <- switch(type, point = tween_state(all_frames, 
                                                     states[[i]], ease, nframes[i], 
                                                     !!id, enter, exit), 
                           path = transform_path(all_frames, 
                                                 states[[i]], ease, nframes[i], 
                                                 !!id, enter, exit, match), 
                           polygon = transform_polygon(all_frames, 
                                                       states[[i]], ease, nframes[i], 
                                                       !!id, enter, exit, match), 
                           sf = transform_sf(all_frames, 
                                             states[[i]], ease, nframes[i], 
                                             !!id, enter, exit), 
                           stop(type, 
                                " layers not currently supported by transition_time", 
                                call. = FALSE))
    }
    true_frame <- seq(times[1], times[length(times)])
    all_frames <- all_frames[
      all_frames$.frame %in% 
        # which(true_frame > 0 & true_frame <= params$nframes),
        true_frame[which(true_frame > 0 & true_frame <= params$nframes)], # tweak line A
      , 
      drop = FALSE]
    # all_frames$.frame <- all_frames$.frame - min(all_frames$.frame) + 1 # remove line B
    all_frames$group <- paste0(all_frames$group, "<", all_frames$.frame, ">")
    all_frames$.frame <- NULL
    all_frames
  })

transition_time2 <- function (time, range = NULL) {
  time_quo <- enquo(time)
  gganimate:::require_quo(time_quo, "time")
  ggproto(NULL, TransitionTime2, 
          params = list(time_quo = time_quo, range = range))
}

Result:

Z %>%
  ggplot(aes(x, y, group = interaction(p,s))) +
  geom_point() +
  facet_wrap(~s) +
  transition_time2(t) +
  ggtitle('{frame_time}')

Workaround 2

Defining entirely new ggproto objects may be overkill, and frankly I don't know enough about the gganimate package to know for certain that doing so hasn't broken anything else down the line.

As a less disruptive alternative, we can simply pre-process the data frame to include the same range of time values for each facet (as well as any other grouping variable of interest), and make the new rows invisible instead:

Z %>% 
  mutate(alpha = 1) %>%
  tidyr::complete(t, s, p, fill = list(alpha = 0)) %>%
  group_by(s, p) %>%
  arrange(t) %>%
  tidyr::fill(x, y, .direction = "up") %>%
  ungroup() %>%

  ggplot(aes(x, y, group = interaction(p, s), alpha = alpha)) +
  geom_point() +
  facet_wrap(~ s) +
  scale_alpha_identity() +
  transition_time(t) +
  ggtitle('{frame_time}')

这篇关于gganimate和(有时)空面的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆