Introduction

The glatos package includes several functions that facilitate the creation of animated videos of animal movements from acoustic telemetry data. The primary functions for building animated videos are the interpolate_path and make_frames functions. interpolate_path calculates interpolated positions of animals between detections and make_frames creates a series of snapshots of interpolated and detected fish locations over time from the output of interpolate_path. In addition to creating and outputting frames, make_frames can also stitch frames together to create an animated video of movements using the open source FFmpeg software. Although make_frames was developed to allow customization of the output content and layout of frames, some may find the level of customization to be insufficient for their needs.

In this vignette, we present a short example of an alternative approach for creating animations using the ggplot2 and gganimate R packages. ^[For more information about obtaining gganmiate and ggplot2, see https://gganimate.com/ and https://ggplot2.tidyverse.org/] ggplot2 is a popular and powerful package for plotting figures based on The Grammer of Graphics approach to visualizing data and developed for R by Hadley Wickham. ^[Leland Wilkinson,https://www.amazon.com/Grammar-Graphics-Statistics-Computing/dp/0387245448] ggplot2 differs from base plotting functions in R by its layered approach where graphical output is defined by combing layers of plot objects.

In ggplot2 variables in data are mapped to aesthetic categories to build the plot. In contrast, calls to base R plotting functions are rendered immediately after they are submitted. The ggplot2 framework enables complex plots to be specified with few lines of code however in our experience, customized ggplot2 figures often require substantial effort. Base R plotting functions often require extra effort to create but are easier customize. Both frameworks will produce publication-quality graphical output and have large user-bases.

The gganimate package is an extension to ggplot2 that provides support for animated graphical output ^[see https://gganimate.com/articles/gganimate.html]. In the following code, we use the make_transition function on an example dataset included in glatos to create a custom transition layer necessary for interpolating fish movements. We then use the interpolate_path function in glatos to interpolate fish positions in space and time ^[See glatos package help for more information about functional arguments and outputs of make_frames, make_transition, and interpolate_path. Additional details about creating animated videos of fish movements using the glatos package is found in the workshop manual "A guide to R for analyzing Acoustic Telemetry Data V2.0". A pdf of manual can be downloaded here https://gitlab.oceantrack.org/GreatLakes/glatos/wikis/Past%20r%20workshops%20and%20manuals].Finally, we end the vignette with a demonstration of code to create an animation of fish movements using ggplot2 and gganimate.

In the first chunk we load necessary packages, obtain example data from glatos, clean up the data, and create a figure of lakes Huron and Erie (Figure 1). The data.table R package is used for data manipulations in this vignette to improve efficiency. ^[Data.table information: https://github.com/Rdatatable/data.table/wiki]

fig.cap1 <- "Lakes Huron and Erie extracted from Great Lakes polygon included in the `glatos` package"
library(glatos)
library(sp)
library(sf)
library(raster)
library(gganimate)
library(ggplot2)
library(data.table)

# get polygon of the Great Lakes 
data(greatLakesPoly)

# convert to sf spatial object
lakes <- st_as_sf(greatLakesPoly)

# crop polygon to extract Lake Huron and Lake Erie
lakes <-  st_crop(lakes, xmin = -84.8, xmax = -79.6, ymin = 41.3, ymax= 46.6)
plot(st_geometry(lakes), col = "grey")

# get example walleye detection data
det_file <- system.file("extdata", "walleye_detections.csv",
                        package = "glatos")
det <- read_glatos_detections(det_file)

# bring in receiver file and convert to sf spatial object
rec_file <- system.file("extdata", "sample_receivers.csv", package = "glatos")
recs <- read_glatos_receivers(rec_file)
setDT(recs) # convert to data.table object- necessary for sf package
recs <- recs[deploy_long < -80,]
recs_sf <- st_as_sf(recs, coords = c("deploy_long", "deploy_lat"), crs = 4326) 

In the next bit of code, we create a transition layer from the lakes object. The transition layer is needed to calculate non-linear interpolated movement paths that avoid impossible overland movements of fish. We set the all_touched argument to true (all_touched = TRUE) to allow any pixel that touches the polygon outline of the Great Lakes to be coded as water in the resulting transition layer. If all_touched is false (all_touched = FALSE), then more than half of the pixel must be in the polygon in order to be coded as water.

# temporarily convert sf spatial to sp
# make_transition only accepts sp SpatialPolygonsDataFrame
lakes <- as(lakes, "Spatial")

# make transition layer.
tran <- make_transition(lakes, res = c(0.01, 0.01), all_touched = TRUE)

# convert back to sf
lakes <- st_as_sf(lakes)
cap2 <- "Plot of transition layer, receivers (orange circles), fish detection location (red circles) in lakes Huron and Erie." 
# create quick plot of fish and receivers to make sure they are "on the map" (Figure 2)
unique_fish <- unique(det, by = c("deploy_lat", "deploy_long")) 
plot(tran$rast)
plot(st_geometry(lakes), add = TRUE)
plot(st_geometry(recs_sf), pch=20, col= "orange", add = TRUE)
points(unique_fish$deploy_long, unique_fish$deploy_lat, pch = 20, col = "red")

Next we interpolate detection data and do some cleanup.

# interpolate detections.
pos2 <- interpolate_path(det, trans = tran$transition, lnl_thresh=0.99)
dump <- capture.output( {pos2 <-interpolate_path(det, trans = tran$transition, lnl_thresh=0.99)})
# convert pos2 to data.table
setDT(pos2)

# extract unique bin_timestamp from the interpolated data
int <- unique(pos2, by = "bin_timestamp")

In the next code chunk, we use a data.table join to add the unique timestamps extracted from pos2 in the last line of code above (named int) to the receiver object. We use receiver deployment and recovery timestamps to identify if the receiver was active in that bin_timestamp. In other words, we need to know what receivers were deployed during each bin_timestamp interval so we can display receiver deployment and recovery in the animation. This step is really easy with data.table and would be a bit complicated using base R functions.

# Add bin_timestamp to receivers, based on deploy/recover timestamps.
# Removes unnecessary columns in output to simplify
# This is a data.table non-equi join...
recs <- recs[int, .(deploy_lat = x.deploy_lat,
                    deploy_long = x.deploy_long,
                    station = x.station,
                    deploy_date_time = x.deploy_date_time,
                    recover_date_time = x.recover_date_time,
                    bin_timestamp = i.bin_timestamp),
             on = .(deploy_date_time <= bin_timestamp,
                    recover_date_time >= bin_timestamp),
             allow.cartesian = TRUE]

We are ready to create the animation. The first step is to create a static image of all receivers, detections, and interpolated detections with ggplot2 (all frames combined). We changed the color of fish detections to red circles, interpolated positions to blue circles and made detections larger than interpolated detections.

cap3 <- "Static figure of fish detections (red), interpolated positions (blue), and receivers (orange) plotted using `ggplot2`"
ggplot(data = lakes) +
  geom_sf(color = "black", fill = "lightgrey") +
  geom_point(data = recs, aes(x = deploy_long, y = deploy_lat), size = 2, color = "orange", inherit.aes = FALSE) +
  geom_point(data=pos2, aes(x=longitude, y=latitude, group=animal_id, color=record_type, size=record_type), inherit.aes=FALSE) +
  xlab("Longitude") +
  ylab("Latitude") +
  scale_color_manual(values=c("red", "blue")) +
  scale_size_manual(values=c(2,1))

Now we will animate the plot with gganimate. We repeat the code from the previous code chunk and add an additional layer to create the animation and a layer to display a clock (transition_time, ggtitle).

cap4 <- "Animated figure of fish movements in lakes Huron and Erie.  Only last image of animation is included in this vignette."
ggplot(data = lakes) +
  geom_sf(color = "black", fill = "lightgrey") +
  geom_point(data = recs, aes(x = deploy_long, y = deploy_lat),
             size = 2, color = "orange", inherit.aes = FALSE) +
  geom_point(data=pos2, aes(x=longitude, y=latitude,
                            group=animal_id, color=record_type,
                            size=record_type), inherit.aes=FALSE) +
  xlab("Longitude") +
  ylab("Latitude") +
  scale_color_manual(values=c("red", "blue"))+
  scale_size_manual(values=c(2,1)) +
  transition_time(bin_timestamp) +
  ggtitle('{frame_time}')

As mentioned previously, the addition of a transition_time and ggtitle component in the code above creates the animation. The resulting animation may be customized (number of frames, duration of animation, frames per second, etc) by specifying arguments to the animate function. Please see ?animate for more information. Also, specifying anim_save may be used to write the animation object to disk.

This vignette provides a basic, high-level summary of creating an animated video with ggplot2 and gganimate. These packages provide much more functionality than included in this vignette. Please refer to documentation for gganimate and ggplot2 for additional examples and customization that may be useful for visualizing fish movements.



jsta/glatos documentation built on July 11, 2022, 7:01 a.m.