The methods provided in this package are applicable to various types of automated radio-telemetry data, including—but not limited to—the Motus Wildlife Tracking System.
## Warning: The 'fix' argument is deprecated and will be removed in a future
## release.
Parse your preprocessed raw data to track() and adjust
additional arguments as needed. Input arguments are unique track IDs,
timestamps, station locations, antenna bearings, and signal strength for
each detection. By default, these input variable names align with
standard Motus variable conventions. Use aRange to specify
theoretical antenna detection ranges in kilometres. This can be a single
integer value or a named list of values for different antenna types
defined in the aType column. By default, approximations for
different Yagi-antennas, as suggested in the Motus
Docs, are used:
| Antenna type | Theoretical range |
|---|---|
| 3-element Yagi | ~5 km |
| 4-element Yagi | ~6 km |
| 5-element Yagi | ~8 km |
| 6-element Yagi | ~10 km |
| 9-element Yagi | ~15 km |
With dTime, specify the time interval in minutes for
which positions are estimated. The default is 2 min, but this should be
adjusted according to the tag’s burst interval and the expected flight
speed—longer burst intervals or faster movements typically require
longer intervals. Specify the number of states in the model using the
states argument, where each state represents a distinct
movement behaviour such as migratory flight, local movement, or stopover
(default: 2). Optionally, the argument i_lambda can be used
to fix the lag-correlation parameter across all tracks (see
vignette("hmm")). Set model = FALSE to extract
the internally calculated coarse raw positions and measurement errors
for each time interval without modelling.
Here, we model the animals’ flight paths from the example data using
track() with default arguments. The number of printed
screen updates is reduced using the refresh argument passed
to cmdstanr::sample(). See there for additional sampling
options.
## Running MCMC with 4 parallel chains...
##
## Chain 1 Iteration: 1 / 2000 [ 0%] (Warmup)
## Chain 2 Iteration: 1 / 2000 [ 0%] (Warmup)
## Chain 3 Iteration: 1 / 2000 [ 0%] (Warmup)
## Chain 4 Iteration: 1 / 2000 [ 0%] (Warmup)
## Chain 2 Iteration: 1000 / 2000 [ 50%] (Warmup)
## Chain 2 Iteration: 1001 / 2000 [ 50%] (Sampling)
## Chain 4 Iteration: 1000 / 2000 [ 50%] (Warmup)
## Chain 4 Iteration: 1001 / 2000 [ 50%] (Sampling)
## Chain 3 Iteration: 1000 / 2000 [ 50%] (Warmup)
## Chain 3 Iteration: 1001 / 2000 [ 50%] (Sampling)
## Chain 2 Iteration: 2000 / 2000 [100%] (Sampling)
## Chain 2 finished in 184.6 seconds.
## Chain 4 Iteration: 2000 / 2000 [100%] (Sampling)
## Chain 4 finished in 188.5 seconds.
## Chain 1 Iteration: 1000 / 2000 [ 50%] (Warmup)
## Chain 1 Iteration: 1001 / 2000 [ 50%] (Sampling)
## Chain 3 Iteration: 2000 / 2000 [100%] (Sampling)
## Chain 3 finished in 220.0 seconds.
## Chain 1 Iteration: 2000 / 2000 [100%] (Sampling)
## Chain 1 finished in 259.2 seconds.
##
## All 4 chains finished successfully.
## Mean chain execution time: 213.1 seconds.
## Total execution time: 259.3 seconds.
track() returns a movetrack object
containing complete posterior distributions for positions at each time
step, along with corresponding distances in m and speeds between
positions in m/s. We can peek at the results by simply running
## # A tibble: 161 × 6
## ID time lon lat distance speed
## <int> <dttm> <dbl> <dbl> <dbl> <dbl>
## 1 49237 2023-08-20 20:16:00 8.80 54.8 NA NA
## 2 49237 2023-08-20 20:18:00 8.80 54.8 1319. 11.0
## 3 49237 2023-08-20 20:20:00 8.79 54.8 1531. 12.8
## 4 49237 2023-08-20 20:22:00 8.79 54.8 1494. 12.5
## 5 49237 2023-08-20 20:24:00 8.78 54.8 1484. 12.4
## 6 49237 2023-08-20 20:26:00 8.77 54.8 1637. 13.6
## 7 49237 2023-08-20 20:28:00 8.76 54.8 1984. 16.5
## 8 49237 2023-08-20 20:30:00 8.75 54.8 2462. 20.5
## 9 49237 2023-08-20 20:32:00 8.73 54.7 2280. 19.0
## 10 49237 2023-08-20 20:34:00 8.73 54.7 1951. 16.3
## # ℹ 151 more rows
and transform it into a data.frame with
We can summarise one or multiple variables and inspect convergence
measures using summary.movetrack():
## # A tibble: 161 × 9
## ID time variable median q5 q95 rhat ess_bulk ess_tail
## <int> <dttm> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 49237 2023-08-20 20:16:00 distance NA NA NA NA NA NA
## 2 49237 2023-08-20 20:18:00 distance 1319. 356. 3501. 1.01 1153. 2098.
## 3 49237 2023-08-20 20:20:00 distance 1531. 377. 3623. 1.00 1838. 2985.
## 4 49237 2023-08-20 20:22:00 distance 1494. 394. 3546. 1.00 1796. 2373.
## 5 49237 2023-08-20 20:24:00 distance 1484. 389. 3398. 1.00 3191. 2877.
## 6 49237 2023-08-20 20:26:00 distance 1637. 424. 3564. 1.00 3681. 3744.
## 7 49237 2023-08-20 20:28:00 distance 1984. 575. 3954. 1.00 3294. 3452.
## 8 49237 2023-08-20 20:30:00 distance 2462. 836. 4634. 1.00 1935. 2336.
## 9 49237 2023-08-20 20:32:00 distance 2280. 687. 4485. 1.00 2751. 2163.
## 10 49237 2023-08-20 20:34:00 distance 1951. 530. 3964. 1.00 4457. 3088.
## # ℹ 151 more rows
To easily extract a defined number of posterior draws from the model for each time interval, run:
## ID time chain iter lon lat tID
## 1 49237 2023-08-20 20:16:00 3 206 8.845960 54.81631 49237_206
## 2 49237 2023-08-20 20:18:00 3 206 8.816311 54.81043 49237_206
## 3 49237 2023-08-20 20:20:00 3 206 8.805651 54.80459 49237_206
## 4 49237 2023-08-20 20:22:00 3 206 8.795321 54.77523 49237_206
## 5 49237 2023-08-20 20:24:00 3 206 8.772312 54.77892 49237_206
## 6 49237 2023-08-20 20:26:00 3 206 8.752631 54.79020 49237_206
## 7 49237 2023-08-20 20:28:00 3 206 8.767458 54.79145 49237_206
## [ reached 'max' / getOption("max.print") -- omitted 1603 rows ]
We can plot the results per individual and output variable using
plot():
Estimated median longitude together with 90% highest posterior density intervals over time.
We can visualise the results on a static map using
mapTrack():
Modelled movement trajectories per individual. Posterior means are shown together with 50 posterior draws, circles on the map indicate receiver locations with detections of the animals.
We could also create an interactive Leaflet map using the following code:
library(sfheaders)
library(leaflet)
# Extract draws
draws <- getDraws(fit) |>
sf_linestring("lon", "lat", linestring_id = "tID")
# Leaflet map
fit |>
as.data.frame() |>
sf_linestring("lon", "lat", linestring_id = "ID") |>
leaflet() |>
addTiles() |>
addPolylines(data = draws, color = "grey", weight = 1, opacity = 0.2) |>
addPolylines(color = ~ c("orange", "blue"))