These functions assemble a timeline and observations for the particle filter (pf_filter()
).
Usage
assemble_timeline(.datasets = list(), .step, .trim = FALSE)
assemble_acoustics(.timeline, .detections, .moorings, .services = NULL)
assemble_archival(.timeline, .archival)
assemble_custom(.timeline, .dataset)
assemble_xinit_containers(
.timeline,
.xinit = list(),
.radius,
.mobility,
.map = NULL,
.threshold = NULL
)
assemble_acoustics_containers(
.timeline,
.acoustics,
.mobility,
.map = NULL,
.threshold = NULL
)
assemble_containers(...)
Arguments
- .datasets, .step, .trim
Arguments for
assemble_timeline()
..datasets
—Alist
ofdata.table::data.table
s, one for each data type, each containing atimestamp
column;.step
—Acharacter
(such as"2 mins"
), passed tolubridate::round_date()
andseq.POSIXt()
, that defines the resolution of the timeline;.trim
—Alogical
variable that defines whether or not to trim the timeline to the overlapping period between datasets;
- .timeline
A
POSIXct
vector of regularly spaced time stamps that defines the timeline for the simulation (optionally fromassemble_timeline()
). Here,timeline
is used to:Define the resolution of observations;
- .detections, .moorings, .services
The
data.table::data.table
s forassemble_acoustics()
(seepat_setup_data()
)..detections
is adata.table::data.table
of acoustic detections for a single individual. This must contain thereceiver_id
andtimestamp
columns..moorings
is adata.table::data.table
of acoustic receiver deployments. This must contain thereceiver_id
,receiver_start
, andreceiver_end
columns, plus (optional) additional parameter columns.(optional)
.services
is adata.table::data.table
of servicing events. This must contain thereceiver_id
,service_start
andservice_end
columns.
- .archival
For
assemble_archival()
,.archival
is adata.table::data.table
of depth observations for a single individual withtimestamp
andobs
columns (see.dataset
, below).- .dataset
For
assemble_custom()
,.dataset
is adata.table::data.table
of observations (such as depth measurements) for a single individual. This must containtimestamp
andobs
columns plus (optional) additional parameter columns.- .xinit, .radius,
Dataset arguments for
assemble_xinit_containers()
..xinit
is a namedlist
, with elements"forward"
and"backward"
. Elements should beNULL
or adata.table::data.table
of initial state(s) (i.e., capture or recapture locations) for the corresponding filter run. If capture/recapture locations are known exactly, use a one-rowdata.table::data.table
. If starting/ending locations are not known exactly, multi-rowdata.table::data.table
s (with all possible starting/ending locations) are permitted..radius
is a double that defines the radius of the container around the capture/recapture location that particles must reach (inassemble_acoustics_containers()
,.radius = .acoustics$detection_gamma
).
- .mobility, .map, .threshold
Shared container threshold arguments (for
assemble_xinit_containers()
andassemble_acoustics_containers()
)..mobility
is the maximum movement distance (m) between two time steps (and sets the rate of container contraction)..map
,.threshold
are distance threshold options. Specify.map
or.threshold
:.map
is a two-columnmatrix
of the four coordinates of the study area or aterra::SpatRaster
orterra::SpatVector
from which such amatrix
can be obtained. On Linux, the latter two options are only possible ifJUILA_SESSION = "FALSE"
..threshold
is set automatically based on the distances between container centroids and the boundaries of the study area.Otherwise,
.threshold
is adouble
that defines the distance threshold.
- .acoustics
Dataset arguments for
assemble_acoustics_containers()
..acoustics
is adata.table::data.table
of acoustic observations, fromassemble_acoustics()
.
- ...
For
assemble_containers()
,...
represents containerlist
s for multiple data types, such as capture events (assemble_xinit_containers()
) and acoustic observations (assemble_acoustics_containers()
).
Value
assemble_timeline()
returns a POSIXct vector;assemble_acoustics()
,assemble_archival()
andassemble_custom()
return adata.table::data.table
forpf_filter()
;assemble_xinit_containers()
,assemble_acoustics_containers()
andassemble_containers()
return a namedlist
, with one element (data.table::data.table
) for (a) the forward and (b) the backward runs ofpf_filter()
;
Assemble timeline
assemble_timeline()
is a simple function that defines a regular timeline, of resolution .step
, from a list
of input datasets.
If
.trim = FALSE
, this defines a sequence of regular time stamps across the full range of time stamps in the input datasets.If
.trim = TRUE
, the timeline is trimmed to the overlapping period between datasets.
Assemble datasets
assemble_{dataset}()
functions are helper routines that prepare timelines observations for different data types as required for the particle filter (pf_filter()
). The filter expects a named list
of datasets (one for each data type). Each dataset must contain the following columns:
timestamp
sensor_id
obs
Additional columns with the parameters of the observation model (see
glossary
).
assemble_acoustics()
, assemble_archival()
and assemble_custom()
assemble 'standard' observational time series:
assemble_acoustics()
prepares a timeline of acoustic observations as required by the filter for a single individual. This function expects a 'standard' detection dataset (that is, adata.table::data.table
likedat_detections
but for a single individual) that defines detections at receivers alongside a moorings dataset (likedat_moorings
) that defines receiver deployment periods and, optionally, adata.table::data.table
of servicing events (when receiver(s) were non-operational).assemble_acoustics()
uses these datasets to assemble a complete time series of acoustic observations; that is, adata.table::data.table
of time stamps and receivers that defines, for each time step and each operational receiver whether (1L
) or not (0L
) a detection was recorded at that time step. Duplicate observations (that is, detections at the same receiver in the same time step) are dropped. If available in.moorings
, additional columns (receiver_alpha
,receiver_beta
andreceiver_gamma
) are included as required for the default acoustic observation model (that is,ModelObsAcousticLogisTrunc
). If observation model parameters vary both by receiver and through time, simply amend these columns as required.assemble_archival()
prepares a timeline of archival functions for a single individual. This simply wrapsassemble_custom()
and is informally deprecated.assemble_custom()
prepares a timeline of observations for other data types, as required by the filter. This function expects adata.table::data.table
that includes, at a minimum, thetimestamp
andobs
columns. The latter defines the observations. Thesensor_id
column (if unspecified) is simply set to1L
. The function re-expresses time stamps at the resolution specified bytimeline
. Duplicate observations (that is, multiple measurements in the same time step) throw awarning
.
Assemble containers
assemble_containers()
, assemble_xinit_containers()
and assemble_acoustics_containers()
assemble 'container' datasets. A container is a circular region within which an individual must be located, given:
A 'future' observation in a particular place, such as a capture/recapture event or an acoustic detection;
The time until that observation;
An individual's maximum movement speed (
.mobility
);
As we approach a 'future' observation, the container within which an individual must be located, according to that observation, shrinks towards the location in which the observation was recorded. Encoding this knowledge in the particle filter (by killing particles outside of a container that are incompatible with a future observation) facilitates convergence with fewer particles, assuming regular resampling (see pf_filter()
). (Otherwise, we rely on some particles ending up in the right region by chance, which can be unlikely if the region in which particles must end up is small (e.g., a receiver's detection container).)
Consider acoustic containers as an example. Suppose an individual can move up to .mobility
= 500 m per time step, and two time steps elapse between the first and second detections, then at the moment of the first detection the individual must be within 1000 m of the detection range (say, receiver_gamma
= 750 m) of the second receiver; that is, at the moment of first detection, the maximum possible distance of the individual from the receiver that recorded the next detection is 1750 m. As time passes, the container shrinks towards the receiver(s) that recorded the next detection(s), in line with the individual's .mobility
. Pro-actively killing particles that move outside these containers (and are incompatible with the next detection) facilitates convergence.
The following functions assemble containers:
assemble_xinit_containers()
assembles containers for initial capture or recapture events. This is designed for situations in which you know the starting and/or ending location for an individual. Suppose you know the starting location of an individual. We can specify this in the forward filter run via.xinit
inpf_filter()
. But what about the backward filter run? For particle smoothing (viapf_smoother_two_filter()
), we need a forward and a backward filter run and these must align sufficiently. Containers provide a mechanism that encourages the backward filter run to end up in the known starting location. Similarly, if we know a recapture location, containers in the forward filter run provide a mechanism that encourages the filter to end up in that location. Inassemble_xinit_containers()
,.xinit
defines the initial locations for the forward/backward filter runs and.radius
defines the radius of the starting/ending container. If you know capture/recapture locations exactly, strictly speaking.radius = 0
. But it may help to permit some flexibility (e.g.,.radius = .mobility
) to facilitate convergence (it is unlikely that particles will finish in exactly the right location). If capture/recapture locations are not known exactly, the centroid of the input coordinates is taken as the container's centroid. The container radius at the time of capture/recapture event is given by the maximum distance from the centroid to any of the coordinates plus.radius
. As time passes, containers shrink towards this region in line with.mobility
.assemble_acoustics_containers()
prepares a dataset of acoustic containers, given the acoustic time series fromassemble_acoustics()
. Acoustic containers define the region within which an individual must be located at a given time step according to the receiver(s) at which it was next detected. The radius depends on the time until the next detection, the maximum movement speed and the detection range around the receiver at the time of the detection. This function requires thetidyr::nest()
,tidyr::unnest()
andzoo::na.locf()
functions (suggested dependencies).assemble_containers()
is a post-processing function. Use this function to collate the containerdata.table::data.table
s from multiple datasets, i.e., if you have capture/recapture and acoustic containers.
All assemble_*_containers()
functions assemble a list
of data.table::data.table
s (with one element for the forward filter run and one element for the backward filter run). Each row defines the maximum distance (radius
) of the individual from the location in which a future observation was recorded. For computational efficiency, data.table::data.table
s only include containers with a radius
< .threshold
. If .map
is supplied, the .threshold
is set to the maximum distance between each location (e.g., receiver) and the furthest corner of the study area. Otherwise, set the .threshold
to the desired value. The data.table::data.table
s are used to instantiate a Vector of ModelObsContainer
instances in Julia
. (Only one sub-type is used for all container structures for speed.) In the particle filter (pf_filter()
), for each particle, we compute the log-probability of the particle from the distance of the particle from the relevant location (0.0 or -Inf). By re-sampling particles with replacement, particles that move in a way that is incompatible with the future data (e.g., a detection) are killed.
Julia
implementation
In Julia
, datasets are translated into a hash-table (Dict
) of observations (via Patter.assemble_yobs()
). For each time stamp with an observation, this includes a Vector
of Tuple
s, each containing the observation and the associated ModelObs
instance that defines the parameters of the observation model. The particle filter (Patter.particle_filter()
) iterates over each time step in the timeline, uses a movement model to simulate animal movement and, for the time stamps with observations, evaluates the likelihood of those observations for the simulated locations (particles).
assemble_*()
routines are only required for real-world analyses.
See also
Particle filters and smoothers sample states (particles) that represent the possible locations of an individual through time, accounting for all data and the individual's movement.
To simulate artificial datasets, see
sim_*()
functions (especiallysim_path_walk()
,sim_array()
andsim_observations()
).To assemble real-world datasets for the filter, see
assemble
_*()
functions.pf_filter()
runs the filter:To run particle smoothing, use
pf_smoother_two_filter()
.To map emergent patterns of space use, use a
map_*()
function (such asmap_pou()
,map_dens()
andmap_hr()
).
Examples
if (patter_run(.julia = FALSE, .geospatial = TRUE)) {
library(data.table)
library(dtplyr)
library(dplyr, warn.conflicts = FALSE)
#### Define example dataset(s) for a selected individual
# Study area map
map <- dat_gebco()
# Acoustic detection time series
# * Observation model parameters are defined in `.moorings`
det <-
dat_detections |>
filter(individual_id == 25L) |>
select("timestamp", "receiver_id") |>
as.data.table()
# Archival time series
# * Observation model parameters must be included
# * Here, we define parameters for `?ModelObsDepthNormalTruncSeabed`
arc <-
dat_archival |>
filter(individual_id == 25L) |>
select("timestamp", obs = "depth") |>
mutate(depth_sigma = 50, depth_deep_eps = 20) |>
as.data.table()
#### Example (1): Define a timeline
# Define a timeline manually
timeline <- seq(as.POSIXct("2016-03-01 00:00:00", tz = "UTC"),
as.POSIXct("2016-04-01 00:00:00"),
by = "2 mins")
# Use `assemble_timeline()` with `.trim = FALSE`
timeline <- assemble_timeline(list(det, arc), .step = "2 mins")
range(timeline)
# Use `assemble_timeline()` with `.trim = TRUE`
timeline <- assemble_timeline(list(det, arc), .step = "2 mins", .trim = TRUE)
timeline <- timeline[1:1440]
range(timeline)
#### Example (2): Assemble an acoustic timeline
# Assemble a timeline of acoustic observations (0, 1) and model parameters
# * The default acoustic observation model parameters are taken from `.moorings`
# * But can be modified or added afterwards for custom observation models
acoustics <- assemble_acoustics(.timeline = timeline,
.detections = det,
.moorings = dat_moorings)
head(acoustics)
#### Example (3): Assemble an archival timeline
# Assemble a timeline of archival observations and model parameters
archival <- assemble_archival(.timeline = timeline,
.archival = arc)
head(archival)
#### Example (4): Assemble custom datasets
temperature <-
data.table(timestamp = c(as.POSIXct("2016-03-17 01:50:30", tz = "UTC"),
as.POSIXct("2016-03-17 02:00:30 UTC", tz = "UTC")),
obs = c(7.6, 7.7))
temperature <- assemble_custom(.timeline = timeline,
.dataset = temperature)
head(temperature)
#### Example (5): Assemble xinit (capture/recapture) containers
# (A) If we know the capture location, containers are defined for the backward filter run
capture_xy <- data.table(x = 708913.6, y = 6256280)
xinit_containers <-
assemble_xinit_containers(.timeline = timeline,
.xinit = list(forward = capture_xy,
backward = NULL),
.radius = 750,
.mobility = 750,
.map = map)
# (B) If we know the recapture location, containers are defined for the forward run
recapture_xy <- data.table(x = 707816.5, y = 6265746)
xinit_containers <-
assemble_xinit_containers(.timeline = timeline,
.xinit = list(forward = NULL,
backward = recapture_xy),
.radius = 750,
.mobility = 750,
.map = map)
# (C) If we know capture & recapture locations, containers are defined for both filter runs
xinit_containers <-
assemble_xinit_containers(.timeline = timeline,
.xinit = list(forward = capture_xy,
backward = recapture_xy),
.radius = 750,
.mobility = 750,
.map = map)
# (D) A set of possible capture/recapture locations is permitted:
# (i) Define possible starting locations
capture_xy <-
cbind(capture_xy$x, capture_xy$y) |>
terra::vect(crs = terra::crs(map)) |>
terra::buffer(width = 500) |>
terra::spatSample(size = 10L) |>
terra::crds(df = TRUE) |>
setDT()
# (ii) Define possible recapture locations
recapture_xy <-
cbind(recapture_xy$x, recapture_xy$y) |>
terra::vect(crs = terra::crs(map)) |>
terra::buffer(width = 500) |>
terra::spatSample(size = 10L) |>
terra::crds(df = TRUE) |>
setDT()
# (iii) Define containers
xinit_containers <-
assemble_xinit_containers(.timeline = timeline,
.xinit = list(forward = capture_xy,
backward = recapture_xy),
.radius = 750,
.mobility = 750,
.map = map)
#### Example (6): Assemble acoustic containers
# Assemble acoustic containers for the `acoustics` dataset above
acoustics_containers <- assemble_acoustics_containers(.timeline = timeline,
.acoustics = acoustics,
.mobility = 750,
.map = map)
# As for assemble_xinit_containers(), this function returns a list:
summary(acoustics_containers)
# Use the `forward` element for `pf_filter()` with `.direction = "forward"`
head(acoustics_containers$forward)
# Use the `backward` for `pf_filter()` with `.direction = "backward"`
head(acoustics_containers$backward)
#### Example (7): Collate containers for multiple datasets
containers <- assemble_containers(xinit_containers, acoustics_containers)
#### Example (8): Implement particle filter
# Use `pf_filter()` to implement the particle filter
# A list of assembled datasets is passed to the `yobs` argument
# The corresponding `ModelObs` sub-types must also be specified, e.g.:
# * `ModelObsAcousticLogisTrunc`
# * `ModelObsDepthUniformSeabed`
# * `ModelObsContainer`
}