Top-level entry point for models with several responses per site,
individual, species, or study. Start from a wide data frame with
traits() on the formula left-hand side, or from already-stacked long
data with one row per (unit, trait) observation. Both routes estimate
the same trait covariance, pairwise correlations, shared latent axes,
and trait-specific variance. The formula syntax also supports fixed
effects plus covariance-structure keywords organised by
correlation source (none / animal / phylo / spatial) and
mode (scalar / independent / dependent / latent):
Usage
gllvmTMB(
formula,
data,
trait = "trait",
unit = "site",
unit_obs = "site_species",
cluster = "species",
cluster2 = NULL,
family = gaussian(),
weights = NULL,
REML = FALSE,
mesh = NULL,
phylo_vcv = NULL,
phylo_tree = NULL,
known_V = NULL,
lambda_constraint = NULL,
Xcoef_fixed = NULL,
control = gllvmTMBcontrol(),
missing = miss_control(),
impute = NULL,
silent = TRUE,
engine = c("tmb", "julia"),
ci_method = c("none", "wald", "profile", "bootstrap"),
ci_level = 0.95,
ci_nboot = 200L,
ci_seed = 0L,
site = NULL,
species = NULL
)Arguments
- formula
A glmmTMB-style formula, e.g.
value ~ 0 + trait + (0 + trait):env_temp + (0 + trait):env_precip. Fixed effects and any of the four-mode grid covstructs above are supported (plusphylo_slope(),animal_slope(), andmeta_V()).- data
A data frame. With an ordinary response LHS such as
value ~ ...,datais long: one row per(unit, trait)observation, with the trait column named bytrait. With atraits()LHS,datais wide: one row per unit and one column per response named insidetraits(...). Missing response values are allowed: by default they are dropped before fitting, whilemissing = miss_control(response = "include")masks them out of the likelihood and keeps original-row accounting forpredict_missing(). Ordinary missing predictors, grouping variables, and design-matrix values still error; explicitly modelled missing predictors usemi(x)withmissing = miss_control(predictor = "model")andimpute = list(...).- trait
Name of the column holding the trait factor (the "trait" dimension of the unit × trait response matrix). Default
"trait".- unit
Name of the column holding the between-unit grouping factor (the "unit" dimension of the unit × trait response matrix). Examples:
"site"for site × species data,"individual"for behavioural-syndrome data,"species"for PGLLVM,"paper"for systematic mapping. Default"site".- unit_obs
Name of the column holding the within-unit grouping factor — one level per (unit, replicate) cell — used by
latent(0 + trait | unit_obs, ...)andindep(0 + trait | unit_obs)for the W-tier covariance. Default"site_species"(the conventional name in joint species distribution modelling; safe for site × species data). For other domains pass e.g.unit_obs = "obs"for behavioural syndromes.- cluster
Name of the column holding the third grouping factor (the "cluster" slot). When the column name matches the row/column names of
phylo_vcv(or the tip labels ofphylo_tree), this slot also drives the phylogenetic random effects (phylo_latent,phylo_scalar,phylo_indep,phylo_slope). When the column does not match a phylogenetic correlation, the slot still functions as a regular crossed/nested third grouping (e.g.cluster = "population"for 3-level personality data,cluster = "study"for multi-study meta-analysis). Default"species". For datasets with no third grouping the column may be absent fromdata; the engine will synthesise a placeholder.The engine does not enforce nesting between
unit_obs,unit, andcluster— crossed and nested designs both fit. Two canonical patterns:Functional biogeography (crossed):
unit = "site",unit_obs = "site_species",cluster = "species". Sites and species are crossed;site_speciesindexes the intersection cell.3-level personality (strictly nested):
unit = "individual",unit_obs = "session_id",cluster = "population".
- cluster2
Optional name of the column holding a second independent grouping factor (the "cluster2" slot), for fitting two crossed (or nested) plain diagonal per-trait variance components at once. Default
NULL(slot inactive). It is a plain crossed/nested diagonal grouping only – it carries no phylogenetic or spatial correlation (those stay bound tocluster/coords). Aindep(0 + trait | <cluster2 col>)term then fits a per-trait variance at this second grouping, exactly asclusterdoes at the third slot. Example:cluster = "site",cluster2 = "year"to fit a site variance and a year variance simultaneously. As with the other slots, nesting is not enforced (crossed and nested both fit). The cluster2 column must be disjoint from theunit/unit_obs/clustercolumns (a diagonal term routes to whichever slot its grouping column matches).- family
A
familyobject. The multivariate engine (formulas withlatent(),indep(), etc.) supportsgaussian(),binomial()(logit / probit / cloglog),poisson()(log link),ordinal_probit()(the gllvmTMB-native ordinal threshold family with sigma_d = 1 fixed exactly; no delta-method approximation),lognormal()(log link),Gamma(link = "log"),nbinom2()(NB2 negative binomial; log link),nbinom1()(NB1 negative binomial; log link),tweedie()(compound Poisson-Gamma; log link),Beta()(logit link, mean-precision parameterisation; y in (0, 1)),betabinomial()(logit link, mean-precision parameterisation; k-of-n data),student()(heavy-tailed continuous; identity link),truncated_poisson()(zero-truncated count; log link),truncated_nbinom2()(zero-truncated NB2; log link), and the hurdle / two-part familiesdelta_lognormal()/delta_gamma()(Bernoulli for presence + Lognormal/Gamma for the positive component; share one linear predictor under the current implementation – see Details). For multi-trial binomial or beta-binomial (k-of-n) data, write the LHS offormulaascbind(successes, failures)(the canonical R /glm()/glmmTMBconvention); the engine then uses the per-row trial countsuccesses + failuresas the binomial size. Aweights =numeric vector of trial counts is also accepted as an alternative API (see Details). The negative-binomial families fit one log-dispersion parameter per trait:nbinom1()uses Var = mu * (1 + phi), whilenbinom2()uses Var = mu + mu^2 / phi. Thetweedie()family fits one log-phi and one logit-(p-1) per trait, with p in (1, 2). TheBeta()andbetabinomial()families each fit one log-precision per trait (a = mu*phi, b = (1-mu)*phi; Smithson & Verkuilen 2006). Thestudent()family fits one log-sigma and one log(df-1) per trait (df > 1; passstudent(df = 3)to fix df at a known value). Thetruncated_*()families require strictly positive integer responses (y >= 1). The delta families fit one log-dispersion of the positive component per trait (sigma_lognormal or phi_gamma).- weights
Optional numeric vector of length
nrow(data). Family- conditional semantics (lme4 / glmmTMB convention):For non-binomial families,
weightsis interpreted as per- observation likelihood multipliers — each row's log-likelihood contribution is multiplied byweights[i]. This matches theweightsargument oflme4::lmer(),glmmTMB::glmmTMB()andstats::glm(). Use this for relative-abundance weighting (sqrt(p_si)), inverse-variance weighting (1 / var_ifor heteroscedastic Gaussian fits), or to down-weight outliers. Must be non-negative and finite;weights[i] = 0zeroes that row's contribution to the joint NLL.For binomial families fit without a
cbind(succ, fail)LHS,weightscontinues to be interpreted as the per-row trial count (binomial size; alternative API tocbind(succ, fail)). This pre-existing semantics is preserved on a per-row basis: in mixed- family fits, binomial rows useweights[i]as their trial count and non-binomial rows use it as the likelihood multiplier. DefaultNULLis equivalent to a length-nrow(data)vector of ones (unweighted Bernoulli on binomial rows). Wide matrix calls throughgllvmTMB_wide()and wide data-frame calls throughtraits()normalise their accepted weight shapes to this same stacked response vector before fitting.
- REML
Logical; use restricted maximum likelihood for Gaussian-only fits. The current REML pilot is deliberately narrow: all response rows must be Gaussian, observation weights are not supported, missing responses must use the default
miss_control(response = "drop"), andmi()predictor models, predictor-informedlatent(..., lv = ~ x)scores, andXcoef_fixedmaps are not supported. The observed fixed design must be full rank with positive residual degrees of freedom. The defaultFALSEkeeps the historical ML fit.- mesh
Optional mesh object from
make_mesh(). Required for anyspatial_*()orspatial()term unless that term supplies its ownmesh =argument; ignored only when the model has no spatial term.- phylo_vcv
(legacy global) Optional tip-only
n_species × n_speciesphylogenetic correlation matrix. The canonical syntax isvcv =inside eachphylo_*()keyword.— prefer
tree =. The dense path inverts viaMatrix::solve(), giving the same MLE as the sparse path but atO(n^2)memory andO(n^3)Cholesky cost. Use only when you have a Cphy in hand and no Newick tree (e.g. comparing againstnlme::corPagel).- phylo_tree
(legacy global) Optional
ape::phylotree. The current syntax is to passtree =inside eachphylo_*()keyword (e.g.phylo_latent(species, d = K, tree = tree)); this argument is the older outer-level fallback and is slated for soft deprecation. When supplied (in either form) thephylo_*()terms build a sparse \(\mathbf{A}^{-1}\) over tips + internal nodes natively, using only ape and Matrix – noMCMCglmmdependency. The construction is the deterministic Hadfield & Nakagawa (2010) sparse phylogenetic inverse (appendix eqs. 26-29), adopted from that method and ported from the sister package drmTMB (seeinst/COPYRIGHTS). The result is~5nnon-zeros for ann-tip tree, vsn^2for the dense path. This is the recommended path at anyn_species; the speedup grows to ~24× atn_species = 1000.- known_V
Optional list of block-diagonal sampling-error matrices
V_t. Used by theequalto()two-stage workflow when sampling-error matrices are available from a prior stage of estimation.- lambda_constraint
Optional list with elements
unitand / orunit_obs(plusphy,spdefor structural levels), each ann_traits × dmatrix of confirmatory loading constraints (galamm-style).NAentries are estimated; numerical entries are pinned. Upper-triangle entries are silently ignored — the engine's lower-triangular parameterisation already fixes those at zero. DefaultNULLuses the engine's exploratory lower-triangular convention. Seeconfirmatory_lambda()to build the matrix from functional-group membership, orsuggest_lambda_constraint()for a minimum statistical identification scaffold when you have no biological hypothesis. Deprecated legacy element namesBandWare still accepted (with a one-shot soft deprecation message) and map tounitandunit_obsrespectively.- Xcoef_fixed
Optional named numeric vector of fixed-effect coefficient constraints. Names must match the expanded fixed-effect design columns (
fit$X_fix_names); values must currently be0, pinning those coefficients exactly at structural zero. Use this when a predictor is meaningful for some responses but should be fixed at zero for others. Native TMB fits use a parameter map; admittedengine = "julia"fixed-effect-X rows pass the same zero mask to GLLVM.jl. This is ML-only:REML = TRUEstops loudly. Native fitted objects still report all fixed-effect rows; pinned rows haveestimate = 0,std.error = NA, andstatus = "fixed"intidy(fit, "fixed").- control
Output of
gllvmTMBcontrol().- missing
Output of
miss_control()configuring missing-data handling. The defaultmiss_control()(response = "drop",predictor = "fail") is the historical complete-case behaviour.miss_control(response = "include")keeps rows with a missing response and masks them out of the likelihood, preserving original-row accounting infit$missing_data.- impute
Optional specification of the covariate model for a predictor declared missing with
mi(x)informula, used only whenmissing = miss_control(predictor = "model"). Supply a two-sided predictor-model formula (for Gaussian sugar, e.g.x ~ z) or animpute_model()object for an explicit predictor family. The defaultNULLis appropriate when nomi()term is present.- silent
Logical; suppress TMB and gllvmTMB chatter. Default
TRUE.- engine
Character;
"tmb"(default) fits with the native TMB engine,"julia"routes the fit through the experimental GLLVM.jl bridge fitting path via JuliaCall (seeR/julia-bridge.R). The Julia path currently maps the unconstrained-ordination core (a singlelatent()block + per-trait intercepts) and errors on structures it does not yet support.- ci_method
Confidence-interval route requested at fit time for admitted
engine = "julia"no-X rows. One of"none"(default),"wald","profile", or"bootstrap". Nativeengine = "tmb"fits useconfint()after fitting; non-defaultci_*arguments therefore error unlessengine = "julia". Grouped-dispersion rows, per-trait ordinal rows, response masks, mixed-family vectors, and fixed-effect-X rows remain gated.- ci_level
Nominal confidence level when
ci_method != "none"on the Julia bridge.- ci_nboot
Number of parametric bootstrap replicates when
ci_method = "bootstrap"on the Julia bridge.- ci_seed
Seed passed to the Julia bootstrap CI route.
- site
(deprecated) alias for
unit. Kept for backward compatibility. Useunit = ...in new code.- species
(deprecated) alias for
cluster. Kept for backward compatibility. Usecluster = ...in new code.
Value
A gllvmTMB object. With no covariance-structure terms in
the formula the result has class "gllvmTMB" (single-response
engine); with latent() or other covstruct sugar it has class
c("gllvmTMB_multi", "gllvmTMB") (multi-trait engine).
Either way, S3 methods such as tidy(), predict(),
vcov(), logLik() etc. dispatch on gllvmTMB; gllvmTMB_multi-
specific methods (e.g. trait-level extract_ICC_site(),
extract_communality()) are available for multi-trait fits.
Details
| source \ mode | scalar | independent | dependent | latent |
| none | (omit) | indep() | dep() | latent() |
| animal | animal_scalar() | animal_indep() | animal_dep() | animal_latent() |
| phylo | phylo_scalar() | phylo_indep() | phylo_dep() | phylo_latent() |
| spatial | spatial_scalar() | spatial_indep() | spatial_dep() | spatial_latent() |
The three covariance modes (indep / dep / latent) encode
covstruct intent across traits:
latent— the decomposition mode \(\boldsymbol\Sigma = \boldsymbol\Lambda \boldsymbol\Lambda^\top + \boldsymbol\Psi\): a low-rank shared part plus a diagonal \(\boldsymbol\Psi\) companion. Ordinarylatent()carries \(\boldsymbol\Psi\) by default; theunique =argument controls it (latent(..., unique = FALSE)for the loadings-only subset,*_latent(..., unique = TRUE)to fold the \(\boldsymbol\Psi\) companion into a source-specific term).indep— the marginal-only mode: each trait gets its own variance, no cross-trait covariance.dep— the full unstructured mode: \(\boldsymbol\Sigma\) is free with \(T(T+1)/2\) parameters via a Cholesky factor.
Plus the supporting phylo_slope() / animal_slope() (random
slopes), meta_V() (known-V meta-analytic; meta_known_V() is a
deprecated alias), and the engine-internal propto() /
equalto() covstructs (used by the canonical keywords above; not
typically called directly). See the
formula keyword grid article
for a one-paragraph tour of each cell.
gllvmTMB() parses the glmmTMB-style formula, converts wide
traits() input to the same internal stacked-trait representation as
explicit long data, and dispatches to the underlying TMB template.
Covariance-structure terms (latent(), indep(), propto(),
equalto(), spatial()) are processed by extending the formula parser
and the TMB template.
Per the manuscript, when stacking traits one should set
dispformula = ~ 0 so that no implicit residual variance competes
with structured indep(0 + trait | …) terms. gllvmTMB() enforces
this internally.
Multi-trial binomial. The TMB engine evaluates dbinom(y, n_trials, p)
so binomial fits are not restricted to Bernoulli (size = 1). To pass a
per-row trial count, use either:
cbind(successes, failures) ~ ...on the LHS of the formula (canonical R /glmmTMBconvention; recommended), ora flat response
ywithweights = n_trials(the alternative glmmTMB API).
Both interfaces produce identical fits. For Bernoulli data, omit
weights and use a 0/1 response; the engine sets n_trials = 1 and
the likelihood is identical to the previous Bernoulli-only behaviour.
Missing responses and predictors. NA response cells are treated as
unobserved unit-trait cells. Under the default
miss_control(response = "drop"), they are dropped before the TMB likelihood
is built; with miss_control(response = "include"), they are kept in the
model data, masked out of the likelihood, and available through
predict_missing(). For cbind(successes, failures) binomial responses, a
row is treated as missing when either response component is missing.
Observation weights, when supplied, are subset to retained likelihood rows
before validation. Ordinary missing predictors, grouping variables, or
fixed-effect design values still error because the model cannot construct a
design row; explicitly modelled missing predictors use mi(x),
miss_control(predictor = "model"), and impute = list(...).
These contracts are covered by the package's missing-data validation tests,
both for responses and for predictors.
Delta (hurdle) families. delta_lognormal() and delta_gamma()
use a single linear predictor for both components: presence is
\(\Pr(y > 0) = \mathrm{invlogit}(\eta)\) and the positive-component
mean is \(E[y \mid y > 0] = \exp(\eta)\). This matches sdmTMB's
standard delta default (one fixed-effects matrix, one set of trait
random effects). The shared-predictor scheme is the simplest hurdle
formulation; a future release may decouple the two predictors so
presence and abundance can have independent fixed and random effects.
Only type = "standard" (the default) is currently wired in the
multivariate engine; type = "poisson-link" is not implemented. Each
delta family carries one per-trait dispersion of the positive
component (no extra Bernoulli dispersion). The response must be
non-negative.
Per-trait residual variance: when does it activate?
"Residual" in a mixed-effects model is scale-relative: what
counts as residual variance shifts as you add levels to the model.
In a Gaussian fit without a per-row diagonal term, the residual is row-level
noise captured by a single shared sigma_eps. Once you add a per-row
indep(0 + trait | obs) term,
the row-level residual is now T per-trait random-effect variances and
sigma_eps is auto-suppressed to avoid double-counting. If you also add a
site-level diagonal term on top, the row-level term remains the residual and
the site-level term is now an additional, higher-level random effect, not
a residual at all. The dispatch table below records this explicitly for the
configurations the engine supports today.
Gaussian and lognormal responses have one residual scale parameter,
sigma_eps. Ordinary Gamma responses instead carry a per-trait shape
phi_gamma (CV = 1 / sqrt(phi_gamma)). Per-trait residual variances
only appear if you explicitly add a per-row indep(...) term.
The dispatch is automatic:
- No per-row
indep, Gaussian/lognormal present One shared
sigma_epsacross Gaussian/lognormal rows. Not per-trait.- No per-row
indep, no Gaussian/lognormal rows sigma_epsis mapped off; the family's intrinsic dispersion handles the residual (for ordinary Gamma,phi_gamma).indep(0 + trait | g)whereghas fewer levels than rows (e.g.g = "site")sigma_epsis still estimated as the row-level residual; the diagonal term adds a per-trait random effect at levelgon top.indep(0 + trait | obs)at the per-row level (one level per row), Gaussian/lognormal rows fittedsigma_epsis auto-suppressed (mapped off, fixed at a tiny stabiliser); the T per-trait diagonal random effects are the residual. A one-shotcli::cli_informfires at fit time announcing the auto-suppression.indep(0 + trait | obs)at the per-row level, non-Gaussian or mixed-family fitTreated as observation-level random effects (OLRE). For Bernoulli traits the OLRE is statistically unidentifiable and is mapped off; for hurdle / delta families a warning is emitted (see "Per-family-aware OLRE selection" below).
Mnemonic: Gaussian/lognormal sigma_eps is the default; ordinary Gamma uses
phi_gamma; per-row indep replaces the Gaussian/lognormal scalar residual
with T per-trait residuals; non-per-row indep adds a higher-level random
effect on top of the family residual;
non-continuous families never carry sigma_eps regardless.
Per-family-aware OLRE selection. When
indep(0 + trait | <unit_obs>) is at
per-row resolution, i.e. one row per (trait, unit_obs) cell, the resulting
per-trait random effects on the linear predictor are an observation-level
random effect (OLRE). The engine now decides per trait what to do with the
OLRE variance based on the trait's response family:
single-trial Bernoulli (
binomial(), all rows haven_trials == 1):theta_diag_W[t]and the correspondings_Wcolumn are mapped off. The OLRE is statistically unidentifiable here (no within-cell replication) and the MLE is the trivial boundary \(\sigma_W \to 0\); pinning the parameter at \(\sigma_W \approx 10^{-6}\) removes the spurious free parameter and emits a one-shot informational message. Multi-trial binomial (viacbind(succ, fail)orweights = n_trials) is identifiable and fit normally.delta / hurdle families (
delta_lognormal(),delta_gamma()): OLRE is fit as before, but a one-shot warning is emitted because the OLRE acts on the shared linear predictor of the hurdle and mixes presence and positive-component noise.all other families: OLRE is fit as before.
In a mixed-family fit (per-row family list) the per-trait decision applies only to the affected trait. References: Nakagawa & Schielzeth (2010) Biol. Rev. 85: 935-956; Nakagawa, Johnson & Schielzeth (2017) J. R. Soc. Interface 14: 20170213.
See also
traits() for wide data-frame formula input;
gllvmTMB_wide() for wide matrix/data-frame input;
simulate_site_trait() for
generating recovery test data;
extract_Sigma() for the unified post-fit covariance API;
gllvmTMB_diagnose() for a one-stop convergence + identifiability
health check; ordinal_probit() for the gllvmTMB-native ordinal
threshold family.
Examples
if (FALSE) { # \dontrun{
set.seed(1)
sim <- simulate_site_trait(n_sites = 50, n_species = 8, n_traits = 3,
mean_species_per_site = 5)
fit <- gllvmTMB(
value ~ 0 + trait + (0 + trait):env_1 + (0 + trait):env_2,
data = sim$data,
family = gaussian(),
trait = "trait",
unit = "site"
)
summary(fit)
} # }
