This vignette demonstrates the wolverine spatial capture-recapture model, as in “Efficient MCMC for Spatial Capture-Recapture Models” (Turek et al, submitted). Specifically, we implement the final version of the wolverine model and MCMC using nimble (de Valpine et al. 2017; NIMBLE Development Team 2020). Details of the functions and procedure are provided therein.
We compiled all functions in the nimbleSCR package version 0.1.0 (Bischof et al. 2020).
Here, we define the nimble
model structure.
code <- nimbleCode({
## priors
psi ~ dunif(0, 1)
sigma ~ dunif(0, 50)
p0 ~ dunif(0, 1)
## loop over individuals
for(i in 1:n.individuals) {
## AC coordinates
sxy[i,1] ~ dunif(0, x.max)
sxy[i,2] ~ dunif(0, y.max)
## habitat constraint
ones[i] ~ dHabitatMask( s = sxy[i,1:2],
xmin = lowerCoords[1],
xmax = upperCoords[1],
ymin = lowerCoords[2],
ymax = upperCoords[2],
habitat = habitat.mx[1:y.max,1:x.max])
## latent dead/alive indicators
z[i] ~ dbern(psi)
## likelihood
y[i, 1:nMaxDetectors] ~ dbinomLocal_normal( detNums = nbDetections[i],
detIndices = yDets[i,1:nMaxDetectors],
size = trials[1:n.detectors],
p0 = p0,
s = sxy[i,1:2],
sigma = sigma,
trapCoords = detector.xy[1:n.detectors,1:2],
localTrapsIndices = detectorIndex[1:n.cells,1:maxNBDets],
localTrapsNum = nDetectors[1:n.cells],
resizeFactor = ResizeFactor,
habitatGrid = habitatIDDet[1:y.maxDet,1:x.maxDet],
indicator = z[i])
}
## derived quantity: total population size
N <- sum(z[1:n.individuals])
})
We load the wolverine example data available from the Dryad Digital Repository (C. Milleret et al. 2018). See Cyril Milleret et al. (2019) for a complete description of the data.
The data file can be downloaded at [https://doi.org/10.5061/dryad.42m96c8].
We also create objects code
, constants
,
data
, and inits
for later use in the function
nimbleModel
.
data <- list(y = my.jags.input$y,
z = my.jags.input$z,
detector.xy = my.jags.input$detector.xy,
habitat.mx = my.jags.input$habitat.mx,
ones = my.jags.input$OK,
lowerCoords = c(0,0),
upperCoords = c(
dim(my.jags.input$habitat.mx)[2],
dim(my.jags.input$habitat.mx)[1]),
trials = rep(1, dim(my.jags.input$detector.xy)[1]))
constants <- list(n.individuals = my.jags.input$n.individuals,
n.detectors = dim(my.jags.input$detector.xy)[1],
y.max = dim(my.jags.input$habitat.mx)[1],
x.max = dim(my.jags.input$habitat.mx)[2])
inits <- list(sxy = inits.1$sxy,
z = inits.1$z,
p0 = 0.05,
psi = 0.5,
sigma = 6)
The dbinomLocal_normal()
distribution incorporates three
features to increase computational efficiency.
This step restricts calculations of the detection probabilities (here
using a halfnormal function) to detectors (traps) within a radius where
detections are realistically possible (Cyril
Milleret et al. 2019). We use the function
getLocalObjects
to identify the set of detectors that are
within a certain distance dmax
of each habitat cell center (blue
points in the plot below). These reduced sets of detectors are
stored in the localIndices
matrix and are later used in the
local evaluation of the detection model to speed up calculations. The
value of dmax
should be as small as possible in order to reduce computation but always
large enough so that for any particular individual, the set of local
traps associated with the coordinates of the activity center
s
include all detectors at which that individual was
detected. The dmax
value will therefore affect the number of columns in
localIndices
Here, we use dmax = 38.
with the coordinates of the activity center s … We also aggregated the
habitat matrix to obtain larger habitat cells (lower resolution) and
obtain objects with smaller dimensions. This reduces the number of
habitat cells for which we have to identify the set of detectors that
are within dmax
of the cell center. The goal is to create the object
localIndices
of the smallest dimension possible, that
balances the cost of looking up relevant grid cells and reducing
calculations for each grid cell.
Here, we resize the habitat matrix by a factor of 24, which
corresponds to the resizeFactor
argument. This means that
24x24 cells are aggregated into a single cell. The
resizeFactor
value will affect how many rows
localIndices
will be composed of.
set.seed(2)
DetectorIndex <- getLocalObjects(habitatMask = data$habitat.mx,
coords = data$detector.xy,
dmax = 38,
resizeFactor = 24)
constants$y.maxDet <- dim(DetectorIndex$habitatGrid)[1]
constants$x.maxDet <- dim(DetectorIndex$habitatGrid)[2]
constants$ResizeFactor <- DetectorIndex$resizeFactor
constants$n.cells <- dim(DetectorIndex$localIndices)[1]
constants$maxNBDets <- DetectorIndex$numLocalIndicesMax
data$detectorIndex <- DetectorIndex$localIndices
data$nDetectors <- DetectorIndex$numLocalIndices
data$habitatIDDet <- DetectorIndex$habitatGrid
We re-express y
as a sparse representation of the
detection matrix to reduce its size. In this representation, we turn the
detection matrix y
into three objects:
detIndices
: where each row (corresponding to one
individual) contains the identification numbers of detectors at which
that individual was detected.y
: a second matrix of identical dimension, containing
the number of detections of a given individual at each detector. This
second matrix is necessary for modelling non-binary detections
(e.g. binomial
observation models)detNums
: a vector containing the number of detectors at
which each individual was detected.The function dbinomLocal_normal()
takes the
logical
argument indicator
that specifies
whether the individual i
is available (zi = 1) for
detection or not (zi = 0). When
zi = 0,
calculations of pij are
not performed and therefore increases MCMC efficiency.
Now, we can create the nimble
model object, using the
model structure defined in code
, and the constants, data,
and initial values.
We configure an MCMC algorithm to the Rmodel
model
object.
We assign MCMC monitors to N, σ, and p0.
We also remove the univariate Metropolis-Hastings samplers that were
assigned by default to each dimension of the sxy
variables
(ACs). Instead, we add joint Metropolis-Hastings samplers
(RW_block
) samplers to each x and y coordinate pair
sxy[i, 1:2]
.
conf <- configureMCMC(Rmodel, monitors = c("N", "sigma", "p0"), print = FALSE)
conf$removeSamplers("sxy")
ACnodes <- paste0("sxy[", 1:constants$n.individuals, ", 1:2]")
for(node in ACnodes) {
conf$addSampler(target = node,
type = "RW_block",
control = list(adaptScaleOnly = TRUE),
silent = TRUE)
}
Rmcmc <- buildMCMC(conf)
First, we can extract the MCMC runtime (5.2 minutes in this case):
## elapsed
## 5.2
Next, we can check the posterior effective sample size (ESS) resulting from our 10 000 posterior samples for the three parameters we tracked (N, σ, and p0):
## N p0 sigma
## 463.24 290.02 179.18
We can also calculate the MCMC efficiency for each parameter; this corresponds to the rate of generating effectively independent posterior samples, per second of MCMC runtime:
## N p0 sigma
## 1.48 0.93 0.57
Summary of posterior distributions for each parameter:
## Mean Median St.Dev. 95%CI_low 95%CI_upp
## N 390.54 390.00 25.34 343.00 444.00
## p0 0.05 0.05 0.01 0.04 0.06
## sigma 5.86 5.84 0.27 5.38 6.40
Examine traceplots and posterior distributions: