This repository has been archived by the owner on May 10, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 23
/
transition_filter.Rmd
281 lines (210 loc) · 11.8 KB
/
transition_filter.Rmd
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
---
title: "transition_filter"
author: "Anna Quaglieri"
date: "22/11/2018"
output:
github_document:
toc: true
---
```{r setup, include=FALSE,message=FALSE}
knitr::opts_chunk$set(echo = TRUE,cache=TRUE)
```
```{r eval=FALSE,message=FALSE}
devtools::install_github("thomasp85/gganimate")
devtools::install_github("thomasp85/transformr")
```
```{r message=FALSE}
library(tidyverse)
library(gganimate)
library(transformr)
library(emo)
```
This is what the help function tells us `transition_filter` does.
```{r transition_filter help}
?transition_filter
```
> ## Transition between different filters
>
> This transition allows you to transition between a range of filtering conditions. The conditions are expressed as logical statements and rows in the data will be retained if the statement evaluates to TRUE. It is possible to keep filtered data on display by setting keep = TRUE which will let data be retained as the result of the exit function. Note that if data is kept the enter function will have no effect.
> ## Usage
> transition_filter(transition_length, filter_length, ..., wrap = TRUE,
keep = FALSE)
Let's start simple!! I believe that's where all big things start...
* This is how a simple xy plot showing `hp` vs `mpg` from the `mtcars` data frame looks like using `ggplot() + geom_point()`
```{r}
mydf <- mtcars
p1=ggplot(mydf, aes(x = hp, y = mpg)) +
geom_point()
p1
```
* Let's add `transition_filter()`
```{r}
p1 + transition_filter(transition_length = 10,
filter_length = 0.2,
wrap = TRUE,
keep = FALSE,
qsec < 15, qsec > 16) +
labs(title="{closest_expression}")
```
To figure out what's happening at different stages of the transition you can ask `gganimate` to output the filters that are applied at different stages of the animation. This was achieved in the code above by setting the title to `{closest_expression}`. Every `gganimate` function has its own options which you can find listed in the **`Label variables`** section of a function's help page. For example these are three out of the **`Label variables`** for `gganimate::transition_filter`.
> ## Label variables
> * **transition_filter** makes the following variables available for string literal interpretation
> * **transitioning** is a boolean indicating whether the frame is part of the transitioning phase
> * **previous_filter** The name of the last filter the animation was at
## How do the filtering functions actually work?
`transition_filter()` requires at least two logical conditions that it will use to produce the different instances of the animation. For example, in the example below `transition_filter()` will plot first `hp` vs `mpg` only for rows that satisfies `qsec < 15` and then it will plot `hp` vs `mpg` only for rows that satisfies `qsec > 16`. You can add as many conditions as you like!
Below I ran `transition_filter()` with three filters and added several `Label variables` to the title to give an idea about which filters are used at each state.
```{r}
p1 + transition_filter(transition_length = 10,
filter_length = 0.2,
wrap = TRUE,
keep = FALSE,
qsec < 14, qsec > 16, qsec > 20) +
labs(title = "closest expression : {closest_expression}, \n transitioning : {transitioning}, \n closest_filter : {closest_filter}")
```
You can also provide a `TRUE\FALSE` column from your data frame as the logical condition!!!
```{r }
mydf <- mydf %>% mutate(cond1 = qsec < 15,
cond2 = qsec > 15 & qsec < 20,
cond3 = qsec > 20)
# update ggplot plot
p1=ggplot(mydf, aes(x = hp, y = mpg)) +
geom_point()
head(mydf[,c("cond1","cond2","cond3")])
```
```{r}
p1 + transition_filter(transition_length = 10,
filter_length = 0.2,
wrap = TRUE,
keep = FALSE,
cond1,cond2,cond3) +
labs(title = "closest expression : {closest_expression}")
```
## Let's investigate the `keep` argument.
In the previous examples `keep` has always been `FALSE`. To make things a little bit exciting I will set it now to `TRUE`.
```{r}
g=ggplot(mtcars, aes(x = hp, y = mpg, colour = factor(cyl))) +
geom_point() + transition_filter(transition_length = 10,
filter_length = 0.2,
wrap = TRUE,
keep = TRUE,
qsec < 14,qsec > 16,qsec > 20) +
labs(title = "closest expression : {closest_expression}, \n transitioning : {transitioning}, \n closest_filter : {closest_filter}")
animate(g, nframes = 10, fps = 2)
```
Great! But... now I cannot really see what's being filtered at each stage.
Thanks to the `gganimate` `r emo::ji("package")` author Thomas for helping with understanding `keep` in this [issue](https://github.com/thomasp85/gganimate/issues/220#issuecomment-441178296).
Normally, you would set `keep = TRUE` if you are interested in seeing how the filters modify your selection with respect to the whole background of observations (which comprises observations that would normally be excluded by the logical condition). The argument `keep` becomes useful in combination with an exit function **that does not make the observations disappear**. If you are not familar with exit functions you can find some examples [here](https://github.com/ropenscilabs/learngganimate/blob/master/enter_exit/enter_exit.md). In general, the exit function will do something, that you choose, to the observations that should be exiting a transition.
The example below is adapted from this [issue](https://github.com/thomasp85/gganimate/issues/220#issuecomment-441178296).
```{r}
g=ggplot(mtcars, aes(x = hp, y = mpg, colour = factor(cyl))) +
geom_point(size = 2) +
transition_filter(transition_length = 10,
filter_length = 0.2,
wrap = TRUE,
keep = TRUE,
qsec < 14,qsec > 16,qsec > 20) +
labs(title = "closest expression : {closest_expression}") +
exit_manual(function(x) dplyr::mutate(x, colour = "grey",size= 1))
animate(g, nframes = 30, fps = 2)
```
In the example above, all the observations are always plotted, and by using the `exit_manual` function we can change the appearance of the observations that should actually be filtered out and highlight the ones that stay in!
On a different note, it is really interesting what happens with the code: `dplyr::mutate(x, colour = "grey",size= 1)`! The exter/exit functions receive the data with enter/exit the animations and they are stored in the standard data frame object. This means that we could use the `dplyr::mutate()` function to specify how we want to modify the entering/exiting data!
## How does the `wrap` argument work?
`wrap` is a common argument to different `gganimate()` functions and it is pretty self explanatory:
> **wrap** Should the animation wrap-around? If TRUE the last filter will be transitioned into the first.
When setting `r wrap = FALSE` below, you can see that the transition between `qsec > 20` (last condition) and `qsec < 14` (first condition) happens with a sort of "*jump*". Whereas when setting `wrap = TRUE` the transition between the last and first condition is wrapped and happens in the same way as between the other ones, like in a cycle.
```{r}
g=ggplot(mtcars, aes(x = hp, y = mpg, colour = factor(cyl))) +
geom_point(size = 2) +
transition_filter(transition_length = 10,
filter_length = 0.2,
wrap = FALSE,
keep = TRUE,
qsec < 14,qsec > 16,qsec > 20) +
labs(title = "closest expression : {closest_expression}") +
exit_manual(function(x) dplyr::mutate(x, colour = "grey",size= 1))
animate(g, nframes = 30, fps = 2)
```
```{r}
g=ggplot(mtcars, aes(x = hp, y = mpg, colour = factor(cyl))) +
geom_point(size = 2) +
transition_filter(transition_length = 10,
filter_length = 0.2,
wrap = TRUE,
keep = TRUE,
qsec < 14,qsec > 16,qsec > 20) +
labs(title = "closest expression : {closest_expression}") +
exit_manual(function(x) dplyr::mutate(x, colour = "grey",size= 1))
animate(g, nframes = 30, fps = 2)
```
## Errors encountered along the way
If you ever get: `Error in transform_path(all_frames, next_filter, ease, params$transition_length[i],transformr is required to tween paths and lines` install the package `transformr`.
```{r eval=FALSE,echo=FALSE}
# Halloween Candy data!
# Did you know how many types of Halloween Candies you could get?? I have been trying to understand how the sugar content and the price correlate depending whether is a `chocolate`, `fruity` or `caramel` candy!
#
# Not sure that makes much sense though :/
#
# Probably, we should try to prevent the labels created from `ggrepel` from moving while transitioning, since the dots remains in the same position.
# I have been trying using the `exclude_layer = 5` inside `transition_filter` that Danielle successfully used in `shadow_wake` https://github.com/ropenscilabs/learngganimate/blob/master/shadow_wake.md but it does not like it.
library(fivethirtyeight)
library(ggrepel)
data(candy_rankings)
candy_rankings_long <- candy_rankings %>% gather(candy_type, logical, chocolate:pluribus)
p1=ggplot(candy_rankings) +
geom_point(aes(x=sugarpercent,y=winpercent,colour=candy_type))+
theme_bw()+
theme(legend.position = "none") +
geom_text_repel(aes(x=sugarpercent,y=pricepercent,colour=competitorname,label=competitorname)) +
transition_filter(transition_length = 10,
filter_length = 0.2,
wrap = TRUE,
keep = FALSE,
chocolate,fruity,caramel) +
labs(title = "closest expression : {closest_expression}")
animate(p1,nframes=50,duration=60)
p1=ggplot(candy_rankings_long) +
geom_point(aes(x=sugarpercent,y=winpercent,colour=candy_type))+
theme_bw()+
theme(legend.position = "none") +
transition_filter(transition_length = 10,
filter_length = 0.2,
wrap = TRUE,
keep = TRUE,
candy_type %in% "chocolate" & logical == TRUE,
candy_type %in% "caramel" & logical == TRUE,
candy_type %in% "crispedricewafer" & logical == TRUE) +
labs(title = "closest expression : {closest_expression}")+
exit_manual(function(x) dplyr::mutate(x, size = 1, colour="grey"))
animate(p1,nframes=50,duration=60)
```
```{r eval=FALSE,echo=FALSE}
library(fivethirtyeight)
library(ggrepel)
data(candy_rankings)
library(tweenr)
library(ggimage)
dta <- data.frame(y = c(0.10, 0.10),
x = c(0, 1),
tstep = c("a" , "b"),
image = rep("halloween_candies.jpg",2))
p1=ggplot(candy_rankings) +
geom_point(aes(x=sugarpercent,y=pricepercent,colour=competitorname))+
theme_bw()+
theme(legend.position = "none")
p2 <- p1 + geom_image(data=dta, aes(x,y, image=image), size=0.2)
p1=ggplot(candy_rankings) +
geom_point(aes(x=sugarpercent,y=pricepercent,colour=competitorname))+
theme_bw()+
theme(legend.position = "none") +
geom_text_repel(aes(x=sugarpercent,y=pricepercent,colour=competitorname,label=competitorname)) +
geom_image(data=dta, aes(x,y, image=image), size=0.2) +
transition_filter(transition_length = 1,
filter_length = 0.2,
wrap = TRUE,
keep = FALSE,
chocolate,fruity,caramel) +
labs(title = "closest expression : {closest_expression}")
animate(p1,nframes=50,duration=60)
```