---
title: '**secrlinear** - spatially explicit capture--recapture for linear habitats'
author: "Murray Efford"
date: '`r Sys.Date()`'
output:
pdf_document:
toc: true
vignette: >
%\VignetteIndexEntry{Spatially Explicit Capture--Recapture for Linear Habitats}
%\VignetteEngine{knitr::rmarkdown}
\usepackage[utf8]{inputenc}
---
\vspace{12pt}
The R package **[secrlinear]** extends **[secr]** for animal populations
in linear habitats. A habitat is considered to be 'linear' if it is
natural to express population density as number per unit length (e.g.,
animals per km) rather than number per unit area (e.g., animals per
ha). For example, species such as trout and riverine otters may live
almost entirely in stream channels. **secrlinear** provides functions
to manipulate linear habitat masks (the 'linearmask' class) and to
approximate distances within a network defined by a linear
mask. The linear mask and network distance function may be used directly in
**secr** functions such as `secr.fit`. **secr** $\ge 2.9.0$ allows the
simulation and analysis of linear populations (e.g., `sim.popn` now has a
'linear' option for its argument 'model2D').
See [secr-spatialdata.pdf] for additional information about the handling of spatial data in **secr**. **secrlinear** continues to rely on classes defined in **sp** (Pebesma and Bivand 2005).
\vspace{12pt}
# Introductory example
Water voles (*Arvicola amphibius*) were trapped monthly in 1984 along
0.9 km of the River Glyme near Woodstock in Oxfordshire, U.K. (Efford
1985). Two sheet-aluminium traps were set at stations 20 m apart along
one bank and checked morning and evening for 3 days. Voles were marked
with individually colour-coded ear tags. We use data from
June 1984. This was early in the vole breeding season when most voles
were overwintered adults: only 3 were young-of-the-year and these were
omitted.
Raw data files "Jun84capt.txt" and "glymetrap.txt" are provided in the
'extdata' folder of the **secrlinear** installation. These are in the
format used by **secr** (see the vignette
[secr-datainput.pdf][] for details). We first load all packages that
will be needed for this vignette and import the capture data, exactly
as for 2-dimensional habitat in **secr**. Although single-catch traps
were used, we set the detector type to "multi" to avoid later
warnings.
```{r setup, message = FALSE, warning = FALSE}
library(secrlinear) # also loads secr
options(digits = 4) # for more readable output
inputdir <- system.file("extdata", package = "secrlinear")
```
```{r readarvicola, eval = TRUE}
captfile <- paste0(inputdir, "/Jun84capt.txt")
trapfile <- paste0(inputdir, "/glymetrap.txt")
arvicola <- read.capthist(captfile, trapfile, covname = "sex")
```
Next we need to define the linear habitat geometry. For one simple
line (no branches) we can read the coordinates of the river bank from
a text file. Coordinates are in a Cartesian system with an arbitrary
origin; distances are in metres. We form a 'linearmask' object by
cutting the line at 4-m intervals.
```{r readglyme, eval = TRUE}
habitatmap <- paste0(inputdir, "/glymemap.txt")
glymemask <- read.linearmask(file = habitatmap, spacing = 4)
```
Now we can display the data:
```{r plotglyme, eval = TRUE, fig.width = 7, fig.height = 3.5}
par(mar = c(1,1,4,1))
plot(glymemask)
plot(arvicola, add = TRUE, tracks = TRUE)
plot(traps(arvicola), add = TRUE)
```
**Fig. 1.** Water vole captures along R. Glyme in June 1984. Traps (red
crosses) spanned 860 m of river bank.
We can fit a spatially explicit model to the water vole data in
three ways: (i) ignoring the linearity of the habitat (fit2DEuc), (ii)
with a linear habitat map and the default Euclidean distance model
(fit1DEuc), or (iii) with both linear habitat and an appropriate
non-Euclidean distance function (fit1DNet):
```{r fit1, eval = TRUE, warning = FALSE}
# 2-D habitat, Euclidean distance
fit2DEuc <- secr.fit(arvicola, buffer = 200, trace = FALSE)
# 1-D habitat, Euclidean distance
fit1DEuc <- secr.fit(arvicola, mask = glymemask, trace = FALSE)
# 1-D habitat, river distance
fit1DNet <- secr.fit(arvicola, mask = glymemask, trace = FALSE,
details = list(userdist = networkdistance))
```
The second fit displays the warning "using Euclidean distances with
linear mask". We compare the parameter estimates from the three
models:
```{r predict, eval = TRUE}
predict(fit2DEuc)
predict(fit1DEuc)
predict(fit1DNet)
```
Analysis with a 2-dimensional mask and Euclidean distances gives an
estimated density of 1.3 voles per ha. When the mask is linear,
density is expressed in animals per km (here $\hat D = 26.5$/km, SE
5.3/km). Using the correct distances (i.e. distances measured along
the river with function `networkdistance`) has only a slight effect in
this instance because the river is unbranched and nearly straight.
In the rest of this vignette we explore in more detail the options for
linear SECR that are available in **secrlinear**.
# Linear habitat masks
The representation of linear habitat in **secrlinear** is based on the
SpatialLinesDataFrame object class defined in the package **sp**
(Pebesma and Bivand 2005). This provides the habitat template - e.g.,
a map of a stream channel network. For ease of modelling the mask is
discretized into equal-length line segments, each represented by the
coordinates of its central point as for other habitat masks in
**secr**. The discretized form is the most visible aspect of a linear
mask. The underlying linear data are stored as an attribute
('SLDF'). The topology of the network is represented by an **igraph**
(Csardi and Nepusz 2006) graph object saved as the attribute
'graph'. This is an undirected graph with the length of each edge
stored as the attribute "weight".
Other objects (detectors, animals) are assumed to lie on the linear
mask. In effect, their x-y locations are snapped to the nearest mask point
(the discretized representation).
The function `read.linearmask` creates a linear mask from
inputs that define the linear network. These may be a
polyline shapefile (ESRI 1998), a SpatialLinesDataFrame, a dataframe of coordinates for line vertices,
or a file containing coordinate data (as in the introductory
example). The first two methods allow for complex combinations of lines, including branching networks. The 'spacing' argument of `read.linearmask` determines the
coarseness of the discretization.
## Input from a polyline shapefile
The 'file' argument of `read.linearmask` may be the name of a shapefile, including the
'.shp' extension. The function `st_read` from package **sf** (Pebesma 2018) is used internally to read the shapefile, from which a SpatialLinesDataFrame is created.
We can try this with a shapefile for the Silverstream catchment near Dunedin,
New Zealand, based on data from Land Information New Zealand (see
?Silverstream for details). The relevant files are 'silverstream.shp',
'silverstream.dbf' and 'silverstream.shx' in the 'extdata' folder of
**secrlinear**.
```{r silvermask, eval = TRUE}
habitatmap <- paste0(inputdir, "/silverstream.shp")
silverstreammask <- read.linearmask(file = habitatmap, spacing = 50)
par(mar = c(1,1,1,1))
plot(silverstreammask)
```
**Fig. 2.** Linear mask for Silverstream catchment, Dunedin, New Zealand.
The river flows to the bottom left. Mask points are grey dots at 50-m spacing;
these merge together in the plot.
The discretized length of a complex mask will tend to be shorter than the
original linear network as incomplete segments are dropped from the ends
of branches. See the following section for more details.
```{r networklength, eval = TRUE}
sldf <- attr(silverstreammask, "SLDF")
networklength <- sum(sp::SpatialLinesLengths(sldf)) / 1000 # km
discrepancy <- networklength - masklength(silverstreammask) # km
```
In this case the discrepancy is `r round(discrepancy,2)` km.
## Input from a SpatialLinesDataFrame
The 'data' argument of `read.linearmask` may be a SpatialLinesDataFrame object. This allows for data to be imported from GIS sources other than shapefiles, and for selection of features, re-projection, or other manipulation.
**secrlinear** is based on classes defined in the package **sp**, but we use the more modern package **sf** to read the shapefile before converting to an **sp** SpatialLinesDataFrame. For example,
```{r silvermask2, eval = FALSE}
habitatmap <- paste0(inputdir, "/silverstream.shp")
silverstreamsf <- st_read(habitatmap)
silverstreamSLDF <- as(silverstreamsf, 'Spatial')
silverstreammask <- read.linearmask(data = silverstreamSLDF, spacing = 50)
```
## Input from a dataframe of coordinates
Alternatively, a simple mask may be constructed from a dataframe of coordinates.
This example generates an artificial geometry.
```{r dataframemask, eval=TRUE}
x <- seq(0, 4*pi, length = 200)
xy <- data.frame(x = x*100, y = sin(x)*300)
linmask <- read.linearmask(data = xy, spacing = 20)
```
```{r plotlinmask, eval = TRUE}
plot(linmask)
```
**Fig. 3.** Artificial linear habitat mask. Each mask segment
is 20 m long (pixel centres are indicated by a grey dot).
## Input from a text file of coordinates
See water vole example.
# Network distance
If the movement of animals is largely confined to the linear network
then it makes sense to measure distances along the network rather than
as the crow flies. Network distances are non-Euclidean. If the animals
concentrate their movements around linear features, but do not use
them exclusively, then other non-Euclidean approaches may be
appropriate (cf Royle et al. 2013, Sutherland et al. 2015). These
require the estimation of an additional parameter and use a
2-dimensional habitat mask.
The **igraph** function `shortest.distance` is used within the
**secrlinear** function `networkdistance` to compute approximate
distances on the network implied by a linear mask. In the case of a
single line, the distance is simply the product of the mask spacing
and the number of steps (intervening points + 1).
The interactive `showpath` function allows you to verify the
distance computation for chosen points on the network:
```{r showpath, eval = FALSE}
# start interactive session and click on two points
showpath(silverstreammask, lwd = 3)
```
\setkeys{Gin}{height=85mm, keepaspectratio=true}
![][showpath]
**Fig. 4.** Network path between two arbitrary points, displayed using
`showpath`. The dashed line corresponds to the Euclidean distance.
Euclidean and network distances are reported.
The network graph used by `networkdistance` is typically that
generated by `read.linearmask` and saved as the 'graph' attribute of a
linearmask. Network distance is then the sum of distances
between adjacent mask points along the shortest network path. The
distance is approximate because --
1. A simple adjacency rule is used to construct the network from the mask
points, supplemented by the terminal points of lines. Points are considered 'adjacent' if their Euclidean separation is less
than the mask 'spacing' times the mask attribute 'spacingfactor'
(default 1.5). The graph formed using this rule mirrors the linear
network except for occasional shortcut 'skips' where lines meet obliquely.
2. The distance is based on discretization of the linear network as a set of
segments represented by their centroids. Incomplete terminal segments are dropped if they occur at the end of a Lines object. The component Line objects within a Lines object (Pebesma and Bivand 2005) are effectively placed end-on-end, and centroids are spaced equally along the combined length.
The convenience of the discretized network generally outweighs the
cost of any imprecision, especially when the mask spacing is
small. The risk of skips increases with the spacing
factor. Reducing the spacing factor increases the risk of introducing
breaks in the network.
The user may replace the default graph with one that more precisely
represents the linear network. For example, nodes may be added
precisely at the intersections of streams, and linked by edges to the
adjoining mask points, while deleting 'skip' edges. You are mostly
on your own here, but see https://rpubs.com/edzer/spatialnetworks/ and
possibly https://CRAN.R-project.org/package=shp2graph/.
The functions `showedges`, `replot` and `deleteedges` are provided for editing the graph (see Examples in `help(deleteedges)`). `addedges` may be used to replace edges deleted by mistake or to bridge unwanted gaps in a network. `cleanskips` drops all but the shortest edge joining any two different lines; it is called by default by `read.linearmask`.
# Detector layouts
Detector layouts ('traps' objects in **secr**) are the same in linear and
2-dimensional habitat models, except for the fact that detectors are
located along the linear habitat features. There is no special 'lineartraps' class.
Input formats for detector layouts are described in [secr-datainput.pdf][].
The function `make.line` generates a traps object with
detectors spaced equally along each line in a linear mask, or in clustered patterns, possibly with a random offset as shown here:
```{r makeline, eval = TRUE}
trps <- make.line(linmask, detector = "proximity", n = 40, startbuffer = 0, by = 300,
endbuffer = 80, cluster = c(0,40,80), type = 'randomstart')
```
```{r plotline, eval = TRUE, fig.width = 7, fig.height = 3.5}
plot(linmask)
plot(trps, add = TRUE, detpar = list(pch = 16, cex = 1.2, col='red'))
```
**Fig. 5.** Detectors placed with `make.line`
`make.line` places detectors on each component line of the mask, independently of other lines. The 'randomstart' option is not guaranteed to provide a spatially representative sample. You may also locate detectors in the field by GPS and import the data with `read.traps`. For ad hoc tests, detectors may be placed just by clicking on a map:
```{r snappoints, eval = FALSE}
plot(silverstreammask)
loc <- locator(30)
xy <- snapPointsToLinearMask(data.frame(loc), silverstreammask)
tr <- read.traps(data = xy, detector = 'multi')
plot(tr, add = TRUE)
```
For distance calculations in **secrlinear**, the location of each detector is 'snapped' to the nearest mask point. This behaviour is slightly undesirable because it limits network distances
to discrete values (usually multiples of the mask spacing), but it should have little effect as long as mask spacing is much less than detector spacing.
The 'graph' attribute this will be used by
`networkdistance` to compute distances. Edges should have a 'weight' attribute equal to the distance between centroids (equal to the pixel size for most pixel pairs, but no all).
Only point detector types are allowed. This excludes area search
(polygon, polygonX) and transect search (transect, transectX). Data
are commonly collected by searching a linear habitat (e.g., using
dogs to search for scat along riverbanks). Model such data by
discretizing the searched transect(s) as a series of point detectors. There are several ways to do this. Probably the most straightforward is to import both the transect track(s) and the point detections to **secr** as a capthist object with detector type 'transect'; the transect(s) may then be cut into equal segments with the `snip` function, and the snipped transects converted to point detectors with the `reduce` method for capthist data. The x-y coordinates for the centre of each segment may also be generated manually or with the **secrlinear** function `make.line`, but this requires more effort to match detections to searched segments. Data from searches most likely are of detector type 'count' to allow a Poisson number of detections per
animal per occasion per segment.
This code shows the steps to take; see [secr-datainput.pdf][] for data formats.
```{r transect, eval = FALSE}
transects <- read.traps('transectxy.txt', detector = 'transect')
capt <- read.table('capt.txt')
tempCH <- make.capthist(capt, transects, fmt = 'XY')
tempCH <- snip(tempCH, by = 100) # for 100-m segments
CH <- reduce(tempCH, outputdetector = "count")
```
# Simulating detection data
Here we describe the core functions used to simulate detection data for linear habitats. See also [Evaluating study designs](#evaluating-study-designs).
`sim.linearpopn` generates a population of a known density
distributed at random along a linear mask. This is merely a simple
wrapper for the more elaborate **secr** function `sim.popn`.
Further, the **secr** function `sim.capthist` may be used
to generate detection datasets ('capthist' objects) from a specified
population and detector layout ('traps' object). (This capability is
used in the help files to fake data for demonstrating other
functions).
For 2-dimensional habitat, `sim.capthist` will generate a
population automatically if none is provided. For linear habitat, the
user must explicitly simulate a population and supply this as the
value of the 'popn' argument of `sim.capthist`.
The linear mask supplied to `sim.linearpopn` is retained as an
attribute of the simulated population and used in `sim.capthist` as an
argument of the supplied 'userdist' function. This enables detection
probabilities to be modelled in `sim.capthist` using the network
distance between each detector and an animal's home-range centre.
Here's how it works, carrying on our Silverstream example:
```{r silvertrps, eval = TRUE, echo = FALSE}
trapfile <- paste0(inputdir, "/silverstreamtraps.txt")
tr <- read.traps(trapfile, detector = "multi")
```
```{r simCH, eval = TRUE, cache = TRUE}
# simulate population of 2 animals / km
pop <- sim.linearpopn(mask = silverstreammask, D = 2)
# simulate detections using network distances
CH <- sim.capthist(traps = tr, popn = pop, noccasions = 4,
detectpar = list(g0 = 0.25, sigma = 500),
userdist = networkdistance)
summary(CH) # detector spacing uses Euclidean distances
```
```{r plotsim, eval=TRUE}
# and plot the simulated detections...
par(mar = c(1,1,1,1))
plot(silverstreammask)
plot(CH, add = TRUE, tracks = TRUE, varycol = TRUE, rad = 100, cappar = list(cex = 2))
plot(tr, add = TRUE)
```
**Fig. 6.** Simulated detections of linear population. Colours vary
(subtly) among individuals. Red crosses indicate trap
locations. Movements ('tracks') are shown as-the-crow-flies rather than along the network.
# Model fitting
Spatially explicit capture--recapture models for linear populations
may be fitted using the **secr** function `secr.fit` with only slight
alterations:
1. A linear mask is provided as the 'mask' argument.
2. Network distances are used instead of Euclidean distances.
Network distances may be specified either by passing a pre-computed
matrix of distances between each trap (rows) and each mask point
(columns), or by passing the function `networkdistance` that will
compute the matrix on-the-fly. Either the function or the matrix is
provided as the value of the 'details' component 'userdist' e.g.,
`details = list(userdist = networkdistance)`. Continuing the
Silverstream example,
```{r sfit, eval = FALSE}
userd <- networkdistance(tr, silverstreammask)
userd[!is.finite(userd)] <- 1e8 # testing
sfit <- secr.fit(CH, mask = silverstreammask, details = list(userdist = userd))
predict(sfit)
```
Linear habitat models differ from 2-dimensional models in the
interpretation of the parameters. Population density (D) is expressed
as the expected or realised number of animals per kilometer of
habitat, rather than per hectare. The detection function represents
the probability of detection ($g$) or the expected number of
detections ($\lambda$) at a given *network distance* from an
animal's home-range centre. In other words, the fitted parameter $\sigma$
scales with network distance (the units for $\sigma$ are metres, as with
a 2-dimensional model).
# Advanced topics
## Population size and effective sampling area
The usual **secr** functions for population size in a defined region,
and for derived (conditional-likelihood) estimates, may be applied to
1-D models as for 2-D models, but the interpretation of the output
differs. We can see this with the water vole models fitted earlier:
```{r regionN, eval = TRUE}
region.N(fit2DEuc)
region.N(fit1DNet)
```
The region of interest and the population model have been defined
quite differently for 1-D and 2-D, and it is no surprise that the
estimated populations are also different. For 2-D, the region is the
original mask (area `r round(maskarea(fit2DEuc$mask),1)` ha) and the
estimated density is extrapolated across its full extent although we believe animals are confined to the riverbank: this makes little sense. For 1-D, the region is the length of 'glymemask' along the river
(`r round(masklength(fit1DNet$mask),2)` km).
```{r plotregion, eval = TRUE, fig.width = 6.5, fig.height=3}
par(mfrow = c(1,2), mar = c(1,1,1,1))
plot(fit2DEuc$mask)
plot(traps(arvicola), add = TRUE)
mtext(side = 3,line = -1.8, "fit2DEuc$mask", cex = 0.9)
plot(fit1DNet$mask)
plot(traps(arvicola), add = TRUE)
mtext(side = 3,line = -1.8,"fit1DNet$mask", cex = 0.9)
```
**Fig. 7.** Comparison of 2-D and 1-D masks for water voles on R. Glyme. Traps in red.
Comparison of results from the `derived` function is also instructive:
```{r derived, eval = TRUE}
derived(fit2DEuc)
derived(fit1DNet)
```
In the 1-D model 'esa' (or $a$) refers to 'effective linear extent of
sampling' rather than 'effective sampling area', and the units are
km. The relative precision of $\hat a$ (CVa) is very much better for the 1-D
model than the 2-D model, and this improves the precision of the
density estimate. The other component of precision (CVn) is identical
for the two models when `distribution = "poisson"` (the default).
## Linear habitat covariates
Mask covariates are used to model spatial variation in population
density. If the input to `read.linearmask` is a SpatialLinesDataFrame
then its line-level attributes become covariates of the final
mask. Covariates may also be added either directly, by assigning new
columns in the 'covariates' dataframe, or indirectly, using the
**secr** function `addCovariates`. `addCovariates` extracts data for
each mask point from another spatial data source; this will usually be
a SpatialPolygonsDataFrame or 2-dimensional mask object.
Suppose we wish to model density as a function of distance from the
main channel (spine). In this code we interactively identify the
multiple lines comprising the spine and compute a new column for the
covariates dataframe.
```{r covariates, eval = FALSE}
# interactively obtain LineID for central 'spine' by clicking on
# each component line in plot
tmp <- getLineID(silverstreammask)
# extract coordinates of 'spine'
spine <- subset(silverstreammask, LineID = tmp$LineID)
# obtain network distances to spine and save for later use
netd <- networkdistance(spine, silverstreammask) # matrix dim = c(nrow(spine), nrow(mask))
dfs <- apply(netd, 2, min) / 1000 # km
covariates(silverstreammask)$dist.from.spine <- dfs
```
Now let's plot the covariate --
```{r plotcovariate, eval = FALSE}
par(mar=c(1,1,1,4))
plot(silverstreammask, covariate = 'dist.from.spine', col = topo.colors(13),
cex = 1.5, legend = FALSE)
strip.legend('right', legend = seq(0, 6.5, 0.5), col = topo.colors(13),
title = 'dist.from.spine km', height = 0.35)
plot(spine, add = TRUE, linecol = NA, cex = 0.3)
```
\setkeys{Gin}{height=90mm, keepaspectratio=true}
![][disttospine]
**Fig. 8.** Computed covariate of Silverstream mask points -- distance in km from central channel (spine)
The covariate may appear later as a predictor of density in
`secr.fit` formulae (e.g., D $\sim$ dist.from.spine). Remember
that if you wish to predict detection parameters (g0, sigma etc.) the
relevant covariate(s) generally should be associated with detectors (the 'traps'
component of a 'capthist' object).
## Discontinuities and traversable non-habitat
'Habitat' is the set of places where an animal may live, which we interpret for SECR as the set of possible home-range centres (loosely defined). Under the usual 2-dimensional SECR model, detection is a function of distance between a detector and the home-range centre irrespective of the continuity of habitat. This entails the (usually unstated) assumption that non-habitat is traversable, and detection is not prevented by intervening non-habitat. The reverse assumption (non-habitat is not traversed) is the default in linear SECR models. This is because distance is calculated from the network graph, and mask points that are not connected, either directly or indirectly, are considered to be an infinite distance apart.
Detections of an individual at unconnected detectors are strictly impossible under the default model, and such data will cause `secr.fit` to fail. This may result simply from data entry errors, or from a local failure of the algorithm used by `read.linearmask` to construct the graph. The function `checkmoves` finds animals whose sequential re-detection distances ('moves') lie outside a specified acceptable range:
```{r checkmoves, eval = FALSE, strip.white = TRUE}
# initially OK (no movement > 1000 m)--
checkmoves(arvicola, mask = glymemask, accept = c(0,1000))
# deliberately break graph of linear mask
attr(glymemask, 'graph')[200:203,201:204] <- NULL
# no longer OK --
out <- checkmoves(arvicola, mask = glymemask, accept = c(0,1000))
# display captures of animals 32 and 35 whose records span break
out$df
```
```{r showedges, eval = FALSE}
# problem shows up where voles recaptured either side of break:
showedges(glymemask, col = 'red', lwd = 6)
plot(out$CH, add = TRUE, tracks = TRUE, rad=8,cappar=list(cex=1.5))
pos <- traps(arvicola)['560.B',]
text(pos$x+5, pos$y+80, 'break', srt=90, cex=1.1)
```
Where individual detection histories are found to span a linear habitat gap, or for biological reasons we expect them to do so, the default network graph is an inadequate model for movement. It may be sufficient to 'bridge' breaks in the graph by adding edges manually:
```{r plotglymeedges, eval = FALSE}
plot(glymemask)
replot(glymemask) # click on corners to zoom in
showedges(glymemask, col = 'red', lwd = 2, add=T)
glymemask <- addedges(glymemask)
```
The edge weight (edge length) assigned by `addedges` defaults to the Euclidean geographical distance between pixel centroids, which is an adequate approximation for small distances. The appropriate weight for edges added to bridge zones of non-habitat is a larger question that will depend on the biology of the species, and may involve estimated non-Euclidean distance parameters as in Royle et al. (2013) and [secr-noneuclidean.pdf][].
## Linear home-range size
The implementation of the SECR model in **secrlinear** uses $\sigma$ as the parameter to represent the spatial scale of detection. For many purposes $\sigma$ may also be interpreted as the spatial scale of animal movements, or an index of home-range size. However, there are subtleties in the relation between $\sigma$ and home-range size for both 2-dimensional and linear habitat. Two animals with the same value of $\sigma$ will have the same home-range size (perhaps defined as a 95\% activity area) only if there is an equal extent of habitat within their respective neighbourhoods. This need not be the case for individuals in a dendritic network (Fig. 9).
```{r linearHR, eval = FALSE}
par(mfrow = c(1,1), mar = c(1,1,1,5))
plot(silverstreammask)
centres <- data.frame(locator(4))
OK <- networkdistance(centres, silverstreammask) < 1000
for (i in 1:nrow(OK)) {
m1 <- subset(silverstreammask, OK[i,])
plot(m1, add = TRUE, col = 'red', cex = 1.7)
ml <- masklength(m1)
points(centres, pch = 16, col = 'yellow', cex = 1.4)
text (1406000, mean(m1$y), paste(ml, 'km'), cex = 1.2)
}
```
\setkeys{Gin}{height=90mm, keepaspectratio=true}
![][networklength]
**Fig. 9.** Varying lengths of stream within 1 km of four arbitrarily located home range centres (yellow dots) (distances measured along network). Stream length associated with each centre depends on local habitat geometry.
It is possible, in principle, to fit a model in which $\sigma$ varies across the network inversely with the channel density, so as to maintain a constant length of channel in each animal's home range.
## Evaluating study designs
The R package **[secrdesign]** is a convenient wrapper for the
simulation capability of **secr**, and this extends to linear
habitats. Use **secrdesign** to assess potential study
designs with respect to measures such as the likely number of
detections and repeat detections, and the expected bias and precision
of density estimates from a fitted model. Here is a simple example
using two linear detector layouts with a population
at two densities (50/km, 200/km) in a linear habitat.
```{r secrdesign, eval = TRUE, warning = FALSE}
library(secrdesign)
# create a habitat geometry
x <- seq(0, 4*pi, length = 200)
xy <- data.frame(x = x*100, y = sin(x)*300)
linmask <- read.linearmask(data = xy, spacing = 5)
# define two possible detector layouts
trp1 <- make.line(linmask, detector = "proximity", n = 80, start = 200, by = 30)
trp2 <- make.line(linmask, detector = "proximity", n = 40, start = 200, by = 60)
trplist <- list(spacing30 = trp1, spacing60 = trp2)
# create a scenarios dataframe
scen1 <- make.scenarios(D = c(50,200), trapsindex = 1:2, sigma = 25, g0 = 0.2)
# we specify the mask, rather than construct it 'on the fly',
# we will use a non-Euclidean distance function for both
# simulating detections and fitting the model...
det.arg <- list(userdist = networkdistance)
fit.arg <- list(details = list(userdist = networkdistance))
# run the scenarios and summarise results
sims1 <- run.scenarios(nrepl = 50, trapset = trplist, maskset = linmask,
det.args = list(det.arg), fit.args = list(fit.arg),
scenarios = scen1, seed = 345, fit = FALSE)
summary(sims1)
```
Given some more time, we could set 'fit = TRUE' and assess the
bias and precision of density estimates from the four levels of
sampling intensity crossed with density.
```{r sims2, eval = FALSE}
sims2 <- run.scenarios(nrepl = 5, trapset = trplist, maskset = linmask,
det.args = list(det.arg), fit.args = list(fit.arg), scenarios = scen1,
seed = 345, fit = TRUE)
summary(sims2)
```
# Limitations, tips and troubleshooting
1. `read.linearmask` treats each Lines component of a SpatialLinesdataFrame as a separate entity, and discretizes it independently of other lines. The input (even a
SpatialLinesDataFrame) has no inherent topology (junctions are not coded). The discretized
form has an integer number of equal-length line segments for each
Lines component in the input. This has a minor
distorting effect on network distances. Each 'Lines' component may be composed of several 'line' objects, and these need not connect. However, from version 1.0.2 onwards, terminal fragments are 'run-on' to the start of the next 'line' within the same 'Lines' object, reducing the potential truncation error.
2. Many functions in **secr** for manipulating 2-dimensional detector
arrays or model fits are unsuitable for models fitted with a linear
mask, and in some cases a linear analogue has yet to be
programmed. Examples are `buffer.contour`, `fxi.contour`,
`fxi.total`, `circular.p`, `trap.builder`, `cluster.centres`,
`distancetotrap`, `nearesttrap`, `plot.Dsurface`, and
`pdot.contour`. Many other functions have not been tested with
linear inputs: please report problems.
3. Area-search methods (polygon detector types) are not appropriate.
4. Linear search (transect detector types) are appropriate, and
would be useful, but have yet to be implemented. The problem is that
the numerical integration algorithm used in C code to predict
overlap between home ranges and the searched transect requires that
distances be computed on-the-fly for arbitrary locations, rather
than from a pre-computed matrix.
5. Two-dimensional masks are often constructed by placing a 'buffer'
around the detector array. The appropriate buffer width is a multiple
of the movement scale $\sigma$, perhaps $4\sigma$. A similar procedure
may be followed for linear masks. We note that when movements (and
home ranges) are strictly linear, a smaller multiple of $\sigma$ is
needed to encompass a given fraction of the animal's active
locations. While a radius of $2.45\sigma$ encompasses 95\% of a
circular bivariate normal home range, a span of $\pm 1.96\sigma$ spans
the same fraction of a 2-sided linear normal home range.
# References
Borchers, D. L. and Efford, M. G. (2008) Spatially explicit maximum
likelihood methods for capture--recapture studies. *Biometrics*
**64**, 377--385.
Csardi, G. and Nepusz, T. (2006) The igraph software package for
complex network research, InterJournal, Complex Systems
1695. https://igraph.org/.
Efford, M. G. (1985) *The structure and dynamics of water vole
populations*. D.Phil thesis, University of Oxford.
Efford, M. G. and Mowat, G. (2014) Compensatory heterogeneity in
spatially explicit capture--recapture data. *Ecology* **95**,
1341--1348.
ESRI (1998) *ESRI shapefile technical description*.
https://www.esri.com/library/whitepapers/pdfs/shapefile.pdf.
Pebesma, E.J. and Bivand, R. S. (2005) Classes and methods for spatial
data in R. *R News* **5(2)**, https://cran.r-project.org/doc/Rnews/.
Pebesma, E. (2018) Simple features for R: standardized support for
spatial vector data. *The R Journal* 10(1), 439--446.
https://doi.org/10.32614/RJ-2018-009
Royle, J. A., Chandler, R. B., Gazenski, K. D. and Graves,
T. A. (2013) Spatial capture--recapture models for jointly estimating
population density and landscape connectivity. *Ecology* **94**
287--294.
Sutherland, C., Fuller, A. K. and Royle, J. A. (2015) Modelling
non-Euclidean movement and landscape connectivity in highly structured
ecological networks. *Methods in Ecology and Evolution* **6**, 169--177.
# Appendix. Extended water vole example. {#appendix}
Here we continue the water vole analysis with one or two twists... The
`hcov' formulation estimates the sex ratio and allows for sex-based
models of detection (see ?hcov).
We assume the data have been prepared as in the introductory example.
```{r appendix, eval = FALSE}
# It is efficient to pre-compute a matrix of distances between traps (rows)
# and mask points (columns)
distmat <- networkdistance (traps(arvicola), glymemask, glymemask)
# Morning and evening trap checks as a time covariate
tcov <- data.frame(ampm = rep(c("am","pm"),3))
glymefit1 <- secr.fit(arvicola, mask = glymemask, trace = FALSE,
details = list(userdist = distmat),
model = g0~1, hcov = "sex")
glymefit2 <- secr.fit(arvicola, mask = glymemask, trace = FALSE,
details = list(userdist = distmat),
model = g0~ampm, timecov = tcov, hcov = "sex")
glymefit3 <- secr.fit(arvicola, mask = glymemask, trace = FALSE,
details = list(userdist = distmat),
model = g0~ampm + h2, timecov = tcov, hcov = "sex")
glymefit4 <- secr.fit(arvicola, mask = glymemask, trace = FALSE,
details = list(userdist = distmat),
model = list(sigma~h2, g0~ampm + h2),
timecov = tcov, hcov = "sex")
fitlist <- secrlist(glymefit1, glymefit2, glymefit3, glymefit4)
# dropping the detectfn (halfnormal) column to save space...
AIC(fitlist)[,-2]
# model npar logLik AIC AICc dAICc AICcwt
# glymefit4 D~1 g0~ampm + h2 sigma~h2 pmix~h2 7 -322.5 659.1 665.3 0.00 1
# glymefit3 D~1 g0~ampm + h2 sigma~1 pmix~h2 6 -347.3 706.7 711.1 45.80 0
# glymefit2 D~1 g0~ampm sigma~1 pmix~h2 5 -353.5 717.0 720.0 54.66 0
# glymefit1 D~1 g0~1 sigma~1 pmix~h2 4 -356.8 721.6 723.5 58.20 0
# summaries of estimated density and sex ratio under different models
options(digits=3)
# model does not affect density estimate
collate(fitlist, perm = c(2,3,1,4))[,,1,"D"]
# estimate SE.estimate lcl ucl
# glymefit1 26.5 5.27 18.0 39.0
# glymefit2 26.4 5.26 18.0 38.9
# glymefit3 26.3 5.25 17.9 38.8
# glymefit4 27.2 5.45 18.5 40.2
# model does affect the estimate of sex ratio (here proportion female)
collate(fitlist, perm=c(2,3,1,4))[,,1,"pmix"]
# estimate SE.estimate lcl ucl
# glymefit1 0.615 0.0954 0.421 0.779
# glymefit2 0.615 0.0954 0.421 0.779
# glymefit3 0.634 0.0938 0.439 0.793
# glymefit4 0.669 0.0897 0.477 0.817
# predictions from best model
newdata <- expand.grid(ampm = c("am", "pm"), h2 = c("F", "M"))
predict(glymefit4, newdata = newdata)
# $`ampm = am, h2 = F`
# link estimate SE.estimate lcl ucl
# D log 27.239 5.4478 18.477 40.158
# g0 logit 0.218 0.0463 0.141 0.322
# sigma log 13.624 1.8764 10.414 17.823
# pmix logit 0.669 0.0897 0.477 0.817
#
# $`ampm = pm, h2 = F`
# link estimate SE.estimate lcl ucl
# D log 27.239 5.4478 18.4768 40.158
# g0 logit 0.116 0.0293 0.0694 0.186
# sigma log 13.624 1.8764 10.4136 17.823
# pmix logit 0.669 0.0897 0.4774 0.817
#
# $`ampm = am, h2 = M`
# link estimate SE.estimate lcl ucl
# D log 27.239 5.4478 18.4768 40.158
# g0 logit 0.153 0.0392 0.0908 0.246
# sigma log 70.958 10.0551 53.8247 93.545
# pmix logit 0.331 0.0897 0.1829 0.523
#
# $`ampm = pm, h2 = M`
# link estimate SE.estimate lcl ucl
# D log 27.2394 5.4478 18.4768 40.158
# g0 logit 0.0782 0.0201 0.0468 0.128
# sigma log 70.9581 10.0551 53.8247 93.545
# pmix logit 0.3311 0.0897 0.1829 0.523
```
Some general observations:
The likelihood used is strictly for a multi-catch trap type (detector
= "multi" rather than detector = "single"), but given the low trap
saturation we do not expect this to cause any problem.
Water voles are active both day and night. The greater g0 for morning
trap checks is explained by the longer interval between the afternoon
and morning checks.
Precision is poor (RSE about 20\%). If we were concerned only with the
realised density on this river we would be justified in setting
distribution = ``binomial''; this improves the RSE ('CVD') to about 10\%:
```{r derivedapp, eval = FALSE}
derived(glymefit4, distribution = 'binomial')
# estimate SE.estimate lcl ucl CVn CVa CVD
# esa 0.9545 NA NA NA NA NA NA
# D 27.2396 2.867 22.17 33.46 0.1038 0.01747 0.1053
```
Adult males had much larger home ranges than adult females at this
time of year (many females were suckling young in burrows; males
search out and fight over oestrous females).
Reciprocal variation in g0 and sigma between the sexes to some extent
mitigated the effect of sex differences on density estimates (cf
Efford and Mowat Ecology 2014).
The terminal "buffer" on the linear mask (200--240 m beyond the
terminal traps) was adequate, even for males.
[disttospine]: disttospine.png
[showpath]: showpath.png
[networklength]: networklength.png
[secr-manual.pdf]: https://www.otago.ac.nz/density/pdfs/secr-manual.pdf
[secr-datainput.pdf]: https://www.otago.ac.nz/density/pdfs/secr-datainput.pdf
[secr-noneuclidean.pdf]: https://www.otago.ac.nz/density/pdfs/secr-noneuclidean.pdf
[secr]: https://CRAN.R-project.org/package=secr/
[secrlinear]: https://CRAN.R-project.org/package=secrlinear/
[secrdesign]: https://CRAN.R-project.org/package=secrdesign/
[secr-spatialdata.pdf]: https://www.otago.ac.nz/density/pdfs/secr-spatialdata.pdf