File: gganimate.Rmd

package info (click to toggle)
r-cran-gganimate 1.0.9-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 6,876 kB
  • sloc: sh: 13; makefile: 2
file content (304 lines) | stat: -rw-r--r-- 11,056 bytes parent folder | download | duplicates (2)
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
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
---
title: "Getting Started"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Getting Started}
  %\VignetteEncoding{UTF-8}
  %\VignetteEngine{knitr::rmarkdown}
editor_options: 
  markdown: 
    wrap: 72
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  gganimate = list(
    nframes = 50,
    width = 1000,
    height = 650,
    units = "px",
    res = 144
  ),
  out.width = '100%'
)
```

*gganimate* is an extension of the *grammar of graphics*, as implemented
by the [*ggplot2*](https://ggplot2.tidyverse.org) package, that adds
support for declaring animations using an API familiar to users of
*ggplot2*.

> The following introduction assumes familiarity with *ggplot2* to the
> extent that constructing static plots and reading standard *ggplot2*
> code feels natural. If you are new to both *ggplot2* and *gganimate*
> you'll benefit from exploring the trove of *ggplot2* documentation,
> tutorials, and courses available online first (see the [*ggplot2*
> webpage](https://ggplot2.tidyverse.org/#learning-ggplot2) for some
> pointers).

## Your First Animation

We'll jump right into our first animation. Don't worry too much about
understanding the code, as we'll dissect it later.

```{r}
library(gganimate)

# We'll start with a static plot
p <- ggplot(iris, aes(x = Petal.Width, y = Petal.Length)) + 
  geom_point()

plot(p)
```

You go from a static plot made with *ggplot2* to an animated one, simply
by adding on functions from *gganimate*.

```{r}
anim <- p + 
  transition_states(Species,
                    transition_length = 2,
                    state_length = 1)

anim
```

> ❗ `transition_states()` splits up plot data by a discrete variable
> and animates between the different states.

As can be seen, very few additions to the plot results in a quite
complex animation. So what did we do to get this animation? We added a
type of *transition*. Transitions are functions that interpret the plot
data in order to somehow distribute it over a number of frames.
`transition_states()` specifically splits the data into subsets based on
a variable in the data (here `Species`), and calculates intermediary
data states that ensures a smooth transition between the states
(something referred to as *tweening*). *gganimate* provides a range of
different transitions, but for the rest of the examples we'll be
sticking to `transition_states()` and see how we can modify the output.

## Easing

When `transition_states()` calculates intermediary data for the
tweening, it needs to decide how the change from one value to another
should progress. This is a concept called *easing*. The default easing
is *linear*, but others can be used, potentially only targeting specific
aesthetics. Setting easing is done with the `ease_aes()` function. The
first argument sets the default easing and subsequent named arguments
sets it for specific aesthetics.

```{r}
anim + 
  ease_aes('cubic-in-out') # Slow start and end for a smoother look
```

> ❗ `ease_aes()` defines the velocity with which aesthetics change
> during an animation.

```{r}
anim + 
  ease_aes(y = 'bounce-out') # Sets special ease for y aesthetic
```

## Labeling

It can be quite hard to understand an animation without any indication
as to what each time point relates to. *gganimate* solves this by
providing a set of variables for each frame, which can be inserted into
plot labels using [*glue*](https://glue.tidyverse.org) syntax.

```{r}
anim + 
  ggtitle('Now showing {closest_state}',
          subtitle = 'Frame {frame} of {nframes}')
```

> ❗ Use *glue* syntax to insert frame variables in plot labels and
> titles.

Different transitions provide different frame variables. `closest_state`
only makes sense for `transition_states()` and is thus only available
when that transition is used.

## Object Permanence

In the animation above, it appears as if data in a single measurement
changes gradually as the flower being measured on somehow morphs between
three different iris species. This is probably not how Fisher conducted
the experiment and got those numbers. In general, when you make an
animation, *graphic elements should only transition between instances of
the same underlying phenomenon*. This sounds complicated but it is more
or less the same principle that governs makes sense to draw a line
between two observations. You wouldn't connect observations from
different iris species, but repeated observations on the same plant
would be fine to connect. Same thing with animations.

Just to make this very clear (it is an important concept). The line plot
equivalent of our animation above is:

```{r}
ggplot(iris, aes(x = Petal.Width, y = Petal.Length)) + 
  geom_line(aes(group = rep(1:50, 3)), colour = 'grey') + 
  geom_point()
```

Ugh...

So, how do we fix this and tell *gganimate* to not morph observations
from different species into each others? The key is the *group
aesthetic*. You may be familiar with this aesthetic from plotting lines
and polygons, but in *gganimate* it takes a more central place. Data
that have the same group aesthetic are interpreted as being linked
across states. The semantics of the group aesthetic in *ggplot2* is such
that if it is undefined it will get calculated based on the interaction
of all discrete aesthetics (sans `label`). If none exists, such as in
our iris animation, all data will get the same group, and will thus be
matched by *gganimate*. So, there are two ways to fix our plot:

1.  Add some aesthetics that distinguish the different species

```{r}
ggplot(iris, aes(x = Petal.Width, y = Petal.Length)) + 
  geom_point(aes(colour = Species)) + 
  transition_states(Species,
                    transition_length = 2,
                    state_length = 1)
```

2.  Set the group directly

```{r}
ggplot(iris, aes(x = Petal.Width, y = Petal.Length)) + 
  geom_point(aes(group = seq_along(Species))) + 
  transition_states(Species,
                    transition_length = 2,
                    state_length = 1)
```

> ❗ The group aesthetic defines how the data in a layer is matched
> across the animation.

In general *2)* is preferred as it makes the intent explicit. It also
makes it possible to match data with different discrete aesthetics such
as keeping our (now obviously faulty) transition while having different
colour for the different species)

```{r}
ggplot(iris, aes(x = Petal.Width, y = Petal.Length)) + 
  geom_point(aes(colour = Species, group = 1L)) + 
  transition_states(Species,
                    transition_length = 2,
                    state_length = 1)
```

## Enter and Exit

While we may have made our animation more correct by separating the data
from the different species, we have also made it quite a bit more
boring. Now it simply appears as three static plots shown one at a time,
which is hardly an attention grabber. If only there were a way to
animate the appearance and disappearance of data...

Enter the `enter` and `exit` functions. These functions are responsible
for modifying the state of appearing (entering) and disappearing
(exiting) data, so that the animation can tween from and to the new
state. Let's spice up our animation a bit:

```{r}
anim <- ggplot(iris, aes(x = Petal.Width, y = Petal.Length)) + 
  geom_point(aes(colour = Species), size = 2) + 
  transition_states(Species,
                    transition_length = 2,
                    state_length = 1)

anim + 
  enter_fade() + 
  exit_shrink()
```

> ❗ `enter` and `exit` functions are used to modify the aesthetics of
> appearing and disappearing data so that their entrance or exit may be
> animated.

*gganimate* comes with a range of different functions, and using the
`enter_manual()` and `exit_manual()` functions you can create your own.
Enter and exit functions are composable though, so you can often come
pretty far by combining preexisting ones

```{r}
anim + 
  enter_fade() + enter_drift(x_mod = -1) + 
  exit_shrink() + exit_drift(x_mod = 5)
```

## Rendering

In the examples above the animations has simply appeared when we printed
the animation object, just like we would expect from *ggplot2*. As a lot
of things are happening automatically, and you might want to take
control, this section will give a brief overview of the rendering.

*gganimate*'s model for an animation is dimensionless in the same way as
*ggplot2* describe plots independent of the final width and height of
the plot. This means that the final number of frames and its frame-rate
are only ever given when you ask *gganimate* to render the animation.
When you print an animation object the `animate()` function is called on
the animation with default arguments, some of which are:

-   **nframes** sets the number of frames (defaults to `100`)
-   **fps** sets the number of frames (defaults to `10`)
-   **dev** sets the device used to render each frame (defaults to
    `'png'`)
-   **renderer** sets the function used to combine each frame into an
    animate (defaults to `gifski_renderer()`)

There are other arguments as well (e.g. `...` will be passed on to the
device so you can set width, height, dpi, etc), but these are the most
important. If you don't like the defaults you can either call
`animate()` directly with values of your choosing, or modify the
defaults by setting new with `options(gganimate.<argument> = <value>)`.

A topic that requires some additional words are the renderers. The
default will use [gifski](https://github.com/r-rust/gifski) to combine
the frames into a gif. gifs are great because they are virtually
supported everywhere, and gifski is both a very fast, and very high
quality converter. Still, you may have reasons to want a different
output. *gganimate* is quite agnostic to how you want to combine the
frames and, while it comes with a set of predefined renderers, any
function that takes a vector of paths to image files along with a
frame-rate, will do. The return value of your renderer is what is
ultimately returned by the `animate()` function.

Below are a couple of examples of different `animate()` calls:

```{r, eval=requireNamespace('av', quietly = TRUE)}
# Video output
animate(
  anim + enter_fade() + exit_fly(y_loc = 1),
  renderer = av_renderer()
)
```

```{r, out.width=NULL}
# Different size and resolution
animate(
  anim + ease_aes(x = 'bounce-out') + enter_fly(x_loc = -1) + exit_fade(),
  width = 400, height = 600, res = 35
)
```

If you need to save the animation for later use you can use the
`anim_save()` function. It works much like `ggsave()` from *ggplot2* and
automatically grabs the last rendered animation if you do not specify
one directly.

## Want more?

This guide is far from exhaustive, but have hopefully given you a broad
understanding of how *gganimate* works. There are still more to explore
as we have only scratched the surface of transitions, let alone
mentioned views and shadows. But for now this is left to you. There is a
fantastic joy in discovery and with the things you have now learned you
are ready to go digging on your own.