Skip to contents

Deprecated alias. Engine-internal name; users should write spatial_unique() (or one of the other spatial_scalar() / spatial_latent() keywords) in formulas. Kept for backward compatibility and as the canonical place to document the underlying Matérn / SPDE kernel.

Usage

spde(coords, trait)

Arguments

coords

A formula-token placeholder. The actual coordinate columns are read from mesh (the second argument to gllvmTMB()).

trait

An unquoted token; usually the literal trait (the long-format engine treats every level of this factor as a separate response, with one SPDE field per level).

Value

A formula marker; never evaluated as a call.

Details

A formula keyword for adding a spatial random field to a gllvmTMB() fit, one independent field per trait, sharing a common range parameter. The field is approximated as a Gaussian Markov random field (GMRF) on a triangulated mesh via the Lindgren–Rue–Lindström SPDE construction.

What kernel is this?

The SPDE construction approximates a Matérn covariance function:

$$\mathrm{cov}\bigl(\,r(\mathbf{s}_i),\, r(\mathbf{s}_j)\,\bigr) \;=\; \sigma^{2}\,\frac{2^{1-\nu}}{\Gamma(\nu)}\, \bigl(\kappa\, \|\mathbf{s}_i - \mathbf{s}_j\|\bigr)^{\nu}\, K_{\nu}\bigl(\kappa\, \|\mathbf{s}_i - \mathbf{s}_j\|\bigr),$$

where \(\nu\) is the smoothness parameter, \(\kappa > 0\) is the inverse-range parameter, and \(K_{\nu}\) is the modified Bessel function of the second kind. gllvmTMB::spatial_unique() (and its siblings spatial_scalar() / spatial_latent()) use \(\alpha = 2\) in the Lindgren–Rue–Lindström operator, which (in 2 spatial dimensions) corresponds to \(\nu = 1\).

\(\nu = 1\) sits between the two extremes that practitioners often default to:

Kernel\(\nu\)SmoothnessUsed by
Exponential\(1/2\)non-differentiableglmmTMB::exp(), metafor::SPEXP, brms::gp(... cov="exponential")
Matérn (this engine)\(1\)once mean-square differentiablegllvmTMB::spatial_unique(), sdmTMB::sdmTMB(spatial="on"), INLA::inla.spde2.matern()
Matérn 3/2\(3/2\)once differentiablenot implemented here
Matérn 5/2\(5/2\)twice differentiablenot implemented here
Squared-exponential / Gaussian\(\infty\)infinitely smoothbrms::gp(... cov="exp_quad")

Matérn \(\nu = 1\) is the de facto standard for spatial ecological data: smooth enough to be sensible for organisms or processes that vary continuously in space, but rough enough to capture genuinely fine-scale variation. It is also the default of every R-INLA and sdmTMB workflow, so spatial_unique() results compare cleanly against those packages.

How the model is parameterised

Each trait \(t\) gets its own GMRF \(r_t\) on the same mesh, with:

  • Shared inverse-range parameter \(\kappa\) (i.e. one kappa is estimated; trait-specific ranges would require a per-trait kappa, which is not currently implemented). The practical range is \(\sqrt{8}/\kappa\), the distance at which correlation drops to ~0.13.

  • Per-trait precision parameter \(\tau_t\). The marginal variance is \(\sigma_t^{\,2} = 1 / (4\pi\,\kappa^{2}\,\tau_t^{\,2})\).

On the precision side the SPDE/GMRF prior is \(\mathbf{Q}_t \;=\; \tau_t^{\,2}\,(\kappa^{4}\,\mathbf{M}_0 + 2\kappa^{2}\,\mathbf{M}_1 + \mathbf{M}_2)\), where \(\mathbf{M}_0, \mathbf{M}_1, \mathbf{M}_2\) are the fmesher-built finite-element mass and stiffness matrices. The resulting matrix is sparse (each row has a small constant number of non-zeros), so TMB's sparse Cholesky scales linearly with mesh size — that's the speed advantage over glmmTMB::exp()'s dense \(n \times n\) covariance matrix (see vignette("spde-vs-glmmTMB") for the live benchmark).

Reduced-rank spatial loadings (spatial_latent())

spatial_unique() gives one independent field per trait. The reduced-rank analogue — \(K\) shared spatial fields driving all \(T\) traits via a \(T \times K\) loading matrix \(\boldsymbol\Lambda_{\mathrm{spa}}\), exactly what phylo_latent() does for phylogeny — is the canonical spatial_latent() keyword. Internally the same SPDE engine fits both: when spatial_latent() is active the template's spde_lv_k switch flips on, swapping the per-trait \(\boldsymbol\omega_t\) for a packed \(\boldsymbol\Lambda_{\mathrm{spa}}\) multiplied into K shared fields \(\boldsymbol\omega_k\) (each given the same Matérn prior as the per-trait fields, with \(\tau\) absorbed into \(\boldsymbol\Lambda_{\mathrm{spa}}\) for identifiability — the standard rr / phylo_latent() convention).

Usage

Inside a gllvmTMB() formula, paired with a mesh built from the coordinate columns of data:

df$pos <- glmmTMB::numFactor(df$lon, df$lat)   # only if comparing
mesh   <- make_mesh(df, c("lon", "lat"), cutoff = 0.07)
fit    <- gllvmTMB(
  value ~ 0 + trait + spatial_unique(0 + trait | coords),
  data = df, mesh = mesh
)

The canonical 0 + trait | coords syntax (LHS = the trait factor, RHS = the coords placeholder) means "one independent SPDE field per level of trait, indexed by the coordinate columns named in mesh". The actual coordinate column names live in the mesh object (see make_mesh()); the coords token in the formula is just a placeholder. The pre-0.1.4 orientation coords | trait is also accepted but emits a one-shot lifecycle deprecation warning per session; see spatial_unique().

References

Lindgren, F., Rue, H. & Lindström, J. (2011). An explicit link between Gaussian fields and Gaussian Markov random fields: the stochastic partial differential equation approach. Journal of the Royal Statistical Society: Series B (Statistical Methodology) 73(4): 423–498. https://doi.org/10.1111/j.1467-9868.2011.00777.x

See also

make_mesh(), add_utm_columns(), vignette("spde-vs-glmmTMB")

Examples

if (FALSE) { # \dontrun{
set.seed(2025)
s <- simulate_site_trait(
  n_sites = 60, n_species = 1, n_traits = 3, mean_species_per_site = 1,
  spatial_range = 0.3, sigma2_spa = rep(0.4, 3), seed = 1
)
df   <- s$data
mesh <- make_mesh(df, c("lon", "lat"), cutoff = 0.07)
fit  <- gllvmTMB(value ~ 0 + trait + spatial_unique(0 + trait | coords),
                 data = df, mesh = mesh)
fit$report$kappa            # inverse-range parameter
sqrt(8) / fit$report$kappa  # practical range
} # }