Recreating the U.S. River Conditions animations in R
Use R to generate a video and gif similar to the Vizlab animation series, U.S. River Conditions.
For the last few years, we have released quarterly animations of streamflow conditions at all active USGS streamflow sites . These visualizations use daily streamflow measurements pulled from the USGS National Water Information System (NWIS) to show how streamflow changes throughout the year and highlight the reasons behind some hydrologic patterns. Here, I walk through the steps to recreate a similar version in R.

The workflow to recreate the U.S. River Conditions animations can be divided into these key sections:
- Workflow setup
- Fetch data
- Prepare data for mapping
- Create animation frames
- Combine frames into a gif
- Combine frames into a video
- Optimize your animations
Background on the animation series
The U.S. River Conditions animation series (visit the full portfolio of USGS Vizlab products to see examples) was inspired by the USGS streamflow conditions website, WaterWatch . WaterWatch depicts national streamflow conditions by coloring each gage’s dot by how it’s daily streamflow value relates to that day’s record of streamflow values. Hydrologists and other water data experts use this nuanced metric to inform daily decisions.
In contrast to WaterWatch, the U.S. River Conditions animation series was intended to target a new audience of users who may be unfamiliar with the USGS streamgaging network. To make the content more accessible to non-technical audiences, we deviated from WaterWatch by 1) adding context to the data by including callout text, 2) building a file type that can be directly embedded on social media platforms (e.g Twitter, Facebook, Instagram), and 3) using streamflow metrics that were not as complex. To make the streamflow metrics less complex, the animation colors each gage’s dot by how it’s daily streamflow value relates to its entire record of streamflow values (not the individual day’s record as WaterWatch does). This change helps reinforce knowledge that lots of people already have about seasonal patterns (e.g. wet springs and dry summers) and builds on that existing framework to introduce streamflow data.
We rebuild the U.S. River Conditions animation every three months to summarize the last quarter’s streamflow
events to the public. To be able to do this efficiently and programmatically, the entire visualization is
coded in R. The only manual effort required is to update the timeframe of interest and
configure the text for hydrologic events. You can see the code for the visualization
here on GitHub
.
The visualization pipeline is completely reproducible, but does use a custom, internal
pipelining tool built off of the R package, remake
(very powerful, but
almost like learning a new language). To make the processing more transparent and accessible,
I created this blog to demonstrate the main workflow for how we create the U.S. River Conditions
animations in R (independent of the pipeline tool). The workflow below will
not recreate them exactly, but it should give you the tools to make similar
video and gif animations from R.
Setup your workflow
This code is set up to operate based on all CONUS states. You should be able to change the state(s), projection, and date range to quickly generate something relevant to your area!
Fetch your data
To fetch data for this example, will be using the USGS R
package, dataRetrieval
. To learn more about how to use these functions and discover other
capabilities of the package, see Laura DeCicco’s blog post: dataRetrieval Tutorial - Using R to Discover Data
. You can also
explore our online, self-paced course here
.
Find the site numbers
Find the gages that have data for your states and time period.
Next, get the statistics data
The biggest processing hurdle for the U.S. River Conditions animation is
to fetch and process EVERY streamflow data point in the entire NWIS
database in order to calculate historic statistics. We have a separate
pipeline to pull the data (see
national-flow-observations
)
and steps in the U.S. River Conditions code to calculate the percentiles
(see this
script
).
However, for the purposes of this workflow, we will use the stat
service (beta)
from NWIS
so that we don’t need to pull down all of the historic data. This means
that the final viz will show the values relative to that day’s historic
values (as WaterWatch does); however, using the stat service will
greatly simplify the processing part of this example so we can get to the
animation!
For this map, we will create a scale of 3 categories from low (less than 25th percentile), normal (between 25th and 75th percentile), and high (greater than 75th percentile). So, let’s fetch those stats for the sites we found in the last code block. You can visit the stats service documentation for more on what stats are available to download and this website to learn about percentiles in streamflow .
Now get the daily values
Now that we have our sites and their statistics, we can pull the data for the timeframe of our visualization. These are the values that will be compared with the site statistics to determine if our gage is experiencing low, normal, or high streamflow conditions.
Process and prepare your data for mapping
Categorize daily values by comparing to the statistics
We now have all of the data! Next, we can categorize each day’s data into low, normal, or high based on the statistics. The category is what we will use to determine how to style the points for each frame of the animation.
Setup visualization configs
I like to separate visual configurations, such as frame dimensions and symbol colors/shapes, so that it is easy to find them when I want to make adjustments or reuse the code for another project.
Get data ready for mapping
We are going to need to do two last things to get our data ready
to visualize. First, add columns to specify the style of each point called viz_pt_color
,
viz_pt_type
, viz_pt_size
, and viz_pt_outline
. These will be passed in to our
plotting step so that each point has the appropriate style applied. Second, join in the latitude
and longitude columns (dec_lat_va
, dec_long_va
) and then convert to a
spatial data frame.
Get background map data ready
Using the states the user picked, create an sf
object to use as the
map background.
Build the frames
Test by making a single frame
To create each frame, we use base R plotting functions. In the end, there will be one PNG file per day. Pick the first day in the date sequence to test out a single frame and adjust style, as needed, with this single frame before moving on to the full build.
You should now have a PNG file in your working directory called test_single_day.png
. When you open this file, you should see the following image (unless you made customizations along the way). Continue to iterate here before moving on if you would like to edit the style because it is easiest to do with just a single frame.

Create all animation frames
Now that we are satisified with how our single frame looks, you can move on to building one frame per day.
Combine frames into animations
Now that you have all of your frames for the animation ready, we can stitch them together into a GIF or a video!
GIF
To create GIFs, I use a software called ImageMagick
. It is not an R package,
but we can run its commands by wrapping them in the system
function in R. A colleage
recently told me about the
R package magick
,
which is a wrapper for ImageMagick
and would allow you to avoid the system
function.
It sounds great but I just learned about it and have not had time to practice it yet.
I might return to this post in the future with an update using magick
once I learn it.
For these commands to work, you will first need to install the
ImageMagick
library. Go to
imagemagick.org
to do so.
Once installed, make sure you restart R if you had it open before
installing.
After running that code, you should now have a GIF file called animation_2020_10_01_2020_10_31.gif
in your working directory! An optional step is to use an additional non-R library called gifsicle
to simplify the GIF. It will lower the final size of your GIF (which
can get quite large if you have a lot of frames). You will need to
install gifsicle
before you can run
this code.

Video
Videos are created in a similar way to GIFs, but I use a tool called
ffmpeg
. There are a lot of configuration options (I am still
learning about how to use these). The options used in the code below are
what we have found results in the best video. You can see all of the
options and download the tool from
ffmpeg.org
.
Note: This tool requires an extra step if you are on a Windows machine because it cannot use your filenames outright (unless numbered from 1:n) . I have included the extra step to add the filenames to a text file and use that in the video creation command .
This takes a few seconds longer than the GIF creation steps and will print a whole
lot of messages out to your console as it runs. Once it is complete, you will see
a new mp4 file in your working directory called, animation_2020_10_01_2020_10_31.mp4
and
it should look a little something like this one:
This process of creating individual frames and them stitching together into a video or GIF
will work for any set of images you have. To use this code with other frames (if you didn’t
create the ones above), just change out the first line of the GIF or
video code chunks that define the object, frames
, which contains the filepaths of the images
you want to convert to an animation.
Optimizing for various platforms
Image quality
For Windows users, the quality of saved PNG images is pretty low and results in pixelated videos. We were able to overcome this by setting the width and height to double the desired size and later downscaling the video (see this GitHub issue to read about our discovery process for fixing Windows pixelation). If you change the size of the frames you are saving after you iterate on your frame design, you will need to adjust your point and text sizes to fit the new image dimensions and then recreate the animation files.
When creating the PNG files earlier, I used a width and height double the size of what I wanted in the end, so now all I have to do is downscale. If you followed this approach, you can downscale your video to half the size by running the following:
You will now have two new animation files, one GIF and one video, that have _downscaled
appended to their names. They should appear to be about half of the size of the originals
(my GIF went from 817 KB to 441 KB and my video went from 2 MB to 900 KB).
Platform specs
Different social media platforms have different requirements for your content. The most important item to check before planning your animation are the dimensions and aspect ratio you will need. Those can change the way that you need to layout your animation (for example, Instagram videos are often square or portrait but rarely landscape). If I want to put this on multiple platforms, you will likely need to generate multiple animations. I always have three different versions, where each is optimized for the platform on which it will be released. When I do this, I make sure to write functions to share as much of the code as possible but deviate as necessary.
As you plan your animation, consider the following list of technical specifications that platforms may require. To find technical specs by platform, you can use your favorite web search! Try to look at the most recent list available because the specs can change. So far, I have had great success with this updating article from Sprout Social .
- maximum file size
- maximum video length
- maximum/minimum height and width
- specific aspect ratio
- framerate
Other animation options
In the world of R, there are lots of other animation options. There is
an R package called magick
that wraps the ImageMagick
library we used earlier (I only recently
discovered this and may try to update this blog in the future using
that package instead of the system calls). If you are
a ggplot2
user, there is a package called gganimate
that will create
animations without the need for any of the additional tools I listed
here. I have struggled with using that package in the past (though to be
fair since I had been using this method, I didn’t try very hard to
learn) and found it to be very slow compared to the multiframe plus ImageMagick
or FFMPEG method.
There are also non-R tools you can use to stitch together animation frames, such as Photoshop. In addition to not having a Photoshop license, I am a huge R nerd, so the more I can stay in R, the better :)
That should give you some foundations and tools to build frame-based animations in R. Good luck and let us know how it goes @USGS_DataSci !
Categories:
Related Posts
dataRetrieval Tutorial - Using R to Discover Data
January 8, 2021
R is an open-source programming language. It is known for extensive statistical capabilities, and also has powerful graphical capabilities. Another benefit of R is the large and generally helpful user-community.
Jazz up your ggplots!
July 21, 2023
At Vizlab , we make fun and accessible data visualizations to communicate USGS research and data. Upon taking a closer look at some of our visualizations, you may think:
Hydrologic Analysis Package Available to Users
July 26, 2022
A new R computational package was created to aggregate and plot USGS groundwater data, providing users with much of the functionality provided in Groundwater Watch and the Florida Salinity Mapper .
Reproducible Data Science in R: Iterate, don't duplicate
July 18, 2024
Overview This blog post is part of a series that works up from functional programming foundations through the use of the targets R package to create efficient, reproducible data workflows.
Reproducible Data Science in R: Writing better functions
June 17, 2024
Overview This blog post is part of a series that works up from functional programming foundations through the use of the targets R package to create efficient, reproducible data workflows.