Last year, inspired by a tweet from Ilya Kashnitsky, I wrote a snow animation which you can read all about here.
Now, this year, the old code no longer worked due to an update to the gganimate API. Hence, I was about to only refactor the code, but decided to give the whole thing a minor update. Below, you find the 2.0 version of my R snow animation.

# PACKAGES ####
pkg <- c("here", "tidyverse", "gganimate", "animation")
sapply(pkg, function(x){
if (!x %in% installed.packages()){install.packages(x)}
library(x, character.only = TRUE)
})
# CUSTOM FUNCTIONS ####
map_to_range <- function(x, from, to) {
# Shifting the vector so that min(x) == 0
x <- x - min(x)
# Scaling to the range of [0, 1]
x <- x / max(x)
# Scaling to the needed amplitude
x <- x * (to - from)
# Shifting to the needed level
x + from
}
# CONSTANTS ####
N <- 500 # number of flakes
TIMES <- 100 # number of loops
XPOS_DELTA <- 0.01
YSPEED_MIN = 0.005
YSPEED_MAX = 0.03
FLAKE_SIZE_COINFLIP = 5
FLAKE_SIZE_COINFLIP_PROB = 0.1
FLAKE_SIZE_MIN = 4
FLAKE_SIZE_MAX = 20
# INITIALIZE DATA ####
set.seed(1)
size <- runif(N) + rbinom(N, FLAKE_SIZE_COINFLIP, FLAKE_SIZE_COINFLIP_PROB) # random flake size
yspeed <- map_to_range(size, YSPEED_MIN, YSPEED_MAX)
# create storage vectors
xpos <- rep(NA, N * TIMES)
ypos <- rep(NA, N * TIMES)
# loop through simulations
for(i in seq(TIMES)){
if(i == 1){
# initiate values
xpos[1:N] <- runif(N, min = -0.1, max = 1.1)
ypos[1:N] <- runif(N, min = 1.1, max = 2)
} else {
# specify datapoints to update
first_obs <- (N * i - N + 1)
last_obs <- (N * i)
# update x position
# random shift
xpos[first_obs:last_obs] <- xpos[(first_obs-N):(last_obs-N)] - runif(N, min = -XPOS_DELTA, max = XPOS_DELTA)
# update y position
# lower by yspeed
ypos[first_obs:last_obs] <- ypos[(first_obs-N):(last_obs-N)] - yspeed
# reset if passed bottom screen
xpos <- ifelse(ypos < -0.1, runif(N), xpos) # restart at random x
ypos <- ifelse(ypos < -0.1, 1.1, ypos) # restart just above top
}
}
# VISUALIZE DATA ####
cbind.data.frame(ID = rep(1:N, TIMES)
,x = xpos
,y = ypos
,s = size
,t = rep(1:TIMES, each = N)) %>%
# create animation
ggplot() +
geom_point(aes(x, y, size = s, alpha = s), color = "white", pch = 42) +
scale_size_continuous(range = c(FLAKE_SIZE_MIN, FLAKE_SIZE_MAX)) +
scale_alpha_continuous(range = c(0.2, 0.8)) +
coord_cartesian(c(0, 1), c(0, 1)) +
theme_void() +
theme(legend.position = "none",
panel.background = element_rect("black")) +
transition_time(t) +
ease_aes('linear') ->
snow_plot
snow_anim <- animate(snow_plot, nframes = TIMES, width = 600, height = 600)

If you want to see some spin-offs of last years code:
- Keith McNulty combined the R Jingle Bells tune and animated snow to create this very merry video.
- Ioannis Kosmidis generated snow in base R
- Daniel Shiffman dedicated a coding challenge to the topic.
- Cynthia Siew combined sound and image in this Shiny Christmas card.
Hi Paul,
I love the animation. I tried running it, but I had an error with the first for-loop, which said there was an extra “{” char. I looked through the code carefully, and didn’t see a problem there, but I do think that this line is causing trouble:
first_obs <- (Ni – N + 1) last_obs <- (Ni)
1) Should these two statements be on the same line?
2) What is Ni?
After adding a hard return in between these two lines, I received an error stating that Ni could not be found. Then, I tried making it N[i], but that didn't work, either.
LikeLike
Hi Eric, thanks for responding!
You are completely right, these should have definitely not been on the same line. Actually, WordPress is a total misery in code formatting making my life as a programming blogger a hell!
In this case, WordPress atuomatically formatted the combination “*i” and for some reason wrapped two statements together.
They should have actually read:
# specify datapoints to update
first_obs <- (N*i – N + 1)
last_obs <- (N*i)
Thanks for notyfing me. I updated it accordingly and hope the code now runs again. Do test and update me ; )
Cheers,
Paul
LikeLike
Hi Paul,
I tried the new code, and I’m happy to report that it worked great! Your code is well written! Thanks for publishing it. I’ve been meaning to learn gganimate for a while, because now, whenever I need animation, I explicitly code all the individual frames than smash them together manually using ImageMagick. Using gganimate will be a nice higher level abstraction.
Eric
PS A novice user might not realize that they have to explicitly issue “snow_anim” to actually start the animation, so I recommend adding that command as the last line.
LikeLiked by 1 person