| sdmTMB | R Documentation |
Fit a spatial or spatiotemporal generalized linear mixed effects model (GLMM) with TMB (Template Model Builder), by default through the RTMB package. Spatial and spatiotemporal Gaussian random fields are approximated with the SPDE (stochastic partial differential equation) approach, which represents them as Gaussian Markov random fields on a mesh. This allows for efficient modelling of data that are correlated in space and/or time. Areal spatial and spatiotemporal models (simultaneous or conditional autoregressive; SAR or CAR) are also available. See the model description vignette for details.
sdmTMB(
formula,
data,
mesh,
time = NULL,
family = gaussian(link = "identity"),
spatial = c("on", "off"),
spatiotemporal = c("iid", "ar1", "rw", "off"),
offset = NULL,
weights = NULL,
spatial_model = c("spde", "sar", "car"),
share_range = TRUE,
anisotropy = FALSE,
extra_time = NULL,
spatial_varying = NULL,
time_varying = NULL,
time_varying_type = c("rw", "rw0", "ar1"),
dispformula = ~1,
nonlocal_formula = NULL,
nonlocal_data = NULL,
knots = NULL,
censored_upper = NULL,
distribution_column = NULL,
silent = TRUE,
reml = FALSE,
priors = sdmTMBpriors(),
bayesian = FALSE,
control = sdmTMBcontrol(),
previous_fit = NULL,
do_fit = TRUE,
experimental = NULL,
do_index = FALSE,
predict_args = NULL,
index_args = NULL
)
formula |
A model formula. Random intercepts and slopes use lme4
syntax, e.g., |
data |
A data frame. Rows with missing values in the response or any
variable the model uses (including |
mesh |
An object from |
time |
An optional time column name (as character). Can be left as
|
family |
A family object specifying the response distribution and
link: |
spatial |
Estimate spatial random fields? Options are |
spatiotemporal |
Estimate the spatiotemporal random fields as |
offset |
A numeric vector representing the model offset or a character value representing the column name of the offset. In delta/hurdle models, this applies only to the positive component except for Poisson-link delta models, where it also enters the occurrence-probability calculation. Usually a log transformed variable. |
weights |
An optional numeric vector (not a column name) of weights on
each observation's contribution to the likelihood. As in glmmTMB,
weights need not sum to one and are not rescaled. For binomial-type
families with a proportion response, |
spatial_model |
Spatial process model. |
share_range |
Logical: estimate a single range parameter shared by the
spatial and spatiotemporal fields ( |
anisotropy |
Logical: allow for anisotropy (spatial correlation that is
directionally dependent)? See |
extra_time |
Optional extra time slices (e.g., years) to include for interpolation or forecasting with the predict function. See the Details section below. |
spatial_varying |
An optional one-sided formula of coefficients that
should vary in space as random fields. Allows the effect of a covariate to
differ spatially. You likely want to include the same variable as a fixed
effect in |
time_varying |
An optional one-sided formula of coefficients that vary
through time following the process set by |
time_varying_type |
The process for
For |
dispformula |
A one-sided formula for the dispersion parameter (e.g.,
|
nonlocal_formula |
An optional one-sided formula of non-local
covariate effects: |
nonlocal_data |
An optional data frame supplying the
|
knots |
Optional named list containing knot values to be used for basis
construction of smoothing terms. See |
censored_upper |
Upper bounds for the censored families (see
Families): a numeric vector or the name of a column in |
distribution_column |
For experimental multi-family models, the name of
the column in |
silent |
Silent or include optimization details? Helpful to set to
|
reml |
Logical: use REML (restricted maximum likelihood) estimation
rather than maximum likelihood? REML accounts for uncertainty in estimating
fixed effects and can reduce bias in variance parameter estimates, but
prevents likelihood-based model comparison (e.g., AIC) between models with
different fixed effects. Use |
priors |
Optional penalties/priors via |
bayesian |
Logical indicating if the model will be passed to
tmbstan. If |
control |
Optimization control options via |
previous_fit |
A previously fitted sdmTMB model to initialize the
optimization with. Can greatly speed up fitting. Note that the model must
be set up exactly the same way. However, the data and |
do_fit |
Fit the model ( |
experimental |
A named list for esoteric or in-development options. Here be dragons. |
do_index |
|
predict_args |
|
index_args |
|
Model description
sdmTMB fits GLMMs with spatial and/or spatiotemporal random fields, which account for correlation in the data due to spatial proximity, or alternatively, latent spatial and spatiotemporal effects. Spatial fields represent consistent spatial patterns, while spatiotemporal fields represent spatial patterns that vary over time. See the model description vignette for mathematical details and the paper: \Sexpr[results=rd]{tools:::Rd_expr_doi("10.18637/jss.v115.i02")}
Binomial families
Following the structure of stats::glm() and glmmTMB, a binomial
family can be specified in one of four ways: (1) the response may be a factor
(success is interpreted as any level other than the first level), (2) the
response may be binary (0/1), (3) the response can be a matrix of form
cbind(success, failure), and (4) the response may be observed
proportions, and the weights argument is used to specify the binomial size
(N) parameter (prob ~ ..., weights = N).
Smooth terms
Smooth terms can be included following GAMs (generalized additive models)
using + s(x), which implements a smooth from mgcv::s(). sdmTMB uses
penalized smooths, constructed via mgcv::smooth2random(). This is a similar
approach implemented in gamm4 and brms, among other packages.
Within these smooths, the same syntax commonly used in mgcv::s() or
mgcv::t2() can be applied, e.g. 2-dimensional smooths may be constructed
with + s(x, y) or + t2(x, y); smooths can be specific to various factor
levels, + s(x, by = group); the basis function dimensions may be specified,
e.g. + s(x, k = 4); and various types of splines may be constructed such as
cyclic splines to model seasonality (perhaps with the knots argument also
supplied).
Threshold models
A linear break-point relationship for a covariate can be included via
+ breakpt(variable) in the formula, where variable is a single covariate
corresponding to a column in data. In this case, the relationship is linear
up to a point and then constant (hockey-stick shaped).
Similarly, a logistic-function threshold model can be included via
+ logistic(variable). This option models the relationship as a logistic
function of the 50% and 95% values. This is similar to length- or size-based
selectivity in fisheries, and is parameterized by the points at which f(x) =
0.5 or 0.95. See the
threshold vignette.
Note that only a single threshold covariate can be included. For delta
families, the threshold applies to both components, and threshold terms are
not available if formula is a list.
Extra time: forecasting or interpolating
Extra time slices (e.g., years) can be included for interpolation or
forecasting with the predict function via the extra_time argument. The
predict function requires all time slices to be defined when fitting the
model to ensure the various time indices are set up correctly. Be careful if
including extra time slices that the model remains identifiable. For example,
including + as.factor(year) in formula will render a model with no data
to inform the expected value in a missing year. sdmTMB() makes no attempt
to determine if the model makes sense for forecasting or interpolation. The
options time_varying, spatiotemporal = "rw", spatiotemporal = "ar1",
or a smoother on the time column provide mechanisms to predict over missing
time slices; time_varying and spatiotemporal random-walk or AR(1) fields
include process error.
extra_time can also be used to fill in missing time steps for the purposes
of a random walk or AR(1) process if the gaps between time steps are uneven.
extra_time can include only extra time steps or all time steps including
those found in the fitted data. This latter option may be simpler.
Regularization and priors
You can achieve regularization via penalties (priors) on the fixed effect
parameters. See sdmTMBpriors(). You can fit the model once without
penalties and look at the output of print(your_model) or tidy(your_model)
or fit the model with do_fit = FALSE and inspect
head(your_model$tmb_data$X_ij[[1]]) if you want to see how the formula is
translated to the fixed effect model matrix. Also see the
Bayesian vignette.
Delta/hurdle models
Delta models (also known as hurdle models) can be fit as two separate models
or at the same time by using an appropriate delta family (see the list under
family). Delta families with type = "poisson-link" use a Poisson-link
parameterization instead of a classic hurdle model; see the Poisson-link vignette.
If fit with a delta family, by default the formula, spatial, and spatiotemporal
components are shared. Some elements can be specified independently for the two models
using a list format. These include formula, spatial, spatiotemporal,
and share_range. The first element of the list is for the binomial component
and the second element is for the positive component (e.g., Gamma).
Other elements must be shared for now (e.g., spatially varying coefficients,
time-varying coefficients), and dispformula applies to the positive
component only. Furthermore, there are currently limitations if
specifying two formulas as a list: smoothers and random effect terms must
be identical between the two formulas, and threshold effects must be
specified through a single formula that is shared across the two models.
The main advantage of specifying such models using a delta family (compared
to fitting two separate models) is (1) coding simplicity and (2) calculation
of uncertainty on derived quantities such as an index of abundance with
get_index() using the generalized delta method within TMB. Also, selected
parameters can be shared across the models.
See the delta-model vignette.
Censored families
Censored families treat some observations as known only to lie within a
range, e.g., catch counts on longlines where competition for hooks hides
the true number caught. Supply the bounds with censored_upper. All but
censored_poisson() need the RTMB backend (the default). See Families
and the hook competition vignette.
Areal models
For data on areal units (e.g., grid cells or management areas) rather than
point locations, build a domain with make_areal_domain(), pass it to
mesh, and set spatial_model = "sar" or "car". share_range and
anisotropy do not apply. See the areal model vignette.
Index standardization
For index standardization, you may wish to include 0 + as.factor(year)
(or whatever the time column is called) in the formula. See a basic
example of index standardization in the relevant
package vignette.
You will need to specify the time argument. See get_index().
An object (list) of class sdmTMB. Useful elements include:
model: output from stats::nlminb()
sd_report: output from TMB::sdreport() or RTMB::sdreport()
gradients: gradients of the marginal log likelihood with respect to
each fixed effect
data: the data used in fitting (rows with missing values removed)
spde: the object supplied to mesh
family: the family object, including the inverse link function
family$linkinv()
backend: "rtmb" or "tmb"; see sdmTMBcontrol()
tmb_obj: the objective function object from TMB::MakeADFun() or
RTMB::MakeADFun()
tmb_data, tmb_params, tmb_map: the data, parameter, and map lists
passed to MakeADFun()
Main reference introducing the package to cite when using sdmTMB:
Anderson, S.C., E.J. Ward, P.A. English, L.A.K. Barnett, J.T. Thorson. 2025. sdmTMB: an R package for fast, flexible, and user-friendly generalized linear mixed effects models with spatial and spatiotemporal random fields. Journal of Statistical Software. 115(2):1–46. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.18637/jss.v115.i02")}.
Reference for local trends:
Barnett, L.A.K., E.J. Ward, S.C. Anderson. 2021. Improving estimates of species distribution change by incorporating local trends. Ecography. 44(3):427-439. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1111/ecog.05176")}.
Further explanation of the model and application to calculating climate velocities:
English, P., E.J. Ward, C.N. Rooper, R.E. Forrest, L.A. Rogers, K.L. Hunter, A.M. Edwards, B.M. Connors, S.C. Anderson. 2022. Contrasting climate velocity impacts in warm and cool locations show that effects of marine warming are worse in already warmer temperate waters. Fish and Fisheries. 23(1) 239-255. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1111/faf.12613")}.
Discussion of and illustration of some decision points when fitting these models:
Commander, C.J.C., L.A.K. Barnett, E.J. Ward, S.C. Anderson, T.E. Essington. 2022. The shadow model: how and why small choices in spatially explicit species distribution models affect predictions. PeerJ 10: e12783. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.7717/peerj.12783")}.
Application and description of threshold/break-point models:
Essington, T.E., S.C. Anderson, L.A.K. Barnett, H.M. Berger, S.A. Siedlecki, E.J. Ward. 2022. Advancing statistical models to reveal the effect of dissolved oxygen on the spatial distribution of marine taxa using thresholds and a physiologically based index. Ecography. 2022: e06249 \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1111/ecog.06249")}.
Application to fish body condition:
Lindmark, M., S.C. Anderson, M. Gogina, M. Casini. 2023. Evaluating drivers of spatiotemporal variability in individual condition of a bottom-associated marine fish, Atlantic cod (Gadus morhua). ICES Journal of Marine Science. 80(5): 1539–1550.
Non-local covariates:
Lindmark, M., Anderson, S.C., and Thorson, J.T. 2026. Estimating scale-dependent covariate responses using two-dimensional diffusion derived from the stochastic partial differential equation method. Methods in Ecology and Evolution 17: 207–218. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1111/2041-210X.70177")}.
Thorson, J.T., Anderson, S.C., and Lindmark, M. 2026. Temperature carryover effect revealed for marine fishes using spatio-temporal distributed lag models. EcoEvoRxiv. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.32942/X2W95P")}.
Several sections of the original TMB model code were adapted from the VAST R package:
Thorson, J.T. 2019. Guidance for decisions using the Vector Autoregressive Spatio-Temporal (VAST) package in stock, ecosystem, habitat and climate assessments. Fish. Res. 210:143–161. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1016/j.fishres.2018.10.013")}.
Code for the family R-to-TMB implementation, selected parameterizations of
the observation likelihoods, general package structure inspiration, and the
idea behind the TMB prediction approach were adapted from the glmmTMB R
package:
Brooks, M.E., K. Kristensen, K.J. van Benthem, A. Magnusson, C.W. Berg, A. Nielsen, H.J. Skaug, M. Maechler, B.M. Bolker. 2017. glmmTMB Balances Speed and Flexibility Among Packages for Zero-inflated Generalized Linear Mixed Modeling. The R Journal, 9(2):378-400. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.32614/rj-2017-066")}.
Implementation of geometric anisotropy with the SPDE and use of random field GLMMs for index standardization:
Thorson, J.T., A.O. Shelton, E.J. Ward, H.J. Skaug. 2015. Geostatistical delta-generalized linear mixed models improve precision for estimated abundance indices for West Coast groundfishes. ICES J. Mar. Sci. 72(5): 1297–1310. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1093/icesjms/fsu243")}.
library(sdmTMB)
# Build a mesh to implement the SPDE approach:
mesh <- make_mesh(pcod_2011, c("X", "Y"), cutoff = 20)
# - this example uses a fairly coarse mesh so these examples run quickly
# - 'cutoff' is the minimum distance between mesh vertices in units of the
# x and y coordinates
# - 'cutoff = 10' might make more sense in applied situations for this dataset
# - or build any mesh in 'fmesher' and pass it to the 'mesh' argument in make_mesh()
# - the mesh is not needed if you will be turning off all
# spatial/spatiotemporal random fields
# Quick mesh plot:
plot(mesh)
# Fit a Tweedie spatial random field GLMM with a smoother for depth:
fit <- sdmTMB(
density ~ s(depth),
data = pcod_2011, mesh = mesh,
family = tweedie(link = "log")
)
fit
# Extract coefficients:
tidy(fit, conf.int = TRUE)
tidy(fit, effects = "ran_par", conf.int = TRUE)
# Perform several 'sanity' checks:
sanity(fit)
# Predict on the fitted data; see ?predict.sdmTMB
p <- predict(fit)
# Predict on new data:
p <- predict(fit, newdata = qcs_grid)
head(p)
# Visualize the depth effect with ggeffects:
ggeffects::ggpredict(fit, "depth [all]") |> plot()
# Visualize depth effect with visreg: (see ?visreg_delta)
visreg::visreg(fit, xvar = "depth") # link space; randomized quantile residuals
visreg::visreg(fit, xvar = "depth", scale = "response")
visreg::visreg(fit, xvar = "depth", scale = "response", rug = FALSE)
# Add spatiotemporal random fields:
fit <- sdmTMB(
density ~ 0 + as.factor(year),
time = "year", #<
data = pcod_2011, mesh = mesh,
family = tweedie(link = "log")
)
fit
# Make the fields AR1:
fit <- sdmTMB(
density ~ s(depth),
time = "year",
spatial = "off",
spatiotemporal = "ar1", #<
data = pcod_2011, mesh = mesh,
family = tweedie(link = "log")
)
fit
# Make the fields a random walk:
fit <- sdmTMB(
density ~ s(depth),
time = "year",
spatial = "off",
spatiotemporal = "rw", #<
data = pcod_2011, mesh = mesh,
family = tweedie(link = "log")
)
fit
# Depth smoothers by year:
fit <- sdmTMB(
density ~ s(depth, by = as.factor(year)), #<
time = "year",
spatial = "off",
spatiotemporal = "rw",
data = pcod_2011, mesh = mesh,
family = tweedie(link = "log")
)
fit
# 2D depth-year smoother:
fit <- sdmTMB(
density ~ s(depth, year), #<
spatial = "off",
data = pcod_2011, mesh = mesh,
family = tweedie(link = "log")
)
fit
# Turn off spatial random fields:
fit <- sdmTMB(
present ~ poly(log(depth)),
spatial = "off", #<
data = pcod_2011, mesh = mesh,
family = binomial()
)
fit
# Which matches glm():
fit_glm <- glm(
present ~ poly(log(depth)),
data = pcod_2011,
family = binomial()
)
summary(fit_glm)
AIC(fit, fit_glm)
# Delta/hurdle binomial-Gamma model:
fit_dg <- sdmTMB(
density ~ poly(log(depth), 2),
data = pcod_2011, mesh = mesh,
spatial = "off",
family = delta_gamma() #<
)
fit_dg
# Delta model with different formulas and spatial structure:
fit_dg <- sdmTMB(
list(density ~ depth_scaled, density ~ poly(depth_scaled, 2)), #<
data = pcod_2011, mesh = mesh,
spatial = list("off", "on"), #<
family = delta_gamma()
)
fit_dg
# Delta/hurdle truncated NB2:
pcod_2011$count <- round(pcod_2011$density)
fit_nb2 <- sdmTMB(
count ~ s(depth),
data = pcod_2011, mesh = mesh,
spatial = "off",
family = delta_truncated_nbinom2() #<
)
fit_nb2
# Regular NB2:
fit_nb2 <- sdmTMB(
count ~ s(depth),
data = pcod_2011, mesh = mesh,
spatial = "off",
family = nbinom2() #<
)
fit_nb2
# IID random intercepts by year:
pcod_2011$fyear <- as.factor(pcod_2011$year)
fit <- sdmTMB(
density ~ s(depth) + (1 | fyear), #<
data = pcod_2011, mesh = mesh,
family = tweedie(link = "log")
)
fit
# Correlated random intercepts and slopes by year:
fit <- sdmTMB(
density ~ (depth | fyear), #<
data = pcod_2011, mesh = mesh,
family = tweedie(link = "log")
)
# Spatially varying coefficient of year:
pcod_2011$year_scaled <- as.numeric(scale(pcod_2011$year))
fit <- sdmTMB(
density ~ year_scaled,
spatial_varying = ~ 0 + year_scaled, #<
data = pcod_2011, mesh = mesh, family = tweedie(), time = "year"
)
fit
# Time-varying effects of depth and depth squared:
fit <- sdmTMB(
density ~ 0 + as.factor(year),
time_varying = ~ 0 + depth_scaled + depth_scaled2, #<
data = pcod_2011, time = "year", mesh = mesh,
family = tweedie()
)
print(fit)
# Extract values:
est <- as.list(fit$sd_report, "Estimate")
se <- as.list(fit$sd_report, "Std. Error")
est$b_rw_t[, , 1]
se$b_rw_t[, , 1]
# Linear break-point effect of depth:
fit <- sdmTMB(
density ~ breakpt(depth_scaled), #<
data = pcod_2011,
mesh = mesh,
family = tweedie()
)
fit
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.