2.2 Animation with gganimate
2.2 Animation with gganimate
Learning objectives
By the end of this chapter, you can:
- judge whether animation clarifies or distracts from a data story using the two-column checklist.
- apply
transition_states(),transition_reveal(), andtransition_time()with their title placeholders. - tune the language of motion using
ease_aes()andshadow_mark()/shadow_trail(). - control the duration / fps / nframes budget, deriving one from the other two, and render with
animate(). - compare GIF and MP4 size and compatibility, choosing for the delivery channel.
- embed animation in Quarto while providing static alternatives for PDF and accessibility.
Prerequisite check (≤5 minutes)
Continue when you can write a grouped ggplot2 scatterplot or line chart; otherwise revisit Chapter 1.7.
- The same observations have values for 1952 and 2007. Name at least two ways to show both years in a static chart.
- Give one example of an animated statistical chart whose movement helped you understand the data, and one where it was merely decorative.
1. When animation clarifies—and when it distracts
Used well, animation lets viewers see a process: how a trajectory develops, a gap widens, or a ranking reverses. Static endpoint charts cannot show that process in the same way. Used poorly, animation becomes flicker.
| Animation clarifies ✅ | Animation distracts ⚠️ |
|---|---|
| Change over time is central: catching up, surges, paths | A side-by-side static endpoint comparison already tells the story |
| A few clearly identified subjects or categories | Too many subjects flicker between frames |
| Viewers need the process, not just the result | Viewers need precise values; motion impairs reading |
| Readers can pause and replay a web delivery | A time-limited live presentation or poor connection |
You are the first audience for an animation. Building it forces you to examine how data change from frame to frame, which is itself analysis. Be honest at delivery: if readers need only the start and end, two static charts respect their time better than ten seconds of animation.
Accessibility reminder: motion can cause discomfort for some readers, and GIFs cannot be paused. A formal deliverable should preferably provide a static version alongside the animation.
2. Three transitions: states / reveal / time
Start with the signature example: country-level points moving through time, colored by continent.
library(ggplot2)
library(gganimate)
library(gapminder)
# Without gifski, retain frame images to study transitions; GIF / MP4 export is in §4–§5.
options(gganimate.renderer = if (requireNamespace("gifski", quietly = TRUE)) {
gifski_renderer()
} else {
message("gifski is not installed: preview uses frame images; install it to export GIF.")
file_renderer()
})
anim <- ggplot(gapminder, aes(x = gdpPercap, y = lifeExp,
size = pop, color = continent, group = country)) +
geom_point(alpha = 0.7, show.legend = FALSE) +
scale_x_log10() +
labs(title = "Year: {frame_time}") +
transition_time(year)
animPlaceholders such as {frame_time} put information about the current frame in the title. Each transition serves a different data structure:
| Transition | Data structure | Typical question |
|---|---|---|
transition_time() |
Continuous time; each frame represents a moment | “How does each subject move over time?” |
transition_states() |
Discrete states or categories | “What do the group distributions look like, and how do they transition?” |
transition_reveal() |
Gradual unfolding along a continuous variable | “How did this line develop step by step?” |
Smoothly morph between states:
p_states <- ggplot(gapminder, aes(x = lifeExp, fill = continent)) +
geom_density(alpha = 0.6) +
labs(title = "Continent: {closest_state}") +
transition_states(states = continent,
transition_length = 2, state_length = 1)Gradually reveal a line:
ggplot(ggplot2::economics, aes(x = date, y = unemploy)) +
geom_line() +
geom_point(aes(group = seq_along(date)), size = 1) + # Reveal points along the line
labs(title = "{frame_along}") +
transition_reveal(date)Think of animation as N complete ggplots plus gganimate’s interpolation engine. Rendering time and file size therefore grow with frame count. Printing an animation object directly renders 100 frames by default.
① Replace the states example’s placeholder with {next_state}. Does the title lead or lag the display? ② Before running anything, explain which of transition_length and state_length controls movement and which controls the pause. Then render with (5, 5) and (1, 5) to check.
3. Easing and shadows: The tone of motion
Easing determines the feel of movement between frames:
p_states + ease_aes(default = "sine-in-out") # Gentle starts and stops for formal reports
p_states + ease_aes(default = "bounce-out") # Playful bouncing: use cautiouslyAn easing name combines a base curve (linear, cubic, sine, exponential, back, bounce, …) with a modifier (-in, -out, -in-out). For data animation, start aesthetically with cubic-in-out or sine-in-out; bounce and elastic belong closer to playful graphics.
Shadows make the past visible:
anim + shadow_mark() # Retain past marks to show country trajectories
anim + shadow_trail(distance = 0.05) # Evenly spaced trails emphasize motionUse shadow_mark() to tell a story about trajectories, retaining faint past marks. Use shadow_trail() to emphasize movement; smaller distance values create denser trails.
4. Timing: duration / fps / nframes
The timing relationship is nframes = duration × fps. Specify at most two; the third is derived:
if (requireNamespace("gifski", quietly = TRUE)) {
animate(anim, duration = 10, fps = 10, renderer = gifski_renderer()) # 100 frames
gif <- animate(anim, nframes = 60, fps = 10, renderer = gifski_renderer()) # 6 seconds
anim_save("gapminder.gif", animation = gif)
} else {
message("Skipping GIF export: install gifski first.")
}If duration, fps, and nframes are all specified inconsistently, animate() reports an error. This asks you to resolve the timing budget. A useful starting point is 8–12 seconds at 10 fps for a data story; 100 frames is an upper budget for many uses, not an automatic target.
5. Rendering and size: GIF versus MP4
if (requireNamespace("gifski", quietly = TRUE)) {
gif <- animate(anim, duration = 10, fps = 10, renderer = gifski_renderer())
anim_save("gapminder.gif", animation = gif)
} else {
message("Skipping GIF export: install gifski first.")
}
if (requireNamespace("av", quietly = TRUE)) {
animate(anim, duration = 10, fps = 10,
renderer = av_renderer("gapminder.mp4"))
} else {
message("Skipping MP4 export: install av first.")
}
if (all(file.exists(c("gapminder.gif", "gapminder.mp4")))) {
file.size("gapminder.gif") / file.size("gapminder.mp4")
}| Format | Compression | Size | Compatibility |
|---|---|---|---|
| GIF | Frame-based, 256-color palette | Large | Broadest; works in an <img> |
| MP4 (H.264) | Compression between frames | Often an order of magnitude smaller | Web <video>; email support varies |
For straightforward HTML embedding, GIF is convenient. When size matters, such as email or pages with several animations, use MP4. Watch for banding from the GIF palette in gradient-based charts.
6. Embedding animation in Quarto
In an executable code cell, animate() or printing an animation object embeds it in HTML output and allows cross-referencing:
```r
#| label: fig-lifeexp
#| fig-cap: "Life expectancy and GDP per capita (1952–2007)"
animate(anim, duration = 10, fps = 10, width = 720, height = 405)
```GIFs do not animate in Word / PDF output. Provide a static version by removing the transition layer from the ggplot or using facet_wrap(). Ensure the written conclusion does not require having watched the animation.
Both frame count and resolution increase file size. Use anim_save() and inspect file.size() before finalizing embedding parameters; do not let one high-resolution, 100-frame animation overwhelm the document.
Repeat the signature animation in §2 and the §4 call to animate(duration = 10, fps = 10) followed by anim_save("gapminder.gif"). Record the actual frame count and GIF size in MB.
Render the states chart with ease_aes(default = "sine-in-out") and "bounce-out", using 30 frames each. Explain which suits a formal report and why. Then add shadow_mark() to the §2 time animation. In two lines, explain how retaining past marks changes your understanding of the 2007 endpoint.
Round 1 (AI prohibited): Choose unemploy, psavert, or uempmed from ggplot2::economics and build a reveal animation. Use {frame_along} in the title, reveal points along the line, and keep nframes ≤ 60. Save with anim_save() and embed it in a Quarto HTML page. Round 2 (AI allowed): Give Posit Assistant the nframes / fps / width / height settings. Ask only: “How much could I reduce the file size, and what would I lose?” Record one suggestion you adopted and verified by measurement.
Capstone
Task: “A ten-second data story.” Choose data that truly change over time: a gapminder subset, economics, or your own dataset. Deliver one Quarto HTML page containing: ① a 200-word explanation of why the story deserves animation, using the §1 checklist; ② an animation lasting ≤10 seconds, with one of the three transitions and deliberate easing and shadows; ③ rendering decisions, including GIF or MP4, nframes/fps, and measured file size; ④ a static endpoint comparison for PDF readers.
| Dimension | Meets expectations | Strong | Excellent |
|---|---|---|---|
| Narrative judgment | Explains why the chart moves | Supports the choice using §1 | Includes evidence from first trying a static alternative |
| Implementation | Correct transition syntax | Easing and shadows serve the narrative | Placeholders, legends, and scales are consistent |
| Timing and size | Animation renders | Explicit nframes/fps budget | Measured GIF versus MP4 comparison with file.size |
| Publication fit | Plays correctly in HTML | Provides a static fallback | States accessibility limits, including inability to pause and motion discomfort |
SOURCES
| Chapter section | Material | Use |
|---|---|---|
| Structure, examples, exercises, capstone, and rubric | This project | Original |
transition_*(), shadow_*(), ease_aes(), and animate() |
Official gganimate documentation, https://gganimate.org | Reference |
| gapminder and economics data | gapminder / ggplot2 R packages; used only for practice | Data |
This chapter is published under CC-BY-SA 4.0.