ban*_*nbh 1 r ggplot2 gganimate
我正在观察一些gganimate我无法解释的行为,我想了解我做错了什么(或者它是否是一个错误)。
例如,这是一个非常简单的数据集及其绘图:
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()
Run Code Online (Sandbox Code Playgroud)
正如预期的那样,第二个方面 (s=0.5) 仅具有 x > 0.5 的数据,其中(根据 tibble Z 的构造方式)来自 t > 0.5。
如果要为上述数据设置动画(t用作时间),我希望动画的前半部分的第二个方面为空,然后在后半部分显示与第一个方面相同的方面。然而:
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}')
Run Code Online (Sandbox Code Playgroud)
上面的代码生成一个gifski_0.8.6带有两个面的动画(使用),其中第二个面仅简要显示其点,并且在错误的时间(即动画开始时)显示它们。
我错过了什么,还是这是一个错误?
这将是一个相当长的答案,分为 3 部分。您可以从这里开始进行解释,或者向下滚动查看两个建议的解决方法。
这似乎是 的一个问题transition_time,当它以一个空的方面开始时,它的行为很奇怪。
通过底层代码调试后,我认为问题出expand_panel在TransitionTime下的函数上。我们可以通过debug(environment(TransitionTime$expand_panel))在绘制相关动画之前运行来证明这一点。在下面的调试代码中注意 AB 行之前和之后发生的情况:
> 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
}
Run Code Online (Sandbox Code Playgroud)
在每个 facet 面板中,all_frames都有一个数据框,其中包含与该特定 facet 对应的原始数据行,以及在它们之间传输的其他行。true_frame是数据应显示的有效帧的整数向量。
对于第一个面板(即 s = 0),这是我们在 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
Run Code Online (Sandbox Code Playgroud)
all_frames 在 AB 行之后没有改变,所以我不会再次重复控制台打印输出。
另一方面,对于第二个面板(即 s = 0.5),AB 线产生了显着差异。这是我们在 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
Run Code Online (Sandbox Code Playgroud)
true_frames覆盖范围 50-100,而帧数all_frames从 49 开始。很好,足够接近,我们可以为与 中的帧匹配的帧的数据帧子集true_frames并使用 删除行.frame < 50,但这不是行 A 中发生的情况。观察:
> 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
Run Code Online (Sandbox Code Playgroud)
我们现在来到 B ( all_frames$.frame <- all_frames$.frame - min(all_frames$.frame) + 1) 行,它基本上将帧重新居中以从 1 开始。因此,这就是我们在 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
Run Code Online (Sandbox Code Playgroud)
你有它:由于行 AB in expand_panel,我们得到了问题中描述的现象:第二个面板中的动画从第 1 帧开始,在消失之前只持续了 3 帧。
由于我们知道是什么导致了问题,我们可以调整 的代码expand_panel,并定义一个稍微不同的版本transition_time来使用它:
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))
}
Run Code Online (Sandbox Code Playgroud)
结果:
Z %>%
ggplot(aes(x, y, group = interaction(p,s))) +
geom_point() +
facet_wrap(~s) +
transition_time2(t) +
ggtitle('{frame_time}')
Run Code Online (Sandbox Code Playgroud)
定义全新的 ggproto 对象可能有点矫枉过正,坦率地说,我对 gganimate 包的了解不够,无法确定这样做并没有破坏其他任何东西。
作为一种破坏性较小的替代方案,我们可以简单地对数据帧进行预处理,以包含每个方面(以及任何其他感兴趣的分组变量)的相同时间值范围,并使新行不可见:
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}')
Run Code Online (Sandbox Code Playgroud)