Это будет довольно длинный ответ в 3 частях. Вы можете начать здесь с объяснения или прокрутить вниз для двух предложенных обходных путей.
Объяснение
Это похоже на проблему с transition_time
, которая ведет себя странно, когда начинается с пустого фасета.
После отладки базового кода, я полагаю, что проблема заключается в функции expand_panel
в TransitionTime . Мы можем продемонстрировать это, запустив debug(environment(TransitionTime$expand_panel))
перед построением анимации. Посмотрите, что происходит до и после строк A-B в отлаженном коде ниже:
> 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
}
Внутри каждой панели фасетов all_frames
- это фрейм данных, который содержит строки необработанных данных, соответствующие этому конкретному фасету, а также дополнительные строки, проходящие между ними. true_frame
- вектор целых чисел для действительных кадров, в течение которых должны отображаться данные.
Для панели first (т.е. где 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
all_frames
не изменяется после строк A-B, поэтому я не буду повторять распечатки с консоли снова.
Для панели секунда (то есть s = 0,5), с другой стороны, линии A-B существенно изменились. Вот что мы имеем перед строкой 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
охватывает диапазон 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
Теперь мы переходим к строке 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
Вот оно: из-за линий AB в expand_panel
мы получаем явление, описанное в вопросе: анимация на второй панели начинается с кадра 1 и длится всего 3 кадра до полного исчезновения вместе.
Обходной путь 1
Так как мы знаем, что является причиной проблемы, мы можем настроить код для 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))
}
Результат:
Z %>%
ggplot(aes(x, y, group = interaction(p,s))) +
geom_point() +
facet_wrap(~s) +
transition_time2(t) +
ggtitle('{frame_time}')
Обходной путь 2
Определение совершенно новых объектов 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}')