| Title: | Modelling Framework for the Estimation of Salmonid Abundance |
|---|---|
| Description: | Estimates capture probabilities and densities from multipass removal (electrofishing) data, following Millar et al. (2016) <doi:10.1016/j.fishres.2016.01.001>. Capture probability is modelled on the logit scale using fixed effects, penalised smooth terms and random effects, for example for between visit and between sample variation. The likelihood is computed using 'RTMB', with random effects integrated out using the Laplace approximation. |
| Authors: | Colin Millar [aut, cre], Rob Fryer [aut], Iain Malcolm [aut], Ross Glover [aut], Marine Scotland Science [cph] |
| Maintainer: | Colin Millar <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 2.0.0 |
| Built: | 2026-10-09 23:29:26 UTC |
| Source: | https://github.com/faskally/ef |
A set of functions to estimate capture probabilities and densities
from multipass removal (electrofishing) data. The main function is
efp, which fits a model for capture probability, and
predictions of capture probability are available from
predict.efp.
Maintainer: Colin Millar [email protected]
Authors:
Rob Fryer
Iain Malcolm
Ross Glover
Other contributors:
Marine Scotland Science [copyright holder]
Millar, C. P., Fryer, R. J., Millidine, K. J. and Malcolm, I. A. (2016) Modelling capture probability of Atlantic salmon (Salmo salar) from a diverse national electrofishing dataset: Implications for the estimation of abundance. Fisheries Research, 177, 1-12. doi:10.1016/j.fishres.2016.01.001
Glover, R. S., Fryer, R. J., Soulsby, C., Bacon, P. J. and Malcolm, I. A. (2018) Incorporating estimates of capture probability and river network covariance in novel habitat - abundance models: Assessing the effects of conservation stocking on catchment-scale production of juvenile Atlantic salmon (Salmo salar) from a long-term electrofishing dataset. Ecological Indicators, 93, 302-315. doi:10.1016/j.ecolind.2018.05.013
Useful links:
Report bugs at https://github.com/Faskally/ef/issues
Converts an efp fit to a gam object, so that the plotting and prediction
tools in the mgcv package can be used. The coefficients and their
variances are those estimated by efp, but the smoothing parameters
and effective degrees of freedom in the gam object are not.
as.gam(object)as.gam(object)
object |
an efp fitted model |
The conversion fits a model using mgcv, which can take a very
long time if the model has many random effects. Predictions do not need
the conversion, see predict.efp.
an object of class gam, see gamObject
data(ef_data) ef_data$sampleID <- factor(paste(ef_data$date, ef_data$siteID, ef_data$lifestage, ef_data$species)) m1 <- efp(count ~ lifestage + s(doy, k = 3), data = ef_data, pass = pass, id = sampleID) m1_gam <- as.gam(m1) plot(m1_gam)data(ef_data) ef_data$sampleID <- factor(paste(ef_data$date, ef_data$siteID, ef_data$lifestage, ef_data$species)) m1 <- efp(count ~ lifestage + s(doy, k = 3), data = ef_data, pass = pass, id = sampleID) m1_gam <- as.gam(m1) plot(m1_gam)
A dataset containing counts from multipass electrofishing samples over three sites and 5 years.
ef_dataef_data
A data frame with 90 rows and 10 variables:
Unique identifier for each electrofishing site
year of data collection
date of data collections
day of the year of data collection, a continuous covariate which can be used to fit smooth terms
the total number of electrofishing passes
the electrofishing pass on which the fish were caught
fish species, Salmon
fish lifestage, Fry or Parr
the number of fish caught per pass for each site visit and species, etc.
the area of river fished
https://www.gov.scot/publications/scotland-river-temperature-monitoring-network-srtmn/
This function uses the marginal likelihood of capture probabilities to estimate model parameters
efp( formula, data = NULL, pass = pass, id = id, offset = NULL, verbose = FALSE, hessian = TRUE, fit = TRUE, control = list(), sparse = TRUE )efp( formula, data = NULL, pass = pass, id = id, offset = NULL, verbose = FALSE, hessian = TRUE, fit = TRUE, control = list(), sparse = TRUE )
formula |
a formula object. Random effects are specified using
|
data |
a data.frame containing all relevant info |
pass |
a vector of integers giving the pass number of the observation |
id |
a vector of integers identifying an observation (a set of electrofishing passes) |
offset |
an optional offset for the linear predictor of capture probability |
verbose |
if TRUE optimiser messages are printed to the screen |
hessian |
if TRUE the hessian is computed and the covariance matrix of the parameters is returned via Vb |
fit |
if TRUE model is fitted if FALSE the data that would be passed to the optimiser is returned |
control |
a list of control commands to be passed to |
sparse |
if TRUE (the default) penalised smooths are set up so that
sparse bases and penalties stay sparse, see Details. If FALSE the
constrained basis and penalties from |
Random effects on the logit capture probability are included by adding
s(f, bs = "re") terms to the formula, where f is a factor
in data. For example, s(visitID, bs = "re") adds between
visit variation and s(sampleID, bs = "re") adds between sample
variation. Each term has its own standard deviation, and the
random effects are integrated out of the likelihood using the Laplace
approximation, so that logLik and AIC are based on the marginal
likelihood. Fitted values are conditional on the estimated random effects.
Smooth terms can also be included in the formula. These are penalised,
and the amount of smoothing is estimated: a smooth is fitted as a random
effect, in which the part of the smooth that is not penalised (for example
a linear trend) is a fixed effect, and the standard deviation of the
random part controls how smooth the term is. A standard deviation close to
zero means that only the unpenalised part of the smooth is needed. As for
other random effects, logLik and AIC are based on the
marginal likelihood.
The recommended smooths are first and second order random walks, using
the Markov random field basis of mgcv with a penalty from
rw_penalty:
s(f, bs = "mrf", xt = list(penalty = rw_penalty(levels(f)))),
where f is a factor whose levels are the steps of the walk. The
basis and the penalty are sparse, and are kept sparse when fitting, so
these models are quick to fit even when there are many levels. The
standard deviation is that of the steps of the random walk, and the
starting values of the walk (the first level for a first order walk, the
first two for a second order walk) are fixed effects: this means that the
intercept is the logit capture probability at the first level of the
walk, and that a second order walk has an additional fixed effect for its
linear trend, which is on an arbitrary scale.
Any other penalised smooth from mgcv can be used, such as
s(x) or te(x, z), but their penalties are dense so they
will take longer to fit, particularly if the basis dimension k is
large. To fit an unpenalised regression spline, whose flexibility is set
entirely by k, use fx = TRUE, for example
s(x, k = 3, fx = TRUE).
The fixed effects are estimated by maximum likelihood, so the likelihood
depends slightly on how the unpenalised part of a smooth is separated
from the penalised part. With sparse = TRUE one coefficient of the
smooth is fixed for each function that is not penalised, as described
above for random walks. With sparse = FALSE, and for smooths with
by variables or models without an intercept, the identifiability
constraints of mgcv are applied to the smooth first, which makes
the basis and the penalty dense. The two approaches give very similar,
but not identical, fits.
If fit = TRUE, an object of class efp, a list which
inherits from glm, containing among other things the fixed effect
estimates (coefficients), the random effect estimates
(random), the standard deviations of the random effects and
smooths (sigma),
the fitted capture probabilities (fitted), the log likelihood
(llik) and the variance matrix of the fixed effects (Vb).
See efp-methods for the methods available. If
fit = FALSE, a list containing the data that would be passed to
the optimiser.
Millar, C. P., Fryer, R. J., Millidine, K. J. and Malcolm, I. A. (2016) Modelling capture probability of Atlantic salmon (Salmo salar) from a diverse national electrofishing dataset: Implications for the estimation of abundance. Fisheries Research, 177, 1-12. doi:10.1016/j.fishres.2016.01.001
# create two electrofishing site visits with 3 and 4 passes and 2 lifestages ef_data <- data.frame(n = c(100, 53, 24, 50, 26, 12, 100, 53, 24, 50, 26, 12), pass = c( 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3), stage = c( 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2), sample = c( 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4)) ef_data2 <- data.frame(n = c(100, 53, 24, 50, 26, 12, 100, 53, 24, 12, 50, 26, 12, 6), pass = c( 1, 2, 3, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4), stage = c( 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2), sample = c( 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4)) ef_data3 <- data.frame(n = c(100, 53, 24, 50, 26, 12, 40, 100, 53, 24, 12, 50, 26, 12, 6, 40), pass = c( 1, 2, 3, 1, 2, 3, 1, 1, 2, 3, 4, 1, 2, 3, 4, 1), stage = c( 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2), sample = c( 1, 1, 1, 2, 2, 2, 5, 3, 3, 3, 3, 4, 4, 4, 4, 6)) # Fit a simple model m2 <- efp(n ~ 1 + factor(stage), data = ef_data, pass = pass, id = sample) cbind(ef_data, fit = fitted(m2)) m3 <- efp(n ~ 1 + factor(stage), data = ef_data2, pass = pass, id = sample) cbind(ef_data2, fit = fitted(m3)) m4 <- efp(n ~ 1 + factor(stage), data = ef_data3, pass = pass, id = sample) cbind(ef_data3, fit = fitted(m4)) # create two electrofishing site visits with 3 and 4 passes and 2 lifestages ef_data <- data.frame(n = c(200, 53, 24, 100, 26, 12, 200, 53, 24, 100, 26, 12), pass = c( 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3), stage = c( 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2), sample = c( 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4)) # Fit a simple model m2 <- efp(n ~ 1 + factor(stage) + factor(replace(pass, pass> 2, 2)), data = ef_data, pass = pass, id = sample) out <- cbind(ef_data, p = fitted(m2, type = "p")) out # a smooth effect of a continuous covariate, day of the year, using a # second order random walk data(ef_data) ef_data$sampleID <- factor(paste(ef_data$date, ef_data$siteID, ef_data$lifestage, ef_data$species)) ef_data$pass12 <- factor(replace(ef_data$pass, ef_data$pass > 2, 2)) days <- min(ef_data$doy):max(ef_data$doy) ef_data$fdoy <- factor(ef_data$doy, levels = days) rw2 <- rw_penalty(days, order = 2) m5 <- efp(count ~ siteID + pass12 + lifestage + s(fdoy, bs = "mrf", xt = list(penalty = rw2)), data = ef_data, pass = pass, id = sampleID) m5 # predicted capture probability over the smooth, with standard errors newdata <- data.frame(siteID = "Site1", pass12 = "1", lifestage = "Fry", fdoy = factor(days, levels = days)) pred <- predict(m5, newdata = newdata, type = "response", se.fit = TRUE) cbind(newdata, fit = pred$fit, se = pred$se.fit) # an unpenalised regression spline m6 <- efp(count ~ siteID + pass12 + lifestage + s(doy, k = 3, fx = TRUE), data = ef_data, pass = pass, id = sampleID) m6# create two electrofishing site visits with 3 and 4 passes and 2 lifestages ef_data <- data.frame(n = c(100, 53, 24, 50, 26, 12, 100, 53, 24, 50, 26, 12), pass = c( 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3), stage = c( 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2), sample = c( 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4)) ef_data2 <- data.frame(n = c(100, 53, 24, 50, 26, 12, 100, 53, 24, 12, 50, 26, 12, 6), pass = c( 1, 2, 3, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4), stage = c( 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2), sample = c( 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4)) ef_data3 <- data.frame(n = c(100, 53, 24, 50, 26, 12, 40, 100, 53, 24, 12, 50, 26, 12, 6, 40), pass = c( 1, 2, 3, 1, 2, 3, 1, 1, 2, 3, 4, 1, 2, 3, 4, 1), stage = c( 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2), sample = c( 1, 1, 1, 2, 2, 2, 5, 3, 3, 3, 3, 4, 4, 4, 4, 6)) # Fit a simple model m2 <- efp(n ~ 1 + factor(stage), data = ef_data, pass = pass, id = sample) cbind(ef_data, fit = fitted(m2)) m3 <- efp(n ~ 1 + factor(stage), data = ef_data2, pass = pass, id = sample) cbind(ef_data2, fit = fitted(m3)) m4 <- efp(n ~ 1 + factor(stage), data = ef_data3, pass = pass, id = sample) cbind(ef_data3, fit = fitted(m4)) # create two electrofishing site visits with 3 and 4 passes and 2 lifestages ef_data <- data.frame(n = c(200, 53, 24, 100, 26, 12, 200, 53, 24, 100, 26, 12), pass = c( 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3), stage = c( 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2), sample = c( 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4)) # Fit a simple model m2 <- efp(n ~ 1 + factor(stage) + factor(replace(pass, pass> 2, 2)), data = ef_data, pass = pass, id = sample) out <- cbind(ef_data, p = fitted(m2, type = "p")) out # a smooth effect of a continuous covariate, day of the year, using a # second order random walk data(ef_data) ef_data$sampleID <- factor(paste(ef_data$date, ef_data$siteID, ef_data$lifestage, ef_data$species)) ef_data$pass12 <- factor(replace(ef_data$pass, ef_data$pass > 2, 2)) days <- min(ef_data$doy):max(ef_data$doy) ef_data$fdoy <- factor(ef_data$doy, levels = days) rw2 <- rw_penalty(days, order = 2) m5 <- efp(count ~ siteID + pass12 + lifestage + s(fdoy, bs = "mrf", xt = list(penalty = rw2)), data = ef_data, pass = pass, id = sampleID) m5 # predicted capture probability over the smooth, with standard errors newdata <- data.frame(siteID = "Site1", pass12 = "1", lifestage = "Fry", fdoy = factor(days, levels = days)) pred <- predict(m5, newdata = newdata, type = "response", se.fit = TRUE) cbind(newdata, fit = pred$fit, se = pred$se.fit) # an unpenalised regression spline m6 <- efp(count ~ siteID + pass12 + lifestage + s(doy, k = 3, fx = TRUE), data = ef_data, pass = pass, id = sampleID) m6
Methods to summarise and extract information from a model fitted
using efp.
## S3 method for class 'efp' fitted(object, type = "p", ...) ## S3 method for class 'efp' vcov(object, ...) ## S3 method for class 'efp' confint(object, parm, level = 0.95, ...) ## S3 method for class 'efp' logLik(object, ...) ## S3 method for class 'efp' formula(x, ...) ## S3 method for class 'efp' summary(object, ...) ## S3 method for class 'summary.efp' print(x, digits = max(3L, getOption("digits") - 3L), ...) ## S3 method for class 'efp' residuals(object, ...) ## S3 method for class 'efp' anova(object, ...) ## S3 method for class 'efp' plot(x, ...) ## S3 method for class 'efp' model.matrix(object, ...) ## S3 method for class 'efp' print(x, digits = max(3L, getOption("digits") - 3L), ...)## S3 method for class 'efp' fitted(object, type = "p", ...) ## S3 method for class 'efp' vcov(object, ...) ## S3 method for class 'efp' confint(object, parm, level = 0.95, ...) ## S3 method for class 'efp' logLik(object, ...) ## S3 method for class 'efp' formula(x, ...) ## S3 method for class 'efp' summary(object, ...) ## S3 method for class 'summary.efp' print(x, digits = max(3L, getOption("digits") - 3L), ...) ## S3 method for class 'efp' residuals(object, ...) ## S3 method for class 'efp' anova(object, ...) ## S3 method for class 'efp' plot(x, ...) ## S3 method for class 'efp' model.matrix(object, ...) ## S3 method for class 'efp' print(x, digits = max(3L, getOption("digits") - 3L), ...)
object, x
|
an efp fitted model |
type |
the type of fitted value: |
... |
further arguments, ignored unless stated |
parm |
the parameters to compute confidence intervals for, either a vector of numbers or names. The default is all fixed effects. |
level |
the confidence level required |
digits |
the number of significant digits to print |
The variance matrix returned by vcov is for the fixed effects
only, the joint variance matrix of the fixed and random effects is
available as object$Vp. Confidence intervals are Wald intervals
based on this variance matrix.
The log likelihood is the marginal log likelihood when the model contains random effects or penalised smooths, and the degrees of freedom are the number of fixed effects plus the number of standard deviations.
Residuals, analysis of deviance tables and diagnostic plots are not available for efp fits, and the corresponding methods return an error.
fitted returns a vector, list or matrix depending on
type; model.matrix a matrix; vcov a matrix;
confint a matrix with columns giving the lower and upper
confidence limits; logLik an object of class logLik;
formula the model formula; summary an object of class
summary.efp, a list containing the coefficient table
(coefficients) and the table of random effect standard
deviations (sigma). The print methods return their
argument invisibly.
data(ef_data) ef_data$sampleID <- factor(paste(ef_data$date, ef_data$siteID, ef_data$lifestage, ef_data$species)) m1 <- efp(count ~ lifestage, data = ef_data, pass = pass, id = sampleID) summary(m1) vcov(m1) confint(m1) logLik(m1) head(fitted(m1))data(ef_data) ef_data$sampleID <- factor(paste(ef_data$date, ef_data$siteID, ef_data$lifestage, ef_data$species)) m1 <- efp(count ~ lifestage, data = ef_data, pass = pass, id = sampleID) summary(m1) vcov(m1) confint(m1) logLik(m1) head(fitted(m1))
This function transforms values on the logistic scale to values on the probability scale.
invlogit(x)invlogit(x)
x |
a numeric vector |
vector of values between 0 and 1
invlogit(c(-2, 0, 2))invlogit(c(-2, 0, 2))
This function transforms values on the probability scale to values on the logistic scale.
logit(p)logit(p)
p |
a numeric vector with values between 0 and 1 |
vector of values between -Inf and Inf
logit(c(0.1, 0.5, 0.9))logit(c(0.1, 0.5, 0.9))
Predictions of capture probability from a fitted efp model, for
the data used to fit the model or for new data.
## S3 method for class 'efp' predict( object, newdata = NULL, type = c("link", "response", "lpmatrix"), se.fit = FALSE, exclude = NULL, ... )## S3 method for class 'efp' predict( object, newdata = NULL, type = c("link", "response", "lpmatrix"), se.fit = FALSE, exclude = NULL, ... )
object |
an efp fitted model |
newdata |
a data.frame containing the covariates in the model formula. If NULL, predictions are for the data used to fit the model. |
type |
the type of prediction: |
se.fit |
if TRUE standard errors of the predictions are also returned |
exclude |
a character vector of terms to leave out of the prediction,
named as in the model output, for example |
... |
further arguments, ignored |
Predictions are conditional on the estimated random effects, unless these
are removed using exclude. For a random walk smooth, predictions can
be made for any level in the penalty, including levels with no data, which
are interpolated. Standard errors on the response scale are
calculated using the delta method.
The offset supplied to efp is included in the
predictions only when newdata is NULL.
a vector of predictions, or if se.fit = TRUE a list with
elements fit and se.fit. If type = "lpmatrix" a
matrix.
data(ef_data) ef_data$sampleID <- factor(paste(ef_data$date, ef_data$siteID, ef_data$lifestage, ef_data$species)) m1 <- efp(count ~ lifestage, data = ef_data, pass = pass, id = sampleID) newdata <- data.frame(lifestage = c("Fry", "Parr")) predict(m1, newdata = newdata, type = "response", se.fit = TRUE)data(ef_data) ef_data$sampleID <- factor(paste(ef_data$date, ef_data$siteID, ef_data$lifestage, ef_data$species)) m1 <- efp(count ~ lifestage, data = ef_data, pass = pass, id = sampleID) newdata <- data.frame(lifestage = c("Fry", "Parr")) predict(m1, newdata = newdata, type = "response", se.fit = TRUE)
Creates a first or second order random walk penalty matrix for the levels
of an ordered covariate, for use with the Markov random field (mrf) basis
of mgcv. This is the recommended way to include smooth terms in
efp: the basis and the penalty are both sparse, so models
are quick to fit even when there are many levels.
rw_penalty(levels, order = 1)rw_penalty(levels, order = 1)
levels |
the levels of the covariate, in order, and assumed to be equally spaced. A level is needed for every step of the random walk, including any for which there are no data. |
order |
the order of the random walk, 1 or 2 |
A first order random walk (order = 1) penalises the differences
between neighbouring levels, and shrinks towards a constant. A second
order random walk (order = 2) penalises the second differences, and
shrinks towards a linear trend. In both cases the standard deviation
estimated by efp is the standard deviation of the steps
of the random walk, on the logit scale.
To use the penalty, the covariate must be a factor with the same levels as the penalty, see the examples.
a square matrix with one row and column for each level, with the
levels as dimnames. The matrix has an attribute, null.space,
which is a basis for the functions which are not penalised.
rw_penalty(1:5) rw_penalty(1:5, order = 2) # a first order random walk over day of the year data(ef_data) ef_data$sampleID <- factor(paste(ef_data$date, ef_data$siteID, ef_data$lifestage, ef_data$species)) days <- min(ef_data$doy):max(ef_data$doy) ef_data$fdoy <- factor(ef_data$doy, levels = days) rw1 <- rw_penalty(days) m1 <- efp(count ~ lifestage + s(fdoy, bs = "mrf", xt = list(penalty = rw1)), data = ef_data, pass = pass, id = sampleID) m1rw_penalty(1:5) rw_penalty(1:5, order = 2) # a first order random walk over day of the year data(ef_data) ef_data$sampleID <- factor(paste(ef_data$date, ef_data$siteID, ef_data$lifestage, ef_data$species)) days <- min(ef_data$doy):max(ef_data$doy) ef_data$fdoy <- factor(ef_data$doy, levels = days) rw1 <- rw_penalty(days) m1 <- efp(count ~ lifestage + s(fdoy, bs = "mrf", xt = list(penalty = rw1)), data = ef_data, pass = pass, id = sampleID) m1
The matrix G should be of dimension n x p, and the parameter vector should be length p
transpar(par, G)transpar(par, G)
par |
fitted model parameters |
G |
The design matrix for a model |
a vector of probabilities of length n
G <- cbind(1, c(0, 0, 1, 1)) transpar(c(0.5, -0.2), G)G <- cbind(1, c(0, 0, 1, 1)) transpar(c(0.5, -0.2), G)