| Title: | Generalised Linear Latent Variable Models |
|---|---|
| Description: | Fits multivariate generalised linear latent variable models using either long-format data or a wide data frame. Models support several response distributions and trait covariance structures, and can estimate correlations, shared latent axes, and trait-specific variances. Binary presence-absence data can be used for joint species distribution analyses. Optional animal, phylogenetic, spatial, and kernel covariance components are available. Estimation uses 'Template Model Builder'. Spatial effects can use sparse Gaussian Markov random field constructions. This 0.7.1 release remains experimental: evidence for estimates is model- and regime-specific. Broad interval coverage is not certified; a 0.94 coverage floor is documented only for one two-sided Gaussian total-variance profile setting. |
| Authors: | Shinichi Nakagawa [aut, cre, cph] (ORCID: <https://orcid.org/0000-0002-7765-5182>) |
| Maintainer: | Shinichi Nakagawa <[email protected]> |
| License: | GPL-3 |
| Version: | 0.7.1 |
| Built: | 2026-10-28 23:34:37 UTC |
| Source: | https://github.com/cran/gllvmTMB |
Transform longitude/latitude columns to an equal-distance UTM coordinate
reference system with sf::st_transform().
add_utm_columns( dat, ll_names = c("longitude", "latitude"), ll_crs = 4326, utm_names = c("X", "Y"), utm_crs = get_crs(dat, ll_names), units = c("km", "m") )add_utm_columns( dat, ll_names = c("longitude", "latitude"), ll_crs = 4326, utm_names = c("X", "Y"), utm_crs = get_crs(dat, ll_names), units = c("km", "m") )
dat |
Data frame containing longitude and latitude columns. |
ll_names |
Character names of longitude then latitude columns. |
ll_crs |
Input coordinate reference system. |
utm_names |
Names for the two new coordinate columns. |
utm_crs |
Output UTM CRS; by default inferred with |
units |
Output coordinate units: kilometres or metres. |
A copy of dat with the requested UTM coordinate columns.
d <- data.frame(lat = c(52.1, 53.4), lon = c(-130, -131.4)) add_utm_columns(d, c("lon", "lat"))d <- data.frame(lat = c(52.1, 53.4), lon = c(-130, -131.4)) add_utm_columns(d, c("lon", "lat"))
animal_dep(0 + trait | id)
Full-rank unstructured additive-genetic covariance
matrix , parameterised as the Cholesky factor
of a rank- (i.e. the
animal_latent() case with ). Mathematical parallel to
phylo_dep().
animal_dep(formula, pedigree = NULL, A = NULL, Ainv = NULL)animal_dep(formula, pedigree = NULL, A = NULL, Ainv = NULL)
formula |
An lme4-bar formula of the form |
pedigree, A, Ainv
|
See |
See animal_scalar().
# Full unstructured additive-genetic trait covariance (rank = n_traits). # Grounded in test-animal-keyword.R. ped <- data.frame( id = paste0("i", 1:12), sire = c(rep(NA, 4), rep(c("i1", "i2"), length.out = 8)), dam = c(rep(NA, 4), rep(c("i3", "i4"), length.out = 8)) ) A <- pedigree_to_A(ped) yvec <- as.numeric(MASS::mvrnorm( 1, mu = rep(0, 2 * 12), Sigma = kronecker(diag(2), A) * 0.5 + diag(2 * 12) * 0.5 )) df <- data.frame( species = factor(rep(ped$id, each = 2), levels = ped$id), trait = factor(rep(c("t1", "t2"), times = 12), levels = c("t1", "t2")), value = yvec ) fit <- gllvmTMB( value ~ 0 + trait + animal_dep(0 + trait | species, A = A), data = df, family = gaussian(), unit = "species", cluster = "species" )# Full unstructured additive-genetic trait covariance (rank = n_traits). # Grounded in test-animal-keyword.R. ped <- data.frame( id = paste0("i", 1:12), sire = c(rep(NA, 4), rep(c("i1", "i2"), length.out = 8)), dam = c(rep(NA, 4), rep(c("i3", "i4"), length.out = 8)) ) A <- pedigree_to_A(ped) yvec <- as.numeric(MASS::mvrnorm( 1, mu = rep(0, 2 * 12), Sigma = kronecker(diag(2), A) * 0.5 + diag(2 * 12) * 0.5 )) df <- data.frame( species = factor(rep(ped$id, each = 2), levels = ped$id), trait = factor(rep(c("t1", "t2"), times = 12), levels = c("t1", "t2")), value = yvec ) fit <- gllvmTMB( value ~ 0 + trait + animal_dep(0 + trait | species, A = A), data = df, family = gaussian(), unit = "species", cluster = "species" )
animal_indep(0 + trait | id)
Per-trait animal-model random intercepts with no cross-trait
covariance, using the bar-form syntax; the .indep marker
disambiguates printing.
Mathematical parallel to phylo_indep().
animal_indep(formula, pedigree = NULL, A = NULL, Ainv = NULL, common = FALSE)animal_indep(formula, pedigree = NULL, A = NULL, Ainv = NULL, common = FALSE)
formula |
An lme4-bar formula of the form |
pedigree, A, Ainv
|
See |
common |
|
See animal_scalar().
# Independent per-trait animal-model intercepts via the bar form, # passing the dense relatedness matrix A directly. # Grounded in test-animal-keyword.R. ped <- data.frame( id = paste0("i", 1:12), sire = c(rep(NA, 4), rep(c("i1", "i2"), length.out = 8)), dam = c(rep(NA, 4), rep(c("i3", "i4"), length.out = 8)) ) A <- pedigree_to_A(ped) yvec <- as.numeric(MASS::mvrnorm( 1, mu = rep(0, 2 * 12), Sigma = kronecker(diag(2), A) * 0.5 + diag(2 * 12) * 0.5 )) df <- data.frame( species = factor(rep(ped$id, each = 2), levels = ped$id), trait = factor(rep(c("t1", "t2"), times = 12), levels = c("t1", "t2")), value = yvec ) fit <- gllvmTMB( value ~ 0 + trait + animal_indep(0 + trait | species, A = A), data = df, family = gaussian(), unit = "species", cluster = "species" )# Independent per-trait animal-model intercepts via the bar form, # passing the dense relatedness matrix A directly. # Grounded in test-animal-keyword.R. ped <- data.frame( id = paste0("i", 1:12), sire = c(rep(NA, 4), rep(c("i1", "i2"), length.out = 8)), dam = c(rep(NA, 4), rep(c("i3", "i4"), length.out = 8)) ) A <- pedigree_to_A(ped) yvec <- as.numeric(MASS::mvrnorm( 1, mu = rep(0, 2 * 12), Sigma = kronecker(diag(2), A) * 0.5 + diag(2 * 12) * 0.5 )) df <- data.frame( species = factor(rep(ped$id, each = 2), levels = ped$id), trait = factor(rep(c("t1", "t2"), times = 12), levels = c("t1", "t2")), value = yvec ) fit <- gllvmTMB( value ~ 0 + trait + animal_indep(0 + trait | species, A = A), data = df, family = gaussian(), unit = "species", cluster = "species" )
animal_latent(id, d = K)
Reduced-rank decomposition of the additive-genetic covariance
matrix using latent factors. Set unique = TRUE to add a
per-trait diagonal companion:
with a loadings matrix
( = number of traits, ). The latent factors
and diagonal companion both carry the same structure
across individuals.
animal_latent( id, d = 1, pedigree = NULL, A = NULL, Ainv = NULL, unique = FALSE )animal_latent( id, d = 1, pedigree = NULL, A = NULL, Ainv = NULL, unique = FALSE )
id |
Bare column name of the individual factor. |
d |
Number of latent factors ( |
pedigree |
A 3-column data frame with columns |
A |
Dense relatedness matrix ( |
Ainv |
Precision matrix (inverse of A). Sparse matrix inputs are preserved for the sparse engine route. |
unique |
Logical; when |
This is the canonical "factor-analytic G-matrix" model from
quantitative genetics (Kirkpatrick & Meyer 2004; Meyer 2009; the
WOMBAT method). Mathematical parallel to phylo_latent() – same
engine path with a pedigree-derived relatedness matrix instead of
phylogenetic VCV.
See animal_scalar().
animal_scalar(), animal_indep(), phylo_latent().
# Factor-analytic G-matrix (d latent factors plus diagonal Psi). # Grounded in test-animal-keyword.R. ped <- data.frame( id = paste0("i", 1:12), sire = c(rep(NA, 4), rep(c("i1", "i2"), length.out = 8)), dam = c(rep(NA, 4), rep(c("i3", "i4"), length.out = 8)) ) A <- pedigree_to_A(ped) yvec <- as.numeric(MASS::mvrnorm( 1, mu = rep(0, 2 * 12), Sigma = kronecker(diag(2), A) * 0.5 + diag(2 * 12) * 0.5 )) df <- data.frame( species = factor(rep(ped$id, each = 2), levels = ped$id), trait = factor(rep(c("t1", "t2"), times = 12), levels = c("t1", "t2")), value = yvec ) fit <- gllvmTMB( value ~ 0 + trait + animal_latent(species, d = 1, pedigree = ped, unique = TRUE), data = df, family = gaussian(), unit = "species", cluster = "species" )# Factor-analytic G-matrix (d latent factors plus diagonal Psi). # Grounded in test-animal-keyword.R. ped <- data.frame( id = paste0("i", 1:12), sire = c(rep(NA, 4), rep(c("i1", "i2"), length.out = 8)), dam = c(rep(NA, 4), rep(c("i3", "i4"), length.out = 8)) ) A <- pedigree_to_A(ped) yvec <- as.numeric(MASS::mvrnorm( 1, mu = rep(0, 2 * 12), Sigma = kronecker(diag(2), A) * 0.5 + diag(2 * 12) * 0.5 )) df <- data.frame( species = factor(rep(ped$id, each = 2), levels = ped$id), trait = factor(rep(c("t1", "t2"), times = 12), levels = c("t1", "t2")), value = yvec ) fit <- gllvmTMB( value ~ 0 + trait + animal_latent(species, d = 1, pedigree = ped, unique = TRUE), data = df, family = gaussian(), unit = "species", cluster = "species" )
animal_scalar(id)
Canonical name for the single-scalar (per-trait) animal-model
random intercept. Each trait carries an independent length-n_ids
draw
where is the additive genetic relatedness matrix
(the pedigree-derived numerator-relationship matrix). The variance
is shared across traits.
animal_scalar(id, pedigree = NULL, A = NULL, Ainv = NULL)animal_scalar(id, pedigree = NULL, A = NULL, Ainv = NULL)
id |
Bare column name of the individual factor. |
pedigree |
A 3-column data frame with columns |
A |
Dense relatedness matrix ( |
Ainv |
Precision matrix (inverse of A). Sparse matrix inputs are preserved for the sparse engine route. |
Mathematical parallel to phylo_scalar() – same engine path; the
only difference is that A is supplied via pedigree = (a
3-column data frame: id, sire, dam), A = (dense
relatedness matrix), or Ainv = (precision matrix). Sparse Ainv
inputs use the sparse precision route.
Used inside a gllvmTMB() formula. Returns invisible(NULL)
when called outside a formula – the keyword is a syntactic marker
that the parser rewrites internally to the canonical
phylo_scalar() path (which is family-agnostic at the math level).
animal_indep(), animal_latent(),
animal_dep(), animal_slope(), phylo_scalar(),
meta_V() (sampling variance, distinct from relatedness).
# Pedigree-derived additive-genetic relatedness; single shared # variance across traits. Grounded in test-animal-keyword.R. ped <- data.frame( id = paste0("i", 1:12), sire = c(rep(NA, 4), rep(c("i1", "i2"), length.out = 8)), dam = c(rep(NA, 4), rep(c("i3", "i4"), length.out = 8)) ) A <- pedigree_to_A(ped) yvec <- as.numeric(MASS::mvrnorm( 1, mu = rep(0, 2 * 12), Sigma = kronecker(diag(2), A) * 0.5 + diag(2 * 12) * 0.5 )) df <- data.frame( species = factor(rep(ped$id, each = 2), levels = ped$id), trait = factor(rep(c("t1", "t2"), times = 12), levels = c("t1", "t2")), value = yvec ) fit <- gllvmTMB( value ~ 0 + trait + animal_scalar(species, pedigree = ped), data = df, family = gaussian(), unit = "species", cluster = "species" )# Pedigree-derived additive-genetic relatedness; single shared # variance across traits. Grounded in test-animal-keyword.R. ped <- data.frame( id = paste0("i", 1:12), sire = c(rep(NA, 4), rep(c("i1", "i2"), length.out = 8)), dam = c(rep(NA, 4), rep(c("i3", "i4"), length.out = 8)) ) A <- pedigree_to_A(ped) yvec <- as.numeric(MASS::mvrnorm( 1, mu = rep(0, 2 * 12), Sigma = kronecker(diag(2), A) * 0.5 + diag(2 * 12) * 0.5 )) df <- data.frame( species = factor(rep(ped$id, each = 2), levels = ped$id), trait = factor(rep(c("t1", "t2"), times = 12), levels = c("t1", "t2")), value = yvec ) fit <- gllvmTMB( value ~ 0 + trait + animal_scalar(species, pedigree = ped), data = df, family = gaussian(), unit = "species", cluster = "species" )
Adds a per-individual random slope on a continuous covariate x,
with individual-level slopes correlated according to the additive-
genetic relatedness matrix . Mathematically:
animal_slope(formula, pedigree = NULL, A = NULL, Ainv = NULL)animal_slope(formula, pedigree = NULL, A = NULL, Ainv = NULL)
formula |
An lme4-bar formula. |
pedigree, A, Ainv
|
One relatedness input for the column-slope aliases: a pedigree, additive-genetic covariance matrix, or its precision. |
This is the canonical quantitative-genetic random-regression
(reaction-norm) model – heritable variation in the slope on an
environmental gradient. Mathematical parallel to phylo_slope();
the engine recycles the same prepared by
animal_scalar() / animal_latent().
See animal_scalar().
animal_scalar(), animal_latent(), phylo_slope().
# Additive-genetic random regression: one heritable slope variance on a # continuous covariate x, slopes correlated by the relatedness matrix A. # Grounded in test-animal-slope-recovery.R. ped <- data.frame( id = paste0("i", 1:12), sire = c(rep(NA, 4), rep(c("i1", "i2"), length.out = 8)), dam = c(rep(NA, 4), rep(c("i3", "i4"), length.out = 8)) ) A <- pedigree_to_A(ped) df <- expand.grid( species = factor(rownames(A), levels = rownames(A)), trait = factor(c("t1", "t2", "t3")), rep = 1:5 ) df$x <- rnorm(nrow(df)) df$value <- rnorm(nrow(df)) fit <- gllvmTMB( value ~ 0 + trait + animal_slope(x | species, A = A), data = df, unit = "species", cluster = "species" )# Additive-genetic random regression: one heritable slope variance on a # continuous covariate x, slopes correlated by the relatedness matrix A. # Grounded in test-animal-slope-recovery.R. ped <- data.frame( id = paste0("i", 1:12), sire = c(rep(NA, 4), rep(c("i1", "i2"), length.out = 8)), dam = c(rep(NA, 4), rep(c("i3", "i4"), length.out = 8)) ) A <- pedigree_to_A(ped) df <- expand.grid( species = factor(rownames(A), levels = rownames(A)), trait = factor(c("t1", "t2", "t3")), rep = 1:5 ) df$x <- rnorm(nrow(df)) df$value <- rnorm(nrow(df)) fit <- gllvmTMB( value ~ 0 + trait + animal_slope(x | species, A = A), data = df, unit = "species", cluster = "species" )
animal_unique(id)
animal_unique(id, pedigree = NULL, A = NULL, Ainv = NULL)animal_unique(id, pedigree = NULL, A = NULL, Ainv = NULL)
id |
Bare column name of the individual factor. |
pedigree |
A 3-column data frame with columns |
A |
Dense relatedness matrix ( |
Ainv |
Precision matrix (inverse of A). Sparse matrix inputs are preserved for the sparse engine route. |
animal_unique() is soft-deprecated as compatibility syntax in gllvmTMB
0.2.0. Use animal_indep() for standalone marginal diagonal animal-model
terms. Paired explicit-Psi use remains accepted; use
animal_latent(..., unique = TRUE) when the folded latent term itself
should carry this diagonal companion.
Canonical name for the D independent animal-model random
intercept on a shared relatedness matrix . Each
trait gets its own variance ; traits
share the same per-individual draws (rows of A act on each trait
independently). Mathematical parallel to phylo_unique().
See animal_scalar().
Used with an intercept+slope bar,
animal_unique(1 + x | id, pedigree = ped) fits a correlated
additive-genetic random-regression (reaction norm)
,
where is a (intercept, slope)
covariance with a free intercept-slope correlation. This routes
through the same augmented engine as phylo_unique() with a
1 + x | sp bar; no separate likelihood. For the diagonal
() special case use animal_indep() with the same
1 + x | id bar. Recover with
extract_Sigma(fit, level = "phy").
animal_scalar(), animal_indep(), animal_latent(),
animal_dep(), phylo_indep().
# Per-trait independent additive-genetic variances on a shared A. # Grounded in test-animal-keyword.R. ped <- data.frame( id = paste0("i", 1:12), sire = c(rep(NA, 4), rep(c("i1", "i2"), length.out = 8)), dam = c(rep(NA, 4), rep(c("i3", "i4"), length.out = 8)) ) A <- pedigree_to_A(ped) yvec <- as.numeric(MASS::mvrnorm( 1, mu = rep(0, 2 * 12), Sigma = kronecker(diag(2), A) * 0.5 + diag(2 * 12) * 0.5 )) df <- data.frame( species = factor(rep(ped$id, each = 2), levels = ped$id), trait = factor(rep(c("t1", "t2"), times = 12), levels = c("t1", "t2")), value = yvec ) fit <- gllvmTMB( value ~ 0 + trait + animal_unique(species, pedigree = ped), data = df, family = gaussian(), unit = "species", cluster = "species" )# Per-trait independent additive-genetic variances on a shared A. # Grounded in test-animal-keyword.R. ped <- data.frame( id = paste0("i", 1:12), sire = c(rep(NA, 4), rep(c("i1", "i2"), length.out = 8)), dam = c(rep(NA, 4), rep(c("i3", "i4"), length.out = 8)) ) A <- pedigree_to_A(ped) yvec <- as.numeric(MASS::mvrnorm( 1, mu = rep(0, 2 * 12), Sigma = kronecker(diag(2), A) * 0.5 + diag(2 * 12) * 0.5 )) df <- data.frame( species = factor(rep(ped$id, each = 2), levels = ped$id), trait = factor(rep(c("t1", "t2"), times = 12), levels = c("t1", "t2")), value = yvec ) fit <- gllvmTMB( value ~ 0 + trait + animal_unique(species, pedigree = ped), data = df, family = gaussian(), unit = "species", cluster = "species" )
Constructs a block-diagonal n x n matrix V suitable for the
meta_V() / equalto() covstruct in gllvmTMB(), where
rows belonging to the same study_id are correlated and rows
from different studies are independent. Each block is a compound-
symmetric covariance matrix
with the
per-row sampling variances within study and
the within-study correlation matrix (1 on the diagonal,
rho_within on the off-diagonal).
block_V(study_id, sampling_var, rho_within = 0.5)block_V(study_id, sampling_var, rho_within = 0.5)
study_id |
A factor (or coercible to one) identifying which
study each row belongs to. Rows with the same |
sampling_var |
Numeric vector of per-row sampling variances,
length |
rho_within |
Either a single numeric in (-1, 1) used for every
study's off-diagonals, or a named numeric vector with one entry
per |
Use this when you have multiple effect sizes per study (multiple
outcomes, multiple traits per individual, etc.) and the within-study
sampling errors share a common cause. The independent-rows / single-
effect-per-study case is block_V(..., rho_within = 0), which
equals diag(sampling_var).
A symmetric positive-definite n x n matrix.
meta_V() for the canonical keyword that consumes
the matrix produced here.
set.seed(1) df <- data.frame( study = factor(rep(paste0("s", 1:3), each = 2)), var = c(0.04, 0.05, 0.06, 0.04, 0.05, 0.07) ) V <- block_V(df$study, df$var, rho_within = 0.5) round(V, 3)set.seed(1) df <- data.frame( study = factor(rep(paste0("s", 1:3), each = 2)), var = c(0.04, 0.05, 0.06, 0.04, 0.05, 0.07) ) V <- block_V(df$study, df$var, rho_within = 0.5) round(V, 3)
Use bootstrap_Sigma() when Hessian, Wald, or profile intervals are
unavailable or unsafe but the fitted model still has useful point
estimates. A pdHess = FALSE or skipped sdreport() is an
inference warning, not automatic proof that the fitted mean or
rotation-invariant covariance summaries are unusable. Inspect
check_gllvmTMB() / gllvmTMB_diagnose() first, then use this
helper for parametric simulate-refit uncertainty.
bootstrap_Sigma( fit, n_boot = 999, level = c("unit", "unit_obs", "phy", "B", "W"), what = c("Sigma", "R", "communality", "ICC", "cross_corr"), conf = 0.95, seed = NULL, n_cores = 1, progress = TRUE, keep_draws = FALSE, link_residual = c("auto", "none") )bootstrap_Sigma( fit, n_boot = 999, level = c("unit", "unit_obs", "phy", "B", "W"), what = c("Sigma", "R", "communality", "ICC", "cross_corr"), conf = 0.95, seed = NULL, n_cores = 1, progress = TRUE, keep_draws = FALSE, link_residual = c("auto", "none") )
fit |
A fit returned by |
n_boot |
Integer; number of bootstrap replicates. Default 999.
Values below Two different things bound The default is 999 rather than 1000 so that Raising |
level |
Character vector; which tier(s) to bootstrap.
Use the canonical levels |
what |
Character vector; which summaries to compute.
Subset of |
conf |
Numeric in |
seed |
Optional RNG seed for reproducibility. |
n_cores |
Integer; number of workers for refits. Must be 1 or 2.
Default 1 (sequential); 2 uses |
progress |
Logical; print a one-line status message at each
replicate (sequential only). Default |
keep_draws |
Logical; if |
link_residual |
How to treat family-specific link-implicit
residual variance when extracting |
The function generates n_boot parametric bootstrap replicates of a
fitted model and returns percentile confidence intervals for the
canonical biological summaries: trait covariance matrices
, ;
the corresponding correlation matrices; per-trait communalities
; and
per-trait site-level ICCs
.
Scope: Gaussian bootstrap summaries and mixed-family refit plumbing are covered by current tests. Non-Gaussian bootstrap calibration is experimental: its repeated-sampling behaviour has not been certified, and there is no production calibration evidence for mixed-family intervals. Treat those intervals as indicative.
Each bootstrap replicate (1) draws a new response vector from
simulate(fit, nsim = 1), (2) refits the model with the same formula
on the simulated data, (3) extracts the requested summaries via
extract_Sigma(), extract_communality(), and extract_ICC_site().
Replicates whose refit fails to converge are recorded but excluded
from CI calculation.
Parallel refits use future + future.apply and are limited to two
workers. Set n_cores = 2 to enable parallel refits. When parallel,
replicates use future.apply's L'Ecuyer-CMRG seed stream so the answers are
reproducible given a fixed seed, but they are NOT bit-identical to
an n_cores = 1 run with the same seed (different RNG streams).
A list with components:
point_estNamed list of point estimates for each
requested summary at each requested level (e.g. Sigma_B,
R_B, communality_B, ICC_site).
ci_lower, ci_upper
Named lists of percentile CI bounds,
element-wise the same shape as the corresponding point_est.
n_effectiveNamed list (vector summaries only) giving the
per-element count of finite bootstrap draws. For multiple_r_*
entries, any element whose effective count is below 80% of n_boot
has its CI bounds set to NA (minimum-effective-B floor).
boot_medianNamed list (multiple_r_* entries only) of the
per-element bootstrap median – a cheap sanity comparator against
point_est; a large gap flags gross bootstrap corruption and is not a
coverage certifier.
ci_methodCharacter; currently "percentile".
link_residualCharacter; the link-residual convention used in point estimates and bootstrap refit summaries.
conf, n_boot, n_failed
Configuration metadata.
drawsNULL unless keep_draws = TRUE; otherwise a
named list of bootstrap draw arrays.
Uses the existing simulate.gllvmTMB_multi() method. By default
that method REDRAWS the random effects from the fitted covariance,
which is what makes these intervals span between-unit variability.
CIs reflect parametric simulate-refit variability, not a
Bayesian posterior distribution for variance components.
Intervals are too narrow when the simulator cannot redraw a
tier. Redraw is not implemented for every random-effect tier,
notably the SPDE spatial tier and the diagonal phylogenetic tier.
For a fit using one of those, simulate.gllvmTMB_multi() falls back
to reusing the fitted random-effect modes and emits a one-shot
warning. That fallback understates between-unit variability, so the
intervals returned here are not calibrated for such fits. Treat
the warning as a signal that these intervals cannot be trusted.
Unsupported families fall back through the simulator's own
warning path.
Refits use the same formula reconstructed from
fit$formula and fit$covstructs, and forward the fit's
auxiliary structure (phylo_vcv, phylo_tree, mesh,
lambda_constraint) so each refit matches the original model.
Convergence: replicates whose refit fails or whose
optimiser does not return convergence == 0 are counted in
n_failed and excluded from CIs.
set.seed(1) s <- simulate_site_trait(n_sites = 30, n_species = 1, n_traits = 3, mean_species_per_site = 1, Lambda_B = matrix(c(1, .5, -.4), 3, 1), psi_B = c(.2, .15, .1)) fit <- gllvmTMB(value ~ 0 + trait + latent(0 + trait | site, d = 1), data = s$data, trait = "trait", unit = "site") # Fifty refits illustrate the API; they do not establish interval coverage. boot <- bootstrap_Sigma(fit, n_boot = 50, level = "unit", what = c("Sigma", "R"), seed = 42) boot$point_est$Sigma_B boot$ci_lower$Sigma_B boot$ci_upper$Sigma_Bset.seed(1) s <- simulate_site_trait(n_sites = 30, n_species = 1, n_traits = 3, mean_species_per_site = 1, Lambda_B = matrix(c(1, .5, -.4), 3, 1), psi_B = c(.2, .15, .1)) fit <- gllvmTMB(value ~ 0 + trait + latent(0 + trait | site, d = 1), data = s$data, trait = "trait", unit = "site") # Fifty refits illustrate the API; they do not establish interval coverage. boot <- bootstrap_Sigma(fit, n_boot = 50, level = "unit", what = c("Sigma", "R"), seed = 42) boot$point_est$Sigma_B boot$ci_lower$Sigma_B boot$ci_upper$Sigma_B
categorical() declares the predictor-model family for an unordered
categorical missing predictor used inside mi() with
impute_model(family = categorical()). The missing unordered predictor is marginalised
EXACTLY by a finite-state baseline-category softmax sum over its K
categories (K >= 3); there is no latent variable. The predictor link is a
baseline-category softmax with the FIRST level as baseline
(eta_state(1) = 0); for state k = 2, ..., K,
eta_state(k) = X_x beta_x[block (k-1)], and
Pr(x = k | z) = exp(eta_state(k)) / sum_j exp(eta_state(j)). The
coefficient vector beta_x is packed as K - 1 blocks of the covariate
design width. The missing predictor must be an unordered factor,
character, or integer category scores with at least three levels, and
every category must appear among the observed values; a two-level predictor
uses binomial() (the binary route) and an ordered predictor uses
cumulative_logit().
categorical()categorical()
This is a predictor-model family tag consumed by impute_model(family = ),
NOT a response family for gllvmTMB().
A gllvmTMB_impute_family object for the family argument of
impute_model().
impute_model(), cumulative_logit() for an ordered missing
predictor, and binomial() for a binary missing predictor.
impute_model(habitat ~ z, family = categorical())impute_model(habitat ~ z, family = categorical())
link_residual = "auto" is coherent for this fitInspects the fit's per-row family vector and flags two configurations
that make the link_residual = "auto" path incoherent: within-trait
family mixing (errors) and ordinal-probit traits (warns; the latent
residual is already standardised at 1, so adding 1 again over-counts).
check_auto_residual(fit)check_auto_residual(fit)
fit |
A fit returned by |
Invisibly, a list with components status (one of "ok",
"warn", or "err" – though the "err" path is unreachable
from a successful return because cli::cli_abort halts) and
messages (a character vector of human-readable findings; empty
when status = "ok").
"err" path: cli::cli_abort is fired and the function does
not return.
"warn" path: cli::cli_warn is fired before the function
returns the list invisibly.
"ok" path: silent.
extract_correlations(), extract_Sigma(), extract_Omega().
Run check_gllvmTMB() right after fitting, before interpreting
confidence intervals or covariance summaries. It returns a stable
table of optimiser, gradient, Hessian, sdreport(), restart,
boundary, latent-identifiability, and binomial prevalence/loading
diagnostics. It is the machine-readable companion to
gllvmTMB_diagnose(): use this in simulations, tests, and reports
where parsing printed messages would be brittle.
check_gllvmTMB( object, gradient_thresh = 0.01, se_thresh = 100, weak_axis_thresh = 0.05, psi_thresh = 1e-04, psi_rel_thresh = 0.01, sigma_eps_thresh = 1e-04, cross_loading_thresh = 0.6, binary_prevalence_thresh = 0.9, binary_saturation_prob_thresh = 0.99, binary_saturation_share_thresh = 0.5, loading_relative_thresh = 8, loading_runaway_thresh = 25, loading_absolute_thresh = 8, multinomial_collapse_floor = 1e-10, multinomial_collapse_rel_thresh = Inf, multinomial_rail_thresh = 0.99, multinomial_range_collapse_thresh = 0.02, ordinal_loading_runaway_thresh = Inf, ordinal_loading_absolute_thresh = Inf )check_gllvmTMB( object, gradient_thresh = 0.01, se_thresh = 100, weak_axis_thresh = 0.05, psi_thresh = 1e-04, psi_rel_thresh = 0.01, sigma_eps_thresh = 1e-04, cross_loading_thresh = 0.6, binary_prevalence_thresh = 0.9, binary_saturation_prob_thresh = 0.99, binary_saturation_share_thresh = 0.5, loading_relative_thresh = 8, loading_runaway_thresh = 25, loading_absolute_thresh = 8, multinomial_collapse_floor = 1e-10, multinomial_collapse_rel_thresh = Inf, multinomial_rail_thresh = 0.99, multinomial_range_collapse_thresh = 0.02, ordinal_loading_runaway_thresh = Inf, ordinal_loading_absolute_thresh = Inf )
object |
A fit returned by |
gradient_thresh |
Maximum allowed absolute gradient component. Default 0.01. |
se_thresh |
Threshold above which a fixed-effect standard error is flagged as weakly identified. Default 100. |
weak_axis_thresh |
Minimum acceptable share of shared loading energy for a fitted latent axis. Default 0.05. |
psi_thresh |
Threshold below which a fitted per-trait |
psi_rel_thresh |
Threshold on the ratio of the smallest to the
largest fitted per-trait |
sigma_eps_thresh |
Threshold below which an estimated residual
|
cross_loading_thresh |
Minimum median trait dominance on a single latent axis before a multi-axis loading matrix is treated as block-structured enough for direct interpretation. Default 0.6. |
binary_prevalence_thresh |
Prevalence at or beyond which a binomial trait is treated as near-constant. Default 0.9. |
binary_saturation_prob_thresh |
Response-scale fitted probability threshold for saturation in binomial traits. Default 0.99. |
binary_saturation_share_thresh |
Minimum share of saturated fitted probabilities before a binomial trait is flagged. Default 0.5. |
loading_relative_thresh |
Threshold for the largest trait loading relative to the typical fitted loading size. At this level the loading is only reported alongside an extreme prevalence, because a sparse but genuine loading structure reaches it on healthy fits. Default 8. |
loading_runaway_thresh |
Threshold for the largest trait loading, relative to the typical fitted loading size, at which the loading is reported on its own without requiring an extreme prevalence. This catches an improper solution (Heywood case) from quasi-complete separation, which runs a loading away while the trait's marginal prevalence stays unremarkable. Default 25, calibrated on 6,824 simulated single-family binomial fits: no healthy fit reached it (the largest was 12.1), while it reported 96.3% of fits whose implied covariance was wrong by a factor of five or more. Two limits on that calibration are worth knowing. It has not been measured on mixed-family fits, and it would not transport to another family on its own, because a sparse but genuine loading structure pushes the ratio much higher there. The typical loading size is therefore taken over the binomial traits alone, so that a trait from another family cannot set the scale this threshold is judged against. |
loading_absolute_thresh |
Threshold on the largest trait loading
itself, on the link scale, at which it is reported regardless of the
other traits. A ratio cannot see a loading matrix inflated as a
whole, because scaling every loading leaves every ratio unchanged;
this supplies the absolute reference the ratio lacks. It is
meaningful because the latent scores are standard normal by
identification, so a binomial loading is the trait's latent standard
deviation in link units: a value of this size already implies a
fitted probability indistinguishable from 0 or 1 across an ordinary
swing of the axis. Default 8, raised from 6 (issue #1098) after the
earlier calibration pool (3,944 simulated binomial fits, no healthy
fit above 3.99) turned out to be unrepresentative: its true loading
scale never reached the regime where this arm misfires. A second pool
built specifically to cross |
multinomial_collapse_floor |
Absolute floor on a |
multinomial_collapse_rel_thresh |
Threshold on the ratio of the
smallest to the largest fitted contrast loading energy within one
|
multinomial_rail_thresh |
Threshold on the largest absolute
off-diagonal correlation of the implied contrast-level covariance
within one |
multinomial_range_collapse_thresh |
Threshold on the fitted spatial
practical range ( |
ordinal_loading_runaway_thresh |
Threshold on an |
ordinal_loading_absolute_thresh |
Threshold on an |
Scope: optimisation and inference-risk signals for fitted
models, including latent-axis rotation, weak-axis, near-zero
psi, residual-scale boundary flags, a binomial
near-constant/loading/saturation screen, and the intentional
gllvmTMBcontrol(se = FALSE) point-estimate path. The table
does not calibrate interval coverage, prove formal separation,
or prove the selected latent rank by itself. Target-explicit
known-DGP simulations will decide when broader interval or
rank-selection claims can move beyond diagnostic status.
A WARN row, including pdHess = FALSE, means that Wald standard
errors or curvature-based inference need more care; it is not by
itself proof that the fitted mean, likelihood, or rotation-invariant
covariance summaries are unusable.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
fit <- gllvmTMB(value ~ 0 + trait + latent(0 + trait | site, d = 2),
data = dat, trait = "trait", unit = "site")
check_gllvmTMB(fit)
A data frame with columns component, status, value,
threshold, message, and action. Status values are "PASS",
"WARN", or "FAIL".
Refits the user's data with full unstructured T x T phylogenetic and
non-phylogenetic trait covariances (phylo_dep + dep) using the same
engine, family, link, and unit / cluster grouping as the supplied
two-psi fit. Compares the joint two-psi fit's implied
(and the analogous ) against
the unstructured baseline. Per-component RMSE plus a flag field
(TRUE when any component disagrees beyond threshold) identifies
two-psi identifiability failures.
compare_dep_vs_two_psi( fit_two_psi, threshold = 0.1, phylo_vcv = NULL, phylo_tree = NULL )compare_dep_vs_two_psi( fit_two_psi, threshold = 0.1, phylo_vcv = NULL, phylo_tree = NULL )
fit_two_psi |
A joint two-psi fit returned by |
threshold |
Numeric (default |
phylo_vcv, phylo_tree
|
Optional. The phylogenetic correlation
matrix or |
Conceptual basis: Williams et al. (2025) bioRxiv 2025.12.20.695312
Eq. 3, generalised across traits. The unstructured fit has
free parameters per tier and is the strongest available
benchmark for the implied total covariance. When the joint two-psi fit
is well-identified, its implied total Sigma matches the unstructured
baseline; when it isn't, the diagnostic flags the disagreement and
the user knows to relax the rank or revisit identifiability.
The cross-check separates three questions:
Both fits converged.
This diagnostic answers Level 2 directly. The two estimators target the SAME total covariance with different parameterisations, so agreement is the identifiability check.
Diagnostic does not test Level 3 directly; Level 2 agreement is the pre-requisite for Level 3 to be meaningful.
Computational scope: tractable for T <= ~30. For larger T, prefer
compare_indep_vs_two_psi().
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
library(ape)
tree <- ape::rcoal(200)
tree$tip.label <- paste0("sp", seq_len(200))
Cphy <- ape::vcv(tree, corr = TRUE)
fit <- gllvmTMB(
value ~ 0 + trait + phylo_latent(species, d = 1, unique = TRUE) +
indep(0 + trait | species),
data = df,
trait = "trait",
unit = "species",
cluster = "species",
phylo_vcv = Cphy
)
diag <- compare_dep_vs_two_psi(fit)
diag$flag
diag$agreement
A list with components:
jointThe two-psi fit's implied Sigma_phy and Sigma_non
(T x T matrices).
depThe unstructured phylo_dep + dep baseline's implied
Sigma_phy and Sigma_non (T x T matrices). May contain NULL
entries if the alt fit failed.
agreementData frame with rows for Sigma_phy and
Sigma_non, columns rmse (Frobenius RMSE between joint and
dep), dep_mag (Frobenius magnitude of the dep estimate),
rel_disagreement (rmse / dep_mag), and flag
(rel_disagreement > threshold, or NA when the alt refit
failed so the component could not be tested).
flagLogical: TRUE if any component is flagged, FALSE
if all tested components agreed, NA if nothing could be tested
(distinguishes agreement from a failed refit).
thresholdThe threshold used.
alt_fitThe refit gllvmTMB_multi object (or NULL on
failure), retained so users can inspect convergence and pull
other extractor outputs.
Williams, M. J., McGillycuddy, M., Drobniak, S. M., Bolker, B. M., Warton, D. I., & Nakagawa, S. (2025). Fast phylogenetic generalised linear mixed-effects modelling using the glmmTMB R package. bioRxiv 2025.12.20.695312. doi:10.64898/2025.12.20.695312
Hadfield, J. D. & Nakagawa, S. (2010). General quantitative genetic methods for comparative biology: phylogenies, taxonomies and multi- trait models for continuous and categorical characters. Journal of Evolutionary Biology 23, 494-508. doi:10.1111/j.1420-9101.2009.01915.x
Meyer, K. & Kirkpatrick, M. (2008). Perils of parsimony: properties of reduced-rank estimates of genetic covariance matrices. Genetics 180, 1153-1166. doi:10.1534/genetics.108.090159
Felsenstein, J. (2005). Using the quantitative genetic threshold model for inferences between and within species. Philosophical Transactions of the Royal Society B 360, 1427-1434. doi:10.1098/rstb.2005.1669
Felsenstein, J. (2012). A comparative method for both discrete and continuous characters using the threshold model. American Naturalist 179, 145-156. doi:10.1086/663681
compare_indep_vs_two_psi() for the cheap diagonal fallback
when T is large; extract_Sigma() for the underlying covariance
extractor.
Refits the user's data with phylo_indep + indep – a stacked
multivariate generalisation of T univariate phylogenetic GLMMs in
the spirit of Williams et al. (2025) Eq. 3 – using the same engine,
family, link, and grouping as the supplied two-psi fit. Compares the
per-trait diagonals of the joint two-psi fit's implied
(and )
against the marginal-only baseline. Tests only the diagonals
(cheap); does not test the off-diagonal cross-trait covariances.
compare_indep_vs_two_psi( fit_two_psi, threshold = 0.1, phylo_vcv = NULL, phylo_tree = NULL )compare_indep_vs_two_psi( fit_two_psi, threshold = 0.1, phylo_vcv = NULL, phylo_tree = NULL )
fit_two_psi |
A joint two-psi fit returned by |
threshold |
Numeric (default |
phylo_vcv, phylo_tree
|
Optional. The phylogenetic correlation
matrix or |
Use this diagnostic when T is large (T 30) and the
unstructured fit needed by compare_dep_vs_two_psi() is intractable.
Otherwise prefer compare_dep_vs_two_psi() (gold standard).
Cross-check intent: the per-trait diagonals are the simplest summary the joint two-psi fit and the marginal baseline both target. If the diagonals already disagree beyond the threshold, no inspection of the cross-trait off-diagonals is warranted.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
diag <- compare_indep_vs_two_psi(fit) diag$flag diag$agreement
A list with components:
jointJoint two-psi fit's implied diag(Sigma_phy) and
diag(Sigma_non) (named numeric vectors of length T).
indepThe marginal-only baseline's diag(Sigma_phy) and
diag(Sigma_non) (same shape; may contain NULL if alt fit
failed).
agreementData frame with rows for Sigma_phy_diag and
Sigma_non_diag, columns rmse, indep_mag,
rel_disagreement, and flag.
flag, threshold, alt_fit
As in
compare_dep_vs_two_psi().
Williams, M. J., McGillycuddy, M., Drobniak, S. M., Bolker, B. M., Warton, D. I., & Nakagawa, S. (2025). Fast phylogenetic generalised linear mixed-effects modelling using the glmmTMB R package. bioRxiv 2025.12.20.695312. doi:10.64898/2025.12.20.695312
compare_dep_vs_two_psi() (canonical, full Sigma);
extract_Sigma().
When two latent-variable fits use the same traits, the loading matrices are
only identified up to rotation and sign. Procrustes alignment finds the
orthogonal transform that brings one matrix as close as possible to the
other, then reports the residual disagreement. This is mainly a validation
helper for comparing gllvmTMB() with another implementation.
compare_loadings(Lambda_a, Lambda_b)compare_loadings(Lambda_a, Lambda_b)
Lambda_a, Lambda_b
|
Two |
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
fit_g <- gllvmTMB(value ~ 0 + trait + latent(0 + trait | site, d = 2),
data = df, trait = "trait", unit = "site")
fit_t <- glmmTMB::glmmTMB(value ~ 0 + trait + rr(0 + trait | site, d = 2),
data = df, REML = FALSE)
L_g <- extract_ordination(fit_g, "unit")$loadings
L_t <- attr(glmmTMB::ranef(fit_t)$cond$site, "loadings")
compare_loadings(L_g, L_t)
A list with the optimal rotation R, the rotated Lambda_a_rot,
and the Frobenius distance after alignment.
compare_Sigma_table() joins report-ready extract_Sigma_table() rows to
a known covariance or correlation matrix. It is designed for simulation and
teaching articles that need estimate-vs-truth tables without hand-indexing
matrices inside the article.
compare_Sigma_table( x, truth, level = "unit", part = c("total", "shared", "unique"), measure = c("covariance", "correlation"), entries = c("unique", "all", "upper", "lower", "offdiag", "diag"), link_residual = c("auto", "none") )compare_Sigma_table( x, truth, level = "unit", part = c("total", "shared", "unique"), measure = c("covariance", "correlation"), entries = c("unique", "all", "upper", "lower", "offdiag", "diag"), link_residual = c("auto", "none") )
x |
A fit returned by |
truth |
Square numeric covariance or correlation matrix. Row and column
names should match the trait names in |
level, part, measure, entries, link_residual
|
Passed to
|
Scope: the helper compares fitted or precomputed
extract_Sigma_table() rows against one supplied truth matrix (covered
by the package's Sigma-table validation tests). It is a table helper
only: it does not compute uncertainty, simulate data, or validate
calibration. For the first visual comparison layer, use
plot_Sigma_comparison(); richer article-specific calibration summaries
remain future visualization work.
A data frame with the columns from extract_Sigma_table() plus
truth, error, abs_error, and comparison_status.
extract_Sigma_table(), plot_Sigma_comparison(),
plot_Sigma_table().
rows <- data.frame( estimand = "R_unit[length,mass]", trait_i = "length", trait_j = "mass", i = 1L, j = 2L, level = "unit", component = "total", matrix = "R", estimate = 0.62, lower = NA_real_, upper = NA_real_, interval_method = "none", interval_status = "none", scale = "correlation", diagonal = FALSE, triangle = "upper" ) truth_R <- matrix(c(1, 0.6, 0.6, 1), 2, dimnames = list(c("length", "mass"), c("length", "mass")) ) compare_Sigma_table(rows, truth_R, measure = "correlation")rows <- data.frame( estimand = "R_unit[length,mass]", trait_i = "length", trait_j = "mass", i = 1L, j = 2L, level = "unit", component = "total", matrix = "R", estimate = 0.62, lower = NA_real_, upper = NA_real_, interval_method = "none", interval_status = "none", scale = "correlation", diagonal = FALSE, triangle = "upper" ) truth_R <- matrix(c(1, 0.6, 0.6, 1), 2, dimnames = list(c("length", "mass"), c("length", "mass")) ) compare_Sigma_table(rows, truth_R, measure = "correlation")
Use confint_inspect() when a profile confidence interval is
missing, one-sided, very wide, or hard to trust. Most users should
start with confint.gllvmTMB_multi() or the matching extract_*()
interval helper; this function is the advanced visual check for a
direct profile target.
confint_inspect( fit, parm, level = 0.95, ystep = 0.5, ytol = NULL, parm.range = c(-Inf, Inf) )confint_inspect( fit, parm, level = 0.95, ystep = 0.5, ytol = NULL, parm.range = c(-Inf, Inf) )
fit |
A fit returned by |
parm |
Character. A single profile-target label from
|
level |
Confidence level. Default |
ystep |
Profile grid spacing on the deviance scale, passed
to |
ytol |
Profile maximum-deviance budget, passed to
|
parm.range |
Two-element numeric range (in the TMB-link
scale) for the profile walk, passed to |
It returns the full profile-likelihood curve, the deviance bounds,
and (when ggplot2 is available) a ggplot visualisation showing
the curve, the MLE, the chi-squared threshold, and the resulting
confidence-interval bounds. It surfaces a small set of numerical shape
warnings before the user interprets the bound; the returned label is not a
goodness or calibration certificate.
This helper inspects direct profile-ready targets from
profile_targets(). Nonlinear derived targets such as communality,
repeatability, and trait
correlation do not currently have a public profile-inspection route. A
working direct curve does not establish empirical interval coverage for
every model class.
Common patterns to look for in the returned plot (see the profile-likelihood article):
Quadratic and approximately symmetric near the MLE: Wald and profile may agree closely for this target.
Asymmetric or skewed: inspect the endpoint status and constrained-fit stability before preferring either interval.
Flat near the MLE: the target may be weakly informed, or the numerical profile may need closer inspection.
Hits a parameter-range bound: interpret the endpoint on the target's transformed scale and distinguish a natural boundary from a truncated search.
No usable crossing: treat the endpoint as unavailable; do not replace it automatically with bootstrap output unless simulation, refits, failed replicate counts, and Monte Carlo resolution are credible for that target.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
fit <- gllvmTMB(value ~ 0 + trait +
latent(0 + trait | site, d = 1),
data = sim$data,
trait = "trait",
unit = "site")
inspect <- confint_inspect(fit, parm = "sigma_eps")
inspect$bounds # the CI + Wald comparison
inspect$diagnostics # any shape flags
inspect$plot # ggplot of the profile curve
An object of class gllvmTMB_confint_inspect with
components:
$curveData frame with one row per profile-grid
point. Columns: parm (user-facing label),
parm_value_natural (parameter value on the natural scale
after the registered transformation),
parm_value_link (parameter value on the TMB optimisation
scale), nll (negative log-likelihood), deviance_drop
(2 * (nll - mle_nll), the chi-squared statistic),
excess_over_threshold (deviance_drop - chi2_threshold;
negative is inside CI, positive is outside),
in_ci (logical).
$boundsData frame, 1 row: parm,
estimate_natural, lower_natural, upper_natural,
wald_lower_natural, wald_upper_natural,
wald_profile_disagree_lower,
wald_profile_disagree_upper. Wald bounds come from
fit$sd_report$cov.fixed via the delta method; disagreement
flags are TRUE when the absolute difference exceeds 25% of
the Wald half-width (a heuristic for "look at the plot").
$plotA ggplot object visualising the curve, MLE,
chi-squared threshold, profile bounds (vertical lines), and
Wald bounds (dashed vertical lines for comparison). NULL
if ggplot2 is not installed.
$diagnosticsNamed character vector flagging any of:
"no_heuristic_warning" (none of this helper's shape warnings fired;
this does not prove a quadratic profile), "asymmetric" (skewed,
Wald-vs-profile disagrees), "flat_at_mle",
"hits_lower_bound", "hits_upper_bound",
"no_lower_crossing", "no_upper_crossing",
"profile_failed".
$callThe match.call() of the invocation.
confint.gllvmTMB_multi() (the corresponding CI
extractor; same parm vocabulary), profile_targets() (the
target inventory), tmbprofile_wrapper() (the lower-level profile API),
and the
profile-likelihood article.
Returns 95% (or other-level) confidence intervals for fixed effects, variance components, and trait covariance matrices, with three method choices:
## S3 method for class 'gllvmTMB_multi' confint( object, parm, level = 0.95, method = c("profile", "wald", "bootstrap"), nsim = 500L, seed = NULL, ... )## S3 method for class 'gllvmTMB_multi' confint( object, parm, level = 0.95, method = c("profile", "wald", "bootstrap"), nsim = 500L, seed = NULL, ... )
object |
A fit returned by |
parm |
One of:
|
level |
Confidence level in |
method |
One of |
nsim |
Number of bootstrap replicates passed to |
seed |
Optional integer RNG seed forwarded to |
... |
Additional arguments currently unused. |
method = "profile" (default): profile-likelihood bounds via
TMB::tmbprofile() + stats::uniroot(). A successful
numerical route can represent asymmetric likelihood shape, but its
reference distribution and coverage remain target-specific.
method = "wald": Gaussian-approximation CIs from
sd_report. Fastest; poor near boundaries.
method = "bootstrap": fitted-model parametric bootstrap via
bootstrap_Sigma(). Slowest; inspect failed refits, Monte Carlo
resolution, and whether simulation covers every active model component.
Fixed-effect and direct-parameter intervals use the requested method
where supported, and Sigma-matrix
intervals accept canonical parm = "Sigma_unit" /
"Sigma_unit_obs" names, diagonal extra-grouping
"Sigma_cluster" / "Sigma_cluster2" names, plus legacy
"Sigma_B" / "Sigma_W" aliases. Profile intervals for full
decomposed Sigma entries fall back to
bootstrap because those entries are nonlinear functions of rotation-equivalent
loadings and diagonal . Non-Gaussian bootstrap and broader
derived-target coverage are not universally calibrated; inspect the returned
method and the target-specific article before reporting bounds.
Fits using non-unit likelihood weights target a weighted estimating
objective, not the ordinary likelihood used by these interval routes.
confint() therefore refuses them; no sandwich interval is currently
certified.
Main parm-class dispatch paths:
Sigma matrices – when parm is one of
"Sigma_unit", "Sigma_unit_obs",
"Sigma_cluster", "Sigma_cluster2", or
"sigma_phy" (legacy aliases "Sigma_B" and
"Sigma_W" still work),
returns a tidy data.frame with columns parameter,
estimate, lower, upper, method. Profile is
computed element-wise via TMB::tmbprofile() for the diagonal entries;
structural-zero off-diagonal entries in pure-diagonal tiers are labelled
"structural_zero"; off-diagonals for reduced-rank tiers fall back
to bootstrap (full Sigma sampling) since they mix two parameters in a
non-linear way.
Fixed effects / variance components – when parm
is missing, an integer index, or a character vector of fixed-effect
term names, returns a numeric matrix with rows = parameters and
columns = lower / upper bounds (same shape as
stats::confint()). Method choice applies.
Lambda and derived summaries – "Lambda...",
"icc...", "phylo_signal...", "communality...",
"rho...", and "proportion..." tokens dispatch to their
corresponding direct-profile, Wald, or bootstrap helper where that route
is implemented. Nonlinear penalty-profile prototypes are withheld.
Sigma path – a data.frame with columns
parameter (character, e.g. "Sigma_unit[t1,t1]"),
estimate (point estimate), lower, upper, and
method (the method used for that row, or
"structural_zero" for off-diagonal entries that are fixed at
zero by a diagonal-only tier). The parameter prefix follows the
requested parm, so legacy calls still return legacy
"Sigma_B[...]" or "Sigma_W[...]" labels.
Lambda path – a data.frame with columns
parameter (e.g. "Lambda[trait_1,LV1]" for raw rows or
"rho[trait_1,LV1]" for standardised rows),
estimate, lower, upper, method,
loading_scale, pd_hessian, and ci_status. When
pdHess = FALSE
on a Wald path,
lower/upper are NA and ci_status
flags the reason).
Derived-quantity path ("icc" /
"phylo_signal" / "communality" / "rho" /
"proportion") –
a numeric matrix with two columns named after the requested
level (e.g. "2.5 %" / "97.5 %") and
rownames identifying the entry, e.g. "icc:trait_1",
"communality:unit:trait_1", "rho:unit:1,2", or
"proportion:shared_unit:trait_1".
Fixed-effects / variance-component path – a numeric
matrix with rownames = parameter names and two columns named
"2.5 %" / "97.5 %" (or the analogous quantiles for
the requested level).
Pawitan, Y. (2001). In All Likelihood: Statistical Modelling and Inference Using Likelihood, Oxford University Press, ch. 9.
Venzon, D. J. & Moolgavkar, S. H. (1988). A method for computing profile-likelihood-based confidence intervals. Applied Statistics 37, 87-94. doi:10.2307/2347496
McCune, K. B., et al. (2024) coxme_icc_ci() – the
Nakagawa-authored coxme-based profile-CI helper that
inspired this work, in
https://github.com/kelseybmccune/Time-to-Event_Repeatability/blob/main/R/rptRsurv.R.
bootstrap_Sigma(), extract_Sigma(), extract_correlations(),
extract_repeatability(), extract_communality(), tmbprofile_wrapper(),
loading_ci(), loading_profile(), gllvmTMB().
## Fit a tiny example set.seed(1) s <- simulate_site_trait( n_sites = 30, n_species = 4, n_traits = 3, mean_species_per_site = 4, Lambda_B = matrix(c(0.9, 0.4, -0.3), 3, 1), psi_B = c(0.20, 0.15, 0.10), beta = matrix(0, 3, 2), seed = 1 ) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | site, d = 1), data = s$data, trait = "trait", unit = "site" ) ## Bootstrap CIs for the between-site covariance matrix. ## Fifty refits illustrate the API; they do not establish interval coverage. ## Inspect failed refits and use more replicates for a scientific analysis. ci_unit <- confint(fit, parm = "Sigma_unit", method = "bootstrap", nsim = 50, seed = 42) ci_unit ## Wald CIs for fixed effects confint(fit)## Fit a tiny example set.seed(1) s <- simulate_site_trait( n_sites = 30, n_species = 4, n_traits = 3, mean_species_per_site = 4, Lambda_B = matrix(c(0.9, 0.4, -0.3), 3, 1), psi_B = c(0.20, 0.15, 0.10), beta = matrix(0, 3, 2), seed = 1 ) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | site, d = 1), data = s$data, trait = "trait", unit = "site" ) ## Bootstrap CIs for the between-site covariance matrix. ## Fifty refits illustrate the API; they do not establish interval coverage. ## Inspect failed refits and use more replicates for a scientific analysis. ci_unit <- confint(fit, parm = "Sigma_unit", method = "bootstrap", nsim = 50, seed = 42) ci_unit ## Wald CIs for fixed effects confint(fit)
lambda_constraint matrix from group membershipConstruct the n_species by d confirmatory loading-constraint matrix
M from a discrete functional-group hypothesis. Each named group is
pinned to load on a specific latent axis (zero on all other axes), and
one anchor species per axis is pinned at +1 to fix the scale. Groups
present in group but absent from loads_on remain free on all axes.
confirmatory_lambda( species, group, d, loads_on, anchors = NULL, axis_labels = NULL )confirmatory_lambda( species, group, d, loads_on, anchors = NULL, axis_labels = NULL )
species |
Character vector of species names. Becomes the
rownames of the returned matrix. Must match the species (i.e.
|
group |
Character or factor vector parallel to |
d |
Integer; number of latent axes (matches the |
loads_on |
Named list or named integer vector mapping group code
to latent axis index. For example, |
anchors |
Optional length- |
axis_labels |
Optional length- |
This is the recommended starting point for confirmatory JSDMs where
prior knowledge takes the form "species in group A respond to
gradient 1, species in group B respond to gradient 2, ...". For
free-form constraint patterns (psychometric CFA, idiosyncratic
species-by-species pins), build the matrix directly; for purely
statistical identification scaffolding when no biology is in play,
see suggest_lambda_constraint().
An length(species) by d numeric matrix with NA entries
(estimated), 0 entries (pinned to zero), and 1 entries (pinned
at the anchor), ready to pass to gllvmTMB() as
lambda_constraint = list(unit = M).
suggest_lambda_constraint() for the lower-level statistical
identification scaffold, gllvmTMB() for the lambda_constraint
argument.
species <- c(paste0("A_", 1:3), paste0("B_", 1:3), paste0("C_", 1:4)) group <- c(rep("A", 3), rep("B", 3), rep("C", 4)) # Group A loads on axis 1; group B loads on axis 2; group C is free. M <- confirmatory_lambda( species = species, group = group, d = 2L, loads_on = list(A = 1L, B = 2L) ) Mspecies <- c(paste0("A_", 1:3), paste0("B_", 1:3), paste0("C_", 1:4)) group <- c(rep("A", 3), rep("B", 3), rep("C", 4)) # Group A loads on axis 1; group B loads on axis 2; group C is free. M <- confirmatory_lambda( species = species, group = group, d = 2L, loads_on = list(A = 1L, B = 2L) ) M
cumulative_logit() declares the predictor-model family for an ordered
categorical missing predictor used inside mi() with
impute_model(family = cumulative_logit()). The missing ordered predictor is marginalised
EXACTLY by a finite-state cumulative-logit sum over its K categories
(K >= 3); there is no latent variable. The predictor link is cumulative
logit, with K - 1 free cutpoints c_1 < ... < c_{K-1} and a cell
probability Pr(x = k | z) = F(c_k - eta_x) - F(c_{k-1} - eta_x), where
eta_x = X_x beta_x (no separate predictor intercept; the cutpoints carry
the location). The missing predictor must be an ordered factor (or
integer category scores) with at least three levels; a two-level predictor
uses binomial() (the binary route).
cumulative_logit()cumulative_logit()
This is a predictor-model family tag consumed by impute_model(family = ),
NOT a response family for gllvmTMB(). The predictor cumulative-logit link is DISTINCT from the
ordinal_probit() RESPONSE family (a probit threshold model).
A gllvmTMB_impute_family object for the family argument of
impute_model().
impute_model(), binomial() for a binary missing predictor,
and ordinal_probit() for the ordinal RESPONSE family.
impute_model(score ~ z, family = cumulative_logit())impute_model(score ~ z, family = cumulative_logit())
dep(0 + trait | g)
Canonical name for the always-alone full-unstructured trait
covariance term. Fits a full
with free parameters via a Cholesky parameterisation
. Mathematically
identical to latent(0 + trait | g, d = T) standalone (where
is the number of traits) – the engine's existing packed-triangular
at full rank IS the Cholesky factor of an
unstructured . The keyword choice is
documentary: dep declares user intent that the full unstructured
trait covariance is the model.
dep(formula)dep(formula)
formula |
|
This completes the structural-mode quartet:
latent by default)Shared low-rank
loadings plus the default trait-specific Psi companion:
.
parameters (rotation-removed).
indep standalone)Per-trait variance with
identity correlation: .
parameters.
dep standalone)Free unstructured
, PSD via Cholesky.
parameters.
Use dep() when you want to commit to the full unstructured
interpretation explicitly. This is a standard multivariate response
covariance model. Computational scope: tractable
for with the default optimiser; for larger
, prefer latent(d = K) with as the
rank-reduced approximation.
Combining dep(0 + trait | g) with latent(0 + trait | g, d = K)
on the same grouping is over-parameterised. Combining dep with
unique or indep on the same grouping is redundant (dep
standalone already includes the trait-level diagonal). The parser
raises cli::cli_abort() in any of these cases.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
# Full unstructured trait covariance fit:
fit <- gllvmTMB(value ~ 0 + trait + dep(0 + trait | site),
data = df, trait = "trait", unit = "site")
# The mathematically-equivalent decomposition form (full rank):
fit <- gllvmTMB(value ~ 0 + trait + latent(0 + trait | site, d = T),
data = df, trait = "trait", unit = "site")
# ERROR: dep + latent on the same grouping is over-parameterised.
# gllvmTMB(value ~ 0 + trait +
# dep(0 + trait | site) +
# latent(0 + trait | site, d = 2), data = df)
A formula marker; never evaluated.
latent(), indep(), phylo_dep(),
spatial_dep(), extract_Sigma().
-2 * logLik(object), delegating through logLik.gllvmTMB_multi()
rather than recomputing anything independently. This is deliberate: on a
penalised (ridged) fit – aghq_ridge set to a finite value –
logLik() already discloses, with a warning, that the returned value is
the unpenalised log-likelihood evaluated AT a penalised MAP point, not at
its own maximum. Because deviance() is defined purely in terms of
logLik() here, that same disclosure fires exactly once and is not
repeated or re-derived.
## S3 method for class 'gllvmTMB_multi' deviance(object, ...)## S3 method for class 'gllvmTMB_multi' deviance(object, ...)
object |
A fit returned by |
... |
Currently unused. |
A single numeric value, -2 * as.numeric(logLik(object)). On a
ridged fit this carries the same MAP-point caveat as logLik(): the
value is -2 times the unpenalised likelihood at the penalised
estimate, not at its own maximum, and the warning issued by logLik()
is inherited unchanged.
diagnose_kernel_separability() compares two or more dense fixed kernels on
the same levels before they are used in a multi-kernel model. It is a pre-fit
diagnostic rather than evidence that the components are separately
identifiable: when candidate
kernels are highly overlapping, component-specific Gamma blocks are weak
evidence and should be treated as descriptive unless simulations justify the
split.
Use this helper to screen candidate K_phy and
K_tip definitions, including raw-network and residualized-network choices,
before fitting kernel_latent(..., name = ...) tiers. Note its limits:
this is a diagnostic, not recovery evidence, interval calibration, or an
in-engine identifiability proof.
diagnose_kernel_separability( ..., thresholds = c(near_orthogonal = 0.25, high = 0.7) )diagnose_kernel_separability( ..., thresholds = c(near_orthogonal = 0.25, high = 0.7) )
... |
Two or more named numeric square kernel matrices with the same dimensions and level order. |
thresholds |
Named numeric vector with entries |
A list of class gllvmTMB_kernel_separability with:
A symmetric matrix of off-diagonal Frobenius-style similarities between kernels.
A data frame with pair labels, similarity, overlap class, and a conservative recommendation.
The thresholds used for the classes.
A short claim-boundary note.
A_H <- diag(2) A_P <- diag(2) rownames(A_H) <- colnames(A_H) <- c("H1", "H2") rownames(A_P) <- colnames(A_P) <- c("P1", "P2") W_phy <- matrix(c(1, 0.2, 0.2, 1), 2, 2, dimnames = list(rownames(A_H), rownames(A_P))) W_tip <- matrix(c(0.2, 1, 1, 0.2), 2, 2, dimnames = list(rownames(A_H), rownames(A_P))) K_phy <- make_cross_kernel(A_H, A_P, W_phy, rho = 0.5) K_tip <- make_cross_kernel(A_H, A_P, W_tip, rho = 0.5) diagnose_kernel_separability(phy = K_phy, tip = K_tip)A_H <- diag(2) A_P <- diag(2) rownames(A_H) <- colnames(A_H) <- c("H1", "H2") rownames(A_P) <- colnames(A_P) <- c("P1", "P2") W_phy <- matrix(c(1, 0.2, 0.2, 1), 2, 2, dimnames = list(rownames(A_H), rownames(A_P))) W_tip <- matrix(c(0.2, 1, 1, 0.2), 2, 2, dimnames = list(rownames(A_H), rownames(A_P))) K_phy <- make_cross_kernel(A_H, A_P, W_phy, rho = 0.5) K_tip <- make_cross_kernel(A_H, A_P, W_tip, rho = 0.5) diagnose_kernel_separability(phy = K_phy, tip = K_tip)
diagnostic_table() turns the metadata attached by predictive_check()
and residuals.gllvmTMB_multi() into plain data frames. It is the
public path for articles, reports, and tests that need the plotted
diagnostic rows, residual rows, row-status counts, or fit-health table
without inspecting object attributes directly.
diagnostic_table( x, table = c("data", "row_status", "fit_health_status", "check_gllvmTMB") )diagnostic_table( x, table = c("data", "row_status", "fit_health_status", "check_gllvmTMB") )
x |
A |
table |
Table to extract. |
Scope: table extraction from existing
attr(x, "gllvmTMB_diagnostic") metadata on predictive_check()
plots and diagnostic residual data frames. This helper does not
compute new diagnostics, refit models, run formal residual tests, or
calibrate uncertainty. Richer article tables can build on this
stable metadata path once the diagnostic examples are restored.
A plain data frame.
set.seed(3) n <- 24 df <- data.frame( unit = factor(rep(seq_len(n), each = 2)), trait = factor(rep(c("a", "b"), n)), value = rpois(2 * n, lambda = 2) ) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | unit, d = 1), data = df, trait = "trait", unit = "unit", family = poisson() ) p <- predictive_check(fit, type = "rq_qq", seed = 1) diagnostic_table(p, table = "row_status") diagnostic_table(p, table = "fit_health_status") diagnostic_table(p, table = "data") r <- residuals(fit, type = "randomized_quantile", seed = 1) diagnostic_table(r, table = "data")set.seed(3) n <- 24 df <- data.frame( unit = factor(rep(seq_len(n), each = 2)), trait = factor(rep(c("a", "b"), n)), value = rpois(2 * n, lambda = 2) ) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | unit, d = 1), data = df, trait = "trait", unit = "unit", family = poisson() ) p <- predictive_check(fit, type = "rq_qq", seed = 1) diagnostic_table(p, table = "row_status") diagnostic_table(p, table = "fit_health_status") diagnostic_table(p, table = "data") r <- residuals(fit, type = "randomized_quantile", seed = 1) diagnostic_table(r, table = "data")
extract_coevolution_modules() standardizes a component-specific
cross-lineage covariance block and decomposes it into coupled trait axes.
For a named coevolution kernel tier, the helper computes
R = Sigma_row^{-1/2} Gamma Sigma_col^{-1/2} from the shared covariance
returned by extract_Sigma(), then applies a singular-value decomposition.
This is a point-estimate derived-output helper, covered by the package's
validation tests, for fitted
dense-kernel coevolution models whose component-specific Gamma blocks are
already extractable with extract_Gamma(). It does not report
uncertainty, choose the biological rank, estimate rho, or calibrate null
thresholds. Use simulation, bootstrap, and kernel-separability checks before
treating the returned axes as scientific evidence.
extract_coevolution_modules( fit, level, row_traits, col_traits, scale = c("shape", "effect"), n_modules = NULL, tol = sqrt(.Machine$double.eps) )extract_coevolution_modules( fit, level, row_traits, col_traits, scale = c("shape", "effect"), n_modules = NULL, tol = sqrt(.Machine$double.eps) )
fit |
A fitted |
level |
Character scalar naming the fitted covariance tier. |
row_traits, col_traits
|
Character vectors naming the row-lineage and column-lineage traits. |
scale |
Character scalar. |
n_modules |
Optional positive integer limiting the number of returned singular axes. By default all axes are returned. |
tol |
Numerical tolerance for the generalized inverse square roots of the within-lineage shared covariance blocks. |
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
mods <- extract_coevolution_modules(
fit,
level = "phy",
row_traits = c("host_size", "host_defence"),
col_traits = c("partner_size", "partner_attack")
)
mods$modules
mods$row_axes
mods$col_axes
A list with R, modules, row_axes, and col_axes. R is the
standardized cross-lineage correlation-like block. modules has one row
per singular axis with its singular value and squared-value share.
row_axes and col_axes contain trait loadings for each coupled axis.
extract_communality( fit, level = c("unit", "unit_obs", "phy", "B", "W"), link_residual = c("auto", "none"), ci = FALSE, conf_level = 0.95, method = c("wald", "bootstrap", "profile"), nsim = 500L, seed = NULL )extract_communality( fit, level = c("unit", "unit_obs", "phy", "B", "W"), link_residual = c("auto", "none"), ci = FALSE, conf_level = 0.95, method = c("wald", "bootstrap", "profile"), nsim = 500L, seed = NULL )
fit |
A fit returned by |
level |
|
link_residual |
For binomial fits: |
ci |
Logical. When |
conf_level |
Confidence level when |
method |
One of |
nsim |
Number of bootstrap replicates when |
seed |
Optional RNG seed for the bootstrap. |
The proportion of trait 's variance that is shared with the
other traits via the latent factors. Bounded between 0 and 1. Calls
extract_Sigma() internally for the chosen level, so the diagonal
uses the full
decomposition. Ordinary latent() includes
by default.
If the fit uses latent(..., unique = FALSE) at the requested
level, then and c_t^2 = 1 for
every trait. This is mathematically correct for the no-residual
subset but tells you nothing about trait integration. The
extract_Sigma() advisory message will fire to flag this. To get
meaningful communalities, use ordinary latent() with the default
unique = TRUE.
For binomial fits the link-specific implicit residual (
for logit, 1 for probit, for cloglog) is added to the
denominator by default; pass link_residual = "none" to suppress.
When ci = FALSE: a numeric vector indexed by trait.
When ci = TRUE: a data frame with columns trait, tier, c2,
lower, upper, method. For a bootstrap_Sigma input, the interval
columns are copied from the bootstrap object.
extract_Sigma(); extract_ICC_site();
extract_correlations(); extract_repeatability();
confint.gllvmTMB_multi().
sim <- simulate_site_trait( n_sites = 20, n_species = 6, n_traits = 4, mean_species_per_site = 4, seed = 1 ) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | site, d = 2), data = sim$data, trait = "trait", unit = "site" ) ## Per-trait between-unit communality. extract_communality(fit, level = "unit") ## With delta-method Wald CIs. extract_communality(fit, level = "unit", ci = TRUE, method = "wald") # Fifty refits illustrate the API; they do not establish interval coverage. boot <- bootstrap_Sigma(fit, n_boot = 50, level = "unit", what = "communality", progress = FALSE) extract_communality(boot, level = "unit", ci = TRUE)sim <- simulate_site_trait( n_sites = 20, n_species = 6, n_traits = 4, mean_species_per_site = 4, seed = 1 ) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | site, d = 2), data = sim$data, trait = "trait", unit = "site" ) ## Per-trait between-unit communality. extract_communality(fit, level = "unit") ## With delta-method Wald CIs. extract_communality(fit, level = "unit", ci = TRUE, method = "wald") # Fifty refits illustrate the API; they do not establish interval coverage. boot <- bootstrap_Sigma(fit, n_boot = 50, level = "unit", what = "communality", progress = FALSE) extract_communality(boot, level = "unit", ci = TRUE)
Returns the implied cross-trait correlations from a fit returned by
gllvmTMB() at one or more covariance levels. Use the canonical input
names "unit", "unit_obs", "phy", and
"spatial"; legacy aliases "B", "W", and
"spde" still work. The helper returns 95% (or other-level)
point estimates by default. Fisher-z and bootstrap intervals can be
requested explicitly with the method argument; the former profile
token is retained only to return a clear withdrawal error:
extract_correlations( fit, tier = "all", pair = NULL, level = 0.95, method = c("none", "fisher-z", "profile", "wald", "bootstrap"), n_eff = NULL, nsim = 500L, seed = NULL, link_residual = c("auto", "none") )extract_correlations( fit, tier = "all", pair = NULL, level = 0.95, method = c("none", "fisher-z", "profile", "wald", "bootstrap"), n_eff = NULL, nsim = 500L, seed = NULL, link_residual = c("auto", "none") )
fit |
A fit returned by |
tier |
Character vector. Use |
pair |
Optional length-2 character or integer vector specifying
one trait pair ( |
level |
Confidence level in (0, 1). Default 0.95. |
method |
One of |
n_eff |
Optional positive integer (>= 4): override the
effective sample size used in Fisher's
|
nsim |
Number of bootstrap replicates when
|
seed |
Optional RNG seed for the bootstrap. |
link_residual |
How to treat the family-specific link-residual
variance on the diagonal of the implied
Behaviour change in this release: the previous version
hardcoded |
"none" (default): point correlations only. No universal
interval calibration has been established across covariance tiers,
families, and targets.
"fisher-z": Fisher's z-transform heuristic interval.
Computes ,
,
constructs the CI on z, then back-transforms via
(so bounds are guaranteed inside ).
Fast (seconds for any T), but the classical iid-correlation variance is
not a calibrated mixed-model standard error. Treat these bounds as a
sensitivity display and see n_eff.
"profile": withdrawn. The nonlinear penalty-profile
prototype did not yet provide a sufficiently strict constraint and
constrained-optimizer diagnostic contract. This token stops with an
explanation rather than returning bounds.
"wald": backward-compat alias of "fisher-z"
with the same numerics. Emits a one-shot inform pointing at
the canonical name. Kept for scripts that filter on
method == "wald".
"bootstrap": parametric bootstrap via
bootstrap_Sigma. Slowest (full sampling
distribution); use when a fitted model gives useful point estimates
but Hessian- or profile-based intervals are not the right uncertainty
summary. Structured tiers not yet resampled by
bootstrap_Sigma, currently the SPDE spatial tier, return
an explicit Wald/Fisher-z fallback with a message rather than fake
bootstrap support.
For T traits at one tier, there are T(T-1)/2 unique correlations. A fit with T = 6 and four covariance levels present has up to 60 cross-trait correlations to report.
A data frame (tibble-like) with columns:
tierCharacter level label. The current output stores
internal labels "B", "W", "phy", and
"spde"; use "unit" and "unit_obs" as input
names in new calls.
trait_i, trait_j
Trait names with i < j.
correlationPoint estimate.
lower, upper
Confidence-interval bounds.
methodMethod used to compute the CI.
interval_statusClaim-boundary marker:
"none" for point-only output,
"heuristic_unvalidated" for Fisher-z/Wald bounds, and
"target_specific_uncalibrated" for bootstrap bounds. The
bootstrap route computes intervals, but this function does not certify
their frequentist coverage for the fitted target.
For an engine = "julia" bridge fit, lower/upper are
NA, method = "none", and interval_status = "none".
The former nonlinear penalty-profile route is withheld pending an exact constraint solver and explicit constrained-fit diagnostics.
Bootstrap uses bootstrap_Sigma refits and is the
practical fallback when point estimates are useful but Hessian- or
profile-based intervals are unavailable. Inspect bootstrap warnings,
failed replicates, and interval width before treating the intervals
as final.
extract_Sigma, bootstrap_Sigma,
confint.gllvmTMB_multi,
extract_communality.
set.seed(1) s <- simulate_site_trait( n_sites = 80, n_species = 6, n_traits = 4, mean_species_per_site = 4, Lambda_B = matrix(c(0.9, 0.4, -0.3, 0.5), 4, 1), psi_B = c(0.4, 0.3, 0.5, 0.2) ) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | site, d = 1), data = s$data, trait = "trait", unit = "site" ) ## Default: point correlations only. cors <- extract_correlations(fit, tier = "unit") ## Opt-in Fisher-z sensitivity bounds with an explicitly justified n_eff. cors2 <- extract_correlations(fit, tier = "unit", method = "fisher-z", n_eff = 60L) ## Fifty refits illustrate the API, rather than establish coverage. ## Inspect failed refits and increase B for a scientific analysis. cors_b <- extract_correlations(fit, tier = "unit", method = "bootstrap", nsim = 50, seed = 42)set.seed(1) s <- simulate_site_trait( n_sites = 80, n_species = 6, n_traits = 4, mean_species_per_site = 4, Lambda_B = matrix(c(0.9, 0.4, -0.3, 0.5), 4, 1), psi_B = c(0.4, 0.3, 0.5, 0.2) ) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | site, d = 1), data = s$data, trait = "trait", unit = "site" ) ## Default: point correlations only. cors <- extract_correlations(fit, tier = "unit") ## Opt-in Fisher-z sensitivity bounds with an explicitly justified n_eff. cors2 <- extract_correlations(fit, tier = "unit", method = "fisher-z", n_eff = 60L) ## Fifty refits illustrate the API, rather than establish coverage. ## Inspect failed refits and increase B for a scientific analysis. cors_b <- extract_correlations(fit, tier = "unit", method = "bootstrap", nsim = 50, seed = 42)
For a fit where a multinomial() trait shares a latent factor with
other-family traits, report the association between the nominal trait and each
partner trait. A nominal trait spans K-1 baseline-category contrasts, so its
association with a single-scale partner is a vector, summarized two ways:
multiple_r: the reference-invariant multiple correlation
between partner p and the whole K-1 contrast block c. Invariant to the
baseline category; magnitude in [0, 1].
contrast_r (when contrasts = TRUE): the (K-1)-vector of individual
contrast correlations, labelled by category-vs-baseline.
extract_cross_correlations( fit, level = "unit", contrasts = FALSE, link_residual = c("auto", "none"), method = c("point", "bootstrap", "profile", "wald"), conf = 0.95, nsim = 500L, seed = NULL )extract_cross_correlations( fit, level = "unit", contrasts = FALSE, link_residual = c("auto", "none"), method = c("point", "bootstrap", "profile", "wald"), conf = 0.95, nsim = 500L, seed = NULL )
fit |
a fitted |
level |
covariance tier. Only the ordinary |
contrasts |
if |
link_residual |
passed to |
method |
one of |
conf |
confidence level for the interval columns. Default 0.95. |
nsim |
number of parametric-bootstrap replicates when
|
seed |
optional RNG seed for the bootstrap. |
a data.frame, one row per (nominal, partner) pair. With
method = "point" the columns are nominal, partner, multiple_r
(and contrast_r when contrasts = TRUE). With method = "wald",
"bootstrap", per-estimand interval columns are added:
multiple_r_lower/multiple_r_upper/multiple_r_method/multiple_r_interval_status
(scalar), and when contrasts = TRUE
contrast_r_lower/contrast_r_upper (list columns) plus
contrast_r_method/contrast_r_interval_status. The estimand NOT served by
the requested method keeps point-only interval columns (NA bounds,
method = "point"). Computed intervals carry an interval_status flag
("heuristic_unvalidated" for "wald", "target_specific_uncalibrated"
for "bootstrap") – coverage is not yet certified for any
route (recovery evidence for this route is not yet complete).
set.seed(12) n_unit <- 120L n_rep <- 3L unit_id <- rep(seq_len(n_unit), each = n_rep) score <- stats::rnorm(n_unit) score_obs <- score[unit_id] gaussian_y <- 0.8 * score_obs + stats::rnorm(length(unit_id), sd = 0.4) category_y <- vapply(seq_along(unit_id), function(i) { eta <- c(0, 0.9 * score_obs[i], -0.6 * score_obs[i]) prob <- exp(eta - max(eta)) sample.int(3L, 1L, prob = prob / sum(prob)) }, integer(1L)) dat <- data.frame( unit = factor(c(unit_id, unit_id)), trait = factor(rep(c("g", "cat"), each = length(unit_id)), levels = c("g", "cat")), family = factor(rep(c("g", "m"), each = length(unit_id)), levels = c("g", "m")), value = c(gaussian_y, category_y) ) families <- list(g = gaussian(), m = multinomial()) attr(families, "family_var") <- "family" fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | unit, d = 1, unique = TRUE), data = dat, family = families, trait = "trait", unit = "unit" ) extract_cross_correlations(fit, contrasts = TRUE)set.seed(12) n_unit <- 120L n_rep <- 3L unit_id <- rep(seq_len(n_unit), each = n_rep) score <- stats::rnorm(n_unit) score_obs <- score[unit_id] gaussian_y <- 0.8 * score_obs + stats::rnorm(length(unit_id), sd = 0.4) category_y <- vapply(seq_along(unit_id), function(i) { eta <- c(0, 0.9 * score_obs[i], -0.6 * score_obs[i]) prob <- exp(eta - max(eta)) sample.int(3L, 1L, prob = prob / sum(prob)) }, integer(1L)) dat <- data.frame( unit = factor(c(unit_id, unit_id)), trait = factor(rep(c("g", "cat"), each = length(unit_id)), levels = c("g", "cat")), family = factor(rep(c("g", "m"), each = length(unit_id)), levels = c("g", "m")), value = c(gaussian_y, category_y) ) families <- list(g = gaussian(), m = multinomial()) attr(families, "family_var") <- "family" fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | unit, d = 1, unique = TRUE), data = dat, family = families, trait = "trait", unit = "unit" ) extract_cross_correlations(fit, contrasts = TRUE)
For traits fitted with ordinal_probit(), returns a tidy data frame
with the K - 2 estimated cutpoints
per trait, with optional standard errors from the joint sdreport.
extract_cutpoints(fit, quiet = FALSE)extract_cutpoints(fit, quiet = FALSE)
fit |
A fit returned by |
quiet |
Suppress the note emitted when |
Convention: gllvmTMB follows Hadfield (2015) — is
fixed for identifiability and the K - 2 free cutpoints are reported
as cutpoint_2, cutpoint_3, etc. This differs from brms, which
reports K - 1 cutpoints as Intercept[1..K-1].
This extractor includes Wald standard errors, so it refuses fits made with non-unit likelihood weights. The fitted cutpoint parameters remain point estimates, but weighted-objective uncertainty is not calibrated.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
fit <- gllvmTMB( value ~ 0 + trait + phylo_indep(species, tree = tree), data = ordinal_dat, trait = "trait", unit = "individual", cluster = "species", family = ordinal_probit() ) extract_cutpoints(fit)
A data frame with columns
traitTrait label (factor level from data[[trait]]).
cutpoint_indexInteger index .
cutpoint_labelCharacter label "cutpoint_<k>".
tau_estimateEstimated on the latent (probit)
scale.
tau_seStandard error from the joint sdreport, or NA if
the report is unavailable.
If the fit contains no ordinal_probit() traits, returns a
zero-row data frame with the same columns.
Hadfield, J. D. (2015). Increasing the efficiency of MCMC for hierarchical phylogenetic models of categorical traits using reduced mixed models. Methods Ecol. Evol. 6:706-714. doi:10.1111/2041-210X.12354
ordinal_probit() for the family constructor and the
threshold-trait theory reference list.
extract_Gamma() slices the shared covariance matrix returned by
extract_Sigma() to give the row-lineage by column-lineage block
Gamma_shape = Lambda_row Lambda_col^T. In a fitted cross-lineage kernel
model, level is the named kernel_*() tier, row_traits are the host
traits, and col_traits are the partner traits. This
is a point-estimate extractor for the shared covariance block of a
fitted dense-kernel model, covered by the package's validation tests at the
fixed-rho scale: when the fitted kernel tier was built from make_cross_kernel(),
scale = "effect" returns Gamma_effect = rho * Gamma_shape using the
fixed rho recorded on that kernel. Uncertainty for Gamma is not yet
reported by this helper; use bootstrap workflows when interval estimates
are required. Use profile_cross_rho() to compare a fixed rho
grid; in-engine rho estimation and profile intervals remain planned work.
extract_Gamma(fit, level, row_traits, col_traits, scale = c("shape", "effect"))extract_Gamma(fit, level, row_traits, col_traits, scale = c("shape", "effect"))
fit |
A fitted |
level |
Character scalar naming the covariance tier. For
|
row_traits, col_traits
|
Character vectors of trait names defining the rows and columns of the returned block. |
scale |
Character scalar. |
rho is part of the supplied K matrix, not a fitted parameter in the
current engine. To profile it, use profile_cross_rho() to rebuild
K_star over a small rho grid, refit the same formula, and compare
logLik() values. scale = "effect" is therefore a fixed-kernel
transformation, not an estimate of rho.
Treat Gamma from a single association matrix W as data-condition
sensitive: the C2 recovery test includes a sparse-versus-dense W check,
and sparse or poorly replicated host-partner links should be reported as
weaker evidence rather than as a precise coevolution estimate.
For fixed multi-kernel fits, extract_Gamma() also inspects the fitted
kernel-similarity diagnostics. If the requested component participates in a
high-overlap kernel pair, the returned block is still available, but a
warning reminds the caller that component-specific separation is weak
evidence.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
Gamma_HP <- extract_Gamma(
fit,
level = "cross",
row_traits = c("host_size", "host_defence"),
col_traits = c("partner_size", "partner_attack")
)
A numeric matrix with rows row_traits and columns col_traits.
Returns the trait loading matrix from a fit returned by gllvmTMB(). This is
the canonical snake_case accessor in the extract_*() family; getLoadings()
is an accepted compatibility spelling for readers coming from gllvm. Both
return the same matrix.
extract_loadings(fit, level = "unit", rotate = c("none", "varimax", "promax"))extract_loadings(fit, level = "unit", rotate = c("none", "varimax", "promax"))
fit |
A fitted multivariate model returned by |
level |
|
rotate |
Optional |
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
extract_loadings(fit, level = "unit", rotate = "varimax")
An n_traits x d numeric matrix.
extract_ordination() for scores and loadings together;
getLoadings() for the gllvm-style spelling.
For a predictor-informed ordinary latent fit, latent(..., lv = ~ x) uses
the unit-level score model
where the innovation keeps the usual standard-normal prior.
extract_lv_effects() reports either the raw axis-scale
coefficients or the induced trait-scale contribution
.
The axis-scale table is the default because it matches the usual constrained
latent-variable / ordination coefficient. It is conditional on the fitted
loading constraint and axis orientation: like the loadings themselves
(extract_loadings()), is identified only up to
the same orthogonal (or oblique) rotation, so raw axis-scale coefficients
from two fits – even of the same model on the same data – need not sit
on a common axis and are not directly comparable without alignment. This
is intrinsic to latent-variable identifiability, not a defect. For
cross-fit comparison, prefer type = "trait_effect", the
rotation-invariant induced slope surface on the trait linear-predictor
scale, described next.
extract_lv_effects( fit, level = "unit", type = c("axis_effect", "trait_effect"), conf.level = 0.95, method = c("wald", "profile", "bootstrap"), ... )extract_lv_effects( fit, level = "unit", type = c("axis_effect", "trait_effect"), conf.level = 0.95, method = c("wald", "profile", "bootstrap"), ... )
fit |
A fit returned by |
level |
Currently |
type |
|
conf.level |
Confidence level for the interval (Wald, or the confidence level passed to the profile / bootstrap CI). |
method |
Interval method. |
... |
Reserved for future interval routes; currently unused. |
If the axis-scale representation is specifically required and cross-fit
alignment matters, apply the same rotation used for the loadings: the
mean latent score is , so it rotates
exactly as the scores in rotate_loadings() do – for the rotation matrix T returned by
rotate_loadings(fit, level, method)$T (verified to reconstruct the
rotated mean score to machine precision). There is no separate exported
helper that performs this composition; it must be assembled from
rotate_loadings()'s T and the estimate column here.
For native TMB fits, std.error is populated from a positive-definite
sdreport() when available. Axis-effect SEs come from the fixed-parameter
block for alpha_lv_B; trait-effect SEs come from TMB's delta-method
ADREPORT(B_lv_unit) output. lower and upper are Wald intervals using
conf.level. Broad repeated-sampling coverage has not been established, so
finite bounds remain experimental rather than nominally calibrated. For Gaussian,
Poisson, NB2, Gamma, Beta, and binomial logit/probit/cloglog
engine = "julia" bridge fits, ci_method = "none" exposes point estimates
only (std.error, lower, and upper are NA). When the bridge supplies a
retained delta-method uncertainty data, extract_lv_effects() surfaces
finite std.error, lower, and upper. Those Julia bridge values are not
coverage-calibrated intervals.
A data frame. For type = "axis_effect", columns are level,
axis, predictor, estimate, std.error, lower, upper,
rotation_status and uncertainty_status. For
type = "trait_effect", columns are level, trait, predictor,
estimate, std.error, lower, upper, and uncertainty_status.
The uncertainty_status field states why standard errors are unavailable
(for example, skipped sdreport(), a non-positive-definite Hessian, or
non-finite standard errors) or labels finite Wald bounds as not yet
coverage-calibrated.
Sum of matrices across the user-
selected tiers. The canonical PGLLVM use case is the species-level
three-piece decomposition:
Here tiers = c("phy", "B") returns
where
aggregates the non-phylogenetic shared and unique components.
extract_Omega(fit, tiers = NULL, link_residual = c("auto", "none"))extract_Omega(fit, tiers = NULL, link_residual = c("auto", "none"))
fit |
A fit returned by |
tiers |
Character vector. Subset of |
link_residual |
For non-Gaussian fits: |
For two-level behavioural-syndrome fits, tiers = c("B", "W")
returns the phenotypic trait covariance
.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
# PGLLVM three-piece total
om <- extract_Omega(fit, tiers = c("phy", "B"))
round(om$R_Omega, 2)
# Phenotypic covariance from a two-level behavioural-syndromes fit
om2 <- extract_Omega(fit, tiers = c("B", "W"))
A list with Omega (T × T summed covariance), R_Omega
(correlation), tiers_used (which tiers were actually summed),
note (notes from each underlying extract_Sigma() call), and
(when link_residual = "auto") residual_split — the per-trait
data frame
from extract_residual_split().
extract_Sigma(); extract_phylo_signal();
extract_proportions(); extract_residual_split().
Extract ordination scores and loadings from a fitted multivariate model
extract_ordination( fit, level = "unit", component = c("total", "innovation", "mean") )extract_ordination( fit, level = "unit", component = c("total", "innovation", "mean") )
fit |
A fitted multivariate model returned by |
level |
|
component |
Score component to return. |
A list with scores (units or within-unit observations in rows,
latent axes in columns) and loadings (traits in rows, axes in columns).
sim <- simulate_site_trait( n_sites = 20, n_species = 6, n_traits = 4, mean_species_per_site = 4, seed = 1 ) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | site, d = 2), data = sim$data, trait = "trait", unit = "site" ) ord <- extract_ordination(fit, level = "unit") head(ord$scores) ord$loadingssim <- simulate_site_trait( n_sites = 20, n_species = 6, n_traits = 4, mean_species_per_site = 4, seed = 1 ) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | site, d = 2), data = sim$data, trait = "trait", unit = "site" ) ord <- extract_ordination(fit, level = "unit") head(ord$scores) ord$loadings
For a phylogenetic species-level GLLVM, decomposes each trait's between-species latent variance into three additive components that sum to one:
where
is the total between-species latent variance for trait
.
extract_phylo_signal( fit, ci = FALSE, conf_level = 0.95, method = c("profile", "wald", "bootstrap"), nsim = 500L, seed = NULL, link_residual = c("none", "auto") )extract_phylo_signal( fit, ci = FALSE, conf_level = 0.95, method = c("profile", "wald", "bootstrap"), nsim = 500L, seed = NULL, link_residual = c("none", "auto") )
fit |
A fit returned by |
ci |
Logical. When |
conf_level |
Confidence level when |
method |
One of |
nsim |
Number of bootstrap replicates when
|
seed |
Optional RNG seed for the bootstrap. |
link_residual |
|
Interpretation:
phylogenetic signal — proportion of between-
species latent variance attributable to phylogenetically structured
variation ("evolutionary conservatism"). When the model uses the folded
phylo_latent(..., unique = TRUE) decomposition,
is the sum
and reflects the total phylogenetic variance.
non-phylogenetic communality — proportion of variance attributable to shared non-phylogenetic axes ("coordinated tip-level lability" across traits).
uniqueness — proportion of variance not captured by any shared axis ("relative modularity").
Requires phylo_latent() (optionally with unique = TRUE for a
phylogenetic Psi) plus a species-level latent() term, which carries its
diagonal Psi companion by default. If the species-tier Psi is absent (a
latent(..., unique = FALSE) subset), for all traits and a
cli::cli_inform() advisory fires.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
fit <- gllvmTMB(
value ~ 0 + trait + phylo_latent(species, d = 2) +
latent(0 + trait | species, d = 2) +
indep(0 + trait | species),
data = df,
trait = "trait",
unit = "species",
cluster = "species",
phylo_tree = tree
)
extract_phylo_signal(fit)
A data frame with columns trait, H2, C2_non, Psi,
V_eta (the denominator), one row per trait. The three proportions
sum to 1.0 by construction. With link_residual = "auto" an extra
link_residual proportion column is inserted before V_eta, the
four proportions sum to 1.0, and V_eta includes .
When ci = TRUE, three additional
columns are added: H2_lower, H2_upper, H2_method.
The point estimates are the supported claim. When ci = TRUE, the interval
methods are provided for exploration: their empirical coverage is not
certified for this estimand, so treat the intervals as exploratory rather
than coverage-calibrated. See NEWS.md for the current coverage status.
V_eta is the species-level latent variance: only components whose
grouping is the cluster column contribute. Concretely
, where
collects the species-grouped non-phylogenetic variance.
When unit == cluster (the usual unit = "species" setup) the ordinary
latent() term and its Psi companion are species-level, so both enter.
When unit != cluster (a crossed site x species design) Lambda_B and
its Psi are site-level and do not enter; the species-level
non-phylogenetic variance is the cluster-tier q_it term
(indep(0 + trait | <cluster>), reported as sd_q^2).
The denominator is defined at the species level. Other defensible
definitions exist — for instance a total-variance denominator
that also absorbs site-level variation, which would give a smaller H2 for
the same fit. Before 2026-07-08 this function used the unit tier
unconditionally, which silently reported H2 = 1 for every trait in a
crossed design because the q_it variance was never read. Compare
extract_proportions() if you want every component reported separately
rather than folded into a species-level ratio.
With the default link_residual = "none" the denominator contains only
the estimated species-level components, so a phylogenetic-only fit
reports H2 = 1 for every trait. For threshold/liability families the
conventional phylogenetic heritability (Mizuno et al. 2025 J. Evol. Biol.,
eq 4/18/19; de Villemereuil & Nakagawa 2014) instead includes the
distribution-specific latent residual in the
denominator. Request it with link_residual = "auto", which adds each
trait's fixed link-scale residual (see extract_Sigma()'s
"Family-aware link residuals" table) as a fourth component:
ordinal_probit() (and binomial probit):
exactly, so a phylogenetic-only fit reports the paper's
per trait.
multinomial(): each of the K-1 baseline-category contrasts is a
logit, so per contrast and a
phylogenetic-only fit reports for contrast . Contrast heritabilities are
baseline-referenced: the softmax link residual is the full matrix
— the shared baseline category
couples the contrasts with off-diagonals — so each
is the heritability of the "category k vs baseline"
liability under that baseline choice, not of "the trait". Do not
average or otherwise collapse the per-contrast values into a single
scalar heritability; changing the baseline changes the contrasts.
When a categorical family (fid 14/16) is present and
link_residual = "none", an advisory fires pointing at
link_residual = "auto"; the default is unchanged for backward
compatibility. ci = TRUE is not yet implemented for the
liability-scale denominator and refuses with a typed error.
extract_Sigma(); extract_Omega(); extract_proportions();
extract_repeatability(); extract_communality();
extract_correlations().
For each trait, returns the proportion of total latent variance attributable to each component present in the model: shared (rr) and unique (diag) at each tier (unit, unit_obs, phy), diagonal cluster / cluster2 components when present, plus optionally the binomial link's implicit residual.
extract_proportions( fit, link_residual = c("auto", "none"), format = c("long", "wide") )extract_proportions( fit, link_residual = c("auto", "none"), format = c("long", "wide") )
fit |
A fit returned by |
link_residual |
For non-Gaussian fits: |
format |
|
This is the most general proportion-decomposition function in the
package. extract_phylo_signal() is the focused PGLLVM
convenience wrapper for the
species-level case; extract_communality() gives the rr-only
"shared" proportion at one tier; extract_ICC_site() gives the
between-vs-within proportion. extract_proportions() returns all of
these in one tidy frame.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
extract_proportions(fit) # long format extract_proportions(fit, format = "wide") # one row per trait
Long format: data frame with columns trait, component
(e.g. "shared_unit", "unique_unit", "unique_cluster",
"shared_phy", "link_residual"),
variance (the absolute variance), proportion (the share of
the total). Wide format: data frame with one column per component
plus a total_variance column; the per-trait proportions sum to 1.
OLRE interpretation: for fits with a genuine observation-level
diagonal term, usually written as per-row indep() in new code (see
extract_residual_split()), the historical unique_W component in this
output corresponds to (the estimated OLRE variance) and
the link_residual component corresponds to (the
distribution-specific latent residual).
extract_phylo_signal() — the PGLLVM-specific shortcut;
extract_communality(); extract_ICC_site();
extract_residual_split() — explicit
decomposition for OLRE fits.
Returns the per-trait repeatability
where
and
The first two terms include the shared latent and diagonal companion variance
at the unit and observation tiers; is the
family-specific link residual used for non-Gaussian traits. The function is
intended for a fit returned by gllvmTMB() with the relevant ordinary unit
and observation-level components.
Also known as the intraclass correlation coefficient (ICC) at the unit
level.
extract_repeatability( fit, level = 0.95, method = c("wald", "profile", "bootstrap"), nsim = 500L, seed = NULL )extract_repeatability( fit, level = 0.95, method = c("wald", "profile", "bootstrap"), nsim = 500L, seed = NULL )
fit |
A fit returned by |
level |
Confidence level. Default 0.95. |
method |
One of |
nsim |
Number of bootstrap replicates when
|
seed |
Optional RNG seed for the bootstrap. |
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
fit <- gllvmTMB(
value ~ 0 + trait +
latent(0 + trait | site, d = 1) +
latent(0 + trait | site_species, d = 1),
data = df,
trait = "trait",
unit = "site",
unit_obs = "site_species"
)
extract_repeatability(fit)
boot <- bootstrap_Sigma(fit, n_boot = 50, level = c("unit", "unit_obs"),
what = "ICC", progress = FALSE)
extract_repeatability(boot)
A data frame with columns trait, R (point
estimate), lower, upper, method.
The point estimates are the supported claim. The Wald and bootstrap interval
methods here are provided for exploration: their empirical coverage is not
certified for this estimand, so treat intervals as exploratory rather than
coverage-calibrated. See NEWS.md for the current coverage status.
"wald" (default): Gaussian-approximation CI via the delta method
on log(v_B) - log(v_W), transformed with plogis().
"profile": withdrawn. It aborts rather than silently
substituting a different interval method or estimand.
"bootstrap": parametric bootstrap via bootstrap_Sigma().
Nakagawa, S. & Schielzeth, H. (2010) Repeatability for Gaussian and non-Gaussian data: a practical guide for biologists. Biological Reviews 85, 935-956. doi:10.1111/j.1469-185X.2010.00141.x
extract_communality,
extract_correlations,
extract_phylo_signal,
confint.gllvmTMB_multi.
Canonical snake_case accessors in the extract_*() family for the implied
trait covariance and correlation at level = "unit" or "unit_obs".
getResidualCov() and getResidualCor() are accepted gllvm-compatibility
spellings; each pair returns the same matrix.
extract_residual_cov(fit, level = "unit") extract_residual_cor(fit, level = "unit")extract_residual_cov(fit, level = "unit") extract_residual_cor(fit, level = "unit")
fit |
A fitted multivariate model returned by |
level |
|
An n_traits x n_traits matrix.
extract_Sigma() for the unified Sigma API;
getResidualCov() for the gllvm-style spellings.
Returns one row per trait and latent axis from a fitted gllvmTMB() model.
This is the report-ready companion to rotate_loadings() and to
plot(type = "ordination"): the same rotation, axis-ordering, sign-anchor,
and optional loading-standardization conventions are recorded in columns
instead of left inside a matrix.
extract_rotated_loadings_table( fit, level = "unit", method = c("varimax", "promax", "none"), order_axes = TRUE, sign_anchor = c("auto", "none"), anchor_traits = NULL, loading_scale = c("raw", "standardized") )extract_rotated_loadings_table( fit, level = "unit", method = c("varimax", "promax", "none"), order_axes = TRUE, sign_anchor = c("auto", "none"), anchor_traits = NULL, loading_scale = c("raw", "standardized") )
fit |
A fitted multivariate model returned by |
level |
|
method |
One of |
order_axes |
Logical. When |
sign_anchor |
One of |
anchor_traits |
Optional character vector of trait names used for sign anchoring. Supply one trait per axis after ordering. Axes without a supplied anchor use the trait with the largest absolute loading. |
loading_scale |
One of |
Scope: covered for fitted latent() components at level = "unit" or
level = "unit_obs" with raw or standardized point-estimate loadings.
This table does not attach uncertainty intervals to loadings. Bootstrap
or simulation-based uncertainty is not yet implemented and remains
possible future work for a later inference slice.
A data frame with columns level, trait, axis, loading,
abs_loading, axis_variance, axis_share, rotation, order_axes,
sign_anchor, anchor_trait, and loading_scale. axis_variance and
axis_share are computed from the rotated raw loading matrix before any
optional standardization; these are the quantities used to order axes.
rotate_loadings(), extract_ordination(), extract_Sigma(),
plot.gllvmTMB_multi().
sim <- simulate_site_trait( n_sites = 60, n_species = 12, n_traits = 4, Lambda_B = matrix(c(1.0, 0.7, -0.3, 0.5, 0.3, -0.5, 0.8, 0.2), nrow = 4, ncol = 2), psi_B = c(0.3, 0.3, 0.3, 0.3), seed = 1 ) fit <- gllvmTMB(value ~ 0 + trait + latent(0 + trait | site, d = 2), data = sim$data, trait = "trait", unit = "site") extract_rotated_loadings_table( fit, level = "unit", method = "varimax", loading_scale = "standardized" )sim <- simulate_site_trait( n_sites = 60, n_species = 12, n_traits = 4, Lambda_B = matrix(c(1.0, 0.7, -0.3, 0.5, 0.3, -0.5, 0.8, 0.2), nrow = 4, ncol = 2), psi_B = c(0.3, 0.3, 0.3, 0.3), seed = 1 ) fit <- gllvmTMB(value ~ 0 + trait + latent(0 + trait | site, d = 2), data = sim$data, trait = "trait", unit = "site") extract_rotated_loadings_table( fit, level = "unit", method = "varimax", loading_scale = "standardized" )
Implements the decomposition
where ordinary latent() now carries both and the
diagonal companion by default. This is the same
decomposition the behavioural-syndromes / phenotypic-integration literature
uses (Bartholomew et al. 2011).
extract_Sigma( fit, level = c("unit", "unit_slope", "unit_obs", "phy", "phy_slope", "column_slope", "spatial", "spde_slope", "cluster", "cluster2", "B", "B_slope", "W", "spde"), part = c("total", "shared", "unique", "psi"), link_residual = c("auto", "none"), .skip_warn = FALSE )extract_Sigma( fit, level = c("unit", "unit_slope", "unit_obs", "phy", "phy_slope", "column_slope", "spatial", "spde_slope", "cluster", "cluster2", "B", "B_slope", "W", "spde"), part = c("total", "shared", "unique", "psi"), link_residual = c("auto", "none"), .skip_warn = FALSE )
fit |
A fit returned by |
level |
One of |
part |
One of |
link_residual |
For non-Gaussian fits. |
.skip_warn |
Internal flag (default |
extract_Sigma_B() and extract_Sigma_W() are soft-deprecated
compatibility wrappers. They still return their historical Sigma_B /
R_B and Sigma_W / R_W field names, respectively, but new code should
call this function with level = "unit" or level = "unit_obs".
If the formula deliberately uses
latent(0 + trait | unit, d = K, unique = FALSE), the engine fits only the
component. Calling
extract_Sigma(fit, level, part = "total") then returns just the shared
component. This is useful for an intentionally loadings-only target. It should
not be described as a full latent-liability covariance unless the relevant
family/link residual is also included. Whether the diagonal is incomplete
depends on the estimand and response family.
For Gaussian / lognormal / Gamma fits this function emits an advisory note
when a reduced-rank tier has no Psi component. Use the ordinary
latent(..., unique = TRUE) default for
.
For non-Gaussian families (binomial, Poisson, Gamma) the latent-scale
residual variance has a closed-form approximation that should be added
to the diagonal of – see the link_residual
argument below.
part argument"total" (default)
– the matrix users almost always want for reporting correlations.
"shared" only – the
reduced-rank, rotation-invariant component. Diagonals are
; these are not the trait
variances, they are the shared part of the trait variances.
"unique" only – the trait-specific unique
variances, returned as a length-T named numeric vector (the
diagonal of ).
"shared" vs "unique" partition is only weakly identifiedThe total
is rotation-invariant, so part = "total" avoids arbitrary loading rotation.
Rotation invariance does not guarantee that the data identify every covariance
entry; fit health, sample design, and uncertainty still matter. The split
between
and is only weakly identified – different optimiser
starts can flow trait 's variance more into the shared
("shared") or unique ("unique") component, with the same total
likelihood. In a synthetic Poisson + OLRE recovery run with target
, gllvmTMB returned
– total trait variances correct, but the
partition arbitrary.
This is the standard rotation-and-shift indeterminacy of factor models
(gllvm and Hmsc have it too); communality computed from
a single fit therefore inherits the same indeterminacy. For
interpretation, lean on part = "total" for correlations and use
extract_communality() only when paired with bootstrap_Sigma() or
the variance-decomposition consistency checks in
gllvmTMB_diagnose().
For non-Gaussian responses each row carries an implicit observation-level
residual on the latent (link) scale. Adding it to the per-trait diagonal
of defines one family-specific latent-scale convention.
It can make the denominator explicit, but it does not make different response
families directly commensurate without additional scientific assumptions.
Per-family formulas (Nakagawa & Schielzeth 2010; Nakagawa, Johnson & Schielzeth 2017):
gaussian (identity) |
|
binomial(link = "logit") |
|
binomial(link = "probit") |
|
binomial(link = "cloglog") |
|
poisson(link = "log") |
(lognormal-Poisson approx.) |
lognormal(link = "log") |
(sigma_eps already models the log-scale residual) |
Gamma(link = "log") |
where is the per-trait shape |
nbinom2(link = "log") |
where is the per-trait NB2 dispersion |
tweedie(link = "log") |
(delta method) |
Beta(link = "logit") |
(Smithson & Verkuilen 2006) |
betabinomial(link = "logit") |
|
multinomial() is the exception to the scalar-per-trait table: its
distribution-specific residual is not a scalar but the
matrix over the category-contrast
pseudo-traits – on the diagonal (each baseline contrast is a
logit, exactly as for binomial(link = "logit")) and
off-diagonal (the shared baseline category couples the contrasts). This is the
residual covariance implied by the softmax's random-utility (Gumbel)
representation (McFadden 1974), and it reduces to the binomial at
.
(It is distinct from MCMCglmm's arbitrary fixed identification residual
and that package's MCMC-specific
overdispersion correction, neither of which a direct softmax fit needs.)
link_residual = "auto" (the default) applies this full-matrix residual: the
block is added to the multinomial
trait's -contrast block of (the diagonal
entry-by-entry with the other families, the
off-diagonal as a block); "none" returns the latent-scale V unchanged. The
addition is tier-agnostic – it applies to whichever tier
is requested (level = "phy", a spatial or kernel tier, or an ordinary latent
term) – and it makes the categorical block commensurable with any single-scale
(e.g. Gaussian) trait sharing the fit. Relatedly, a diagonal among-category V
is not independence: the independence null for the contrasts is itself
-structured (equal diagonal, equal off-diagonal).
For mixed-family fits the residual is computed per trait when that trait has
one unambiguous family/link, then added to the diagonal
of entry-by-entry. The default
link_residual = "auto" applies this; "none" returns the fitted model
covariance without link-residual additions
( where a
latent decomposition is present). For continuous-only Gaussian or lognormal fits
"auto" is a no-op (their per-trait is zero).
Citations: Nakagawa & Schielzeth (2010); Nakagawa, Johnson & Schielzeth (2017); McFadden (1974) for the multinomial-logit random-utility residual — see References below.
The engine currently supports two latent() tiers ("unit" and
"unit_obs"); legacy level = "B" and "W" aliases are still accepted
with a soft-deprecation message.
level = "phy" extracts the phylogenetic implied
from phylo_latent(). If a future
release adds 3+ latent tiers, level = "<colname>" will dispatch to the
corresponding tier without API change. For now, custom strings error
with a clear unsupported-level message.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
fit <- gllvmTMB(
value ~ 0 + trait +
latent(0 + trait | unit, d = 2),
data = df,
trait = "trait",
unit = "unit"
)
extract_Sigma(fit, level = "unit", part = "total")$Sigma # full T x T cov
extract_Sigma(fit, level = "unit", part = "shared")$Sigma # rr-only
extract_Sigma(fit, level = "unit", part = "unique")$s # diag(s_unit)
For part = "total" or "shared": a list with components
Sigma (T x T matrix), R (T x T correlation matrix; only for
"total"), level, part, and note (character vector of
advisory messages, e.g. about a no-Psi unique = FALSE fit).
For part = "unique": a list with s (length-T named numeric
vector of unique variances), level, part, note.
For a phylo_dep(1 + x1 + ... + xs | species) fit with one or more
slopes, call with level = "phy": the result is the single full
unstructured (1+s)T x (1+s)T covariance over the trait-stacked
(intercept, slope_1, ..., slope_s) random-effect columns – a list with
Sigma and R carrying INTERLEAVED dimnames (per trait:
intercept.<t>, then slope.<t> for s == 1 or slope.<x_j>.<t> for
s >= 2), level = "phy_dep", part = "dep", and a note. The part and
link_residual arguments do not apply to this single unstructured
block and are ignored. (The unit / unit_obs tiers return NULL for a
dep-only fit, as it carries no between/within-unit covariance term.)
Current phylo_indep(1 + x | species) uses the same interleaved 2T
report channel with all cross-trait blocks fixed to zero. Its return has
level = "phy_indep_slope" and part = "indep"; each trait retains its
own intercept-slope block. This is distinct from the legacy shared 2 x 2
augmented phylo_unique channel.
For augmented spatial slopes, call with level = "spatial".
spatial_unique(1 + x | coords) retains the shared 2 x 2 field-scale
channel and returns level = "spde_base_slope". Current
spatial_indep(1 + x | coords) returns the interleaved, cross-trait
block-diagonal 2T x 2T field covariance with
level = "spde_indep_slope" and part = "indep"; spatial_dep returns
the full unstructured block with level = "spde_dep" and part = "dep".
These SPDE matrices are on the fitted field-covariance scale; the returned
note records the marginal-scale conversion.
For an ordinary individual-level random-regression fit with
latent(1 + x | unit, d = K), indep(1 + x | unit), or their long-form
equivalents, call with level = "unit_slope": the result is the augmented
2T x 2T covariance over trait-specific intercept and slope coefficients,
with row names intercept.<trait> and slope.<x>.<trait>. As for
level = "unit", part = "shared" returns Lambda_aug Lambda_aug^T,
part = "unique" returns the augmented diagonal, and part = "total"
returns their sum.
Nakagawa, S. & Schielzeth, H. (2010). Repeatability for Gaussian and non-Gaussian data: a practical guide for biologists. Biological Reviews 85, 935-956. doi:10.1111/j.1469-185X.2010.00141.x
Nakagawa, S., Johnson, P. C. D., & Schielzeth, H. (2017). The coefficient
of determination and intra-class correlation coefficient from
generalized linear mixed-effects models revisited and expanded.
Journal of the Royal Society Interface 14(134), 20170213.
doi:10.1098/rsif.2017.0213
McFadden, D. (1974). Conditional logit analysis of qualitative choice
behavior. In P. Zarembka (Ed.), Frontiers in Econometrics (pp. 105-142).
Academic Press, New York. (Random-utility origin of the multinomial-logit
contrast residual.)
extract_communality() for the per-trait shared / unique
variance share at one tier; extract_proportions() for the
canonical per-trait variance decomposition across tiers;
extract_Omega() for the multi-tier sum.
extract_Sigma() returns matrices because that is the most convenient
interactive representation. extract_Sigma_table() returns the same
covariance target as one row per matrix entry so articles, tables, and
plot helpers can work without hand-indexing matrices. It can also turn a
bootstrap_Sigma() result into the same row schema with bootstrap interval
columns filled in.
extract_Sigma_table( fit, level = "unit", part = c("total", "shared", "unique"), measure = c("covariance", "correlation"), entries = c("unique", "all", "upper", "lower", "offdiag", "diag"), link_residual = c("auto", "none") )extract_Sigma_table( fit, level = "unit", part = c("total", "shared", "unique"), measure = c("covariance", "correlation"), entries = c("unique", "all", "upper", "lower", "offdiag", "diag"), link_residual = c("auto", "none") )
fit |
A fit returned by |
level |
Character vector of covariance levels, or |
part |
One of |
measure |
One of |
entries |
Which symmetric matrix entries to return. |
link_residual |
Passed to |
The helper reshapes the same covariance and correlation matrices returned by
extract_Sigma(). A bootstrap_Sigma() object can supply interval columns.
Julia bridge fits currently expose only point estimates for the ordinary unit
tier. The helper does not create profile or Wald intervals.
The table is a point-estimate view over extract_Sigma(). It does not
compute confidence intervals from a fitted model directly. Use
extract_correlations() when you need pairwise correlation intervals, or
bootstrap_Sigma() followed by extract_Sigma_table() when you need
bootstrap intervals for Sigma or correlation matrix entries.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
fit <- gllvmTMB(
value ~ 0 + trait +
latent(0 + trait | unit, d = 2),
data = df,
trait = "trait",
unit = "unit"
)
extract_Sigma_table(fit, level = "unit")
extract_Sigma_table(fit, level = "unit", measure = "correlation")
extract_Sigma_table(fit, level = c("unit", "unit_obs"), entries = "all")
boot <- bootstrap_Sigma(fit, n_boot = 50, level = "unit",
what = "Sigma", progress = FALSE)
extract_Sigma_table(boot, level = "unit", entries = "upper")
A data frame with one row per requested entry and stable columns:
estimand, trait_i, trait_j, integer indices i and j, level,
component, matrix, estimate, lower, upper, interval_method,
interval_status, scale, diagonal, and
triangle. Interval columns are NA with interval_method = "none"
because this helper is point-estimate only.
extract_Sigma() for the underlying matrix extractor;
extract_correlations() for pairwise correlation intervals;
bootstrap_Sigma() for bootstrap uncertainty.
A thin wrapper over predict.gllvmTMB_multi() at the training rows
(newdata = NULL). Returns the same long data frame predict() returns
– one row per training observation, carrying its unit/species/trait
identifiers alongside the fitted value – rather than a bare vector, so
row identity against the original data is preserved. ... is forwarded
to predict() unchanged, so its other arguments (se.fit, re_form)
work here too.
## S3 method for class 'gllvmTMB_multi' fitted(object, type = c("response", "link"), ...)## S3 method for class 'gllvmTMB_multi' fitted(object, type = c("response", "link"), ...)
object |
A fit returned by |
type |
One of |
... |
Forwarded to |
A data frame with the original row identifiers (unit, species,
trait columns) plus an est column on the requested scale – identical
in shape to predict(object, newdata = NULL, type = type, ...). For a
multinomial() fit this is .predict_multinomial()'s output: K rows
per observation (one per category, including the baseline), not one.
A simple decision aid in the spirit of Zientek & Thompson (2007,
doi:10.3758/BF03193163): given per-entry CIs on Lambda, flag the
entries whose conf_level confidence interval does not exclude
a user-supplied "null region" — a band around zero considered
biologically negligible on the interval's recorded loading_scale.
Loadings flagged as unreliable = TRUE
are the ones for which the data do not provide evidence that the
species responds non-trivially to the axis.
flag_unreliable_loadings( fit, null_region = c(-0.1, 0.1), level = c("unit", "unit_obs"), method = "wald", conf_level = 0.95, loading_scale = NULL )flag_unreliable_loadings( fit, null_region = c(-0.1, 0.1), level = c("unit", "unit_obs"), method = "wald", conf_level = 0.95, loading_scale = NULL )
fit |
A multivariate |
null_region |
Length-2 numeric, sorted ascending. The
"negligible" band. Defaults to |
level, method, conf_level, loading_scale
|
Forwarded to |
Pinned entries (set explicitly by lambda_constraint) are reported
with unreliable = NA because no inference is being made about them.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
## Grounded in tests/testthat/test-loading-ci.R. flag_unreliable_loadings() ## accepts either a fit or a loading_ci() data frame directly. fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | site, d = 2L), data = df, family = stats::binomial(link = "probit") ) ## From a fit (computes per-entry Lambda CIs internally): flag_unreliable_loadings(fit, null_region = c(-0.1, 0.1)) ## Or pass a precomputed loading_ci() data frame: ci <- loading_ci(fit, level = "unit") flag_unreliable_loadings(ci, null_region = c(-0.1, 0.1))
The loading_ci() data frame with one extra logical column
unreliable: TRUE if the CI overlaps null_region, FALSE if
it lies entirely outside, NA for pinned entries.
Infer a UTM coordinate reference system
get_crs(dat, ll_names = c("longitude", "latitude"))get_crs(dat, ll_names = c("longitude", "latitude"))
dat |
Data frame containing longitude and latitude columns. |
ll_names |
Character names of longitude then latitude columns. |
An EPSG code for the predominant UTM zone.
bridge_fit).Fit a GLLVM with the Julia engine (GLLVM.jl bridge_fit).
gllvm_julia_fit( y, family = "gaussian", num.lv = 2L, N = NULL, X = NULL, X_lv = NULL, coef_fixed = NULL, mask = NULL, units_are_rows = FALSE, ci_method = c("none", "wald", "profile", "bootstrap"), ci_level = 0.95, ci_nboot = 200L, ci_seed = 0L, ... )gllvm_julia_fit( y, family = "gaussian", num.lv = 2L, N = NULL, X = NULL, X_lv = NULL, coef_fixed = NULL, mask = NULL, units_are_rows = FALSE, ci_method = c("none", "wald", "profile", "bootstrap"), ci_level = 0.95, ci_nboot = 200L, ci_seed = 0L, ... )
y |
Response matrix, p x n (traits x units), or n x p (set |
family |
A family object/string, or a list of them (one per trait -> mixed). |
num.lv |
Number of latent variables (K). |
N |
Binomial trials (matrix or scalar), or |
X |
Fixed-effect design (p x n x q array), or |
X_lv |
Unit-level predictor-informed latent-score design
(n x q_lv matrix), or |
coef_fixed |
Optional logical vector of length |
mask |
Optional logical response-observation mask with the same orientation
as |
units_are_rows |
If |
ci_method |
Confidence-interval route for admitted no-X bridge rows:
Gaussian, Poisson, Bernoulli binomial, and grouped-dispersion NB2, NB1,
Beta, and Gamma. One of |
ci_level |
Nominal confidence level when |
ci_nboot |
Number of parametric bootstrap replicates when
|
ci_seed |
Seed passed to the Julia bootstrap CI route. |
... |
Passed to |
A list of class gllvmTMB_julia with the bridge contract fields
(loadings, Sigma, correlation, loglik, aic, bic,
converged, ...). If ci_method != "none" and the row is admitted, the
list also carries flat CI fields consumed by confint().
## Not run: # Requires a local GLLVM.jl install (see [gllvm_julia_setup()]). # `y` is traits x units (p x n): two Gaussian traits, 40 units, K = 2. set.seed(1) y <- matrix(rnorm(2 * 40), nrow = 2) fit <- gllvm_julia_fit(y, family = "gaussian", num.lv = 2) fit$loglik fit$Sigma # Predictor-informed latent-score means are point-only on the bridge. rownames(y) <- paste0("trait", seq_len(nrow(y))) colnames(y) <- paste0("site", seq_len(ncol(y))) x <- seq(-1, 1, length.out = ncol(y)) fit_lv <- gllvm_julia_fit( y, family = "gaussian", num.lv = 1, X_lv = matrix(x, ncol = 1, dimnames = list(colnames(y), "x")) ) extract_lv_effects(fit_lv) # Wald confidence intervals for an admitted no-X row: fit_ci <- gllvm_julia_fit( y, family = "gaussian", num.lv = 2, ci_method = "wald" ) ## End(Not run)## Not run: # Requires a local GLLVM.jl install (see [gllvm_julia_setup()]). # `y` is traits x units (p x n): two Gaussian traits, 40 units, K = 2. set.seed(1) y <- matrix(rnorm(2 * 40), nrow = 2) fit <- gllvm_julia_fit(y, family = "gaussian", num.lv = 2) fit$loglik fit$Sigma # Predictor-informed latent-score means are point-only on the bridge. rownames(y) <- paste0("trait", seq_len(nrow(y))) colnames(y) <- paste0("site", seq_len(ncol(y))) x <- seq(-1, 1, length.out = ncol(y)) fit_lv <- gllvm_julia_fit( y, family = "gaussian", num.lv = 1, X_lv = matrix(x, ncol = 1, dimnames = list(colnames(y), "x")) ) extract_lv_effects(fit_lv) # Wald confidence intervals for an admitted no-X row: fit_ci <- gllvm_julia_fit( y, family = "gaussian", num.lv = 2, ci_method = "wald" ) ## End(Not run)
Set up JuliaCall and load the GLLVM.jl engine (once per R session).
gllvm_julia_setup( jl_path = getOption("gllvmTMB.GLLVM.jl.path", Sys.getenv("GLLVM_JL_PATH", "")), julia_home = getOption("gllvmTMB.julia_home", Sys.getenv("JULIA_HOME", "")) )gllvm_julia_setup( jl_path = getOption("gllvmTMB.GLLVM.jl.path", Sys.getenv("GLLVM_JL_PATH", "")), julia_home = getOption("gllvmTMB.julia_home", Sys.getenv("JULIA_HOME", "")) )
jl_path |
Path to the GLLVM.jl project that provides |
julia_home |
Julia |
Invisibly TRUE once ready.
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 / kernel) and
three modes (independent / dependent / latent). The common = TRUE
modifier on *_indep() gives the one-shared-variance special case:
gllvmTMB( formula, data, trait = "trait", unit = "site", unit_obs = NULL, cluster = NULL, 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, estimator = c("ml", "mspl") )gllvmTMB( formula, data, trait = "trait", unit = "site", unit_obs = NULL, cluster = NULL, 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, estimator = c("ml", "mspl") )
formula |
A glmmTMB-style formula, e.g.
An In long format the offset column carries one value per A nonzero offset on a non-count trait is an error naming that trait. Zero is always allowed and does nothing (on the log scale it is a multiplier of one), which is how a mixed-family model gives an offset to its count traits and not the rest. |
data |
A data frame. With an ordinary response LHS such as
|
trait |
Name of the column holding the trait factor (the
"trait" dimension of the unit × trait response matrix). Default
|
unit |
Name of the column holding the between-unit grouping
factor (the "unit" dimension of the unit × trait response matrix).
Examples: |
unit_obs |
Optional. Name of the column holding the within-unit
grouping factor, with one level per (unit, replicate) cell, used by
|
cluster |
Optional. Name of the column holding the third
grouping factor (the "cluster" slot). When the column name matches
the row/column names of The engine does not enforce nesting between
|
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 |
family |
A |
weights |
Optional numeric vector of length
|
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, and REML is validated for all-Gaussian fits only. For non-Gaussian families
an opt-in escape hatch exists
( |
mesh |
Optional mesh object from |
phylo_vcv |
(legacy global) Optional tip-only |
phylo_tree |
(legacy global) Optional |
known_V |
Optional list of block-diagonal sampling-error matrices
|
lambda_constraint |
Optional list with elements |
Xcoef_fixed |
Optional named numeric vector of fixed-effect
coefficient constraints. Names must match the expanded fixed-effect
design columns ( |
control |
Output of |
missing |
Output of |
impute |
Optional specification of the covariate model for a predictor
declared missing with |
silent |
Logical; suppress TMB and gllvmTMB chatter. Default |
engine |
Character; |
ci_method |
Confidence-interval route requested at fit time for
admitted |
ci_level |
Nominal confidence level when |
ci_nboot |
Number of parametric bootstrap replicates when
|
ci_seed |
Seed passed to the Julia bootstrap CI route. |
site |
(deprecated) alias for |
species |
(deprecated) alias for |
estimator |
Estimation criterion. |
| source \ mode | independent | dependent | latent |
| none | indep() |
dep() |
latent() |
| animal | animal_indep() |
animal_dep() |
animal_latent() |
| phylo | phylo_indep() |
phylo_dep() |
phylo_latent() |
| spatial | spatial_indep() |
spatial_dep() |
spatial_latent() |
| kernel | kernel_indep() |
kernel_dep() |
kernel_latent()
|
The grid lists available syntax. Reliability depends on the family
and uncertainty method. Gaussian indep() and dep() point fits are the
narrow tested starting routes; structured-source and non-Gaussian routes
need their named evidence boundary. General interval coverage is not claimed.
The three covariance modes (indep / dep / latent) encode
covstruct intent across traits:
latent: the decomposition mode
:
a low-rank shared part plus a diagonal companion.
Ordinary latent() carries by default; the
unique = argument controls it (latent(..., unique = FALSE) for the
loadings-only subset, *_latent(..., unique = TRUE) to fold the
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:
is free with 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 / glmmTMB convention; recommended), or
a flat response y with weights = 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(...).
The modelled-mi() route is native Laplace only; the variational route
refuses mi() predictors. Dense missing-response masks have their own
narrower VA fence and do not imply predictor-model support.
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
and the positive-component
mean is . 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.
"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:
indep, Gaussian/lognormal presentOne
shared sigma_eps across Gaussian/lognormal rows. Not per-trait.
indep, no Gaussian/lognormal rowssigma_eps
is mapped off; the family's intrinsic dispersion handles the residual
(for ordinary Gamma, phi_gamma).
indep(0 + trait | g) where g has fewer levels than
rows (e.g. g = "site")sigma_eps is still estimated as the
row-level residual; the diagonal term adds a per-trait random
effect at level g on top.
indep(0 + trait | obs) at the per-row level (one
level per row), Gaussian/lognormal rows fittedsigma_eps is
auto-suppressed (mapped off, fixed at a tiny stabiliser); the
T per-trait diagonal random effects are the residual. A
one-shot cli::cli_inform fires 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 have
n_trials == 1): theta_diag_W[t] and the corresponding
s_W column are mapped off. The OLRE is statistically
unidentifiable here (no within-cell replication) and the MLE
is the trivial boundary ; pinning the
parameter at removes the
spurious free parameter and emits a one-shot informational
message. Multi-trial binomial (via cbind(succ, fail) or
weights = 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.
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).
S3 methods are registered on the subclasses, not on bare
gllvmTMB: for a multi-trait fit (gllvmTMB_multi), tidy(),
predict(), summary(), confint() and logLik() are available, and
AIC() / BIC() dispatch through logLik() only when the fitted point is
an unpenalised native-Laplace ML maximum. AGHQ uses a different integration
objective and a loading ridge returns a penalised MAP point, so ordinary
likelihood comparisons do not apply. An MSPL result has leading class
"gllvmTMB_mspl"; likelihood comparison, standard errors, intervals,
profiles, and hypothesis-test methods fail closed because this experimental
surface is point estimation only. Trait-level extractors such as
extract_ICC_site() and extract_communality() are multi-trait only.
vcov() and coef() are available too; see
gllvmTMB_multi-vcov. coef() works on any fit; vcov() needs the
sdreport() and raises the same typed errors confint() does when it
is missing or non-finite.
History, 2026-08-04: this block used to say vcov() dispatched on
gllvmTMB, which was never true of any release. It and coef()
existed only for gllvmTMB_va, where they refuse. The wording was
corrected first, then the two methods were added, which is why the
promise now holds.
The fitted object's $report carries per-family dispersion quantities on
the natural scale, each a vector of length n_traits indexed by trait
(except sigma_eps, a single scalar). Their names follow the engine's
internal parameterisation, not standard R distribution arguments;
several are easy to misread. What each one IS, and how it maps to
standard R arguments (mu = exp(eta) unless noted):
| Family (report name) | What it is | Standard-R conversion |
gaussian, lognormal (sigma_eps) |
residual SD / sdlog; one scalar shared by all gaussian and lognormal traits | pnorm(y, mean = eta, sd = sigma_eps); plnorm(y, meanlog = eta, sdlog = sigma_eps) |
Gamma (phi_gamma) |
the shape, not a dispersion | pgamma(y, shape = phi, scale = mu / phi); CV(y) = 1 / sqrt(phi) |
nbinom2 (phi_nbinom2) |
the NB size |
pnbinom(y, size = phi, mu = mu); Var(y) = mu + mu^2 / phi |
nbinom1 (phi_nbinom1) |
linear overdispersion, Var(y) = mu * (1 + phi) |
pnbinom(y, size = mu / phi, mu = mu); the size is mean-dependent |
tweedie (phi_tweedie, p_tweedie) |
dispersion and power p in (1, 2) |
no base-R CDF; tweedie::ptweedie(y, mu = mu, phi = phi, power = p) |
Beta (phi_beta) |
the precision | pbeta(y, shape1 = mu * phi, shape2 = (1 - mu) * phi) with mu = plogis(eta) |
betabinomial (phi_betabinom) |
precision of the Beta mixing | a = mu * phi, b = (1 - mu) * phi with mu = plogis(eta); no base-R CDF |
student (sigma_student, df_student) |
the scale, not the SD | pt((y - eta) / sigma, df); SD(y) = sigma * sqrt(df / (df - 2)), undefined for df <= 2 |
truncated_nbinom2 (phi_truncnb2) |
the NB size; a separate vector from phi_nbinom2 |
pnbinom(y, size = phi, mu = mu) renormalised by 1 - pnbinom(0, ...) |
delta_lognormal (sigma_lognormal_delta) |
sdlog of the positive part | plnorm(y, meanlog = eta, sdlog = sigma) for y > 0; P(y > 0) = plogis(eta) |
delta_gamma (phi_gamma_delta) |
the CV of the positive part, not a shape | pgamma(y, shape = 1 / phi^2, scale = mu * phi^2) for y > 0; P(y > 0) = plogis(eta)
|
A fitted object carries two different surfaces that both look like "the objective", and they answer different questions.
report$joint_nll_unpenalized and report$joint_nll_penalized are
conditional joint quantities computed on the TMB tape (the negative
log-likelihood of the data and random effects at their current values,
before and after TMB-side MSPL penalty terms are added). They see only
penalties applied inside the C++ template – the LA-MSPL penalties
documented above – and never see anything computed at the R level.
fit$objective_components is the marginal surface: the value TMB's
Laplace approximation integrates the random effects out of, decomposed
into likelihood_nll (the unpenalised marginal negative log-likelihood),
ridge_penalty (the R-level loading ridge from aghq_ridge, added
outside the TMB objective – see .gllvmTMB_penalised_gradient()),
optimization_nll (their sum, the objective the optimiser actually
minimised), and optimizer_reported (the value nlminb() / optim()
returned). This is the only place the R-level loading ridge appears;
report$joint_nll_* cannot see it because it never reaches the TMB tape.
The two surfaces are not expected to match, on a ridged fit or otherwise, and a difference between them is not itself an error: they are conditional-joint and marginal quantities from different penalty regimes.
Sterzinger, P. and Kosmidis, I. (2023). Maximum softly-penalized likelihood for mixed effects logistic regression. Statistics and Computing, 33, 53. doi:10.1007/s11222-023-10217-3. This paper is the logistic mixed-effects antecedent for the fixed-effect Jeffreys component. The probit, complementary-log-log, reduced-rank GLLVM, and spatial penalties implemented here are gllvmTMB-specific extensions; that paper does not validate those extensions.
screen_gllvmTMB() for the opt-in fixed-design separation
certificate to use before fitting; estimator = "mspl" is documented
in this topic. 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.
set.seed(1) sim <- simulate_site_trait(n_sites = 50, n_species = 8, n_traits = 3, mean_species_per_site = 5) # Long format: one response value per site-species-trait row. fit_long <- gllvmTMB( value ~ 0 + trait + (0 + trait):env_1 + (0 + trait):env_2, data = sim$data, family = gaussian(), trait = "trait", unit = "site_species" ) # Wide format: one row per site-species, with the same three traits. # `traits()` expands `1 + env_1 + env_2` to the explicit long terms above. wide_values <- stats::reshape( sim$data[c("site_species", "trait", "value")], idvar = "site_species", timevar = "trait", direction = "wide" ) wide <- merge( unique(sim$data[c("site_species", "env_1", "env_2")]), wide_values, by = "site_species", sort = FALSE ) names(wide) <- sub("^value[.]", "", names(wide)) fit_wide <- gllvmTMB( traits(trait_1, trait_2, trait_3) ~ 1 + env_1 + env_2, data = wide, family = gaussian(), unit = "site_species" ) summary(fit_long) summary(fit_wide)set.seed(1) sim <- simulate_site_trait(n_sites = 50, n_species = 8, n_traits = 3, mean_species_per_site = 5) # Long format: one response value per site-species-trait row. fit_long <- gllvmTMB( value ~ 0 + trait + (0 + trait):env_1 + (0 + trait):env_2, data = sim$data, family = gaussian(), trait = "trait", unit = "site_species" ) # Wide format: one row per site-species, with the same three traits. # `traits()` expands `1 + env_1 + env_2` to the explicit long terms above. wide_values <- stats::reshape( sim$data[c("site_species", "trait", "value")], idvar = "site_species", timevar = "trait", direction = "wide" ) wide <- merge( unique(sim$data[c("site_species", "env_1", "env_2")]), wide_values, by = "site_species", sort = FALSE ) names(wide) <- sub("^value[.]", "", names(wide)) fit_wide <- gllvmTMB( traits(trait_1, trait_2, trait_3) ~ 1 + env_1 + env_2, data = wide, family = gaussian(), unit = "site_species" ) summary(fit_long) summary(fit_wide)
gllvmTMB_check_consistency() is an advanced validation helper for
the Laplace approximation, not a first-use diagnostic. Start with
check_gllvmTMB() or gllvmTMB_diagnose(). Use this function when
a fit looks numerically acceptable but you need a direct check of
whether the approximate marginal score is centred.
gllvmTMB_check_consistency(fit, n_sim = 100L, seed = NULL, estimate = FALSE)gllvmTMB_check_consistency(fit, n_sim = 100L, seed = NULL, estimate = FALSE)
fit |
A fit returned by |
n_sim |
Integer number of simulate-evaluate replicates.
Default |
seed |
Optional integer seed for the simulation RNG. NULL (default) draws a random seed. |
estimate |
Logical. When |
It simulates n_sim datasets from the fitted model under the joint
parameter vector at the MLE, then tests whether the approximate
marginal score function is centred at zero across the simulations. A
non-centred score is a sign that the Laplace approximation is
unreliable for this fit – typically because the random-effects
posterior is far from Gaussian (saturated binomial / Beta, sparse
counts, weakly identified random effects) or the data don't constrain
the random effects well.
Scope: local Laplace-consistency diagnostics for a fitted model. This is a diagnostic signal rather than a substitute for bootstrap, profile, or external Bayesian calibration. Broader calibration evidence is not yet available.
This is a complementary signal to sanity_multi() (which checks
Hessian definiteness, gradient magnitude, convergence flags) and
to a target-explicit known-DGP study of rank-selection behaviour. Slower
than the first-line checks – n_sim likelihood evaluations – but the only diagnostic
that targets the Laplace approximation itself rather than
the optimisation outcome or the parameter identification.
A large marginal-score p-value is an absence of evidence against
centring in this finite simulation; it is not a certificate that the
Laplace approximation is accurate. If the result is small (p_value <= 0.05) or
large flagged-parameter list), consider:
Refitting with a richer random-effects structure (so the conditional posterior is more Gaussian).
Switching to a parametric bootstrap CI (which does not depend on the Laplace approximation being a good fit).
Validating against tmbstan::tmbstan(fit$tmb_obj) (the
audit's recommended Bayesian-comparison path).
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
fit <- gllvmTMB(value ~ 0 + trait +
latent(0 + trait | site, d = 1),
data = sim$data,
trait = "trait",
unit = "site")
res <- gllvmTMB_check_consistency(fit, n_sim = 50L, seed = 1)
res
res$marginal_bias
res$flagged_parameters
An object of class gllvmTMB_check_consistency with
components:
$marginal_p_valueJoint chi-squared p-value across
all marginal-score components. NA if TMB's information
matrix could not be inverted (often the case on tiny
fixtures).
$marginal_biasNamed numeric vector: per-parameter bias (mean of the simulated score, normalised by its SE). Large absolute values flag specific parameters where the Laplace approximation is biased.
$joint_p_valueJoint-score p-value. NA unless
estimate = TRUE. Slower because each replicate refits.
$flagged_parametersCharacter names of parameters
whose marginal bias exceeds |bias| > 0.5 (a heuristic;
finite-n_sim noise can produce small spurious biases,
but |0.5| is generally beyond noise for n_sim = 100).
$n_simThe n_sim actually run.
$thresholdThe marginal-bias threshold used.
$diagnosticsCharacter vector of one or more of:
"no_centring_warning" (the finite simulation did not detect
non-centring; this is not an accuracy certificate),
"marginal_score_non_centred",
"joint_score_non_centred",
"information_matrix_singular" (TMB couldn't invert it;
interpret with caution), "marginal_p_value_unavailable"
(TMB returned NA without a captured warning; usually
happens on tiny / weakly-identified fixtures).
$rawThe full TMB::checkConsistency() return value
in case the user wants the per-replicate gradient matrix.
$callmatch.call() of the invocation.
sanity_multi() (structural / convergence checks),
check_auto_residual() (mixed-family safeguard),
gllvmTMB_diagnose() (holistic fit summary),
TMB::checkConsistency() (the underlying TMB call).
This is the human-readable diagnostic to call right after
fit <- gllvmTMB(...). It combines the quick numerical screen
(sanity_multi()), the rotation identifiability advisory, and key
biological summaries (correlation diagonals, ICCs, communalities)
into a single report with explicit next-step hints for any WARN
signal. Use check_gllvmTMB() when you need the same fit-health
checks as a stable table for scripts or reports.
gllvmTMB_diagnose( object, gradient_thresh = 0.01, se_thresh = 100, big_corr_thresh = 0.5, verbose = TRUE )gllvmTMB_diagnose( object, gradient_thresh = 0.01, se_thresh = 100, big_corr_thresh = 0.5, verbose = TRUE )
object |
A fit returned by |
gradient_thresh, se_thresh
|
Forwarded to |
big_corr_thresh |
Threshold above which a |
verbose |
If |
Scope: first-line convergence, Hessian, standard-error, restart, and rotation diagnostics. It reports risks and summaries but does not replace profile, bootstrap, or simulation calibration. Target-explicit validation will decide which interval warnings can be promoted to broader guarantees.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
fit <- gllvmTMB(value ~ 0 + trait + latent(0 + trait | site, d = 2),
data = dat, trait = "trait", unit = "site")
gllvmTMB_diagnose(fit)
Invisibly a list with components: sanity (the
sanity_multi() flags), rotation (rotation-advisory list),
Sigma_B, Sigma_W, ICC_site, communality_B, communality_W,
and hints (character vector of suggested actions).
check_gllvmTMB(), sanity_multi(),
suggest_lambda_constraint(),
extract_Sigma(), extract_communality(),
compare_dep_vs_two_psi() / compare_indep_vs_two_psi() for
identifiability cross-checks on the paired phylogenetic fit.
Small S3 surface for fits returned by gllvmTMB(..., engine = "julia") or
gllvm_julia_fit(). These methods expose the flat bridge payload that has
already passed the R admission gates. They are point-estimate summaries:
prediction and ordinary response/Pearson residuals are in-sample
retained-payload reconstructions only. Simulation is conditional on retained
fitted values for admitted one-family rows and complete balanced mixed-family
rows. Unit-tier covariance honors native link_residual scale semantics,
and raw ordination accessors are routed; richer extractor parity remains a
separate bridge row.
Confidence intervals are
routed for admitted no-X Gaussian, Poisson, Bernoulli binomial, NB2, NB1,
Beta, and Gamma rows; response-mask CIs are routed for the same non-Gaussian
rows when X = NULL; complete-response fixed-effect-X CIs are routed for
Gaussian, Poisson, Bernoulli binomial, NB2, Beta, and Gamma rows. They may
be requested at fit time through
gllvmTMB(ci_method = ...), or retrieved and recomputed through confint().
Predictor-informed latent(..., lv = ~ x) rows admit ci_method = "wald"
at fit time; retained Wald payloads are surfaced as std.error, lower,
and upper through extract_lv_effects() rather than confint().
## S3 method for class 'gllvmTMB_julia' logLik(object, ...) ## S3 method for class 'gllvmTMB_julia' print(x, ...) ## S3 method for class 'gllvmTMB_julia' coef(object, ...) ## S3 method for class 'gllvmTMB_julia' predict( object, newdata = NULL, type = c("link", "response", "prob", "class"), ... ) ## S3 method for class 'gllvmTMB_julia' fitted(object, type = c("response", "link", "prob", "class"), ...) ## S3 method for class 'gllvmTMB_julia' residuals(object, type = c("response", "pearson"), ...) ## S3 method for class 'gllvmTMB_julia' simulate( object, nsim = 1, seed = NULL, newdata = NULL, condition_on_RE = TRUE, ... ) ## S3 method for class 'gllvmTMB_julia' confint( object, parm, level = 0.95, method = c("stored", "wald", "profile", "bootstrap"), ci_nboot = 200L, ci_seed = 0L, ... ) ## S3 method for class 'gllvmTMB_julia' summary(object, ...) ## S3 method for class 'summary.gllvmTMB_julia' print(x, digits = 3, ...)## S3 method for class 'gllvmTMB_julia' logLik(object, ...) ## S3 method for class 'gllvmTMB_julia' print(x, ...) ## S3 method for class 'gllvmTMB_julia' coef(object, ...) ## S3 method for class 'gllvmTMB_julia' predict( object, newdata = NULL, type = c("link", "response", "prob", "class"), ... ) ## S3 method for class 'gllvmTMB_julia' fitted(object, type = c("response", "link", "prob", "class"), ...) ## S3 method for class 'gllvmTMB_julia' residuals(object, type = c("response", "pearson"), ...) ## S3 method for class 'gllvmTMB_julia' simulate( object, nsim = 1, seed = NULL, newdata = NULL, condition_on_RE = TRUE, ... ) ## S3 method for class 'gllvmTMB_julia' confint( object, parm, level = 0.95, method = c("stored", "wald", "profile", "bootstrap"), ci_nboot = 200L, ci_seed = 0L, ... ) ## S3 method for class 'gllvmTMB_julia' summary(object, ...) ## S3 method for class 'summary.gllvmTMB_julia' print(x, digits = 3, ...)
object, x
|
A fit returned by |
... |
Unused. |
newdata |
Unsupported for Julia bridge fits; current prediction and simulation methods return in-sample values from the retained bridge payload. |
type |
Prediction or residual scale. For |
nsim |
Number of replicate response vectors to draw for |
seed |
Optional RNG seed for |
condition_on_RE |
Logical for |
parm |
Optional integer or character vector of CI terms for |
level |
Confidence level requested by |
method |
CI route for |
ci_nboot |
Number of parametric bootstrap replicates when
|
ci_seed |
Seed passed to the Julia bootstrap CI route. |
digits |
Number of digits printed by summary methods. |
logLik() returns a "logLik" object. coef() returns a named list
of available point-estimate components. confint() returns a conventional
confidence-interval matrix for stored or recomputed Julia CI payloads.
predict() returns an in-sample data frame. Non-ordinal rows use
trait, unit, and est columns; ordinal probability rows use trait,
unit, category, and prob; ordinal class rows use trait, unit,
and est. fitted() returns the in-sample fitted matrix with traits in
rows and units in columns for non-ordinal rows, and an array of
category probabilities or a modal-class matrix for ordinal rows.
residuals() returns an in-sample residual matrix with the same shape as
fitted() for non-ordinal rows and keeps masked response cells as
NA. simulate() returns an n_obs x nsim matrix in the same trait-major
cell order as predict() and keeps masked response cells as NA.
summary() returns a list with header, coefficients, covariance, and
status fields.
Standard model-object accessors for a multivariate fit returned by
gllvmTMB(), whether the call started from wide traits(...) data
or already-stacked long data. Internally the fit has class
gllvmTMB_multi, which is what these S3 methods dispatch on, but
you call print(fit) and summary(fit) as usual. Likelihood and inference
accessors depend on the estimator, as described below.
## S3 method for class 'gllvmTMB_multi' print(x, ...) ## S3 method for class 'gllvmTMB_multi' summary(object, ...) ## S3 method for class 'summary.gllvmTMB_multi' print(x, digits = 3, ...) ## S3 method for class 'gllvmTMB_multi' logLik(object, ...) ## S3 method for class 'gllvmTMB_multi' nobs(object, ...)## S3 method for class 'gllvmTMB_multi' print(x, ...) ## S3 method for class 'gllvmTMB_multi' summary(object, ...) ## S3 method for class 'summary.gllvmTMB_multi' print(x, digits = 3, ...) ## S3 method for class 'gllvmTMB_multi' logLik(object, ...) ## S3 method for class 'gllvmTMB_multi' nobs(object, ...)
x, object
|
A fit returned by |
... |
Currently unused. |
digits |
Decimal digits in the printed summary. Default 3. |
For ML fits, print() shows the active covstructs, the number of fixed
effects, and the converged log-likelihood.
For ML fits, summary() adds a fixed-effects table with SEs, the global and
local trait correlation matrices, per-trait ICCs,
and global / local communalities.
For an unpenalised native-Laplace ML fit, logLik() is the converged
maximum with df = length(opt$par) and nobs equal to the number of
likelihood-contributing response cells. AGHQ has a distinct integration
objective; a loading ridge is penalised MAP; and non-unit likelihood
weights define an estimating objective. Ordinary likelihood comparison is
unavailable on each of those restricted surfaces.
For estimator = "mspl", print() and summary() identify the
experimental softly penalised Laplace point estimator and show the
unpenalised Laplace value at that point only as provenance. logLik(),
AIC, BIC, likelihood-ratio tests, standard errors, intervals, and profiles
fail closed because the point is not the ordinary likelihood maximum and
repeated-sampling inference is not calibrated.
nobs() returns the number of likelihood-contributing observations –
the observed-response cells. This equals
fit$missing_data$counts$likelihood_rows and the nobs attribute of
logLik(). Under the default miss_control(response = "drop") every fitted
row is observed, so it equals length(fit$tmb_data$y); under
response = "include" the masked rows are excluded. Original-row counts
live in fit$missing_data, never in nobs().
print() returns its input invisibly, after displaying the fit or
summary. summary() returns a list of class summary.gllvmTMB_multi
containing the model header, fixed-effect estimates, covariance summaries,
and convergence diagnostics. logLik() returns a scalar of class logLik
with df and nobs attributes for supported likelihood fits. nobs()
returns the number of likelihood-contributing response cells as a numeric
scalar. Estimator restrictions described below apply to these accessors.
coef() returns the fixed-effect point estimates; vcov() returns their
covariance matrix, taken from the fit's TMB sdreport().
## S3 method for class 'gllvmTMB_multi' coef(object, ...) ## S3 method for class 'gllvmTMB_multi' vcov(object, ...)## S3 method for class 'gllvmTMB_multi' coef(object, ...) ## S3 method for class 'gllvmTMB_multi' vcov(object, ...)
object |
A fitted multivariate model returned by |
... |
Ignored, present for S3 consistency. |
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
coef(fit) vcov(fit)
coef(): a named numeric vector, one entry per fixed-effect term
(object$X_fix_names).
vcov(): a square numeric matrix with those same names on both margins.
Rows and columns for coefficients held fixed via Xcoef_fixed are NA –
a fixed parameter was not estimated, so it has no sampling covariance.
coef() works on any fit, including one made with
gllvmTMBcontrol(se = FALSE) – point estimates are this package's supported
claim and do not depend on sdreport(). vcov() does depend on it, and
raises the same typed errors confint() does when it is missing
(gllvmTMB_confint_no_sdreport) or non-finite
(gllvmTMB_confint_nonfinite_se), naming standard_errors() as the remedy
in the first case. The conditions are shared deliberately: a caller that
handles one should handle the other.
The fixed-effect block of the single TMB sdreport() the fit already
carries – a Wald covariance, with exactly the caveats any Wald quantity
from this package carries. It is not a resampled or profiled quantity, and
no interval built from it has certified coverage. vcov() refuses a fit
with non-unit likelihood weights: that fit targets a weighted estimating
objective and has no certified sandwich covariance.
standard_errors() to compute the sdreport() after fitting;
confint() for intervals; summary() for a coefficient table.
integration = "va") fitsMethods for the object returned by
gllvmTMB(..., control = gllvmTMBcontrol(integration = "va")).
## S3 method for class 'gllvmTMB_va' print(x, digits = 3, ...) ## S3 method for class 'gllvmTMB_va' summary(object, ...) ## S3 method for class 'summary.gllvmTMB_va' print(x, digits = 3, ...) ## S3 method for class 'gllvmTMB_va' nobs(object, ...) ## S3 method for class 'gllvmTMB_va' logLik(object, ...) ## S3 method for class 'gllvmTMB_va' confint(object, parm, level = 0.95, ...) ## S3 method for class 'gllvmTMB_va' vcov(object, ...) ## S3 method for class 'gllvmTMB_va' coef(object, ...) ## S3 method for class 'gllvmTMB_va' residuals(object, ...) ## S3 method for class 'gllvmTMB_va' fitted(object, ...) ## S3 method for class 'gllvmTMB_va' deviance(object, ...) ## S3 method for class 'gllvmTMB_va' df.residual(object, ...) ## S3 method for class 'gllvmTMB_va' weights(object, ...) ## S3 method for class 'gllvmTMB_va' predict(object, ...)## S3 method for class 'gllvmTMB_va' print(x, digits = 3, ...) ## S3 method for class 'gllvmTMB_va' summary(object, ...) ## S3 method for class 'summary.gllvmTMB_va' print(x, digits = 3, ...) ## S3 method for class 'gllvmTMB_va' nobs(object, ...) ## S3 method for class 'gllvmTMB_va' logLik(object, ...) ## S3 method for class 'gllvmTMB_va' confint(object, parm, level = 0.95, ...) ## S3 method for class 'gllvmTMB_va' vcov(object, ...) ## S3 method for class 'gllvmTMB_va' coef(object, ...) ## S3 method for class 'gllvmTMB_va' residuals(object, ...) ## S3 method for class 'gllvmTMB_va' fitted(object, ...) ## S3 method for class 'gllvmTMB_va' deviance(object, ...) ## S3 method for class 'gllvmTMB_va' df.residual(object, ...) ## S3 method for class 'gllvmTMB_va' weights(object, ...) ## S3 method for class 'gllvmTMB_va' predict(object, ...)
x, object
|
A fit returned by |
digits |
Decimal digits in the printed output. Default 3. |
... |
Currently unused. |
parm |
Fixed-effect coefficient indices or names for |
level |
Nominal confidence level for fixed-effect VA-Wald intervals. |
This is an opt-in research route. Its objective is an ELBO – a lower
bound on the log-likelihood – not a log-likelihood, and its inverse
Hessian is not automatically calibrated frequentist uncertainty.
Accordingly logLik(), AIC(), and BIC() remain undefined. vcov()
and confint() expose only fixed-effect VA-Wald uncertainty computed from
the profiled Schur information matrix. The returned values are explicitly
labelled uncalibrated; raw-loading Wald intervals are not available.
The ordination surface is the exception: extract_ordination(),
getLV(), getLoadings(), and extract_loadings() all work for a
variational fit and return latent scores and loadings as POINT
ESTIMATES. getLV(se = TRUE) additionally returns a variational
posterior SD – not a standard error – and only when the fit's
eval_method resolves to "gh". See the integration argument of
gllvmTMB() for the measured accuracy of these point estimates.
print() and print.summary() return their argument invisibly.
summary() returns an object of class "summary.gllvmTMB_va".
nobs() returns an integer count of unit-by-response cells. vcov()
returns the profiled-Schur beta covariance matrix and confint() its
nominal Wald intervals; both carry calibrated = FALSE and
uncertainty_basis = "VA-Wald profiled Schur information" attributes.
gllvmTMB()
Control parameters for gllvmTMB()
gllvmTMBcontrol( d_B = NULL, d_W = NULL, spde_mode = c("per_trait", "shared"), n_init = 1L, optimizer = c("nlminb", "optim"), optArgs = list(), init_jitter = 0.3, init_strategy = c("default", "single_trait_warmup"), start_method = list(method = NULL, jitter.sd = 0), start_from = NULL, se = TRUE, verbose = FALSE, integration = c("laplace", "va"), va_H = 7L, va_eval_method = c("auto", "jj", "gh"), aghq = FALSE, aghq_iter_cap = 1L, aghq_n_adapt = 400L, aghq_ridge = 2, aghq_continuation = TRUE, aghq_shift_tol = 1e-04, aghq_grad_tol = 1e-04, aghq_grad_tol_rel = 1e-06, aghq_multistart = TRUE, aghq_f_tol = 1e-09, aghq_escalate_patience = 3L, aghq_rho_min = 1/64, warn_runaway = TRUE, allow_nongaussian_reml = FALSE, loading_ridge = NULL, ... )gllvmTMBcontrol( d_B = NULL, d_W = NULL, spde_mode = c("per_trait", "shared"), n_init = 1L, optimizer = c("nlminb", "optim"), optArgs = list(), init_jitter = 0.3, init_strategy = c("default", "single_trait_warmup"), start_method = list(method = NULL, jitter.sd = 0), start_from = NULL, se = TRUE, verbose = FALSE, integration = c("laplace", "va"), va_H = 7L, va_eval_method = c("auto", "jj", "gh"), aghq = FALSE, aghq_iter_cap = 1L, aghq_n_adapt = 400L, aghq_ridge = 2, aghq_continuation = TRUE, aghq_shift_tol = 1e-04, aghq_grad_tol = 1e-04, aghq_grad_tol_rel = 1e-06, aghq_multistart = TRUE, aghq_f_tol = 1e-09, aghq_escalate_patience = 3L, aghq_rho_min = 1/64, warn_runaway = TRUE, allow_nongaussian_reml = FALSE, loading_ridge = NULL, ... )
d_B, d_W
|
Latent dimensions for the between-unit and within-unit
reduced-rank components. Set to a positive integer to enable
|
spde_mode |
|
n_init |
Number of random-start replicates. Reduced-rank GLLVMs
are often multimodal because the latent-factor likelihood has many
equivalent local maxima; running several restarts with different
starting values and keeping the fit with the lowest |
optimizer |
One of |
optArgs |
A list of arguments passed to the optimiser. For
|
init_jitter |
Standard deviation of N(0, sigma) jitter applied to
the starting parameter vector across the |
init_strategy |
One of |
start_method |
Optional reduced-rank starting-value method, modelled
after |
start_from |
Optional fitted |
se |
Logical; if |
verbose |
If |
integration |
Which method evaluates the latent-variable integral.
It is admitted only inside the Gate-E implementation/light-fit region:
Offering this value advertises nothing about its accuracy.
Historical explicit-JJ evidence. Recovery of the ordination below was
measured with the earlier JJ evaluator against planted truth at the
admitted cells (
The comparison is not effort-matched: the historical JJ arm ran four starts behind an agreement-and-gradient health gate, the Laplace arm a single start admitted on a positive-definite Hessian.
There is no |
va_H |
Gauss-Hermite quadrature order for the variational route, a
single odd integer Quadrature dominates the cost of a variational fit and the work is linear
in |
va_eval_method |
How the variational route evaluates the per-observation
expectation.
|
aghq |
Adaptive Gauss-Hermite quadrature for the between-unit latent
block. |
aghq_iter_cap |
Optimiser iteration budget for the first adaptation
pass. Default |
aghq_n_adapt |
Maximum number of adaptation passes. Default |
aghq_ridge |
Ridge penalty on the loadings, as the scale |
aghq_continuation |
If |
aghq_shift_tol, aghq_grad_tol, aghq_f_tol
|
Convergence tolerances for the
adaptation loop: the quadrature-point shift, the gradient at the adaptation
point, and the change in objective. Defaults |
aghq_grad_tol_rel |
Relative gradient tolerance, tested as
|
aghq_multistart |
If |
aghq_escalate_patience |
Number of accepted passes before the iteration
cap is escalated. Default |
aghq_rho_min |
Minimum trust-region-style acceptance ratio for a pass.
Default |
warn_runaway |
If |
allow_nongaussian_reml |
Permit |
loading_ridge |
Integration-neutral alias for |
... |
Reserved for future use. Currently ignored with a warning. |
Recommended workflow for two-level latent models. Reduced-rank
likelihoods are notoriously multimodal in two-level fits like
value ~ 0 + trait + latent(0+trait|ID, d_B) + latent(0+trait|obs_ID, d_W).
The recommended workflow is:
Run several restarts (n_init = 5 to 10) and keep the one
with the lowest -logLik.
If that fails to converge cleanly, switch the optimiser to
optim with BFGS:
gllvmTMBcontrol(optimizer = "optim", optArgs = list(method = "BFGS")).
start_method = list(method = "res", jitter.sd = 0.2) is
soft-deprecated as of 0.6.0 and warns once per session. It still
fits: it decomposes the fixed-effect residual matrix into starting
values for and the latent scores. Do not reach for it.
It was retired on measurement. Across 89 simulated fits (Gaussian,
Poisson and negative-binomial, d = 1 to 3, three and five traits),
it was never materially better than the default start (its three
best margins were 0.07, 0.29 and 0.66 log-likelihood units) and was
materially worse eight times, once by 14.6 units. At d >= 2 it made
no difference at all. Every failure reported convergence == 0 and a
positive-definite Hessian on both sides, so the worse fit was silent,
and restarts did not detect it: in one such fit all five n_init = 5
restarts returned the same worse optimum.
If you use it anyway, compare fit$opt$objective against a
default-start fit and keep the lower value.
For Gaussian two-level models, prefer
start_method = list(method = "indep") or manually fit a simpler
model and pass it through start_from = simpler_fit. This is a GLMM
warm start rather than a fixed-effect-only GLM residual start.
A gllvmTMBcontrol list.
# gllvmTMBcontrol() is a pure-R constructor: it builds and validates a # control list without fitting anything. gllvmTMBcontrol() # historical defaults # Multi-start to guard against multimodal reduced-rank likelihoods, with # jitter on the starting parameter vector across restarts. gllvmTMBcontrol(n_init = 3, init_jitter = 0.2) # Switch the optimiser to optim + BFGS for finicky two-level rr fits. gllvmTMBcontrol(optimizer = "optim", optArgs = list(method = "BFGS")) # Experimental, opt-in scale-aware ridge for the evaluated Bernoulli AGHQ # scope. The route uses a fixed 9-node multi-start rule; this is not an # interval-calibration certificate. gllvmTMBcontrol(aghq_ridge = "auto") # The same fixed loading MAP penalty, named independently of integration. gllvmTMBcontrol(loading_ridge = 2) # Experimental scalar variational route: public auto uses GH with H = 7. gllvmTMBcontrol(integration = "va") # H = 61 remains a diagnostic; JJ is explicit binomial-logit only. gllvmTMBcontrol(integration = "va", va_H = 61, va_eval_method = "gh")# gllvmTMBcontrol() is a pure-R constructor: it builds and validates a # control list without fitting anything. gllvmTMBcontrol() # historical defaults # Multi-start to guard against multimodal reduced-rank likelihoods, with # jitter on the starting parameter vector across restarts. gllvmTMBcontrol(n_init = 3, init_jitter = 0.2) # Switch the optimiser to optim + BFGS for finicky two-level rr fits. gllvmTMBcontrol(optimizer = "optim", optArgs = list(method = "BFGS")) # Experimental, opt-in scale-aware ridge for the evaluated Bernoulli AGHQ # scope. The route uses a fixed 9-node multi-start rule; this is not an # interval-calibration certificate. gllvmTMBcontrol(aghq_ridge = "auto") # The same fixed loading MAP penalty, named independently of integration. gllvmTMBcontrol(loading_ridge = 2) # Experimental scalar variational route: public auto uses GH with H = 7. gllvmTMBcontrol(integration = "va") # H = 61 remains a diagnostic; JJ is explicit binomial-logit only. gllvmTMBcontrol(integration = "va", va_H = 61, va_eval_method = "gh")
mi()
impute_model() wraps the model for a covariate declared missing with
mi() in a gllvmTMB() formula. A bare two-sided formula in the impute
argument – for example impute = list(x = x ~ z) – is sugar for a Gaussian
predictor model; use impute_model() when an explicit predictor family is
wanted.
impute_model(formula, family = stats::gaussian())impute_model(formula, family = stats::gaussian())
formula |
Two-sided predictor-model formula. The left-hand side must be
the same variable used inside |
family |
Predictor-model family. Supported values are |
The current interface fits one modelled missing predictor at a time.
A Gaussian predictor (gaussian(), or a bare formula) is treated as a latent
quantity integrated out by the Laplace approximation and may use fixed
covariates, one grouped random intercept, or one phylogenetic structured
intercept. A Gaussian predictor that lives at a coarser observed level can
declare that level with mi_group(group) in the predictor formula. Discrete
predictors are fixed-effect only: binomial(link = "logit") uses exact two-state summation, cumulative_logit() uses exact
ordered K-state summation, and categorical() uses exact unordered K-state
softmax summation. Other predictor families are rejected explicitly.
A gllvmTMB_impute_model object for the impute argument of
gllvmTMB().
gllvmTMB() for the impute = argument, cumulative_logit() for
an ordered categorical predictor model, and miss_control() for the
predictor = "model" switch.
impute_model(x ~ z) impute_model(score ~ z, family = cumulative_logit())impute_model(x ~ z) impute_model(score ~ z, family = cumulative_logit())
gllvmTMB fitFor a gllvmTMB() fit with a modelled missing predictor (mi(x) plus
missing = miss_control(predictor = "model")), imputed() returns a fitted
summary of each missing predictor value. For a continuous Gaussian
predictor (impute_model(x ~ z)), the estimate is the conditional mode
with a local Gaussian/Laplace conditional standard error from the joint
Hessian. For a binary predictor
(impute_model(x ~ z, family = binomial())), the
estimate is the conditional probability P(x = 1 | y, z) from the exact
two-state marginalisation, std_error is NA (the discrete route reports a
fitted distribution, not a single mode with a Hessian SE), and
uncertainty_status is "discrete_no_se".
imputed(object, ...) ## S3 method for class 'gllvmTMB' imputed(object, variable = NULL, rows = c("missing", "all"), se = TRUE, ...)imputed(object, ...) ## S3 method for class 'gllvmTMB' imputed(object, variable = NULL, rows = c("missing", "all"), se = TRUE, ...)
object |
A |
... |
Reserved for future extractor options. |
variable |
Optional missing-predictor name. The default uses the only modelled missing predictor in the fit. |
rows |
Which units to return. |
se |
Logical; include conditional standard errors when the fit contains
a successful |
The missing predictor lives at a declared unit or coarser grouping level, so
imputed() returns one row per missing level value (not one per long-format
model row). The
Gaussian estimates are conditional modes, not conditional means, multiple-
imputation draws, or posterior summaries.
Join keys and row identifiers. level names the unit or coarser group
at which the missing predictor lives, and level_id gives its value. Join
results back to the data using that pair. The original_row and model_row
columns are
latent-level ordinals (the 1-based index of the missing unit / group in
the covariate model's latent level), not indices into the rows of the data
frame you passed to gllvmTMB(). They coincide with original data rows only
in the narrow wide-traits() case where each unit is a single row; off the
wide path (long data, or a coarser mi_group() level) they index the latent
level, not the data. This differs from predict_missing(), whose
original_row maps to the supplied long- or wide-data row.
A data frame with variable, level, level_id, original_row,
model_row, observed, estimate, std_error, source, and
uncertainty_status. level_id is character so the same column can hold
unit, group, or phylogenetic-tip labels.
indep(0 + trait | g)
Canonical name for the always-alone marginal-only per-trait
diagonal variance term, producing
with identity
off-diagonals.
indep(formula, common = FALSE)indep(formula, common = FALSE)
formula |
|
common |
|
The package's covstruct API organises around two mutually exclusive modes, distinguished by convention:
latent by default)Shared cross-trait
covariance plus the default trait-specific Psi companion:
.
indep standalone)Per-trait total variance
with no cross-trait decomposition: .
Use indep() when you want to commit to the marginal-only
interpretation explicitly. Ordinary latent() now carries the
diagonal Psi companion by default when you want the cross-trait
decomposition.
For a scalar marginal-only tier with one variance shared by all traits,
use common = TRUE. This is the non-deprecated standalone replacement for
the legacy scalar diagonal spelling when no latent() term is paired on the
same grouping.
latent()
Combining indep(0 + trait | g) with latent(0 + trait | g, d = K)
on the same grouping is over-parameterised (the model cannot
decide whether trait variance lives in the shared component or the
marginal component). The parser raises a cli::cli_abort() in this
case. Combining indep with unique on the same grouping is
similarly redundant and also errors.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
# Marginal-only fit (per-trait variance, identity correlation):
fit <- gllvmTMB(value ~ 0 + trait + indep(0 + trait | site),
data = df, trait = "trait", unit = "site")
# Scalar marginal-only fit (one shared variance across traits):
fit <- gllvmTMB(value ~ 0 + trait + indep(0 + trait | site, common = TRUE),
data = df, trait = "trait", unit = "site")
# ERROR: indep + latent on the same grouping is over-parameterised.
# gllvmTMB(value ~ 0 + trait +
# indep(0 + trait | site) +
# latent(0 + trait | site, d = 2), data = df)
A formula marker; never evaluated.
latent(), phylo_indep(), spatial_indep(),
extract_Sigma().
Wraps an admitted observation law with the source-specific fixed-effects
formula that describes recording effort or observation bias. The ecological
formula remains in gllvmTMB(); observation contributes columns only on
rows from this declared source. It is therefore not a second ecological
process and does not turn relative intensity into abundance, occupancy, or
detectability.
isdm_source(family, observation)isdm_source(family, observation)
family |
An admitted family law, currently |
observation |
A one-sided formula for source-specific observation
effects, for example |
An internal source declaration accepted by isdm_sources().
isdm_source(poisson(link = "log"), observation = ~ access + popdens)isdm_source(poisson(link = "log"), observation = ~ access + popdens)
isdm_sources(...)isdm_sources(...)
... |
Two or more named arguments; each name is a source label and each
value is a bare admitted observation law or an |
Builds the family argument for an integrated species-distribution fit in
which any number of named observation sources – an opportunistic portal
stream, digitised literature records, checklists, a structured survey –
share one ecological linear predictor while each source keeps its own
observation law.
Each argument is named for a source and gives either a bare observation law
or isdm_source() with its source-specific observation formula. Two laws
are admitted, because both express the observation as a thinning of one
shared intensity: poisson() (a count stream; the offset is log effort) and
binomial("cloglog") (detection/non-detection; the offset is log support,
and the complementary log-log link is what makes a detection consistent with
the same intensity that generates the counts). Other links and families are
refused: a logit detection model, for instance, does not share a scale with
a count arm merely because both converge.
The data must carry an isdm_source column whose values are exactly the
declared source names, and – when the declaration mixes the two laws –
every trait must be observed by every declared source. Everything such a
fit reports is relative intensity: presence-only data cannot identify
absolute abundance, occupancy, or detectability, and none are estimated.
This interface is experimental and may change.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
## `dat` is long, one row per (cell, species, source), with an isdm_source
## column naming each row's source and log_support its known effort/area.
fit <- gllvmTMB(
value ~ 0 + trait + trait:env +
offset(log_support) + latent(0 + trait | cell_id, d = 1),
data = dat, trait = "trait", unit = "cell_id", family = fam
)
A mixed-family list understood by gllvmTMB(), with
family_var = "isdm_source". (The isdm_source_laws attribute is
informational only: internal validation rebuilds the declaration from the
list's names and laws, which survive reordering; the attribute does not.)
fam <- isdm_sources( gbif = isdm_source(poisson(), observation = ~ access + popdens), survey = isdm_source(poisson(), observation = ~ observer + method) ) names(fam)fam <- isdm_sources( gbif = isdm_source(poisson(), observation = ~ access + popdens), survey = isdm_source(poisson(), observation = ~ observer + method) ) names(fam)
kernel_latent() fits a named random-effect tier with a user-supplied
between-unit covariance matrix K. For one named tier,
unique = TRUE adds a kernel-structured diagonal
companion; the default FALSE is loadings-only.
Two or more named kernel_latent() tiers may share the same grouping levels,
each with its own K, loading matrix, and latent field. Multi-kernel fits are
loadings-only: unique = TRUE and kernel_dep() are not available in that
combination. Similar kernels can be
weakly separable, so use diagnose_kernel_separability() before assigning a
distinct interpretation to each component.
A matrix returned by make_cross_kernel() is treated as a fixed supplied
covariance. The model does not estimate its association structure or bridge
strength. extract_Gamma() returns descriptive point summaries; it does not
provide calibrated intervals or establish a causal evolutionary process.
kernel_latent(unit, K, d = 1, name = "kernel", unique = FALSE) kernel_indep(unit, K, name = "kernel", common = FALSE) kernel_dep(unit, K, name = "kernel")kernel_latent(unit, K, d = 1, name = "kernel", unique = FALSE) kernel_indep(unit, K, name = "kernel", common = FALSE) kernel_dep(unit, K, name = "kernel")
unit |
Unquoted grouping column whose levels align with |
K |
Numeric dense positive-semidefinite covariance or correlation matrix aligned to the grouping levels. |
d |
Integer latent rank for |
name |
Character scalar used as the extractor level, e.g.
|
unique |
Logical; |
common |
|
A formula marker; never evaluated as a regular R function.
A <- diag(5) dat <- data.frame( unit = factor(rep(paste0("u", 1:5), each = 2), levels = paste0("u", 1:5)), obs = factor(seq_len(10)), y1 = rnorm(10), y2 = rnorm(10) ) rownames(A) <- colnames(A) <- levels(dat$unit) fit <- gllvmTMB( traits(y1, y2) ~ 1 + kernel_latent(unit, K = A, d = 1, name = "known", unique = TRUE), data = dat, unit = "obs", cluster = "unit", family = gaussian() ) extract_Sigma(fit, level = "known")A <- diag(5) dat <- data.frame( unit = factor(rep(paste0("u", 1:5), each = 2), levels = paste0("u", 1:5)), obs = factor(seq_len(10)), y1 = rnorm(10), y2 = rnorm(10) ) rownames(A) <- colnames(A) <- levels(dat$unit) fit <- gllvmTMB( traits(y1, y2) ~ 1 + kernel_latent(unit, K = A, d = 1, name = "known", unique = TRUE), data = dat, unit = "obs", cluster = "unit", family = gaussian() ) extract_Sigma(fit, level = "known")
kernel_scalar()
kernel_scalar() is the dense-kernel analogue of scalar() /
phylo_scalar(): one variance shared by every trait, coupled between
grouping levels by the supplied dense matrix K
(,
). It uses the
same phylo-equivalent diagonal engine path as kernel_indep(), with the
per-trait variances tied to a single shared parameter, so
extract_Sigma() exposes it under level = name.
kernel_scalar(unit, K, name = "kernel")kernel_scalar(unit, K, name = "kernel")
unit |
Unquoted grouping column whose levels align with |
K |
Numeric dense positive-semidefinite covariance or correlation matrix aligned to the grouping levels. |
name |
Character scalar used as the extractor level, e.g.
|
A formula marker; never evaluated as a regular R function.
kernel_indep(), kernel_dep(), kernel_latent(), scalar(),
phylo_scalar(), extract_Sigma().
kernel_unique()
kernel_unique(unit, K, name = "kernel")kernel_unique(unit, K, name = "kernel")
unit |
Unquoted grouping column whose levels align with |
K |
Numeric dense positive-semidefinite covariance or correlation matrix aligned to the grouping levels. |
name |
Character scalar used as the extractor level, e.g.
|
Only kernel_unique() is deprecated; dense-kernel modelling and
kernel_latent(), kernel_indep(), and kernel_dep() remain current.
This alias is retained for old formulas. Use kernel_indep() for a
standalone diagonal kernel covariance, or kernel_latent(..., unique = TRUE)
when a single latent kernel tier should include its diagonal
companion.
A formula marker; never evaluated as a regular R function.
latent(0 + trait | g, d = K)
Canonical name for a reduced-rank latent-factor random effect at a
grouping g. Formerly rr(0 + trait | g, d = K) – same engine, new
name. The "rr" alias still works for backward compat but emits a
one-shot deprecation warning per session.
latent(formula, d = 1, unique = TRUE, common = FALSE, lv = NULL)latent(formula, d = 1, unique = TRUE, common = FALSE, lv = NULL)
formula |
|
d |
Integer; number of latent factors. |
unique |
Logical; |
common |
Logical; |
lv |
One-sided formula for predictor-informed latent-score means.
Runtime support is limited to ordinary unit-tier
|
Used inside a gllvmTMB() formula:
value ~ 0 + trait + latent(0 + trait | unit, d = 2)
By default, ordinary latent() now fits the canonical decomposition
,
with the diagonal companion included automatically:
value ~ 0 + trait + latent(0 + trait | unit, d = 2)
Set unique = FALSE for the loadings-only subset.
For a scalar diagonal companion shared across traits,
use common = TRUE; this replaces the older two-term spelling for
ordinary intercept-only latent terms.
A formula marker; never evaluated.
indep(), phylo_latent(), diag_re, extract_Sigma(),
extract_lv_effects() for predictor-informed latent-score coefficients.
# Long-format stacked traits: a 2-factor latent random effect at `site`. # Ordinary latent() carries its diagonal Psi by default # (Sigma = Lambda Lambda^T + diag(psi)). df <- simulate_site_trait( n_sites = 30, n_species = 4, n_traits = 4, mean_species_per_site = 4, seed = 42 )$data fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | site, d = 2), data = df, unit = "site" ) extract_Sigma(fit)# Long-format stacked traits: a 2-factor latent random effect at `site`. # Ordinary latent() carries its diagonal Psi by default # (Sigma = Lambda Lambda^T + diag(psi)). df <- simulate_site_trait( n_sites = 30, n_species = 4, n_traits = 4, mean_species_per_site = 4, seed = 42 )$data fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | site, d = 2), data = df, unit = "site" ) extract_Sigma(fit)
Return per-entry CIs on either the raw reduced-rank loading matrix
Lambda_<level> or the standardised loading
rho[t,k] = Lambda[t,k] / sqrt(Sigma_total[t,t]) of a confirmatory
gllvmTMB() fit. A numerical Jacobian of the complete requested loading
vector is combined with the TMB sdreport() covariance. On the
standardised scale this includes covariance among axes and uncertainty in
fitted denominator components such as Psi.
loading_ci( fit, level = c("unit", "unit_obs"), method = c("wald", "wald_asym", "profile"), conf_level = 0.95, sigma_d2 = 1, loading_scale = NULL )loading_ci( fit, level = c("unit", "unit_obs"), method = c("wald", "wald_asym", "profile"), conf_level = 0.95, sigma_d2 = 1, loading_scale = NULL )
fit |
A multivariate |
level |
Which loading matrix to summarise: |
method |
CI method:
|
conf_level |
Confidence level. Defaults to 0.95. |
sigma_d2 |
Deprecated compatibility argument; ignored. Standardised
inference now derives each denominator from model-implied total variance
via |
loading_scale |
|
Per-entry CIs are only well-defined for confirmatory fits — i.e.
fits supplied with a lambda_constraint that fixes enough entries
to pin the rotation. Exploratory fits leave Lambda identified only
up to a d x d orthogonal rotation, so the SE on any single
Lambda[i, k] depends on the rotation convention and is not a
biological quantity. This function therefore errors on exploratory
fits and points the user at confirmatory_lambda() /
suggest_lambda_constraint(), or at extract_communality() /
extract_Sigma() for rotation-invariant summaries.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
# Build a confirmatory fit M <- confirmatory_lambda( species = species_names, group = species_group, d = 2L, loads_on = list(A = 1L, B = 2L) ) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | site, d = 2L), data = df_long, family = binomial(link = "probit"), lambda_constraint = list(unit = M) ) loading_ci(fit, level = "unit") loading_ci(fit, level = "unit", loading_scale = "standardized")
A data frame (one row per Lambda entry) with columns
trait, axis, estimate, se, lower, upper, method,
loading_scale, and pinned. pinned = TRUE records that the raw
loading was fixed by lambda_constraint. Its raw SE is zero, but its
standardised SE can be nonzero because total variance can still vary.
The point estimates are the supported claim. The interval methods here are
provided for exploration: their empirical coverage is not certified for this
estimand (the worked example below is a binomial-probit fit), so treat the
intervals as exploratory rather than coverage-calibrated. See NEWS.md for
the current coverage status.
flag_unreliable_loadings() for a decision-aid summary;
confirmatory_lambda() to build a confirmatory constraint matrix;
extract_communality() for rotation-invariant alternatives.
For each requested entry (i, k) of Lambda_<level>, refit the
model with that entry pinned to a grid of values and return the
resulting log-likelihood curve. The output is a long data.frame
with class profile_loadings; pass it to plot() to visualise the
classic LR U-shape, or to loading_ci() (via method = "profile")
to invert the curve and get CIs that do NOT depend on the fit's
Hessian being positive-definite.
loading_profile( fit, level = c("unit", "unit_obs"), entries = NULL, n_grid = 11L, grid_extent = 6, conf_level = 0.95 )loading_profile( fit, level = c("unit", "unit_obs"), entries = NULL, n_grid = 11L, grid_extent = 6, conf_level = 0.95 )
fit |
A confirmatory |
level |
Which loading matrix: |
entries |
Optional matrix or data.frame of |
n_grid |
Integer; number of grid points per entry. Default 11. |
grid_extent |
Numeric; total grid width as a multiple of a
robust scale estimate of the loading (default 6 — i.e. estimate
± 3 scale units on each side). At pdHess-OK fits the scale is
the Wald SE; otherwise a heuristic based on |
conf_level |
Confidence level for the eventual CI inversion; stored on the output for downstream consumers. |
This is expensive: each entry requires n_grid partial refits,
so a 20-species 2-factor fit at default n_grid = 11 is ~440 fits.
For binary probit at the scale of this article's fixture, expect
minutes. Use entries to restrict to a subset when iterating.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
pf <- loading_profile(fit, level = "unit") plot(pf) # all entries faceted loading_ci(fit, method = "profile") # inverts pf internally
A data.frame of class profile_loadings with columns:
trait, axis, i, k, profile_value, objective
(the negative log-likelihood evaluated at that value),
delta_deviance (= 2 * (objective - min(objective))),
estimate, conf_level. Pinned entries do not appear in the
output.
loading_ci() for the inverted-CI version,
plot.profile_loadings() for the U-shape visualisation.
make_cross_kernel() builds the block relatedness matrix for the C0
coevolution prototype. It builds a correlation-scale
positive-semidefinite matrix
K_star = rbind(cbind(A_H, C_HP), cbind(t(C_HP), A_P)), where A_H
and A_P are within-lineage relatedness matrices and C_HP is the
cross-lineage bridge induced by the association matrix W. The intended
route is a prototype: pass K_star through the
existing phylo_latent(..., vcv = K_star, unique = TRUE) engine. The
generic kernel_*() surface and the
extract_Gamma() coevolution gate are documented
separately; this helper only builds the input kernel.
make_cross_kernel(A_H, A_P, W, rho = 0.5, eps = 1e-08)make_cross_kernel(A_H, A_P, W, rho = 0.5, eps = 1e-08)
A_H, A_P
|
Numeric square correlation matrices for the host and partner lineages. Both must be symmetric, positive semidefinite, and have unit diagonal. |
W |
Numeric association matrix with |
rho |
Scalar bridge strength in |
eps |
Positive numerical floor used when taking symmetric square
roots and scaling an all-zero or near-zero |
A numeric correlation matrix with the host block first and the
partner block second. The matrix carries lightweight metadata for
downstream extractors, including the fixed rho used to build the
host-partner bridge.
A_H <- matrix(c( 1.0, 0.3, 0.1, 0.3, 1.0, 0.2, 0.1, 0.2, 1.0 ), 3, 3, byrow = TRUE) A_P <- matrix(c(1.0, 0.25, 0.25, 1.0), 2, 2) W <- matrix(c(1, 0, 0.5, 0, 1, 0.25), 3, 2, byrow = TRUE) K_star <- make_cross_kernel(A_H, A_P, W, rho = 0.4) min(eigen(K_star, symmetric = TRUE, only.values = TRUE)$values)A_H <- matrix(c( 1.0, 0.3, 0.1, 0.3, 1.0, 0.2, 0.1, 0.2, 1.0 ), 3, 3, byrow = TRUE) A_P <- matrix(c(1.0, 0.25, 0.25, 1.0), 2, 2) W <- matrix(c(1, 0, 0.5, 0, 1, 0.25), 3, 2, byrow = TRUE) K_star <- make_cross_kernel(A_H, A_P, W, rho = 0.4) min(eigen(K_star, symmetric = TRUE, only.values = TRUE)$values)
Construct a triangular mesh and the finite-element quantities used by the
spatial_*() keywords. The helper uses the public fmesher API; the
SPDE/GMRF construction follows Lindgren, Rue, and Lindstrom (2011).
Scope: mesh/FEM construction and fit ingestion are covered by focused
tests; evidence across the broader spatial-family surface remains
partial; directional anisotropy, spatiotemporal fields, barriers, and
likelihood changes are rejected from this helper contract.
make_mesh( data, xy_cols, type = c("cutoff", "kmeans", "cutoff_search"), cutoff, n_knots, seed = 42, mesh = NULL, fmesher_func = fmesher::fm_rcdt_2d_inla, convex = NULL, concave = convex, ... )make_mesh( data, xy_cols, type = c("cutoff", "kmeans", "cutoff_search"), cutoff, n_knots, seed = 42, mesh = NULL, fmesher_func = fmesher::fm_rcdt_2d_inla, convex = NULL, concave = convex, ... )
data |
A data frame containing the coordinate columns. |
xy_cols |
Character names of exactly two finite numeric coordinate
columns in |
type |
Mesh construction method: |
cutoff |
Minimum vertex separation for |
n_knots |
Number of coordinate centres for |
seed |
Seed used only while selecting k-means centres. The caller's RNG state is restored before this function returns. |
mesh |
Optional pre-built |
fmesher_func |
Mesh constructor from fmesher. |
convex, concave
|
Optional non-convex-hull controls passed to
|
... |
Additional arguments passed to |
A gllvmTMBmesh object containing loc_xy, xy_cols, mesh, the
finite-element matrices in spde, mesh centres in loc_centers, and the
observation-to-mesh projection matrix A_st.
Lindgren F, Rue H, Lindstrom J (2011). An explicit link between Gaussian fields and Gaussian Markov random fields: the SPDE approach. Journal of the Royal Statistical Society: Series B, 73, 423-498.
set.seed(1) df <- data.frame(X = runif(40), Y = runif(40)) mesh <- make_mesh(df, c("X", "Y"), cutoff = 0.1) plot(mesh)set.seed(1) df <- data.frame(X = runif(40), Y = runif(40)) mesh <- make_mesh(df, c("X", "Y"), cutoff = 0.1) plot(mesh)
meta_V(V = V)
Canonical formula keyword for the known-sampling-error term in
two-stage meta-regression workflows. The name makes it explicit
that what is "known" is the V matrix (the sampling-error
covariance). meta_known_V() is retained as a soft-deprecated alias; both
names desugar identically in the formula parser.
meta_V(V, type = "exact")meta_V(V, type = "exact")
V |
Known sampling variance or covariance marker. In current
exact-additive fits, pass the actual matrix via the top-level
|
type |
Sampling-covariance mode. |
Pass the matrix V via the top-level known_V = V argument to
gllvmTMB() when using the exact-additive (default) form. For
block-diagonal within-study correlation, build V via block_V().
type = "proportional" is not implemented and is deliberately rejected
rather than silently treated as exact.
A formula marker; never evaluated.
meta_known_V() (deprecated alias); block_V();
gllvmTMB().
# Two-stage meta-regression with a known per-row sampling-variance V. set.seed(131) df <- expand.grid( site = factor(seq_len(50)), trait = factor(paste0("t", 1:3)) ) df$value <- rnorm(nrow(df), sd = 0.5) df$sampling_var <- runif(nrow(df), min = 0.02, max = 0.08) V <- diag(df$sampling_var) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | site, d = 1) + meta_V(V = V, type = "exact"), data = df, trait = "trait", unit = "site", known_V = V )# Two-stage meta-regression with a known per-row sampling-variance V. set.seed(131) df <- expand.grid( site = factor(seq_len(50)), trait = factor(paste0("t", 1:3)) ) df$value <- rnorm(nrow(df), sd = 0.5) df$sampling_var <- runif(nrow(df), min = 0.02, max = 0.08) V <- diag(df$sampling_var) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | site, d = 1) + meta_V(V = V, type = "exact"), data = df, trait = "trait", unit = "site", known_V = V )
gllvmTMB()
Configures how gllvmTMB() treats missing responses and missing
predictors. Pass the result to the missing = argument of
gllvmTMB().
miss_control( response = c("drop", "include"), predictor = c("fail", "model"), engine = "laplace" )miss_control( response = c("drop", "include"), predictor = c("fail", "model"), engine = "laplace" )
response |
How to treat rows whose response value is |
predictor |
How to treat missing predictors (covariates).
|
engine |
The estimation engine. The supported value is |
There is deliberately no estimator argument in miss_control():
estimator choice belongs to gllvmTMB(). The default gllvmTMB() fit uses
ML; gllvmTMB(REML = TRUE) is a Gaussian-only pilot. It combines with either
missing-response policy – including miss_control(response = "include"),
where the restricted likelihood is formed over the observed rows – but not
yet with mi() predictor models. There
is no MI ("multiple imputation") engine here; multiple imputation is a
separate workflow.
Under the default miss_control(response = "drop", predictor = "fail", engine = "laplace") the missing-data layer is an exact no-op: a complete-
data fit is byte-identical to a fit built before this layer existed.
A named list with elements response, predictor, and engine.
gllvmTMB() for the missing = argument; gllvmTMBcontrol() for
optimiser / initialisation control.
miss_control() # defaults: drop / fail / laplace miss_control(response = "include") # keep missing-response rows (masked)miss_control() # defaults: drop / fail / laplace miss_control(response = "include") # keep missing-response rows (masked)
Baseline-category logit / softmax family for a single unordered
categorical response with categories. For a categorical trait
with categories and reference category 1, the model is
for with
, and
(softmax). It recovers
the per-category intercepts and slopes as contrasts against the
reference category.
multinomial(link = "logit", baseline = NULL)multinomial(link = "logit", baseline = NULL)
link |
Character; only the baseline-category |
baseline |
Optional reference category (level of the response factor)
pinned at |
This is a response family and must not be confused with the
categorical() constructor, which is a missing-predictor
imputation family. Because an unordered categorical response spans
latent liability dimensions rather than one, the admitted
structured-term surface is bounded and every cell below was decided by a
signed recovery campaign, not construction alone.
Admitted: fixed effects always; an ordinary
shared latent(0 + trait | unit, d = k) ordination, which connects
a multinomial trait to other-family traits through its
contrast pseudo-traits (the default unique = TRUE works, with the
categorical contrast mapped off); the intercept-only
phylogenetic-machinery tier in all three modes – phylo_latent(species,
d = k) (reduced-rank), phylo_dep() (full unstructured ;
the identical phylo_rr parameterisation as
phylo_latent(d = K - 1)), and phylo_indep() (diagonal
; standalone phylo_unique() is its deprecated alias) –
together with their animal_*() (pedigree/A) and single-name
kernel_*() (dense K) twins, which run the same engine;
and the analogous spatial (SPDE) mode axis – spatial_latent(0 +
trait | coords, d = k) (loadings-only), spatial_indep()
(per-contrast independent fields), and spatial_dep() (verified
identical to spatial_latent(d = n_traits)). All of the
phylogenetic/relatedness cells are loadings-only: none emits a
companion, and unique = TRUE (a free phylogenetic
) is NOT admitted.
The honest evidence, not softened: on the phylogenetic/
relatedness surface, the one-categorical-draw-per-species recovery gate
FAILED (rail rate 8/20 against a 6/20 threshold, identically for
phylo_latent()/animal_latent()/kernel_latent() by
proven engine identity; phylo_dep() rails 8/20 separately). A
pre-registered replication rescue – five categorical draws per species
rather than one – PASSED for the loadings-only and full- cells
(rail rate 4/20, median 0.680 against a true 0.6) but has
NOT been tested for phylo_indep()'s diagonal- mode, whose
own corrected rerun independently FAILED (small contrast variances
collapse; a planted-zero check fails). The spatial mode axis and the
(1 | group) route below both PASSED their signed recovery gates
outright, with no replication rescue needed. One categorical
draw per species does not identify ; five draws per species
does.
A generic (1 | group)
random intercept is also admitted, but its semantics are
baseline-vs-rest, not per-category: the engine adds one draw per
group level to EVERY one of a multinomial observation's
baseline-contrast rows, which shifts versus
without changing the odds between any two
NON-baseline categories (within one fit, across groups). sigma_re's
substantive interpretation is therefore reference-category-
specific, and re-labelling the baseline is not a
reparameterisation of the same model: under baseline = 1 the shared
draw constrains the log-odds between the two non-baseline categories to be
constant across groups, while under a different baseline the SAME
engine shares a (new) draw between a different pair of categories instead
– a different parametric restriction, not the same distribution
relabelled. Re-fitting under a different baseline therefore changes
the fitted response-scale probabilities too, not only sigma_re.
The non-phylogenetic cluster /
cluster2 diagonal tier (indep(0 + trait | g) via the
cluster/cluster2 arguments to gllvmTMB, and its
soft-deprecated standalone unique() alias) is admitted as
per-contrast independent variances – the per-category random intercept
most users want – but common = TRUE (the scalar() modifier)
at that tier is NOT admitted. Both the (1 | group) and cluster/
cluster2 routes abort typed if every level of the grouping factor covers
exactly one categorical observation: that is an observation-level random
effect (OLRE) in disguise, unidentifiable because the softmax latent scale
is fixed.
Refused, not merely deferred: phylo_scalar() /
animal_scalar() / kernel_scalar() / spatial_scalar()
and common = TRUE at the cluster/cluster2 tier – a single shared
level across the contrasts has no interpretable null on the
contrast geometry.
Everything else is still deferred and fails loud
(R/multinomial-fence.R) rather than reaching an untested
categorical path: dep(), explicit unique() /
indep() at the unit tier, latent()/dep() at the
cluster/cluster2 tiers, and the unit_obs grouping
tier; augmented (intercept + slope) forms of every admitted keyword above;
unique = TRUE on phylo_latent()/animal_latent()/
kernel_latent() (a free phylogenetic ) and
spatial_latent()'s paired diagonal companion; standalone
spatial_unique()/deprecated bare spatial() (a
paired-companion alias mechanism, unlike the phylogenetic mode axis's
*_unique(), which IS admitted); multi-kernel (more than one
kernel_latent()/kernel_dep()/kernel_indep() name in
one fit); meta_V() / equalto() (confirmed Gaussian-only, no
route on a categorical-contrast pseudo-trait); and mi() predictor
terms.
A two-category response is exactly binomial(link = "logit") and is
redirected there. See also ordinal_probit() for ordered
categories.
A family object with class c("multinomial", "family").
ordinal_probit() for ordered categories; categorical() for the
missing-predictor imputation namesake.
multinomial()multinomial()
The Wright/Falconer/Dempster-Lerner threshold model for K-category
ordinal data with K >= 3, fitted on the latent (probit) scale. The
latent variable representation is ,
with and the observed category
iff , using cutpoints
, (fixed for identifiability),
, . A
K-category trait therefore estimates K - 2 free cutpoints.
ordinal_probit(link = "probit")ordinal_probit(link = "probit")
link |
Always |
Because has unit variance by construction, the
link-residual variance exactly (no trigamma
correction needed). This fixes the ordinal liability scale; it does not make
an ordinal response directly comparable with an observed continuous response
without an explicit scale definition. In the special two-component model
containing only phylogenetic variance plus the unit probit residual, the
liability-scale ratio is
.
Any additional fitted variance component belongs in the denominator, so do
not use that shortcut for richer models.
Hadfield (2015) eqn 10 shows that the K = 2 case reduces exactly to
binomial(link = "probit"), so use binomial() for binary outcomes
and ordinal_probit() for K >= 3.
Convention: the engine follows Hadfield's notation (
fixed, K - 2 free cutpoints reported as cutpoint_2, ...,
cutpoint_{K-1}). This differs from brms::cumulative(), which
reports K - 1 cutpoints as Intercept[1..K-1].
Cross-engine note: gllvmTMB's ordinal_probit() is a probit
threshold model (unit latent variance; Hadfield 2015), whereas
GLLVM.jl's ordinal family is a cumulative-logit model. Cutpoints
and loadings therefore live on different link scales (differing by a
factor of roughly ) and must not be compared
across engines at machine tolerance; the engine = "julia" bridge
maps probit to probit and does not route a logit ordinal response.
A family object with class c("ordinal_probit", "family").
The cutpoints are estimated as part of the model fit; recover them
with extract_cutpoints().
Dempster, E. R. and Lerner, I. M. (1950). Heritability of threshold characters. Genetics 35:212-236.
Falconer, D. S. and Mackay, T. F. C. (1996). Introduction to Quantitative Genetics, 4th ed. Longman.
Felsenstein, J. (2005). Using the quantitative genetic threshold model for inferences between and within species. Phil. Trans. R. Soc. B 360:1427-1434.
Felsenstein, J. (2012). A comparative method for both discrete and continuous characters using the threshold model. Am. Nat. 179:145-156.
Hadfield, J. D. (2015). Increasing the efficiency of MCMC for hierarchical phylogenetic models of categorical traits using reduced mixed models. Methods Ecol. Evol. 6:706-714. doi:10.1111/2041-210X.12354
Mizuno, A. et al. (2025). Phylogenetic comparative methods for threshold traits. J. Evol. Biol. 38(12):1699-1712.
extract_cutpoints() to recover after fitting.
ordinal_probit()ordinal_probit()
A
Computes the dense numerator-relationship matrix
from a 3-column pedigree (id, sire, dam) using Henderson's
(1976) recursive formula:
pedigree_to_A(pedigree)pedigree_to_A(pedigree)
pedigree |
A data frame with one row per individual and columns identifying the individual, sire, and dam. Column names are resolved BY NAME (MCMCglmm-style) using these synonyms:
If none of the named synonyms is present, the function falls
back to positional access – column 1 = id, column 2 =
sire, column 3 = dam – with a soft note. Unknown parents
encoded as |
, where is the inbreeding
coefficient of individual .
for younger than (pedigree is sorted oldest
first).
Founder individuals (both parents unknown) are assumed unrelated
and non-inbred: .
Users typically don't call pedigree_to_A() directly – pass
pedigree = ped to an animal_scalar() / animal_latent() /
animal_indep() / animal_dep() keyword in
the formula, and the keyword's parser handles the conversion
internally. This function is exported as a public helper for users
who want the matrix for their own diagnostics (e.g. inspection of
inbreeding coefficients or pre-computation for repeated fits).
For very large pedigrees () the cubic-time cost of
constructing a dense can become noticeable. When a sparse
precision is available, pass it directly with Ainv =; for comparison
against established implementations, see nadiv::makeAinv().
Dense numeric matrix with rownames /
colnames equal to the individual IDs.
animal_scalar() and siblings.
Builds the sparse precision matrix for an animal-
model pedigree using only Matrix – no MCMCglmm dependency.
The returned is genuinely sparse (typical density
), so the TMB fit is rather than –
this is the canonical fast path for wild pedigrees with
. The construction is the standard
Henderson (1976) / Quaas (1976) sparse inverse: inbreeding coefficients
are derived from the dense tabular relatedness matrix, then
is assembled directly from the per-individual
Mendelian sampling variances. It reproduces
MCMCglmm::inverseA(pedigree)$Ainv exactly. (The current inbreeding
step forms the dense , an
one-time cost.)
pedigree_to_Ainv_sparse(pedigree)pedigree_to_Ainv_sparse(pedigree)
pedigree |
A 3-column data frame: |
Usage:
Ainv <- pedigree_to_Ainv_sparse(ped)
fit <- gllvmTMB(value ~ 0 + trait +
animal_scalar(id, Ainv = Ainv),
data = df, unit = "id", cluster = "id")
The pedigree follows the conventional (id, sire, dam) column
order, OR named columns matching id/animal, sire/father,
dam/mother for by-name lookup. Unknown parents are encoded as
NA or 0. Founders (both parents unknown) are treated as unrelated.
A sparse dgCMatrix of dimension n_individuals x
n_individuals with rownames and colnames equal to the
individual IDs. Sparse storage; typical density is
.
Henderson, C. R. (1976). A simple method for computing the inverse of a numerator relationship matrix used in prediction of breeding values. Biometrics 32: 69-83.
Quaas, R. L. (1976). Computing the diagonal elements and inverse of a large numerator relationship matrix. Biometrics 32: 949-953.
Hadfield, J. D. (2010). MCMC methods for multi-response generalised
linear mixed models: the MCMCglmm R package. Journal of Statistical
Software 33: 1-22. (The MCMCglmm::inverseA() reference
implementation this builder is validated against.)
pedigree_to_A() for the dense companion;
animal_scalar() and siblings for the keyword family.
ped <- data.frame( id = paste0("i", 1:6), sire = c(NA, NA, "i1", "i1", "i3", "i3"), dam = c(NA, NA, "i2", "i2", "i4", "i4") ) Ainv <- pedigree_to_Ainv_sparse(ped) class(Ainv) # "dgCMatrix" dim(Ainv) # 6 x 6ped <- data.frame( id = paste0("i", 1:6), sire = c(NA, NA, "i1", "i1", "i3", "i3"), dam = c(NA, NA, "i2", "i2", "i4", "i4") ) Ainv <- pedigree_to_Ainv_sparse(ped) class(Ainv) # "dgCMatrix" dim(Ainv) # 6 x 6
phylo() is a unified entry point for the package's three phylogenetic modes
(phylo_indep, phylo_latent, phylo_dep). It accepts an lme4-bar formula on the
first argument and dispatches to the appropriate canonical keyword
based on the LHS shape and the optional mode = ... argument. The
three current keywords stay first-class – phylo() is an additive
alias matching the lme4 / brms / drmTMB convention.
phylo( formula, tree = NULL, vcv = NULL, mode = NULL, d = 1, unique = FALSE, A = NULL, Ainv = NULL )phylo( formula, tree = NULL, vcv = NULL, mode = NULL, d = 1, unique = FALSE, A = NULL, Ainv = NULL )
formula |
An lme4-bar formula ( |
tree |
An |
vcv |
A tip-only phylogenetic correlation matrix
( |
mode |
One of |
d |
Latent rank for |
unique |
Logical; for |
A |
Tip-level relatedness matrix; alias of |
Ainv |
Sparse precision matrix (inverse of |
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
library(ape)
tree <- rcoal(20); tree$tip.label <- paste0("sp", 1:20)
# Single shared phylogenetic variance (= phylo_scalar)
fit_s <- gllvmTMB(value ~ 0 + trait + phylo(1 | species, tree = tree),
data = df,
trait = "trait",
unit = "species",
cluster = "species")
# Per-trait independent phylogenetic variances on shared A
fit_u <- gllvmTMB(value ~ 0 + trait +
phylo(0 + trait | species, mode = "indep", tree = tree),
data = df,
trait = "trait",
unit = "species",
cluster = "species")
# Reduced-rank cross-trait phylogenetic decomposition (= phylo_latent)
fit_l <- gllvmTMB(value ~ 0 + trait +
phylo(0 + trait | species, mode = "latent",
d = 2, tree = tree),
data = df,
trait = "trait",
unit = "species",
cluster = "species")
# Backward-compat: legacy bare-name form
Cphy <- vcv(tree, corr = TRUE)
fit_legacy <- gllvmTMB(value ~ 0 + trait + phylo(species),
data = df,
trait = "trait",
unit = "species",
cluster = "species",
phylo_vcv = Cphy)
A formula marker; never evaluated.
| Form | Mode default | Rewrites to |
phylo(1 \| species) |
"scalar" |
phylo_indep(0 + trait \| species, common = TRUE) |
phylo(0 + trait \| species, mode = "indep") |
(mandatory) | phylo_indep(0 + trait \| species) |
phylo(0 + trait \| species, mode = "latent", d = K) |
(mandatory) | phylo_latent(species, d = K) |
phylo(0 + trait \| species, mode = "dep") |
(mandatory) | phylo_dep(0 + trait \| species)
|
When the LHS expands to a single column (1, intercept-only), mode
is degenerate: it defaults silently to "scalar", and explicit
mode = "scalar" is accepted (no warning). When the LHS is
0 + trait, mode is mandatory – choosing between "indep" /
"latent" / "dep" is a meaningful decision (per-trait
diagonal vs reduced-rank decomposition vs full unstructured) and the
parser refuses to silently default.
Legacy bare-name calls phylo(species) and phylo(species, vcv = Cphy)
continue to work as deprecated aliases of phylo_scalar(species) /
phylo_scalar(species, vcv = Cphy). The legacy form rewrites to
propto(0 + species | trait) internally, picking up the phylogenetic
covariance from the top-level phylo_vcv = argument to gllvmTMB().
The unified phylo() wrapper is limited to intercept-only trait covariance.
For an intercept and one or more slopes, use an explicit structured mode
such as phylo_indep(), phylo_latent(), or phylo_dep(); unsupported LHS
forms fail with a message showing the accepted syntax.
drmTMB also exposes phylo(1 | species, tree = tree) with the same
Hadfield-Nakagawa sparse internal path. Both
packages share the same calling convention; user muscle memory
transfers.
phylo_scalar(), phylo_indep(),
phylo_latent(), phylo_dep().
phylo_dep(0 + trait | species)
Canonical name for the full unstructured cross-trait phylogenetic
covariance ,
with free parameters (
parameterised via Cholesky for PSD-ness). Mathematically identical to
phylo_latent(species, d = T) standalone; the keyword choice is
documentary.
phylo_dep(formula, tree = NULL, vcv = NULL, A = NULL, Ainv = NULL)phylo_dep(formula, tree = NULL, vcv = NULL, A = NULL, Ainv = NULL)
formula |
|
tree |
An |
vcv |
A tip-only phylogenetic correlation matrix
( |
A |
Tip-level relatedness matrix ( |
Ainv |
Sparse precision matrix (inverse of |
Use phylo_dep() when you want an explicit full-unstructured
cross-trait phylogenetic covariance fit. Use
phylo_latent(..., unique = TRUE) for the rank-reduced paired
phylogenetic decomposition. Use
phylo_indep() for the marginal-only per-trait variance fit.
In a Gaussian long-format model, phylo_dep(0 + lat + temp | trait) gives
response-column slope deviations with a full positive-definite covariance
among predictors. Fixed column intercepts remain 0 + trait in the main
formula; this term never adds a random intercept. Retrieve the predictor
covariance with extract_Sigma(fit, level = "column_slope"). Use
phylo_indep() for a diagonal predictor covariance. The helper
phylo_slope(lat + temp | trait) is an alias for this full route.
phylo_latent() / phylo_indep()
Combining phylo_dep(0 + trait | species) with phylo_latent is
over-parameterised; combining with
phylo_indep is redundant (phylo_dep already includes the
diagonal). The parser raises cli::cli_abort() in any of these
cases.
Pass the phylogeny via tree = phylo (canonical, sparse ) or
vcv = Cphy (, dense). See
phylo_latent() for the full discussion of the two paths.
A formula marker; never evaluated.
phylo_latent(), phylo_indep(),
dep(), spatial_dep(), extract_Sigma().
tree <- ape::rcoal(8) # Match the simulator species levels to the covariance matrix row order. tree$tip.label <- as.character(seq_len(8)) sim <- simulate_site_trait( n_sites = 1, n_species = 8, n_traits = 3, mean_species_per_site = 8, Cphy = ape::vcv(tree, corr = TRUE), sigma2_phy = rep(0.3, 3), seed = 1 ) sim$data$species <- factor(sim$data$species, levels = tree$tip.label) fit <- gllvmTMB( value ~ 0 + trait + phylo_dep(0 + trait | species), data = sim$data, trait = "trait", unit = "species", cluster = "species", phylo_tree = tree )tree <- ape::rcoal(8) # Match the simulator species levels to the covariance matrix row order. tree$tip.label <- as.character(seq_len(8)) sim <- simulate_site_trait( n_sites = 1, n_species = 8, n_traits = 3, mean_species_per_site = 8, Cphy = ape::vcv(tree, corr = TRUE), sigma2_phy = rep(0.3, 3), seed = 1 ) sim$data$species <- factor(sim$data$species, levels = tree$tip.label) fit <- gllvmTMB( value ~ 0 + trait + phylo_dep(0 + trait | species), data = sim$data, trait = "trait", unit = "species", cluster = "species", phylo_tree = tree )
phylo_indep(0 + trait | species)
Canonical name for T per-trait phylogenetic variances coupled by
the phylo correlation matrix ; standalone =
T univariate phylogenetic mixed models stacked.
phylo_indep( formula, tree = NULL, vcv = NULL, A = NULL, Ainv = NULL, common = FALSE )phylo_indep( formula, tree = NULL, vcv = NULL, A = NULL, Ainv = NULL, common = FALSE )
formula |
Intercept-only |
tree |
An |
vcv |
A tip-only phylogenetic correlation matrix
( |
A |
Tip-level relatedness matrix ( |
Ainv |
Sparse precision matrix (inverse of |
common |
|
Each trait gets its own variance
on the same phylogenetic correlation
matrix ; trait-specific random vectors
are otherwise independent.
Use phylo_indep() for an explicit marginal-only phylogenetic fit
(no cross-trait phylogenetic decomposition). Use
phylo_latent(..., unique = TRUE) for the paired
phylogenetic decomposition
.
phylo_latent()
Combining phylo_indep(0 + trait | species) with
phylo_latent(species, d = K) is over-parameterised and the
parser raises a cli::cli_abort().
A single-covariate random regression uses
phylo_indep(1 + x | species) in wide syntax or
phylo_indep(0 + trait + (0 + trait):x | species) in explicit long syntax.
It estimates independent trait-specific intercept–slope blocks. The
intercept–slope correlation is estimated within each trait; cross-trait
blocks are structurally zero.
For a long-format Gaussian model, phylo_indep(0 + lat + temp | trait)
adds response-column deviations to the fixed slopes without adding a random
intercept. The phylogeny is indexed by the response-column factor on the
right of |; indep makes the covariance among lat and temp slopes
diagonal. With slope coefficient matrix (rows are
response columns and columns are predictors), let
order coefficients by
response column: lat, temp for column 1, then lat, temp for column
2, and so on. Then
. (The native implementation stores the
permutation-equivalent column-major vector.) Retrieve that predictor-basis covariance with
extract_Sigma(fit, level = "column_slope"). It is not a trait-level
. This V1 route requires bare finite numeric
predictors, exactly the resolved trait column on the RHS, and Gaussian
responses; wide-format and non-Gaussian column slopes are planned work.
Pass the phylogeny via tree = phylo (canonical, sparse ) or
vcv = Cphy (, dense). See
phylo_latent() for the full discussion of the two paths.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
library(ape)
tree <- rcoal(20); tree$tip.label <- paste0("sp", 1:20)
fit <- gllvmTMB(value ~ 0 + trait +
phylo_indep(0 + trait | species, tree = tree),
data = df,
trait = "trait",
unit = "species",
cluster = "species")
A formula marker; never evaluated.
Williams et al. (2025) Phylogenetic generalised linear mixed
models for multi-trait comparative analyses. bioRxiv
2025.12.20.695312. The marginal phylo_indep + indep standalone
form stacked across traits matches their PGLMM Eq. 3.
phylo_latent(), phylo_dep(), indep(),
spatial_indep(), extract_Sigma().
phylo_latent(species, d = K)
Canonical name for the reduced-rank phylogenetic random effect.
Formerly phylo_rr(species, d = K) – same engine, new name.
phylo_latent( species, d = 1, tree = NULL, vcv = NULL, A = NULL, Ainv = NULL, unique = FALSE )phylo_latent( species, d = 1, tree = NULL, vcv = NULL, A = NULL, Ainv = NULL, unique = FALSE )
species |
Unquoted column name for the species factor. |
d |
Integer; number of phylogenetic latent factors. |
tree |
An |
vcv |
A tip-only phylogenetic correlation matrix
( |
A |
Tip-level relatedness matrix ( |
Ainv |
Precision matrix (inverse of |
unique |
Logical; |
tree = (canonical) and vcv = (legacy)Pass the phylogeny inside the keyword via one of two arguments:
tree = phylo (recommended when the tree is available) – the full
ape::phylo object. The package constructs the sparse phylogenetic
precision using its Hadfield–Nakagawa implementation.
vcv = Cphy – a tip-level n_species x n_species correlation
matrix for analyses that begin from a supplied covariance matrix.
These inputs encode the same tip-level covariance target when they are constructed from the same tree and aligned identically. Numerical agreement still depends on labels, scaling, and fitting health; the function does not estimate or report ancestral states.
The two routes differ in one respect worth knowing. The vcv = route adds a
fixed 1e-8 ridge to the supplied matrix before inverting it; the tree =
route builds the sparse precision analytically from branch lengths and adds
no ridge. The two therefore agree with each other only to roughly 1e-5 in
log-density, and less on large or poorly conditioned trees.
The species column is aligned to the phylogeny through the order of its
factor levels: levels(data$species) is looked up against the tip
labels, and whatever order those levels happen to be in is the order the
latent scores are indexed by. Tree tip order is not consulted.
If the factor's levels do not correspond to the species you intend, the model still fits and issues no warning. It simply fits a different phylogeny. Set the levels explicitly:
data$species <- factor(data$species, levels = tree$tip.label)
Every example below does this. It is not decoration.
See the phylogenetic covariance article for the benchmark.
A formula marker; never evaluated.
Hadfield JD, Nakagawa S (2010). General quantitative genetic methods for comparative biology: phylogenies, taxonomies and multi-trait models for continuous and categorical characters. J. Evol. Biol. 23: 494-508. doi:10.1111/j.1420-9101.2009.01915.x
phylo_scalar(), phylo_indep(),
phylo_dep(), phylo_rr() (deprecated alias).
tree <- ape::rcoal(20) # Match the simulator species levels to the covariance matrix row order. tree$tip.label <- as.character(seq_len(20)) sim <- simulate_site_trait( n_sites = 1, n_species = 20, n_traits = 4, mean_species_per_site = 20, Cphy = ape::vcv(tree, corr = TRUE), sigma2_phy = rep(0.3, 4), seed = 1 ) sim$data$species <- factor(sim$data$species, levels = tree$tip.label) fit <- gllvmTMB( value ~ 0 + trait + phylo_latent(species, d = 2), data = sim$data, trait = "trait", unit = "species", cluster = "species", phylo_tree = tree )tree <- ape::rcoal(20) # Match the simulator species levels to the covariance matrix row order. tree$tip.label <- as.character(seq_len(20)) sim <- simulate_site_trait( n_sites = 1, n_species = 20, n_traits = 4, mean_species_per_site = 20, Cphy = ape::vcv(tree, corr = TRUE), sigma2_phy = rep(0.3, 4), seed = 1 ) sim$data$species <- factor(sim$data$species, levels = tree$tip.label) fit <- gllvmTMB( value ~ 0 + trait + phylo_latent(species, d = 2), data = sim$data, trait = "trait", unit = "species", cluster = "species", phylo_tree = tree )
phylo_scalar(species)
Canonical name for the single-scalar (per-trait) phylogenetic random
intercept. Each trait carries an independent length-n_species draw
with one shared scaling across all
traits. Formerly phylo(species) – same engine, new name. Compare to
phylo_indep() (D independent variances) and phylo_latent()
(K-dim factor decomposition).
phylo_scalar(species, tree = NULL, vcv = NULL, A = NULL, Ainv = NULL)phylo_scalar(species, tree = NULL, vcv = NULL, A = NULL, Ainv = NULL)
species |
Unquoted column name for the species factor. |
tree |
An |
vcv |
A tip-only phylogenetic correlation matrix
( |
A |
Tip-level relatedness matrix ( |
Ainv |
Sparse precision matrix (inverse of |
Pass the phylogeny via tree = phylo (canonical, sparse ;
Hadfield & Nakagawa 2010) or vcv = Cphy
(, dense). See
phylo_latent()
for the full discussion of the two paths.
A formula marker; never evaluated.
phylo_latent(), phylo_indep(),
phylo_dep(), phylo() (deprecated alias).
tree <- ape::rcoal(8) # Match the simulator species levels to the covariance matrix row order. tree$tip.label <- as.character(seq_len(8)) sim <- simulate_site_trait( n_sites = 1, n_species = 8, n_traits = 3, mean_species_per_site = 8, Cphy = ape::vcv(tree, corr = TRUE), sigma2_phy = rep(0.3, 3), seed = 1 ) sim$data$species <- factor(sim$data$species, levels = tree$tip.label) fit <- gllvmTMB( value ~ 0 + trait + phylo_scalar(species), data = sim$data, trait = "trait", unit = "species", cluster = "species", phylo_tree = tree )tree <- ape::rcoal(8) # Match the simulator species levels to the covariance matrix row order. tree$tip.label <- as.character(seq_len(8)) sim <- simulate_site_trait( n_sites = 1, n_species = 8, n_traits = 3, mean_species_per_site = 8, Cphy = ape::vcv(tree, corr = TRUE), sigma2_phy = rep(0.3, 3), seed = 1 ) sim$data$species <- factor(sim$data$species, levels = tree$tip.label) fit <- gllvmTMB( value ~ 0 + trait + phylo_scalar(species), data = sim$data, trait = "trait", unit = "species", cluster = "species", phylo_tree = tree )
For a gllvmTMB() fit with a phylogenetic missing-predictor covariate model
(mi(x) with impute = list(x = x ~ ... + phylo(1 | species, tree = tree))),
phylo_signal_mi() reports an effective phylogenetic-signal statistic for the
covariate model and flags the weak-signal case. Phylogenetic imputation helps
when the signal in x is strong and degrades toward the independent
(no-borrowing) model when it is weak; forcing a phylogenetic prior on a
phylogenetically-unstructured trait adds noise rather than information
(Penone et al. 2014 Methods Ecol. Evol. 5:961-970; Molina-Venegas 2024
Methods Ecol. Evol.; Goolsby, Bruggeman & Ane 2017 Methods Ecol. Evol.
8:22-27, Rphylopars).
phylo_signal_mi(object, variable = NULL, threshold = 0.1, warn = FALSE)phylo_signal_mi(object, variable = NULL, threshold = 0.1, warn = FALSE)
object |
A |
variable |
Optional missing-predictor name; defaults to the only modelled missing predictor. |
threshold |
Effective-lambda threshold below which the signal is flagged
as weak (default |
warn |
Logical; when |
The statistic is an effective Pagel's lambda for the covariate,
lambda = sd_x^2 / (sd_x^2 + sigma_x^2), the fraction of species-level
variance in x explained by the phylogenetic field (sd_x) relative to the
i.i.d. residual (sigma_x) – the Pagel partition of the covariate model.
lambda near 1 = strong signal (informative borrowing); lambda near 0 =
weak signal (the covariate model is approximately equivalent to an
independent model). This is a verification / interpretation aid, not part of
the fit; it does not change estimates.
A list with variable, lambda (effective Pagel lambda), sd_x,
sigma_x, weak (logical), and threshold.
imputed() for conditional modes; gllvmTMB() for the impute = phylo
covariate model.
Adds a per-species random slope on a continuous covariate x, with
species-level slopes correlated according to the supplied phylogeny.
Mathematically:
phylo_slope(formula)phylo_slope(formula)
formula |
|
The slope vector has length
(one slope per species). It is shared
across traits – the same multiplies
for every trait of species . This is
the "random regression" pattern from quantitative genetics, with the
phylogenetic A-matrix as the slope-covariance prior.
Used inside a gllvmTMB() formula:
value ~ 0 + trait + (0 + trait):x + # fixed-effect of x per trait
phylo_latent(species, d = 2) + # main phylogenetic random effect
phylo_slope(x | species) # phylo random slope on x
Shares the supplied tree / VCV with phylo_latent() when both terms are
present, but constructs its own RHS-indexed precision and row map (sparse
via phylo_tree =, dense via phylo_vcv =). This lets the slope grouping
differ safely from the top-level cluster.
This helper supports:
One continuous covariate x (a single column name).
One shared slope variance .
Slopes shared across traits (same (i)
for every trait of species ).
A separate multi-predictor long-format alias is available when the RHS is
the response-column factor: phylo_slope(lat + temp | trait, tree = tree)
means phylo_dep(0 + lat + temp | trait, tree = tree), so the two slope
deviations have a full covariance. Use || instead of | for the
diagonal phylo_indep() route. This alias is Gaussian-only, has no random
intercept, and is intentionally outside the 5 x 3 trait-covariance grid.
It does not change the one-predictor helper described above.
For trait-specific intercept-and-slope covariance, use the corresponding
augmented phylo_indep(), phylo_latent(), or phylo_dep() syntax.
A formula marker; never evaluated.
phylo_latent(), phylo_scalar().
tree <- ape::rcoal(20) # Match the simulator species levels to the covariance matrix row order. tree$tip.label <- as.character(seq_len(20)) sim <- simulate_site_trait( n_sites = 10, n_species = 20, n_traits = 3, mean_species_per_site = 10, Cphy = ape::vcv(tree, corr = TRUE), sigma2_phy = rep(0.3, 3), seed = 1 ) sim$data$species <- factor(sim$data$species, levels = tree$tip.label) sim$data$x <- rnorm(nrow(sim$data)) fit <- gllvmTMB( value ~ 0 + trait + (0 + trait):x + phylo_slope(x | species), data = sim$data, trait = "trait", unit = "species", cluster = "species", phylo_tree = tree )tree <- ape::rcoal(20) # Match the simulator species levels to the covariance matrix row order. tree$tip.label <- as.character(seq_len(20)) sim <- simulate_site_trait( n_sites = 10, n_species = 20, n_traits = 3, mean_species_per_site = 10, Cphy = ape::vcv(tree, corr = TRUE), sigma2_phy = rep(0.3, 3), seed = 1 ) sim$data$species <- factor(sim$data$species, levels = tree$tip.label) sim$data$x <- rnorm(nrow(sim$data)) fit <- gllvmTMB( value ~ 0 + trait + (0 + trait):x + phylo_slope(x | species), data = sim$data, trait = "trait", unit = "species", cluster = "species", phylo_tree = tree )
phylo_unique(species)
phylo_unique(species, tree = NULL, vcv = NULL, A = NULL, Ainv = NULL)phylo_unique(species, tree = NULL, vcv = NULL, A = NULL, Ainv = NULL)
species |
Unquoted column name for the species factor. |
tree |
An |
vcv |
A tip-only phylogenetic correlation matrix
( |
A |
Tip-level relatedness matrix ( |
Ainv |
Sparse precision matrix (inverse of |
phylo_unique() is soft-deprecated as compatibility syntax in gllvmTMB
0.2.0. Use phylo_indep() for standalone marginal diagonal phylogenetic
tiers. Paired explicit-Psi use remains accepted; use
phylo_latent(..., unique = TRUE) when the folded latent term itself
should carry this diagonal companion.
Canonical name for the D independent phylogenetic random
intercepts. Each trait gets its own variance
on the same phylogenetic correlation
matrix ; the per-trait random vectors are
otherwise independent. Mathematically:
giving a phylogenetic counterpart of unique() – one independent
variance per trait, all sharing the same phylogenetic precision.
Compare to phylo_scalar() (one shared variance), which collapses
the T variances to a single ; and
phylo_latent() (K factors), which induces across-trait
correlation through a low-rank loading matrix.
phylo_unique() has two complementary modes:
When written without a paired
phylo_latent(species, d = K), the engine implements the term
internally as phylo_latent(species, d = T) with a diagonal
. The diagonal entries of
are the per-trait
phylogenetic SDs. This path stays for backward compatibility.
phylo_latent(..., unique = FALSE) (compatibility)When written together with phylo_latent(species, d = K, unique = FALSE), the
two terms co-fit as separate covariance components:
is filled by the rank-K shared
latent factors; carries
the per-trait phylogenetic variances not absorbed by the K shared
axes. phylo_latent(species, d = K, unique = TRUE) fits this same
manuscript-aligned PGLLVM decomposition as a single term
(Hadfield & Nakagawa 2010; Meyer & Kirkpatrick 2008; Halliwell et
al. 2025). Replication (multiple sites per species) is required
to break the Psi_phy / Psi_non confound at the species level.
Use extract_Sigma() with level = "phy" and part = "shared",
"unique", or "total" to pull each component, the diagonal psi_phy,
or their sum.
Pass the phylogeny via tree = phylo (canonical, sparse ) or
vcv = Cphy (, dense). See
phylo_latent() for the full discussion of the two paths.
A formula marker; never evaluated.
Hadfield, J. D. & Nakagawa, S. (2010) General quantitative genetic methods for comparative biology: phylogenies, taxonomies and multi-trait models for continuous and categorical characters. Journal of Evolutionary Biology 23(3): 494-508. doi:10.1111/j.1420-9101.2009.01915.x
Meyer, K. & Kirkpatrick, M. (2008) Perils of parsimony: properties of reduced-rank estimates of genetic covariance matrices. Genetics 178(4): 2223-2240.
phylo_scalar(), phylo_latent(), phylo_indep(),
phylo_dep(), extract_Sigma().
tree <- ape::rcoal(8) # Match the simulator species levels to the covariance matrix row order. tree$tip.label <- as.character(seq_len(8)) sim <- simulate_site_trait( n_sites = 1, n_species = 8, n_traits = 3, mean_species_per_site = 8, Cphy = ape::vcv(tree, corr = TRUE), sigma2_phy = rep(0.3, 3), seed = 1 ) sim$data$species <- factor(sim$data$species, levels = tree$tip.label) fit <- gllvmTMB( value ~ 0 + trait + phylo_unique(species), data = sim$data, trait = "trait", unit = "species", cluster = "species", phylo_tree = tree )tree <- ape::rcoal(8) # Match the simulator species levels to the covariance matrix row order. tree$tip.label <- as.character(seq_len(8)) sim <- simulate_site_trait( n_sites = 1, n_species = 8, n_traits = 3, mean_species_per_site = 8, Cphy = ape::vcv(tree, corr = TRUE), sigma2_phy = rep(0.3, 3), seed = 1 ) sim$data$species <- factor(sim$data$species, levels = tree$tip.label) fit <- gllvmTMB( value ~ 0 + trait + phylo_unique(species), data = sim$data, trait = "trait", unit = "species", cluster = "species", phylo_tree = tree )
gllvmTMB currently fits isotropic SPDE/GMRF spatial fields. Consequently,
these helpers draw a circle whose radius is the fitted practical range,
sqrt(8) / kappa. Equal axes describe the model assumption (H = I); they
are not evidence that anisotropy was estimated.
Scope: the fitted isotropic-range display is covered by focused tests; the
broader spatial-family evidence remains partial; directional anisotropy,
delta/spatiotemporal fields, and new likelihoods are rejected from this
plotting contract.
plot_anisotropy(object, return_data = FALSE) plot_anisotropy2(object, model = 1L)plot_anisotropy(object, return_data = FALSE) plot_anisotropy2(object, model = 1L)
object |
A fitted |
return_data |
If |
model |
Retained for compatibility. Only |
plot_anisotropy() returns a ggplot2 object, or a data frame when
return_data = TRUE. plot_anisotropy2() draws the same geometry with
base graphics and invisibly returns a list containing data, range,
kappa, H = diag(2), and anisotropy_estimated = FALSE.
These functions do not estimate or diagnose directional anisotropy. Delta and spatiotemporal spatial models are not supported by this plotting contract and fail explicitly.
plot_correlations() turns the tidy rows returned by
extract_correlations() into a horizontal forest plot, confidence-eye plot,
heatmap, or ellipse matrix. Forest and confidence-eye styles keep point
estimates visible as open points when interval bounds are missing, and draw
interval information only for rows with finite lower and upper bounds.
Matrix styles show the same tidy rows without requiring users to reconstruct
or hand-index a correlation matrix.
For fitted-object calls, open points can often be investigated by trying
method = "bootstrap" or another interval method supported by
extract_correlations().
plot_correlations( x, tier = "all", pair = NULL, level = 0.95, method = c("none", "fisher-z", "profile", "wald", "bootstrap"), n_eff = NULL, nsim = 500L, seed = NULL, link_residual = c("auto", "none"), facet = c("level", "none"), sort = c("estimate", "magnitude", "trait", "level"), show_intervals = NULL, style = c("interval", "eye", "raindrop", "heatmap", "ellipse", "oval"), eye_level = level, raindrop_level = NULL, triangle = c("full", "lower", "upper"), include_diagonal = TRUE, label = TRUE, label_type = c("auto", "estimate", "ci", "estimate_ci", "none"), label_digits = 2, matrix_layout = c("by_level", "estimate_ci", "levels"), title = NULL, subtitle = NULL, caption = NULL )plot_correlations( x, tier = "all", pair = NULL, level = 0.95, method = c("none", "fisher-z", "profile", "wald", "bootstrap"), n_eff = NULL, nsim = 500L, seed = NULL, link_residual = c("auto", "none"), facet = c("level", "none"), sort = c("estimate", "magnitude", "trait", "level"), show_intervals = NULL, style = c("interval", "eye", "raindrop", "heatmap", "ellipse", "oval"), eye_level = level, raindrop_level = NULL, triangle = c("full", "lower", "upper"), include_diagonal = TRUE, label = TRUE, label_type = c("auto", "estimate", "ci", "estimate_ci", "none"), label_digits = 2, matrix_layout = c("by_level", "estimate_ci", "levels"), title = NULL, subtitle = NULL, caption = NULL )
x |
Either a fit returned by |
tier, pair, level, method, n_eff, nsim, seed, link_residual
|
Passed to
|
facet |
One of |
sort |
Row ordering: |
show_intervals |
Logical or |
style |
One of |
eye_level |
Confidence level represented by the supplied interval
bounds when |
raindrop_level |
Compatibility alias for |
triangle |
Matrix triangle to show for |
include_diagonal |
Logical. Include muted diagonal self-correlation cells in matrix styles? |
label |
Logical. Print cell labels in matrix styles? |
label_type |
For matrix styles, one of |
label_digits |
Non-negative whole number of decimal places for matrix cell labels. |
matrix_layout |
Matrix-cell layout for heatmap and ellipse styles.
|
title, subtitle, caption
|
Optional scalar character labels for matrix
styles. |
Julia bridge (engine = "julia") fits currently fail with an explicit gate
because this helper depends on interval-bearing extract_correlations()
rows. Use plot_Sigma_table() or plot_Sigma_heatmap() for point-only
Julia bridge correlation displays.
Scope: the helper plots tidy cross-trait correlation rows from
extract_correlations(), extracts those rows from a fit returned by
gllvmTMB() (this is covered by the package's extractor-contract
validation tests), or converts bootstrap_Sigma() correlation summaries to
the same plotting schema (also covered by those tests). Matrix heatmaps and
ellipse/oval displays, including upper-estimate/lower-CI and two-level
upper/lower layouts, are point-estimate summaries with optional numeric
interval labels (covered separately).
The plot does not compute new intervals; it displays whatever
interval method the input rows already contain. Matrix-style visual
comparisons against known truth remain article code rather than part of this
helper.
A ggplot2 plot object with gllvmTMB_meta and gllvmTMB_data
attributes.
extract_correlations(), plot_Sigma_table(),
plot.gllvmTMB_multi().
if (requireNamespace("ggplot2", quietly = TRUE)) { cors <- data.frame( tier = c("unit", "unit", "unit_obs"), trait_i = c("length", "length", "length"), trait_j = c("mass", "wing", "mass"), correlation = c(0.42, -0.18, 0.10), lower = c(0.12, -0.45, NA), upper = c(0.66, 0.12, NA), method = c("fisher-z", "fisher-z", "none") ) plot_correlations(cors) plot_correlations(cors, style = "heatmap", matrix_layout = "estimate_ci") }if (requireNamespace("ggplot2", quietly = TRUE)) { cors <- data.frame( tier = c("unit", "unit", "unit_obs"), trait_i = c("length", "length", "length"), trait_j = c("mass", "wing", "mass"), correlation = c(0.42, -0.18, 0.10), lower = c(0.12, -0.45, NA), upper = c(0.66, 0.12, NA), method = c("fisher-z", "fisher-z", "none") ) plot_correlations(cors) plot_correlations(cors, style = "heatmap", matrix_layout = "estimate_ci") }
A bar-and-whisker visualisation of raw or standardised loadings with
point estimates and Wald (or other-method) confidence intervals from
loading_ci(). Default rendering uses the "Confidence Eye" contract:
a pale CI region (geom_linerange) and a hollow point estimate
(geom_point, shape = 21, fill white) so the visual weight rests on
the uncertainty rather than the point.
plot_loadings_confidence_eye( fit, level = c("unit", "unit_obs"), method = "wald", conf_level = 0.95, null_region = NULL, sort_by = c("trait_order", "magnitude"), ylim = NULL, loading_scale = NULL )plot_loadings_confidence_eye( fit, level = c("unit", "unit_obs"), method = "wald", conf_level = 0.95, null_region = NULL, sort_by = c("trait_order", "magnitude"), ylim = NULL, loading_scale = NULL )
fit |
A multivariate |
level, method, conf_level, loading_scale
|
Forwarded to |
null_region |
Optional length-2 numeric drawn as a shaded
"biologically negligible" band so the reader can tell at a glance
which CIs sit outside it. If supplied, also colours points by
reliability via |
sort_by |
Character, one of |
ylim |
Optional length-2 numeric vector clipping the y-axis
via |
The closest published precedent for an uncertainty-aware per-species loading display in JSDM is the site-score uncertainty plot of Hoegh & Roberts (2020, doi:10.1002/ece3.5752), but that targets site scores rather than species loadings. To my knowledge no equivalent plot of per-species Lambda uncertainty exists in the ML/TMB JSDM lineage (gllvm, sjSDM, ecoCopula).
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
plot_loadings_confidence_eye(fit, level = "unit",
null_region = c(-0.1, 0.1))
A ggplot object.
loading_ci(), flag_unreliable_loadings().
plot_rotated_loadings() turns the tidy rows from
extract_rotated_loadings_table() into a publication-oriented loading
matrix. It can also extract the table from a fitted gllvmTMB() model.
Rows are traits, columns are latent axes, and colour shows the signed
loading after the requested rotation, axis ordering, sign anchoring, and
loading scale.
plot_rotated_loadings( x, level = "unit", method = c("varimax", "promax", "none"), order_axes = TRUE, sign_anchor = c("auto", "none"), anchor_traits = NULL, loading_scale = c("standardized", "raw"), sort = c("dominant", "abs_loading", "trait"), show_values = NULL, digits = 2L, limits = NULL, facet = c("level", "none") )plot_rotated_loadings( x, level = "unit", method = c("varimax", "promax", "none"), order_axes = TRUE, sign_anchor = c("auto", "none"), anchor_traits = NULL, loading_scale = c("standardized", "raw"), sort = c("dominant", "abs_loading", "trait"), show_values = NULL, digits = 2L, limits = NULL, facet = c("level", "none") )
x |
A fit returned by |
level |
|
method |
One of |
order_axes |
Logical. When |
sign_anchor |
One of |
anchor_traits |
Optional character vector of trait names used for sign anchoring. Supply one trait per axis after ordering. Axes without a supplied anchor use the trait with the largest absolute loading. |
loading_scale |
One of |
sort |
Trait ordering. |
show_values |
Logical or |
digits |
Number of decimal places for tile labels. |
limits |
Optional two-number colour-scale limits. When |
facet |
One of |
Scope: the helper plots point-estimate rotated loading rows from
extract_rotated_loadings_table() for fitted latent() components
(this builds on the package's rotated-loadings extraction validation).
The plot does not compute or display loading uncertainty intervals
and does not make rotated axes uniquely biological. Bootstrap- or
simulation-aligned loading uncertainty remains a later inference
slice.
A ggplot2 plot object with gllvmTMB_meta and gllvmTMB_data
attributes.
extract_rotated_loadings_table(), rotate_loadings(),
plot.gllvmTMB_multi().
if (requireNamespace("ggplot2", quietly = TRUE)) { rows <- data.frame( level = "unit", trait = rep(c("body", "wing", "bill"), times = 2), axis = rep(c("LV1", "LV2"), each = 3), loading = c(0.72, 0.61, -0.08, 0.05, -0.18, 0.66), abs_loading = abs(c(0.72, 0.61, -0.08, 0.05, -0.18, 0.66)), axis_variance = rep(c(1.1, 0.5), each = 3), axis_share = rep(c(0.69, 0.31), each = 3), rotation = "varimax", order_axes = TRUE, sign_anchor = "auto", anchor_trait = rep(c("body", "bill"), each = 3), loading_scale = "standardized" ) plot_rotated_loadings(rows) }if (requireNamespace("ggplot2", quietly = TRUE)) { rows <- data.frame( level = "unit", trait = rep(c("body", "wing", "bill"), times = 2), axis = rep(c("LV1", "LV2"), each = 3), loading = c(0.72, 0.61, -0.08, 0.05, -0.18, 0.66), abs_loading = abs(c(0.72, 0.61, -0.08, 0.05, -0.18, 0.66)), axis_variance = rep(c(1.1, 0.5), each = 3), axis_share = rep(c(0.69, 0.31), each = 3), rotation = "varimax", order_axes = TRUE, sign_anchor = "auto", anchor_trait = rep(c("body", "bill"), each = 3), loading_scale = "standardized" ) plot_rotated_loadings(rows) }
plot_Sigma_comparison() turns compare_Sigma_table() rows into a
row-labelled comparison plot. The default style = "difference" shows
estimate - truth for each covariance or correlation row; style = "scatter" shows estimate versus truth with a one-to-one reference line.
Segments in these plots are comparison residuals, not confidence intervals.
plot_Sigma_comparison( x, truth, level = "unit", part = c("total", "shared", "unique"), measure = c("covariance", "correlation"), entries = c("upper", "unique", "all", "offdiag", "lower", "diag"), link_residual = c("auto", "none"), include_diagonal = FALSE, facet = c("level", "comparison", "none"), sort = c("abs_error", "error", "estimate", "truth", "trait", "level"), style = c("difference", "scatter") )plot_Sigma_comparison( x, truth, level = "unit", part = c("total", "shared", "unique"), measure = c("covariance", "correlation"), entries = c("upper", "unique", "all", "offdiag", "lower", "diag"), link_residual = c("auto", "none"), include_diagonal = FALSE, facet = c("level", "comparison", "none"), sort = c("abs_error", "error", "estimate", "truth", "trait", "level"), style = c("difference", "scatter") )
x |
A fit returned by |
truth |
Square numeric covariance or correlation matrix passed to
|
level, part, measure, entries, link_residual
|
Passed to
|
include_diagonal |
Logical. Include diagonal rows if they are present?
The default is |
facet |
One of |
sort |
Row ordering for |
style |
One of |
Scope: the helper plots compare_Sigma_table() rows or builds
them from a fitted model / Sigma table plus one supplied truth matrix
(covered by the package's comparison-table validation tests, which build on
the underlying Sigma-table extraction tests). It is a visual
comparison helper only: it does not run simulations, compute intervals, or
validate calibration.
Article-specific simulation summaries and richer calibration plots
remain future work.
A ggplot2 plot object with gllvmTMB_meta and gllvmTMB_data
attributes.
compare_Sigma_table(), plot_Sigma_table().
if (requireNamespace("ggplot2", quietly = TRUE)) { rows <- data.frame( level = "unit", trait_i = c("length", "length", "mass"), trait_j = c("mass", "wing", "wing"), estimate = c(0.62, -0.10, 0.28), lower = NA_real_, upper = NA_real_, matrix = "R", component = "total", diagonal = FALSE, triangle = "upper", scale = "correlation" ) truth_R <- matrix( c(1, 0.60, -0.05, 0.60, 1, 0.20, -0.05, 0.20, 1), 3, byrow = TRUE, dimnames = list( c("length", "mass", "wing"), c("length", "mass", "wing") ) ) plot_Sigma_comparison(rows, truth_R, measure = "correlation") }if (requireNamespace("ggplot2", quietly = TRUE)) { rows <- data.frame( level = "unit", trait_i = c("length", "length", "mass"), trait_j = c("mass", "wing", "wing"), estimate = c(0.62, -0.10, 0.28), lower = NA_real_, upper = NA_real_, matrix = "R", component = "total", diagonal = FALSE, triangle = "upper", scale = "correlation" ) truth_R <- matrix( c(1, 0.60, -0.05, 0.60, 1, 0.20, -0.05, 0.20, 1), 3, byrow = TRUE, dimnames = list( c("length", "mass", "wing"), c("length", "mass", "wing") ) ) plot_Sigma_comparison(rows, truth_R, measure = "correlation") }
plot_Sigma_heatmap() turns rows from extract_Sigma_table() into a
matrix-style heatmap. It is designed for articles that need to show the
block structure of a covariance or correlation matrix without manually
extracting Sigma, calling cov2cor(), or rebuilding geom_tile() layers.
Heatmap cells are point estimates only; interval columns, when present, stay
available in the returned plot data but are not displayed.
plot_Sigma_heatmap( x, level = "unit", part = c("total", "shared", "unique"), measure = c("correlation", "covariance"), entries = c("all", "unique", "upper", "lower", "offdiag", "diag"), link_residual = c("auto", "none"), include_diagonal = TRUE, facet = c("level", "none"), label = TRUE, label_digits = 2, title = NULL, subtitle = NULL, caption = NULL )plot_Sigma_heatmap( x, level = "unit", part = c("total", "shared", "unique"), measure = c("correlation", "covariance"), entries = c("all", "unique", "upper", "lower", "offdiag", "diag"), link_residual = c("auto", "none"), include_diagonal = TRUE, facet = c("level", "none"), label = TRUE, label_digits = 2, title = NULL, subtitle = NULL, caption = NULL )
x |
A fit returned by |
level, part, measure, entries, link_residual
|
Passed to
|
include_diagonal |
Logical. Include diagonal rows when present? |
facet |
One of |
label |
Logical. Print numeric estimates inside cells? |
label_digits |
Number of decimal places for cell labels. |
title, subtitle, caption
|
Optional scalar character labels. |
Scope: the helper plots point-estimate heatmaps from
extract_Sigma_table() rows or extracts those rows from a fit returned by
gllvmTMB(), an admitted engine = "julia" bridge fit, or from a
bootstrap_Sigma object (covered by the package's heatmap validation
tests, which build on the underlying extractor-contract and Julia-bridge
fit tests). It does not display interval bounds or compare fitted values to
known truth. Use plot_Sigma_table() for interval forests or confidence
eyes, and plot_Sigma_comparison() for estimate-vs-truth displays.
vdiffr snapshots and richer multi-model layout helpers remain future figure
work.
A ggplot2 plot object with gllvmTMB_meta and gllvmTMB_data
attributes.
extract_Sigma_table(), plot_Sigma_table(),
plot_Sigma_comparison().
if (requireNamespace("ggplot2", quietly = TRUE)) { rows <- data.frame( level = "unit", trait_i = rep(c("length", "mass"), each = 2), trait_j = rep(c("length", "mass"), 2), estimate = c(1, 0.35, 0.35, 1), matrix = "R", component = "total", diagonal = c(TRUE, FALSE, FALSE, TRUE), triangle = c("diagonal", "lower", "upper", "diagonal") ) plot_Sigma_heatmap(rows) }if (requireNamespace("ggplot2", quietly = TRUE)) { rows <- data.frame( level = "unit", trait_i = rep(c("length", "mass"), each = 2), trait_j = rep(c("length", "mass"), 2), estimate = c(1, 0.35, 0.35, 1), matrix = "R", component = "total", diagonal = c(TRUE, FALSE, FALSE, TRUE), triangle = c("diagonal", "lower", "upper", "diagonal") ) plot_Sigma_heatmap(rows) }
plot_Sigma_table() turns rows from extract_Sigma_table() into a
forest-style covariance or correlation plot. It is designed for reports and
articles that need to show selected covariance entries without indexing the
Sigma matrix by hand. Rows without finite interval bounds are drawn as
open points. To display Sigma uncertainty, pass interval-bearing rows, for
example from a bootstrap workflow; extract_Sigma_table() itself currently
leaves interval columns as placeholders.
plot_Sigma_table( x, level = "unit", part = c("total", "shared", "unique"), measure = c("covariance", "correlation"), entries = c("upper", "unique", "all", "offdiag", "lower", "diag"), link_residual = c("auto", "none"), include_diagonal = FALSE, facet = c("level", "none"), sort = c("estimate", "magnitude", "trait", "level"), show_intervals = NULL, style = c("interval", "eye", "raindrop"), eye_level = 0.95, raindrop_level = NULL )plot_Sigma_table( x, level = "unit", part = c("total", "shared", "unique"), measure = c("covariance", "correlation"), entries = c("upper", "unique", "all", "offdiag", "lower", "diag"), link_residual = c("auto", "none"), include_diagonal = FALSE, facet = c("level", "none"), sort = c("estimate", "magnitude", "trait", "level"), show_intervals = NULL, style = c("interval", "eye", "raindrop"), eye_level = 0.95, raindrop_level = NULL )
x |
A fit returned by |
level, part, measure, entries, link_residual
|
Passed to
|
include_diagonal |
Logical. Include diagonal rows if they are present
in |
facet |
One of |
sort |
Row ordering: |
show_intervals |
Logical or |
style |
One of |
eye_level |
Confidence level represented by the supplied interval
bounds when |
raindrop_level |
Compatibility alias for |
Scope: the helper plots point-estimate rows from
extract_Sigma_table() or extracts those rows from a fit returned by
gllvmTMB() or an admitted engine = "julia" bridge fit (covered by the
package's extractor-contract validation tests, including the Julia-bridge
fit path). Interval columns are displayed when present and
finite, but this helper does not compute Sigma intervals.
Uncertainty propagation for arbitrary Sigma entries belongs in
bootstrap/profile infrastructure.
A ggplot2 plot object with gllvmTMB_meta and gllvmTMB_data
attributes.
extract_Sigma_table(), plot_correlations(),
plot.gllvmTMB_multi().
if (requireNamespace("ggplot2", quietly = TRUE)) { sigma_rows <- data.frame( level = "unit", trait_i = c("length", "length", "mass"), trait_j = c("mass", "wing", "wing"), estimate = c(0.22, -0.08, 0.15), lower = NA_real_, upper = NA_real_, matrix = "Sigma", component = "total", diagonal = FALSE, triangle = "upper" ) plot_Sigma_table(sigma_rows) }if (requireNamespace("ggplot2", quietly = TRUE)) { sigma_rows <- data.frame( level = "unit", trait_i = c("length", "length", "mass"), trait_j = c("mass", "wing", "wing"), estimate = c(0.22, -0.08, 0.15), lower = NA_real_, upper = NA_real_, matrix = "Sigma", component = "total", diagonal = FALSE, triangle = "upper" ) plot_Sigma_table(sigma_rows) }
Produces a variety of ggplot2 visualisations for a stacked-trait
multivariate GLLVM. Dispatches on type to one of seven panels:
## S3 method for class 'gllvmTMB_multi' plot( x, type = c("correlation", "correlation_ellipse", "loadings", "integration", "communality", "variance", "ordination"), level = c("unit", "unit_obs"), boot = NULL, axes = c(1L, 2L), rotation = c("varimax", "none", "promax"), order_axes = TRUE, sign_anchor = c("auto", "none"), anchor_traits = NULL, standardize_loadings = FALSE, ... )## S3 method for class 'gllvmTMB_multi' plot( x, type = c("correlation", "correlation_ellipse", "loadings", "integration", "communality", "variance", "ordination"), level = c("unit", "unit_obs"), boot = NULL, axes = c(1L, 2L), rotation = c("varimax", "none", "promax"), order_axes = TRUE, sign_anchor = c("auto", "none"), anchor_traits = NULL, standardize_loadings = FALSE, ... )
x |
A fit returned by |
type |
One of |
level |
Note: the default |
boot |
Optional bootstrap object. This can be either a
|
axes |
Length-2 or length-3 integer vector for |
rotation |
One of |
order_axes |
Logical. For rotated |
sign_anchor |
One of |
anchor_traits |
Optional character vector of trait names used for
sign anchoring in rotated |
standardize_loadings |
Logical. For |
... |
Currently unused. |
"correlation"Combined heatmap of trait correlations.
Upper triangle = between-unit correlations (level = "unit"),
lower triangle = within-unit correlations (level = "unit_obs"),
diagonal = 1. Falls back to whichever level is present if the
other tier is absent. Optional boot intervals are carried in the
plot data when supplied.
"correlation_ellipse"Ellipse matrix of trait correlations.
Ellipse direction and eccentricity encode the sign and strength of
the correlation. This is the Figure-3-style alternative to the tile
heatmap. Optional boot intervals mark correlations whose interval
does not cross zero with black borders and stars. These bootstrap
intervals are recovery-grade, and their coverage properties are not
yet established: read a marked cell as "the interval excludes zero
at the fitted point", not as a calibrated significance test.
"loadings"Tile heatmap of Lambda_B (and Lambda_W if
present), faceted by level. Rows = traits, columns = factors.
Pinned cells (from lambda_constraint) are drawn with a heavy
outline.
"integration"Dot-and-whisker plot of repeatability (ICC),
between-tier communality and within-tier communality per trait,
sorted by repeatability. Optional whiskers from a boot object
(skipped if boot = NULL).
"communality"Figure-3-style stacked bars of per-trait
communality (c^2, shared latent proportion) and uniqueness
(1 - c^2) for the available latent tiers. Optional boot
intervals are drawn on the c^2 boundary when supplied.
"variance"Stacked-bar variance partition per trait, using
extract_proportions(format = "long"). One bar per trait,
stacks summing to 1.
"ordination"Dimension-aware latent-score ordination.
1D fits (d = 1) get a horizontal score strip with trait loading
lollipops; 2D fits get a standard biplot; 3D fits get a static
pair-grid biplot for the three axis pairs. For d > 3, pick two
or three axes via axes.
A ggplot object with a gllvmTMB_meta attribute describing
the plot type, source extractor, covariance level, interval status, and
rotation status. Metadata also carries extractor notes when a plotted
summary has an important caveat. Plots also carry a gllvmTMB_data
attribute with the prepared plotting data; ordination stores separate
scores and loadings tables.
Draw mesh edges and the observed locations. This is the plot method for
meshes created by make_mesh().
## S3 method for class 'gllvmTMBmesh' plot(x, ...) ## S3 method for class 'sdmTMBmesh' plot(x, ...)## S3 method for class 'gllvmTMBmesh' plot(x, ...) ## S3 method for class 'sdmTMBmesh' plot(x, ...)
x |
A |
... |
Passed to |
plot.sdmTMBmesh() is a temporary legacy compatibility method. New
meshes have class gllvmTMBmesh and should be plotted with
plot.gllvmTMBmesh().
Invisibly returns x after drawing mesh edges, observations, and
optional k-means centres.
S3 method dispatched on the profile_derived parent class shared by
the public derived-quantity curve object returned from
profile_phylo_signal(). The diagonal-companion ratio and nonlinear
communality, correlation, and proportion curve prototypes are internal and
withheld from the public release. Mirrors plot.profile_loadings(): dotted
chisq cutoff hline, solid grey vline at the estimate, dashed grey
vlines at the inverted CI bounds (when interval = TRUE), blue
curve with white-filled points, faceted by target with free
x-scales.
## S3 method for class 'profile_derived' plot(x, interval = TRUE, ...)## S3 method for class 'profile_derived' plot(x, interval = TRUE, ...)
x |
A |
interval |
Logical; draw inverted CI bounds (default |
... |
Reserved for future options. |
A ggplot object.
S3 method dispatched from plot(). Draws the delta-deviance curve
per entry, the chisq cutoff as a dotted horizontal line, the MLE
as a grey vertical line, and (optionally) the inverted CI bounds
as dashed verticals.
## S3 method for class 'profile_loadings' plot(x, interval = TRUE, ...)## S3 method for class 'profile_loadings' plot(x, interval = TRUE, ...)
x |
A |
interval |
Logical; draw inverted CI bounds (default |
... |
Reserved for future options. |
Design borrowed from drmTMB's plot.profile.drmTMB().
A ggplot object.
predict_cross_covariance() combines a component-specific
Gamma_shape block from extract_Gamma() with entries of the fitted
dense kernel matrix. For a fixed cross-lineage kernel built with
make_cross_kernel(), the off-diagonal K entries already include the
supplied fixed rho, so the pair-specific covariance is
Gamma_shape * K[row_level, col_level]. This helper therefore uses the
shape-scale Gamma and does not multiply by extract_Gamma(scale = "effect").
Fixed dense kernel_latent() tiers, covered by the package's validation
tests, store their
aligned K matrices on the fit, and this helper returns point estimates for
named species/lineage pairs. It does not estimate rho, produce
intervals, calibrate null thresholds, or combine components into a universal
total Gamma.
predict_cross_covariance( fit, level, row_levels = NULL, col_levels = NULL, row_traits, col_traits )predict_cross_covariance( fit, level, row_levels = NULL, col_levels = NULL, row_traits, col_traits )
fit |
A fitted |
level |
Character scalar naming the fitted |
row_levels, col_levels
|
Character vectors naming row and column levels
in the fitted kernel matrix. If both are omitted for a kernel built by
|
row_traits, col_traits
|
Character vectors of trait names defining the
rows and columns of the component-specific |
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
predict_cross_covariance(
fit,
level = "phy",
row_levels = c("H1", "H2"),
col_levels = c("P1", "P2"),
row_traits = c("host_size", "host_defence"),
col_traits = c("partner_size", "partner_attack")
)
A data frame with one row per level-pair and trait-pair. Columns
include component, row_level, col_level, row_trait, col_trait,
kernel_value, gamma_shape, covariance, rho, and
kernel_includes_rho.
For a model fitted with missing = miss_control(response = "include")
(see miss_control()), gllvmTMB() keeps the rows / cells whose response
was missing, masks them out of the likelihood, and predicts them from the
fitted model. predict_missing() returns those masked response cells with
their model-based predictions and the original-row / cell accounting from
fit$missing_data.
predict_missing( object, type = c("link", "response"), se = FALSE, se_route = c("quad", "joint", "joint_load", "sim", "boot"), n_sim = 2000L, sim_seed = NULL, n_boot = 200L, boot_seed = NULL, boot_dgp = c("ml", "reml"), ... )predict_missing( object, type = c("link", "response"), se = FALSE, se_route = c("quad", "joint", "joint_load", "sim", "boot"), n_sim = 2000L, sim_seed = NULL, n_boot = 200L, boot_seed = NULL, boot_dgp = c("ml", "reml"), ... )
object |
A fit returned by |
type |
One of |
se |
EXPERIMENTAL, internal-only. If |
se_route |
EXPERIMENTAL, internal-only. One of |
n_sim |
EXPERIMENTAL, internal-only. Number of Monte Carlo
replicates drawn per masked cell when |
sim_seed |
EXPERIMENTAL, internal-only. Integer RNG seed for
|
n_boot |
EXPERIMENTAL, internal-only. Number of full-refit bootstrap
replicates when |
boot_seed |
EXPERIMENTAL, internal-only. Integer RNG seed for
|
boot_dgp |
EXPERIMENTAL, internal-only. One of |
... |
Unused. |
Missing responses are predicted / reconstructed as fitted values,
not latent covariates. The separate imputed() extractor returns modelled
missing predictors from supported mi() fits.
The point predictions here are the fitted linear predictor (type = "link")
or its inverse-link response (type = "response"). Reconstruction standard
errors and prediction intervals are not currently returned by default.
se = TRUE is EXPERIMENTAL and INTERNAL-ONLY (Design 119 Slice 1):
register status heuristic_unvalidated – no repeated-sampling coverage
evidence exists for se_confidence or se_prediction, and neither is an
interval claim of any kind. It is currently implemented for gaussian
fits only (other families abort). Five routes exist (se_route):
"quad" (default) omits the b_fix/latent-score cross-covariance and any
diag_B ("unique"/Psi) or within-unit (rr_W) random-effect
contribution (sec.3 R1-quad) and OVER-covers se_confidence;
"joint" computes the exact joint-precision variance for the b_fix and
latent-score blocks (sec.3 R1-joint) but omits the loading-uncertainty
block entirely and UNDER-covers; "joint" and "quad" BRACKET nominal
coverage. "joint_load" (sec.7b, R1-joint+loadings) adds that third
block; it is the best-calibrated delta-method route measured and still
fails the gate by ~1.2 points at 95% (sec.7c). "sim" (sec.3 R2) is a
Monte Carlo route: it draws the same gradient-relevant parameter
subvector "joint_load" uses from its exact joint-precision marginal
normal, forms n_sim EXACT (non-linearised) draws of
eta* = x'b* + lambda_t*'u_i*, and adds one gaussian family draw per
replicate for y*. Unlike the delta-method routes it makes NO normality
assumption about the predictive distribution – se_confidence /
se_prediction are the empirical sd of eta* / y*, and it ALSO
returns empirical-quantile columns (q_lo_conf/q_hi_conf,
q_lo_pred/q_hi_pred at nominal 95%, and their *90 companions at
nominal 90%) that a normal-quantile route cannot produce. It still holds
every parameter at its estimate (a plug-in simulation), so it does not
address hyperparameter uncertainty.
"boot" (sec.3 R3, sec.7d) is a parametric bootstrap: it simulates a
complete dataset at the fitted parameters (fresh latent scores AND a
fresh family draw for every cell), masks the same cells, and REFITS the
model n_boot times. Because every replicate is a full refit, it is the
only route that propagates parameter (and dispersion) uncertainty rather
than holding it fixed at the plug-in estimate; it is also by far the
most expensive route (n_boot model fits per call) and, in this slice,
only supports gaussian fits with a single ordinary loadings-only
latent() term (see Details in the source for the derivation and the
scope guard). A refit that errors or fails to converge is dropped from
the pivot sample and counted in n_boot_ok; if fewer than half the
refits survive, se_confidence/se_prediction/the quantile columns are
NA for that fit (with a warning) rather than reporting an interval
built from a thin sample.
boot_dgp (sec.7e, following the wave-3 coverage diagnosis that "boot"
with boot_dgp = "ml" under-covers at the same level as "joint_load")
controls what parameters generate the n_boot complete-data worlds.
"ml" (default, byte-identical to the pre-sec.7e route) simulates from
the SAME ML fit being intervalised – so at small n its known downward
variance/loading bias is "re-imported" into the bootstrap world, making
the pivots systematically narrow. "reml" fits ONE auxiliary REML model
on the same (masked) data/formula first and simulates from ITS
parameters instead – REML's own known bias direction (larger, less
biased at small n, converging to ML as n grows) targets exactly that
gap. The quantity being pivoted (the point estimate the interval is
centred on, and every inner refit) stays ML either way; only the
generative world changes. Ignored unless se_route = "boot".
Do not surface se_confidence / se_prediction / the quantile columns
as calibrated uncertainty in any user-facing output until the Design 119
sec.4 coverage campaign clears a route and family for export.
For ordinal_probit() traits, type = "response" is the expected
category , computed from the fitted cutpoints (Hadfield 2015 convention:
fixed, estimated; see
extract_cutpoints()) – not a probability. It is not an elementwise
pnorm(eta), which is not a category quantity once .
type = "link" is unchanged: the probit-scale linear predictor.
A data frame with one row per masked response cell, with columns:
original_row (the supplied long-data row, the supplied wide-data row
before traits() stacking, or – for a multinomial() fit – the
pre-expansion row of the user's data that the K-1 category-contrast
pseudo-row belongs to),
model_row (the row index into the fitted long-format data / response),
the unit / cluster / trait identifier columns, est (the prediction
on the requested scale), and, when se = TRUE, se_confidence and
se_prediction (see Details – EXPERIMENTAL, not a calibrated
interval). When se = TRUE and se_route is "sim" or "boot",
also q_lo_conf/q_hi_conf, q_lo_pred/q_hi_pred (empirical
2.5%/97.5% quantiles / bootstrap interval endpoints) and
q_lo_conf90/q_hi_conf90, q_lo_pred90/q_hi_pred90 (the nominal
90% companions). When se_route = "boot", also n_boot_ok (the
number of the n_boot refits that converged and entered the pivot
sample). A complete-data fit (no masked cells) returns a zero-row data
frame with the same columns.
gllvmTMB(), miss_control(), predict.gllvmTMB_multi().
Returns the linear predictor or inverse-link response at each observation
in the training data, or at user-supplied newdata. For mixed-family fits,
type = "response" uses the row's own trait/family inverse link rather
than the first trait's link.
## S3 method for class 'gllvmTMB_multi' predict( object, newdata = NULL, type = c("link", "response"), re_form = ~., se.fit = FALSE, ... )## S3 method for class 'gllvmTMB_multi' predict( object, newdata = NULL, type = c("link", "response"), re_form = ~., se.fit = FALSE, ... )
object |
A fit returned by |
newdata |
Optional new data frame. If |
type |
One of
On a mixed-family fit the returned |
re_form |
Random-effect formula controlling which random
effects are included in the predicted linear predictor. The
default On |
se.fit |
Logical, default |
... |
Unused. |
A data frame with the original row identifiers plus an est
column on the requested link or response scale, and (if
se.fit = TRUE) an se.fit column carrying the conditional
delta-method standard error described above.
gllvmTMB fitpredictive_check() compares the observed stacked-trait response to
draws from the fitted model. The frequentist semantics are explicit:
these are fitted-model predictive checks, not Bayesian posterior
predictive checks.
predictive_check( object, type = c("rq_qq", "rootogram", "stat_grouped", "dens_overlay"), nsim = NULL, ndraws = NULL, seed = NULL, trait = NULL, group = NULL, stat = c("mean", "median", "zero_fraction"), residual_type = c("randomized_quantile", "simulation_rank"), condition_on_RE = TRUE, max_count = NULL )predictive_check( object, type = c("rq_qq", "rootogram", "stat_grouped", "dens_overlay"), nsim = NULL, ndraws = NULL, seed = NULL, trait = NULL, group = NULL, stat = c("mean", "median", "zero_fraction"), residual_type = c("randomized_quantile", "simulation_rank"), condition_on_RE = TRUE, max_count = NULL )
object |
A |
type |
Diagnostic plot type. |
nsim, ndraws
|
Number of fitted-model draws. |
seed |
Optional RNG seed. |
trait |
Optional character vector of trait names to keep. |
group |
Row-metadata column used by |
stat |
Grouped statistic for |
residual_type |
Residual type used by |
condition_on_RE |
Logical. Passed to |
max_count |
Optional upper count shown separately in |
The returned object is a ggplot. Its plotted data, fit-health table
from check_gllvmTMB(), and fit$fit_health snapshot are also stored
in attr(plot, "gllvmTMB_diagnostic") so the figure can be audited
without reverse-engineering ggplot layers.
Scope: fitted-model predictive plots and residual Q-Q/rootogram helpers
for gllvmTMB_multi fits, with exact randomized-quantile residuals for
Gaussian, binomial, Poisson, lognormal, Gamma, NB2, Beta, betabinomial,
Student-t, zero-truncated Poisson, zero-truncated NB2, NB1, and
ordinal-probit rows, plus a simulation-rank fallback. These are
diagnostic displays, not formal uniformity, dispersion,
interval-calibration, or Bayesian posterior-predictive tests. Exact
residual support for tweedie, the delta/hurdle families, and multinomial
is not implemented (see residuals.gllvmTMB_multi() for why); the
simulation-rank fallback's own family coverage is separately limited and
is not audited here.
A ggplot object with diagnostic metadata attached in
attr(plot, "gllvmTMB_diagnostic").
set.seed(1) n <- 24 df <- data.frame( unit = factor(rep(seq_len(n), each = 2)), trait = factor(rep(c("a", "b"), n)), value = rpois(2 * n, lambda = 2) ) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | unit, d = 1), data = df, trait = "trait", unit = "unit", family = poisson() ) predictive_check(fit, type = "rq_qq", seed = 1) predictive_check(fit, type = "rootogram", ndraws = 20, seed = 1)set.seed(1) n <- 24 df <- data.frame( unit = factor(rep(seq_len(n), each = 2)), trait = factor(rep(c("a", "b"), n)), value = rpois(2 * n, lambda = 2) ) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | unit, d = 1), data = df, trait = "trait", unit = "unit", family = poisson() ) predictive_check(fit, type = "rq_qq", seed = 1) predictive_check(fit, type = "rootogram", ndraws = 20, seed = 1)
Shared by the augmented phylo_latent (level = "phy_slope") and
augmented spatial_latent (level = "spde_slope") per-LHS-column
cross-trait Sigma extractions; the object's header field selects the
printed keyword.
## S3 method for class 'gllvmTMB_Sigma_phy_slope' print(x, digits = 3, ...)## S3 method for class 'gllvmTMB_Sigma_phy_slope' print(x, digits = 3, ...)
x |
A |
digits |
Number of significant digits. |
... |
Ignored. |
x, invisibly.
Genuine chi-square_1 profile intervals (via fix-and-refit on log V_t) for
the per-trait total variance V_t = (Lambda Lambda')[t,t] + psi[t], the
diagonal of the requested covariance tier. This is the same functional
extract_Sigma() returns on its diagonal, and the same one
bootstrap_Sigma() resamples.
profile_ci_total_variance( fit, tier = c("unit", "unit_obs", "phy", "B", "W"), trait_idx = NULL, level = 0.95 )profile_ci_total_variance( fit, tier = c("unit", "unit_obs", "phy", "B", "W"), trait_idx = NULL, level = 0.95 )
fit |
A fit returned by |
tier |
Covariance tier: |
trait_idx |
Integer indices of traits, or |
level |
Confidence level. Default 0.95. Only 0.95 carries coverage evidence. |
A data frame with one row per trait and columns:
trait, tier
Trait name and the canonical tier name.
estimatePoint estimate of V_t.
lower, upper
Two-sided profile bounds, NA when the profile
could not be computed.
methodAlways "profile".
interval_statusClaim-boundary marker:
"certified-0.94" when the row falls inside the certified regime above,
"route-only" for a computed but uncertified interval, and "none"
for a point-only row with no interval. "certified-0.94" marks regime
membership; it does not certify the individual interval.
One regime of this function has measured frequentist coverage. Under
simulation from a known Gaussian data-generating process, using an
unpenalised native-Laplace fit with n_units >= 150 and d <= 2, the
two-sided intervals met a pre-registered >= 0.94 gate
(0.9467 in both cells, 20,000 replicates each, computed among converged fits
only). That gate is 0.94, not nominal 95% – both cells sit roughly 3.3
cluster standard errors below 0.95, so do not describe these as 95%
intervals. Coverage is a marginal average over the simulated V_t
distribution and does not hold in the smallest-V_t ventile (0.926 at
d = 1). The interval is not equal-tailed, so one-sided use is invalid.
The evidence is one simulated DGP, not a claim about any real dataset.
Everything else the arguments admit is an uncertified computed route: every
AGHQ fit, any loading-ridge fit, every non-Gaussian family, every tier other
than "unit", d > 2, d = 0, n_units < 150, any level other than
0.95, and the off-diagonal and psi targets (the psi target was measured
on the same run and failed).
Rather than refuse those calls – they are legitimately useful for
exploration – the returned interval_status column marks each row.
extract_Sigma() for the point estimate, bootstrap_Sigma() for
the resampling route to the same estimand.
profile_cross_rho() formalises a fixed-kernel sensitivity workflow for
cross-lineage kernels. It rebuilds K_star with
make_cross_kernel() over a user-supplied rho grid, calls a caller-supplied
refit(K, rho, ...) function for each grid value, and returns a tidy
likelihood table.
This is a fixed-kernel profile workflow for
comparing defended rho values. Note its limits: rho is not a TMB
parameter, this helper does not estimate rho, and it does not produce
confidence intervals or null calibration.
profile_cross_rho( A_H, A_P, W, rho, refit, metrics = NULL, eps = 1e-08, keep_fits = FALSE, ... )profile_cross_rho( A_H, A_P, W, rho, refit, metrics = NULL, eps = 1e-08, keep_fits = FALSE, ... )
A_H, A_P, W
|
Inputs passed to |
rho |
Numeric vector of fixed |
refit |
Function called as |
metrics |
Optional function called as
|
eps |
Positive numerical floor passed to |
keep_fits |
Logical; if |
... |
Additional arguments passed to |
A data frame of class gllvmTMB_cross_rho_profile with columns
rho, logLik, relative_logLik, delta_deviance, is_best,
convergence, pd_hessian, status, and error, plus any metric
columns returned by metrics.
A_H <- diag(2) A_P <- diag(2) rownames(A_H) <- colnames(A_H) <- c("H1", "H2") rownames(A_P) <- colnames(A_P) <- c("P1", "P2") W <- matrix(c(1, 0.2, 0.2, 1), 2, 2) dimnames(W) <- list(rownames(A_H), rownames(A_P)) dat <- data.frame(y = c(1, 2, 3, 4), x = c(0, 1, 0, 1)) profile_cross_rho( A_H, A_P, W, rho = c(0, 0.25), refit = function(K, rho) stats::lm(y ~ x, data = dat) )A_H <- diag(2) A_P <- diag(2) rownames(A_H) <- colnames(A_H) <- c("H1", "H2") rownames(A_P) <- colnames(A_P) <- c("P1", "P2") W <- matrix(c(1, 0.2, 0.2, 1), 2, 2) dimnames(W) <- list(rownames(A_H), rownames(A_P)) dat <- data.frame(y = c(1, 2, 3, 4), x = c(0, 1, 0, 1)) profile_cross_rho( A_H, A_P, W, rho = c(0, 0.25), refit = function(K, rho) stats::lm(y ~ x, data = dat) )
rho
Turns a profile_cross_rho() grid into a profile-likelihood sensitivity
interval for the fixed cross-lineage correlation rho. The interval is the
set of rho values whose deviance excess 2 * (logLik_max - logLik) stays
below qchisq(level, df = 1); the bounds are located by linear interpolation
of the profiled delta_deviance curve between the two grid points that
bracket each crossing. Use a rho grid dense enough to bracket the crossings
(a coarse 3-4 point sensitivity grid is usually too sparse for a calibrated
interval). When the profiled curve does not rise above the threshold on one
side within the supplied grid, that bound is reported as the grid edge and
flagged unbounded (lower_bounded / upper_bounded FALSE) – widen the grid.
This is screening-grade profile/sensitivity interval evidence on a
fixed-rho refit grid, not in-engine rho estimation. Direct validation
of this route remains partial, and its coverage has
not been calibrated, so do not report it as a validated confidence
interval.
profile_cross_rho_ci(profile, level = 0.95)profile_cross_rho_ci(profile, level = 0.95)
profile |
A |
level |
Confidence level in |
A list with estimate (the grid rho with the highest logLik),
lower, upper, level, lower_bounded, upper_bounded, and the
deviance threshold. Bounds are clamped to the rho domain [-1, 1].
profile_cross_rho(), make_cross_kernel()
For each requested trait, sweep over a grid of candidate values of
the 2-component phylogenetic signal
,
refit the model under each constraint, and return the LR curve.
Currently supports only the simple 2-component case (phylo_diag +
species-level diag); 3-component decompositions error with a note.
profile_phylo_signal( fit, trait_idx = NULL, n_grid = 21L, grid_extent = 4, conf_level = 0.95 )profile_phylo_signal( fit, trait_idx = NULL, n_grid = 21L, grid_extent = 4, conf_level = 0.95 )
fit |
A fit returned by |
trait_idx |
Integer vector of trait indices, or |
n_grid |
Integer; number of grid points per trait. Default 21. |
grid_extent |
Numeric; total grid width as a multiple of a robust scale (default 4). |
conf_level |
Confidence level. Default 0.95. |
Parallel CURVE endpoint to profile_ci_phylo_signal().
A data.frame of class profile_phylo_signal,
profile_derived, data.frame.
profile_ci_phylo_signal(), plot.profile_derived().
profile_targets() is an advanced inventory for users who need to
diagnose or script profile confidence intervals. Most users should
start with confint.gllvmTMB_multi() or a report-ready extractor
such as extract_correlations(). This helper tells you which
parm labels can be profiled directly.
profile_targets(object, ready_only = FALSE)profile_targets(object, ready_only = FALSE)
object |
A fit returned by |
ready_only |
If |
The returned tidy data frame has one row per profile target and
records whether the target is profile-ready and, if not, why.
Direct targets correspond to a single TMB parameter element (for
example b_fix[1], sigma_eps, or sd_B[2]) and can be passed to
confint(fit, parm = ..., method = "profile"). Nonlinear derived targets
are excluded because their former penalty-profile routes are withheld.
Scope: the controlled vocabulary and direct-profile routing is covered for fitted models with retained TMB objects. Broader target-explicit profile calibration remains future work.
The output uses a controlled vocabulary for direct target classes, transformations, and readiness notes.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
fit <- gllvmTMB(value ~ 0 + trait + latent(0 + trait | site, d = 1),
data = sim$data,
trait = "trait",
unit = "site")
targets <- profile_targets(fit)
head(targets)
profile_targets(fit, ready_only = TRUE)
A data frame with one row per target and the following columns:
parmUser-facing target label, e.g. b_fix[1],
sigma_eps, or sd_B[2].
target_classOne of fixed_effect, variance,
dispersion, loading_packed, threshold, or scaling.
tmb_parameterTMB-side parameter name (matches
names(fit$opt$par)).
index1-based index within the TMB parameter vector.
NA for scalars.
estimatePoint estimate on the natural scale (e.g.
sigma_eps = exp(log_sigma_eps)).
link_estimatePoint estimate on the optimisation scale (the TMB internal scale).
scaleEither "natural" (after applying the
transformation) or "link" (for raw packed entries).
transformationOne of linear_predictor, exp,
logit, logit_p_tweedie, lambda_packed,
or ordinal_threshold.
target_typeAlways "direct" for currently returned rows.
profile_readyTRUE iff the target can be passed
directly to confint(fit, parm = ..., method = "profile").
profile_noteOne of ready, tmb_object_required,
missing_tmb_parameter, or latent_rotation_ambiguous.
confint.gllvmTMB_multi() for the routing of
method = c("wald", "profile"),
tmbprofile_wrapper() for direct-parameter profiling. For nonlinear
summaries, consult the relevant extractor; a method token does not imply
that a profile interval is currently available.
gllvmTMB fitReturns row-wise residual diagnostics for a fitted gllvmTMB_multi
model. type = "randomized_quantile" uses exact family CDFs for
Gaussian, binomial, Poisson, lognormal, Gamma, NB2, Beta, betabinomial,
Student-t, zero-truncated Poisson, zero-truncated NB2, NB1, and
ordinal-probit rows. type = "simulation_rank" uses fitted-model
simulations and is available as a fallback for checking the same row
contract when exact family-CDF plumbing is not implemented for a family;
its own family coverage (what simulate.gllvmTMB_multi() can draw from)
is tracked separately and is not extended here.
## S3 method for class 'gllvmTMB_multi' residuals( object, type = c("randomized_quantile", "simulation_rank"), scale = c("normal", "uniform"), nsim = NULL, ndraws = NULL, seed = NULL, trait = NULL, condition_on_RE = TRUE, ... )## S3 method for class 'gllvmTMB_multi' residuals( object, type = c("randomized_quantile", "simulation_rank"), scale = c("normal", "uniform"), nsim = NULL, ndraws = NULL, seed = NULL, trait = NULL, condition_on_RE = TRUE, ... )
object |
A |
type |
|
scale |
|
nsim, ndraws
|
Number of fitted-model draws for
|
seed |
Optional RNG seed. |
trait |
Optional character vector of trait names to keep. |
condition_on_RE |
Logical. Passed to |
... |
Currently unused. |
Rows are retained even when a residual cannot be computed. Inspect the
status column before treating residuals as complete.
Scope: exact family-CDF randomized-quantile residuals for Gaussian,
binomial, Poisson, lognormal, Gamma, NB2, Beta, betabinomial, Student-t,
zero-truncated Poisson, zero-truncated NB2, NB1, and ordinal-probit rows,
plus simulation-rank residuals from fitted-model draws. Tweedie, the
delta/hurdle families (delta_lognormal, delta_gamma), and
multinomial are deliberately not implemented: tweedie has no closed-form
CDF without a new dependency, the delta/hurdle families mix a point mass
at zero with a continuous part and need an explicit design decision for
splitting the point mass, and multinomial's categories are unordered so a
randomized-quantile residual is undefined without inventing an ordering.
Unsupported families are retained with row status rather than promoted
to exact residual claims. Formal residual tests (beyond the recovery
checks in tests/testthat/test-exact-rq-residuals-families.R) remain
later validation work.
The returned data frame also carries attr(x, "gllvmTMB_diagnostic")
with check_gllvmTMB() output and the fitted object's fit_health
snapshot.
A data frame with row metadata (.row, trait, family_id,
family, link_id), observed, randomized PIT value u,
residual, status, scale, and method metadata. The attribute
method records the residual engine.
set.seed(2) n <- 24 df <- data.frame( unit = factor(rep(seq_len(n), each = 2)), trait = factor(rep(c("a", "b"), n)), value = rpois(2 * n, lambda = 2) ) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | unit, d = 1), data = df, trait = "trait", unit = "unit", family = poisson() ) residuals(fit, type = "randomized_quantile", seed = 1)set.seed(2) n <- 24 df <- data.frame( unit = factor(rep(seq_len(n), each = 2)), trait = factor(rep(c("a", "b"), n)), value = rpois(2 * n, lambda = 2) ) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | unit, d = 1), data = df, trait = "trait", unit = "unit", family = poisson() ) residuals(fit, type = "randomized_quantile", seed = 1)
ridge_path() refits the same model across a grid of loading-ridge
penalty scales (tau, i.e. gllvmTMBcontrol(loading_ridge = tau)) and
reports, for every trait, how the largest loading and its communality
move as the penalty weakens (tau growing toward Inf, i.e. plain
maximum likelihood with no ridge).
ridge_path( formula, data, family, tau = c(0.5, 1, 2, 4, 8, Inf), unit = "site", trait = "trait", weights = NULL, missing = miss_control(), control = gllvmTMBcontrol(), ... )ridge_path( formula, data, family, tau = c(0.5, 1, 2, 4, 8, Inf), unit = "site", trait = "trait", weights = NULL, missing = miss_control(), control = gllvmTMBcontrol(), ... )
formula, data, family, unit, trait, weights, missing
|
Passed to
|
tau |
Numeric vector of ridge scales to sweep. Order does not
matter: the function sorts internally, both for refitting and for the
printed classification. Every entry must be a positive number; |
control |
A |
... |
Passed to |
This is a SENSITIVITY diagnostic, never an identification certificate.
A ridge that stabilises a fit does not prove the underlying model is
identified; it only prevents the optimiser from drifting to an unbounded
point. The path lets you tell two situations apart that a single fit
cannot: a trait whose loading and communality settle down as the penalty
weakens is behaving like an ordinary, data-determined estimate; a trait
that keeps moving toward the boundary as the penalty weakens is one
whose finite estimate under any single tau is being determined by the
penalty, not by the data.
Every grid point is a COLD refit: each tau starts from the model's own
default initial values, not from the previous grid point's solution.
This trades some speed for comparability across tau – a warm-started
path could stay near one local optimum across the whole grid and never
reveal that a later tau has a different, better optimum, which would
understate exactly the instability this function exists to detect.
print() classifies each trait using a SIGNED statistic – a shrinking
or flat loading is always "interior", regardless of magnitude; only a
substantial POSITIVE move counts as "penalty-determined". Two
comparisons are possible, chosen automatically per trait:
tau = Inf point is availableThe largest
finite-tau loading is compared directly to the Inf loading
(the plain signed relative change). A blow-up there – exactly what
the paragraph above calls "moving toward boundary" – is compared
against boundary_rel_thresh (default 0.10, i.e. a >10% increase
from the last finite tau to Inf). This comparison is never
skipped when a converged Inf point exists: it is the diagnostic
the previous paragraph promises.
tau = Inf point is availableThe last two
finite-tau grid points are compared using the log-log slope
(elasticity) d(log loading) / d(log tau), not the raw relative
change. The raw relative change decays like 1/tau for a fixed
absolute grid spacing even when a trait is genuinely still
moving – a tau = c(2000, 2100) pair (a 1.05x ratio) gives a
still-growing trait only a 5% raw change, an artifact of the grid
geometry rather than the trait's behaviour. The elasticity is
invariant to that spacing (for loading ~ tau^k it recovers k
exactly at any two grid points) and is compared against
elasticity_thresh (default 0.10).
Both are heuristics, not formal tests; see print.gllvmTMB_ridge_path's
arguments to adjust either threshold.
tau = Inf requests a plain maximum-likelihood refit with no ridge at
all. That refit can legitimately fail to converge, or converge to an
extreme loading, for exactly the traits this function exists to flag –
that failure or extremity is itself part of the diagnostic evidence, not
a bug to work around. fit_error records the message when a grid point's
refit errors outright, and the row's other columns are NA for that
point; the classifier then falls back to the finite-tau elasticity
comparison, since a failed Inf refit contributes no loading to compare.
A data frame of class c("gllvmTMB_ridge_path", "data.frame")
with one row per tau x trait combination:
tauThe ridge scale for this row's refit.
traitTrait name.
max_loadingThe largest |loading| for this trait across
the model's reduced-rank blocks at this tau.
communalityThis trait's between-unit communality at this
tau (from extract_communality(), level = "unit"); NA if
unavailable.
logLik_at_mapThe UNPENALISED log-likelihood evaluated at
the penalised (MAP) point – not a maximum for any finite tau, so
it is not comparable across tau as a model-selection criterion.
At tau = Inf this is the ordinary maximum log-likelihood.
convergenceThe optimiser's convergence code (0 =
converged); NA if the refit errored.
fit_errorThe error message if this grid point's refit
failed outright; NA otherwise.
screen_gllvmTMB() for the pre-fit response screen this
complements; the loading ridge itself is documented at
gllvmTMBcontrol()'s loading_ridge argument.
n <- 40 df <- data.frame( unit = factor(seq_len(n)), a = rbinom(n, 1, 0.5), b = rbinom(n, 1, 0.5) ) path <- ridge_path( traits(a, b) ~ 1 + latent(1 | unit, d = 1), data = df, family = binomial(), unit = "unit", tau = c(1, 2, 4, Inf) ) pathn <- 40 df <- data.frame( unit = factor(seq_len(n)), a = rbinom(n, 1, 0.5), b = rbinom(n, 1, 0.5) ) path <- ridge_path( traits(a, b) ~ 1 + latent(1 | unit, d = 1), data = df, family = binomial(), unit = "unit", tau = c(1, 2, 4, Inf) ) path
Applies a varimax or promax rotation to the loading matrix
from a fit returned by gllvmTMB(). Use level = "unit" for the
between-unit reduced-rank component and level = "unit_obs" for the
within-unit component. The latent scores are rotated by the complementary
transform so the linear predictor, fitted log-likelihood, and implied
covariance are unchanged.
rotate_loadings( fit, level = "unit", method = c("varimax", "promax", "none"), order_axes = TRUE, sign_anchor = c("auto", "none"), anchor_traits = NULL )rotate_loadings( fit, level = "unit", method = c("varimax", "promax", "none"), order_axes = TRUE, sign_anchor = c("auto", "none"), anchor_traits = NULL )
fit |
A fitted multivariate model returned by |
level |
|
method |
One of |
order_axes |
Logical. When |
sign_anchor |
One of |
anchor_traits |
Optional character vector of trait names used for sign anchoring. Supply one trait per axis after ordering. Axes without a supplied anchor use the trait with the largest absolute loading. |
Rotation is for interpretation of the loading columns, especially figures.
It does not make the axes uniquely "right"; it chooses a readable
orientation among mathematically equivalent orientations. For quantitative
interpretation, start from the model-implied Sigma, correlations,
communality, and uniqueness, then use rotated axes for labels and plots.
A defensible plotting workflow is: fit the model, check convergence,
reconstruct covariance summaries, then rotate loadings for visual
interpretation. Rotate level = "unit" and level = "unit_obs" separately
because they represent different covariance structures. method = "varimax" is the usual first choice for a simple loading pattern; use
method = "promax" only when correlated latent axes are intended.
The rotation is applied to on the left and to the
latent scores on the right using the inverse transform, so
is unchanged. For varimax, T is orthogonal so the rotation
preserves the implied covariance ; for
promax, T is oblique and the columns of
are no longer orthogonal.
A list with rotated Lambda (n_traits × d), rotated
scores (with rows = units or within-unit observations, columns = factors),
and the rotation matrix T such that
. The list also includes
axis_variance, axis_order, axis_sign, and anchor_traits
metadata after any ordering and sign anchoring.
set.seed(1) sim <- simulate_site_trait( n_sites = 60, n_species = 12, n_traits = 4, Lambda_B = matrix(c(1.0, 0.7, -0.3, 0.5, 0.3, -0.5, 0.8, 0.2), nrow = 4, ncol = 2), psi_B = c(0.3, 0.3, 0.3, 0.3), seed = 1 ) fit <- gllvmTMB(value ~ 0 + trait + latent(0 + trait | site, d = 2), data = sim$data, trait = "trait", unit = "site") raw <- extract_ordination(fit, "unit") rot <- rotate_loadings(fit, level = "unit", method = "varimax") # raw$loadings - lower-triangular (hard to read) # rot$Lambda - varimax-rotated (typically simpler structure)set.seed(1) sim <- simulate_site_trait( n_sites = 60, n_species = 12, n_traits = 4, Lambda_B = matrix(c(1.0, 0.7, -0.3, 0.5, 0.3, -0.5, 0.8, 0.2), nrow = 4, ncol = 2), psi_B = c(0.3, 0.3, 0.3, 0.3), seed = 1 ) fit <- gllvmTMB(value ~ 0 + trait + latent(0 + trait | site, d = 2), data = sim$data, trait = "trait", unit = "site") raw <- extract_ordination(fit, "unit") rot <- rotate_loadings(fit, level = "unit", method = "varimax") # raw$loadings - lower-triangular (hard to read) # rot$Lambda - varimax-rotated (typically simpler structure)
Use sanity_multi() as the fast first screen after fitting. It
prints, and returns invisibly, pass / warn status flags covering
optimiser convergence, max gradient component, Hessian definiteness,
parameter standard errors, and identifiability of the latent()
loadings. For report tables use check_gllvmTMB(); for a broader
human-readable summary use gllvmTMB_diagnose().
sanity_multi(object, gradient_thresh = 0.01, se_thresh = 100)sanity_multi(object, gradient_thresh = 0.01, se_thresh = 100)
object |
A fit returned by |
gradient_thresh |
Maximum allowed absolute gradient component. Default 0.01. |
se_thresh |
Threshold above which a parameter SE is flagged as suspiciously large. Default 100. |
Scope: fast numerical and loading-shape checks for fitted models. A PASS here does not prove interval calibration or latent-rank identifiability; use target-explicit known-DGP simulation studies for those heavier questions.
Invisibly a list of diagnostic results.
scalar(0 + trait | g)
Canonical no-prefix name for the one-shared-variance trait covariance:
a single variance shared by every trait with zero cross-trait
covariance, giving . It is the
most parsimonious mode in the covariance grid.
scalar(formula)scalar(formula)
formula |
|
scalar() desugars byte-identically to
indep(0 + trait | g, common = TRUE) – the two spellings fit the same
model, tying all trait variances to one shared parameter. Use scalar()
when you want to commit explicitly to one shared trait-scale variance; use
indep() for a separate variance per trait, or dep() / latent() for
cross-trait covariance.
The shared variance still couples grouping levels through the source
(identity for the no-prefix row). The source-specific phylo_scalar(),
animal_scalar(), and spatial_scalar() carry the same one-shared-variance
meaning on their respective relationship operators.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
# One shared trait variance across all traits (identity cross-trait):
fit <- gllvmTMB(value ~ 0 + trait + scalar(0 + trait | site),
data = df, trait = "trait", unit = "site")
# Equivalent longhand:
fit <- gllvmTMB(value ~ 0 + trait + indep(0 + trait | site, common = TRUE),
data = df, trait = "trait", unit = "site")
A formula marker; never evaluated.
indep(), dep(), latent(), phylo_scalar(), extract_Sigma().
screen_control() stores threshold settings used by
screen_gllvmTMB(). The defaults are deliberately conservative and
advisory: they flag data conditions to inspect before fitting, but they
do not remove traits, choose a latent rank, or validate a model.
screen_control( module = "binomial", rare_warn_n = 10, rare_strong_n = 5, prevalence_warn = c(0.05, 0.95), prevalence_strong = c(0.02, 0.98), phi_warn = 0.9, phi_strong = 0.95, discordant_warn_n = 10, discordant_strong_n = 5, hamming_rate_warn = 0.01, separation = c("none", "fixed"), separation_tolerance = 1e-04 )screen_control( module = "binomial", rare_warn_n = 10, rare_strong_n = 5, prevalence_warn = c(0.05, 0.95), prevalence_strong = c(0.02, 0.98), phi_warn = 0.9, phi_strong = 0.95, discordant_warn_n = 10, discordant_strong_n = 5, hamming_rate_warn = 0.01, separation = c("none", "fixed"), separation_tolerance = 1e-04 )
module |
Screening module. The first implemented module is
|
rare_warn_n, rare_strong_n
|
Minority success/failure counts below these values trigger increasingly strong warnings. |
prevalence_warn, prevalence_strong
|
Length-two numeric vectors giving lower and upper prevalence values for advisory imbalance notes. |
phi_warn, phi_strong
|
Pairwise absolute-phi thresholds. |
discordant_warn_n, discordant_strong_n
|
Pairwise discordant-count thresholds for near-duplicate binary traits. |
hamming_rate_warn |
Pairwise normalized Hamming-distance threshold. |
separation |
Optional fixed-design separation screen. The default
|
separation_tolerance |
Solver tolerance passed to |
A gllvmTMB_screen_control object.
screen_gllvmTMB() is a formula-aware pre-fit screen. It summarises
response and formula conditions before fitting a stacked-trait GLLVM.
The screen is advisory: it does not fit the model, remove traits, choose
a latent rank, prove identifiability, or guarantee convergence.
Binary/binomial screening is implemented. Non-binary modules, optional
comparator checks, and high-dimensional benchmarks are not currently part
of this function.
screen_gllvmTMB( formula, data, family, unit = NULL, trait = "trait", weights = NULL, missing = miss_control(), control = screen_control(), Xcoef_fixed = NULL, known_groups = NULL )screen_gllvmTMB( formula, data, family, unit = NULL, trait = "trait", weights = NULL, missing = miss_control(), control = screen_control(), Xcoef_fixed = NULL, known_groups = NULL )
formula |
A |
data |
A data frame. |
family |
A response family. Version 1 screens single-family
|
unit |
Optional unit/grouping column. If omitted, the first covariance-structure grouping column is used when available. |
trait |
Trait column for long data. Wide |
weights |
Optional weights vector, with the same binomial trial-count
semantics as |
missing |
Missing-data control; defaults to |
control |
A |
Xcoef_fixed |
Optional named structural-zero fixed-effect constraints,
normalized against the expanded fixed-effect columns as in |
known_groups |
Optional named list of character vectors of trait
names, each with at least two distinct names (duplicated names within
one group are an error). Each element declares a set of traits the
user believes forms an exact one-hot (simplex) block or one or more
exact pairwise containment (nesting) relations – for example the
dummy columns of one categorical review-type or geographic-scope
variable, or a broad-realm indicator together with one or more
narrower nested realms. Each declared group is checked against the
observed complete rows with an exact deterministic test: row sums for
a one-hot block, or, for nesting, EVERY pairwise containment among the
declared members (so it does not matter which order – or how many
mutually incomparable narrower members – the group happens to be
written in). When the relations found form a single total chain, the
certificate reports that chain; when they form a partial order instead
(for example one broad member containing two mutually incomparable
narrower ones), every relation found is named and the group still
fails as nesting. A group whose members are all constant on the
complete rows is reported separately (not as nesting: a containment
test is vacuously true against a constant and would not be genuine
evidence); a member that is constant while other members of the same
group are not is excluded from every pairwise comparison for the same
reason. The one-hot test is not whole-group only: when the WHOLE
declared group does not sum to 1 (for example a genuine one-hot block
declared together with one unrelated trait), a bounded exhaustive
search checks every subset of size 2 or more, smallest first, and
reports each MINIMAL one-hot subset found as its own certificate –
|
The first implemented module covers binomial traits. It distinguishes
Bernoulli responses from multi-trial binomial responses, reports the
relevant denominators, and flags constants, sparse minority outcomes,
duplicate or near-duplicate binary traits, rank-deficient fixed-effect
designs, and grouping/rank conditions that should be inspected before
interpretation. In systematic maps, a trait may be a content item or
indicator. In a binary JSDM, a trait may be a species presence-absence
response. Rare or constant species are flagged for inspection and possible
sensitivity analysis, not automatically removed.
The screen also checks the response side for exact higher-order
dependencies that the pairwise duplicate/complement check cannot see –
most commonly a one-hot/simplex block, where several trait columns are
really the dummy coding of one categorical variable and sum to exactly 1
on every complete row (a review-type, geographic-scope, or temporal-scope
set is a typical source). It works by an affine-rank check on the
augmented response matrix M = cbind(1, Y) over complete Bernoulli rows
(rows where every screened trait is observed), using base R's qr() with
tolerance sqrt(.Machine$double.eps) * max(dim(M)) (the usual
dimension-scaled machine-precision rule, so the check tightens
automatically on a larger response matrix rather than using one fixed
constant regardless of size). The $design table's
response_affine_rank row reports the matrix's rank (value) and its
column count (threshold, = ncol(M)); a rank deficiency –
threshold - value – means at least one exact affine dependency exists
among the traits. The $response_dependencies table then adds, where
cheaply recoverable from the matrix's null space, a human-readable
certificate naming the traits and the relation (for example
"A + B + C = 1"). This is a best-effort report, not exhaustive
minimal-subset discovery: with more than one simultaneous exact
dependency the automatic search can fail to decompose the null space into
individually clean certificates, in which case the remaining deficiency
is reported without a certificate (type = "unresolved"), never silently
dropped. Declaring known structure via known_groups sidesteps that
limitation with an exact, deterministic, per-group check. An exact
one-hot block is not itself a bug to silently filter – it is one
categorical variable, and belongs in multinomial() or a deliberate
reference-level coding, not in several unconstrained binary traits.
With screen_control(separation = "fixed"), the screen uses the optional
detectseparation package to classify the observed fixed design. This
includes finite known offsets, whose presence is recorded in the returned
separation table. The offset does not change the fixed-design recession
directions used by the certificate. This is diagnostic support only:
gllvmTMB() currently rejects nonzero offsets for binomial responses.
This certificate covers fixed-effect geometry only. It does not establish finiteness of latent loadings or covariance parameters, validate the full marginal GLLVM likelihood, or select a penalized estimator.
A gllvmTMB_screen object. Use screen_table() to extract
report-ready tables.
Albert A, Anderson JA (1984). On the existence of maximum likelihood estimates in logistic regression models. Biometrika 71:1–10. doi:10.1093/biomet/71.1.1.
Peduzzi P, Concato J, Kemper E, Holford TR, Feinstein AR (1996). A simulation study of the number of events per variable in logistic regression analysis. Journal of Clinical Epidemiology 49:1373–1379. doi:10.1016/S0895-4356(96)00236-3.
Vittinghoff E, McCulloch CE (2007). Relaxing the rule of ten events per variable in logistic and Cox regression. American Journal of Epidemiology 165:710–718. doi:10.1093/aje/kwk052.
Chalmers RP (2012). mirt: A multidimensional item response theory package for the R environment. Journal of Statistical Software 48(6):1–29. doi:10.18637/jss.v048.i06.
Kosmidis I, Schumacher D, Schwendinger F (2026). detectseparation: Detect and Check for Separation and Infinite Maximum Likelihood Estimates. R package version 0.4.0. doi:10.32614/CRAN.package.detectseparation.
df <- data.frame( study = factor(seq_len(12)), a = c(rep(1, 10), 0, 0), b = c(rep(1, 10), 0, 0), c = rep(c(0, 1), 6) ) # The wrapper keeps the rendered example focused on the screen tables; # inspect warnings in interactive work. scr <- suppressWarnings(screen_gllvmTMB( traits(a, b, c) ~ 1 + latent(1 | study, d = 2), data = df, unit = "study", family = binomial() )) screen_table(scr, "traits") screen_table(scr, "recommendations")df <- data.frame( study = factor(seq_len(12)), a = c(rep(1, 10), 0, 0), b = c(rep(1, 10), 0, 0), c = rep(c(0, 1), 6) ) # The wrapper keeps the rendered example focused on the screen tables; # inspect warnings in interactive work. scr <- suppressWarnings(screen_gllvmTMB( traits(a, b, c) ~ 1 + latent(1 | study, d = 2), data = df, unit = "study", family = binomial() )) screen_table(scr, "traits") screen_table(scr, "recommendations")
Extract tables from a pre-fit screen
screen_table( x, table = c("summary", "traits", "pairs", "response_dependencies", "units", "design", "separation", "recommendations", "settings") )screen_table( x, table = c("summary", "traits", "pairs", "response_dependencies", "units", "design", "separation", "recommendations", "settings") )
x |
A |
table |
Table to extract. |
A data frame.
Simulates a three-dimensional dataset from the Nakagawa et al. (in prep) functional-biogeography model:
simulate_site_trait( n_sites = 50, n_species = 20, n_traits = 3, mean_species_per_site = 5, n_predictors = 2, alpha = NULL, beta = NULL, sigma2_eps = 0.5, Lambda_B = NULL, Lambda_W = NULL, psi_B = NULL, psi_W = NULL, sigma2_phy = NULL, sigma2_sp = NULL, Cphy = NULL, spatial_range = NULL, sigma2_spa = NULL, coords = NULL, seed = NULL )simulate_site_trait( n_sites = 50, n_species = 20, n_traits = 3, mean_species_per_site = 5, n_predictors = 2, alpha = NULL, beta = NULL, sigma2_eps = 0.5, Lambda_B = NULL, Lambda_W = NULL, psi_B = NULL, psi_W = NULL, sigma2_phy = NULL, sigma2_sp = NULL, Cphy = NULL, spatial_range = NULL, sigma2_spa = NULL, coords = NULL, seed = NULL )
n_sites |
Integer; number of sites |
n_species |
Integer; number of species |
n_traits |
Integer; number of traits |
mean_species_per_site |
Average number of species observed per site
(Poisson with this mean, truncated at |
n_predictors |
Integer; number of site-level predictors. Default 2. |
alpha |
Optional length- |
beta |
Optional |
sigma2_eps |
Residual variance ( |
Lambda_B, Lambda_W
|
Optional |
psi_B, psi_W
|
Optional length- |
sigma2_phy, sigma2_sp
|
Optional length- |
Cphy |
Optional |
spatial_range, sigma2_spa
|
Optional spatial range and per-trait
variance for an exponential spatial residual |
coords |
Optional |
seed |
Optional RNG seed. |
This simulator is domain-specific: it produces a (site, species, trait) cube, which is the canonical functional-biogeography
layout. The package as a whole works for any (unit, trait)
stacked-trait GLLVM (unit may be site, individual, species,
paper, ...); this particular simulator targets the site × species
× trait special case used in the methods paper. For simulations
from a generic (unit, observation, trait) design without
phylogenetic or spatial structure, use simulate_unit_trait().
Each component can be turned on or off via the corresponding variance / loading argument. The default settings produce a fixed-effects-only dataset suitable for Stage-1 regression tests.
A list with components:
dataLong-format data frame with one row per (site, species,
trait) observation: columns site, species, trait, value,
site_species, predictors env_1, …, env_n_predictors, plus lon
and lat if coords were generated.
truthNamed list of true parameter values (alpha, beta, Lambda_B, Lambda_W, psi_B, psi_W, sigma2_phy, sigma2_sp, sigma2_spa, spatial_range, sigma2_eps).
CphyThe phylogenetic correlation matrix used (or NULL).
coordsSite coordinates used (or NULL).
simulate_unit_trait() for the generic
(unit, observation, trait) simulator without phylogenetic or
spatial machinery.
set.seed(1) sim <- simulate_site_trait(n_sites = 30, n_species = 8, n_traits = 3, mean_species_per_site = 4) head(sim$data) sim$truth$alphaset.seed(1) sim <- simulate_site_trait(n_sites = 30, n_species = 8, n_traits = 3, mean_species_per_site = 4) head(sim$data) sim$truth$alpha
Simulates a long-format dataset from the generic (unit, observation, trait) stacked-trait GLLVM that the package supports. A unit is any
grouping over which a between-unit covariance structure applies
(individual, site, species, paper, ...) and each unit carries
n_obs_per_unit within-unit observations:
where is the between-unit signal (reduced-rank
Lambda_B plus diagonal psi_B), the within-unit
observation-level signal (reduced-rank Lambda_W plus diagonal
psi_W), and a row-level residual.
simulate_unit_trait( n_units = 50L, n_obs_per_unit = 3L, n_traits = 5L, alpha = NULL, Lambda_B = NULL, Lambda_W = NULL, psi_B = NULL, psi_W = NULL, sigma2_eps = 0.5, seed = NULL, ... )simulate_unit_trait( n_units = 50L, n_obs_per_unit = 3L, n_traits = 5L, alpha = NULL, Lambda_B = NULL, Lambda_W = NULL, psi_B = NULL, psi_W = NULL, sigma2_eps = 0.5, seed = NULL, ... )
n_units |
Integer; number of units |
n_obs_per_unit |
Integer; number of within-unit observations |
n_traits |
Integer; number of traits |
alpha |
Optional length- |
Lambda_B |
Optional |
Lambda_W |
Optional |
psi_B |
Optional length- |
psi_W |
Optional length- |
sigma2_eps |
Row-level residual variance. Default 0.5. |
seed |
Optional RNG seed. |
... |
Reserved for future extensions; currently ignored. |
This is the generic sibling of simulate_site_trait(). Use this
function when the design is an abstract (unit, observation, trait)
layout with no phylogenetic or spatial structure. Use
simulate_site_trait() when you need the domain-specific
functional-biogeography (site, species, trait) cube with
phylogenetic species correlation and spatial site coordinates baked
into the DGP.
Each component can be turned on or off via the corresponding variance /
loading argument. The all-NULL default settings produce a
fixed-effects-only dataset (trait intercepts plus row-level residual)
suitable for fast regression fixtures.
A list with components:
dataLong-format data frame with one row per (unit,
observation, trait): columns unit, observation, trait, value,
and unit_observation – the row id used for a per-observation
diagonal within-unit grouping (one level per unit-observation cell).
truthNamed list of true parameter values: alpha, Lambda_B,
Lambda_W, psi_B, psi_W, sigma2_eps.
simulate_site_trait() for the domain-specific
(site, species, trait) functional-biogeography simulator with
phylogenetic and spatial machinery.
set.seed(1) ## Fixed-effects-only default (all structure NULL): sim0 <- simulate_unit_trait(n_units = 20, n_obs_per_unit = 3, n_traits = 4) head(sim0$data) sim0$truth$alpha ## With a between-unit reduced-rank loading and diagonal within-unit term: Lambda_B <- matrix(c(0.9, 0.7, -0.4, 0.2, 0.1, -0.2, 0.6, 0.8), nrow = 4, ncol = 2) sim <- simulate_unit_trait(n_units = 40, n_obs_per_unit = 3, n_traits = 4, Lambda_B = Lambda_B, psi_W = 0.3, seed = 1) str(sim$truth)set.seed(1) ## Fixed-effects-only default (all structure NULL): sim0 <- simulate_unit_trait(n_units = 20, n_obs_per_unit = 3, n_traits = 4) head(sim0$data) sim0$truth$alpha ## With a between-unit reduced-rank loading and diagonal within-unit term: Lambda_B <- matrix(c(0.9, 0.7, -0.4, 0.2, 0.1, -0.2, 0.6, 0.8), nrow = 4, ncol = 2) sim <- simulate_unit_trait(n_units = 40, n_obs_per_unit = 3, n_traits = 4, Lambda_B = Lambda_B, psi_W = 0.3, seed = 1) str(sim$truth)
Draws nsim new response vectors from a fitted model. By default
(condition_on_RE = FALSE) the random effects are redrawn from the
fitted covariance and the response is drawn from the fitted family — the
unconditional simulation appropriate for a parametric bootstrap. Redraw is
not implemented for every tier; a fit using an unhandled tier falls back to
conditional simulation with a warning, and intervals derived from it are too
narrow. Set condition_on_RE = TRUE for the older conditional behaviour,
which reuses the fitted random-effect modes and only adds residual noise.
## S3 method for class 'gllvmTMB_multi' simulate( object, nsim = 1, seed = NULL, newdata = NULL, condition_on_RE = FALSE, ... )## S3 method for class 'gllvmTMB_multi' simulate( object, nsim = 1, seed = NULL, newdata = NULL, condition_on_RE = FALSE, ... )
object |
A fit returned by |
nsim |
Number of replicate response vectors to draw. Default 1. |
seed |
Optional RNG seed. |
newdata |
Optional new data frame; if supplied, predictions are
computed at |
condition_on_RE |
Logical (default Not every tier is covered. A fit using any other active tier —
notably the SPDE spatial tier ( When |
... |
Currently unused. |
A matrix of dimension n_obs x nsim (or nrow(newdata) x nsim
when newdata is supplied). Per-row family coverage (#1079): rows
are drawn from their true fitted distribution for gaussian, binomial
(at the row's actual trial count, not forced to Bernoulli), poisson,
lognormal, Gamma, nbinom2, nbinom1, Beta, betabinomial, student-t,
zero-truncated poisson, zero-truncated nbinom2, delta_lognormal,
delta_gamma, ordinal_probit, and multinomial. The one exception is
tweedie, which has no exact draw implemented; its rows (and any other
unrecognised family) are returned as NA_real_, together with a
warning naming the affected family_id values — never a
Gaussian-on-link-scale substitute, and never silent. The newdata
path (predictions on unseen data) still falls back to a
Gaussian-on-link-scale draw for every family, with its own warning;
family-aware newdata simulation is not yet implemented.
Per-trait confidence intervals on the standard deviation of an augmented random-slope term. All intervals are delta-method Wald intervals built on the log-SD scale so the bounds stay positive by construction. Three underlying routes are covered, dispatched automatically from the fit's structure:
The ordinary augmented diagonal (Psi) route
(theta_diag_B_slope, from latent(0 + trait + (0 + trait):x | unit, d = K) or unique(...)). theta_diag_B_slope is a genuine
univariate log-SD (sd = exp(theta)), so the interval is read
directly off sdreport()'s cov.fixed – no Jacobian.
The phylogenetic Cholesky augmented-slope route
(theta_dep_chol, from phylo_dep(1 + x1 + ... + xs | species) /
phylo_indep(1 + x | species)). The marginal slope SD is a
nonlinear function of several packed Cholesky entries (
Var(slope_t) = L21_t^2 + exp(diag_slope_t)^2 for a single slope),
so its standard error comes from an ADREPORT()ed quantity in the
C++ template (sd_b), letting sdreport() run the multivariate
delta method against the authoritative packing.
The loadings-only augmented random-slope route
(theta_rr_B_slope with no diagonal companion, e.g.
latent(..., unique = FALSE)). The marginal slope variance is a
quadratic form in several loading entries
(Lambda_B_slope \%*\% t(Lambda_B_slope)), again handled via an
ADREPORT()ed quantity (sd_rr_B_slope).
slope_sd_ci(fit, level = 0.95, scale = c("sd", "variance"))slope_sd_ci(fit, level = 0.95, scale = c("sd", "variance"))
fit |
A multi-trait |
level |
Confidence level. Defaults to 0.95. |
scale |
|
A data frame of class gllvmTMB_slope_ci, one row per slope SD
(trait x slope term), with columns:
traitTrait factor level.
termThe slope covariate name.
estimate, lower, upper
On the requested scale. lower
/ upper are NA whenever status != "ok".
component"unique_psi" when the diagonal route's fit also
carries a shared loadings block, so estimate is only part of the
marginal slope SD; "total" otherwise, meaning estimate already
is the total marginal slope SD.
total_sdPoint estimate of the TOTAL marginal slope SD, on
the SD scale regardless of scale. Equal to estimate when
component == "total".
total_lower, total_upper
Interval on total_sd, on the
requested scale. NA whenever total_status != "ok" or the
supporting ADREPORT is unavailable.
total_statusKill-switch status for the total_sd
interval; see below. "unavailable" when no supporting ADREPORT
exists (only possible for the diagonal route on a stale cached
fit).
theta, se_theta
The log-SD estimate and its standard
error, for auditability. For the diagonal route these are the raw
fixed-effect parameter and its cov.fixed SE (unchanged from
slice 1); for the phylogenetic and loadings-only routes these are
derived from an ADREPORTed natural-scale quantity via the delta
method se(log(X)) = se(X) / X.
method"wald_log_scale" for the diagonal route (direct
parameter read), "wald_log_scale_adreport" for the phylogenetic
and loadings-only routes (ADREPORT delta method).
interval_statusAlways "wald_uncalibrated".
status"ok", "no_pd_hessian", "se_nonfinite",
"se_blowup", or "near_zero_relative" – see Kill-switch guard
below.
scaleThe requested scale, "sd" or "variance".
The returned object also carries a hard-coded attribute
calibrated = FALSE (never an argument; only a future coverage
certificate can flip this in the source), and the requested level.
Every interval returned by this function is a transformed Wald
interval read off sdreport(), the same inferential instrument as
loading_ci() and confint_inspect(): it uses the asymptotic normal
approximation and propagates it through a monotone log/exp transform.
It is not a calibrated coverage statement – no repeated-sampling
coverage campaign has been run for any of these estimands, and
interval_status = "wald_uncalibrated" marks every row as such. The
package's validation-debt ledger records this as tested-but-not-
coverage-certified (see the ledger for the current status).
When the fit's random-slope term carries BOTH a diagonal Psi companion
and a shared loadings component (theta_rr_B_slope, the default
alongside theta_diag_B_slope for ordinary latent()), estimate for
that route is the per-trait UNIQUE (Psi) component of slope variance
only – it excludes the shared loadings contribution to the marginal
slope variance, and can understate the total marginal slope SD
substantially (measured up to ~45% on a recovery fixture). This
restriction travels with the data, not only the print method: the
component column reads "unique_psi" in that case ("total"
otherwise – including for the phylogenetic and loadings-only routes,
where the reported quantity already IS the total marginal slope SD).
total_sd always reports the total marginal slope SD as a point
estimate. total_lower / total_upper report a genuine interval on
that total wherever the C++ template ADREPORT()s the combined
expression directly (both routes as of this version); total_status
names the same kill-switch statuses as status when that support is
present, or "unavailable" when it is not (e.g. a fit refit under an
older package version whose ADREPORT does not yet exist). A
cli::cli_warn() fires on the call itself whenever the shared loadings
block is present alongside the diagonal route, not only when the result
is printed.
Applied identically to every route. lower/upper (and, where
supported, total_lower/total_upper) are set to NA (with status
explaining why and a cli::cli_warn()) whenever any of:
fit$sd_report$pdHess is FALSE (status = "no_pd_hessian",
applies to every row);
the log-SD estimate or its standard error is non-finite
(status = "se_nonfinite") – for the ADREPORT-based routes this
also covers a non-positive natural-scale estimate, which cannot be
logged;
se_theta > 10 on the log scale – essentially information-free
(an SE of 10 on the log scale means the interval would span
); status = "se_blowup";
sd_hat is at most 1% of the largest sd_hat among this fit's
OTHER slope coordinates on the SAME route (only evaluated when there
is more than one to compare against); status = "near_zero_relative",
mirroring the package's relative-to-siblings convention for a
collapsed variance component (psi_rel_thresh / near_zero_psi_*,
R/diagnose.R).
The point estimate (estimate, theta) is always returned, following
the house line that a non-PD Hessian disqualifies standard errors but
not point estimates (R/bootstrap-sigma.R, R/cv-internal.R).
loading_ci() for the equivalent interval on Lambda entries;
extract_Sigma() with level = "unit_slope" or level = "phy" for
the full augmented covariance (point estimates only).
set.seed(1) n_ind <- 30L n_traits <- 2L trait_levels <- paste0("t", seq_len(n_traits)) individuals <- paste0("id", seq_len(n_ind)) df <- expand.grid( individual = factor(individuals, levels = individuals), rep = 1:5, trait = factor(trait_levels, levels = trait_levels) ) df$x <- stats::rnorm(nrow(df)) alpha <- c(0.2, -0.1) beta <- c(0.3, -0.2) psi_noise <- matrix(stats::rnorm(4L * n_ind, sd = 0.25), nrow = 4L) eta <- numeric(nrow(df)) for (o in seq_len(nrow(df))) { tt <- as.integer(df$trait[o]) ii <- as.integer(df$individual[o]) base <- 2L * (tt - 1L) eta[o] <- alpha[tt] + beta[tt] * df$x[o] + psi_noise[base + 1L, ii] + psi_noise[base + 2L, ii] * df$x[o] } df$value <- eta + stats::rnorm(nrow(df), sd = 0.3) options(gllvmTMB.quiet_grammar_notes = TRUE) fit <- gllvmTMB( value ~ 0 + trait + (0 + trait):x + unique(0 + trait + (0 + trait):x | individual), data = df, trait = "trait", unit = "individual", control = gllvmTMBcontrol(se = TRUE, optimizer = "optim", optArgs = list(method = "BFGS")) ) slope_sd_ci(fit) slope_sd_ci(fit, level = 0.90, scale = "variance")set.seed(1) n_ind <- 30L n_traits <- 2L trait_levels <- paste0("t", seq_len(n_traits)) individuals <- paste0("id", seq_len(n_ind)) df <- expand.grid( individual = factor(individuals, levels = individuals), rep = 1:5, trait = factor(trait_levels, levels = trait_levels) ) df$x <- stats::rnorm(nrow(df)) alpha <- c(0.2, -0.1) beta <- c(0.3, -0.2) psi_noise <- matrix(stats::rnorm(4L * n_ind, sd = 0.25), nrow = 4L) eta <- numeric(nrow(df)) for (o in seq_len(nrow(df))) { tt <- as.integer(df$trait[o]) ii <- as.integer(df$individual[o]) base <- 2L * (tt - 1L) eta[o] <- alpha[tt] + beta[tt] * df$x[o] + psi_noise[base + 1L, ii] + psi_noise[base + 2L, ii] * df$x[o] } df$value <- eta + stats::rnorm(nrow(df), sd = 0.3) options(gllvmTMB.quiet_grammar_notes = TRUE) fit <- gllvmTMB( value ~ 0 + trait + (0 + trait):x + unique(0 + trait + (0 + trait):x | individual), data = df, trait = "trait", unit = "individual", control = gllvmTMBcontrol(se = TRUE, optimizer = "optim", optArgs = list(method = "BFGS")) ) slope_sd_ci(fit) slope_sd_ci(fit, level = 0.90, scale = "variance")
spatial() is a unified entry point for the package's three spatial modes
(spatial_indep, spatial_latent, spatial_dep). It accepts an lme4-bar formula on
the first argument and dispatches to the appropriate canonical
keyword based on the LHS shape and the optional mode = ... argument.
The three current keywords stay first-class – spatial() is an
additive alias matching the lme4 / brms / drmTMB convention.
spatial( formula, mesh = NULL, coords = NULL, mode = NULL, d = 1, unique = FALSE )spatial( formula, mesh = NULL, coords = NULL, mode = NULL, d = 1, unique = FALSE )
formula |
An lme4-bar formula. The bar's RHS is the spatial
coordinate placeholder (typically |
mesh |
A |
coords |
Optional, retained as a hint of which coordinate
columns correspond to the spatial grouping. The mesh actually
used is whatever is passed to the |
mode |
One of |
d |
Latent rank for |
unique |
Logical; forwarded to |
This is the spatial parallel of the phylo() mode-dispatch wrapper:
same dispatch logic, different engine slots (SPDE Matern instead of
Hadfield A^-1).
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
library(fmesher)
# Single shared spatial-field variance (= spatial_indep(..., common = TRUE))
fit_s <- gllvmTMB(value ~ 0 + trait + spatial(1 | site, mesh = mesh),
data = df,
trait = "trait",
unit = "site")
# Per-trait independent fields on a shared mesh
fit_u <- gllvmTMB(value ~ 0 + trait +
spatial(0 + trait | coords, mode = "indep",
mesh = mesh),
data = df,
trait = "trait",
unit = "site")
# Reduced-rank shared spatial latent factors (= spatial_latent)
fit_l <- gllvmTMB(value ~ 0 + trait +
spatial(0 + trait | coords, mode = "latent",
d = 2, mesh = mesh),
data = df,
trait = "trait",
unit = "site")
# Backward-compat: legacy bare-formula form
fit_legacy <- gllvmTMB(value ~ 0 + trait + spatial(0 + trait | coords),
data = df,
trait = "trait",
unit = "site",
mesh = mesh)
A formula marker; never evaluated.
| Form | Mode default | Rewrites to |
spatial(1 \| site) |
"scalar" |
spatial_indep(0 + trait \| coords, common = TRUE) |
spatial(0 + trait \| coords, mode = "indep") |
(mandatory) | spatial_indep(0 + trait \| coords) |
spatial(0 + trait \| coords, mode = "latent", d = K) |
(mandatory) | spatial_latent(0 + trait \| coords, d = K) |
spatial(0 + trait \| coords, mode = "dep") |
(mandatory) | spatial_dep(0 + trait \| coords)
|
When the LHS expands to a single column (1, intercept-only), mode
is degenerate: it defaults silently to "scalar", and explicit
mode = "scalar" is accepted (no warning). When the LHS is
0 + trait, mode is mandatory – choosing between "indep" /
"latent" / "dep" is a meaningful decision (per-trait
marginal vs reduced-rank decomposition vs full unstructured) and the
parser refuses to silently default.
Legacy bare-formula calls spatial(0 + trait | coords) and
spatial(coords | trait) (no mode argument) continue to work as
deprecated aliases for the per-trait diagonal spatial field (with an
additional orientation-flip lifecycle warning for the pre-0.1.4
orientation). The legacy form rewrites to spde() internally,
picking up the SPDE mesh from the top-level mesh = argument to
gllvmTMB().
The unified spatial() wrapper is limited to intercept-only trait
covariance. For an intercept and slope, use an explicit structured mode such
as spatial_indep(), spatial_latent(), or spatial_dep(); unsupported LHS
forms fail with a message showing the accepted syntax.
drmTMB also exposes spatial(1 | site, mesh = mesh) with the same
SPDE Matern internal path. Both packages share the same calling
convention; user muscle memory transfers.
spatial_indep(),
spatial_latent(), spatial_dep(), phylo() (phylogenetic
parallel), spde() (deeper deprecated alias).
spatial_dep(0 + trait | coords)
Canonical name for the full unstructured cross-trait spatial
covariance ,
with free parameters
( parameterised via Cholesky).
Mathematically identical to spatial_latent(0 + trait | coords, d = T)
standalone; the keyword choice is documentary.
spatial_dep(formula, coords = NULL, mesh = NULL)spatial_dep(formula, coords = NULL, mesh = NULL)
formula |
|
coords |
Character; the column-name pair of spatial coordinates
in |
mesh |
An |
Use spatial_dep() when you want an explicit full-unstructured
cross-trait spatial covariance fit. Use spatial_latent() for the
rank-reduced K-factor model. Use spatial_indep() for the
marginal-only per-trait spatial fit.
A formula marker; never evaluated.
Same convention as the rest of the spatial_* keywords: the
canonical orientation is 0 + trait | coords (LHS = trait factor,
RHS = the coords placeholder). spatial_dep is born with this
orientation; the legacy coords | trait form is not accepted.
spatial_latent() / spatial_indep()
Combining spatial_dep(0 + trait | coords) with spatial_latent is
over-parameterised; combining with
spatial_indep is redundant. The parser raises
cli::cli_abort() in any of these cases.
spatial_latent(), spatial_indep(),
dep(), phylo_dep(), extract_Sigma().
sim <- simulate_site_trait( n_sites = 30, n_species = 6, mean_species_per_site = 5, spatial_range = 0.4, sigma2_spa = rep(0.3, 6), seed = 1 ) mesh <- make_mesh(sim$data, c("lon", "lat"), cutoff = 0.1) fit <- gllvmTMB( value ~ 0 + trait + spatial_dep(0 + trait | site, mesh = mesh), data = sim$data, trait = "trait", unit = "site" )sim <- simulate_site_trait( n_sites = 30, n_species = 6, mean_species_per_site = 5, spatial_range = 0.4, sigma2_spa = rep(0.3, 6), seed = 1 ) mesh <- make_mesh(sim$data, c("lon", "lat"), cutoff = 0.1) fit <- gllvmTMB( value ~ 0 + trait + spatial_dep(0 + trait | site, mesh = mesh), data = sim$data, trait = "trait", unit = "site" )
spatial_indep(0 + trait | coords)
Canonical name for T per-trait spatial fields coupled by the SPDE
precision matrix ; standalone =
T univariate spatial fits stacked.
spatial_indep(formula, coords = NULL, mesh = NULL, common = FALSE)spatial_indep(formula, coords = NULL, mesh = NULL, common = FALSE)
formula |
|
coords |
Character; the column-name pair of spatial coordinates
in |
mesh |
An |
common |
|
Each trait gets its own variance
on a Matern GMRF, with a shared range
parameter .
Use spatial_indep() for an explicit marginal-only spatial fit (no
cross-trait spatial decomposition). Use spatial_latent() for
K shared spatial fields driving all T traits via a T x K loading
matrix.
A formula marker; never evaluated.
Same convention as the rest of the spatial_* keywords: the
canonical orientation is 0 + trait | coords (LHS = trait factor,
RHS = the coords placeholder). Spatial keywords adopted this
orientation at gllvmTMB 0.1.4; spatial_indep is born with it
(no legacy coords | trait orientation is accepted).
spatial_latent()
Combining spatial_indep(0 + trait | coords) with
spatial_latent(0 + trait | coords, d = K) is over-parameterised
and the parser raises a cli::cli_abort().
spatial_latent(), indep(),
phylo_indep(), extract_Sigma().
sim <- simulate_site_trait( n_sites = 20, n_species = 4, mean_species_per_site = 4, spatial_range = 0.4, sigma2_spa = rep(0.3, 4), seed = 1 ) mesh <- make_mesh(sim$data, c("lon", "lat"), cutoff = 0.1) fit <- gllvmTMB( value ~ 0 + trait + spatial_indep(0 + trait | site, mesh = mesh), data = sim$data, trait = "trait", unit = "site" )sim <- simulate_site_trait( n_sites = 20, n_species = 4, mean_species_per_site = 4, spatial_range = 0.4, sigma2_spa = rep(0.3, 4), seed = 1 ) mesh <- make_mesh(sim$data, c("lon", "lat"), cutoff = 0.1) fit <- gllvmTMB( value ~ 0 + trait + spatial_indep(0 + trait | site, mesh = mesh), data = sim$data, trait = "trait", unit = "site" )
spatial_latent(0 + trait | coords, d = K)
Canonical name for the reduced-rank spatial random effect: K shared
SPDE fields drive all T traits via a T x K loading matrix
. The spatial analogue of
phylo_latent() and the third cell of the spatial column of the API
grid (alongside spatial_indep() and spatial_dep()).
spatial_latent(formula, d = 1, unique = FALSE, coords = NULL, mesh = NULL)spatial_latent(formula, d = 1, unique = FALSE, coords = NULL, mesh = NULL)
formula |
|
d |
Integer; number of spatial latent factors (rank K). Defaults to 1. |
unique |
Logical; include a per-trait unique spatial diagonal
companion. |
coords |
Character; the column-name pair of spatial coordinates
in |
mesh |
An |
Internally this rewrites to spde(form, .spatial_latent = TRUE, d = K)
and toggles the TMB template's spde_lv_k switch to K. With
unique = TRUE, the same SPDE tier also keeps the per-trait
omega_spde fields active, giving
. The default unique = FALSE preserves
the older low-rank-only path. The earlier two-term companion spelling
is accepted as compatibility syntax for the same total-covariance
decomposition.
The C++ kernel
then reads a packed lower-triangular Lambda_spde and K shared spatial
fields omega_spde_lv (each prior where is the SPDE precision built
from the mesh), and accumulates
per observation. Per-field is
absorbed into for
identifiability, mirroring the phylo_latent() convention. Like all
rr loadings, is identified
only up to rotation; pin via lambda_constraint = list(spde = ...)
or post-hoc rotate via rotate_loadings().
A formula marker; never evaluated.
The canonical orientation is 0 + trait | coords (parallel to
latent() / indep() and glmmTMB's spatial keywords). The
earlier orientation coords | trait is accepted as a deprecated
alias and emits a one-shot lifecycle::deprecate_warn() per session
(introduced at gllvmTMB 0.1.4).
spatial_indep(), spatial_dep(), phylo_latent().
sim <- simulate_site_trait( n_sites = 30, n_species = 6, mean_species_per_site = 5, spatial_range = 0.4, sigma2_spa = rep(0.3, 6), seed = 1 ) mesh <- make_mesh(sim$data, c("lon", "lat"), cutoff = 0.1) fit <- gllvmTMB( value ~ 0 + trait + spatial_latent(0 + trait | site, d = 2, mesh = mesh), data = sim$data, trait = "trait", unit = "site" )sim <- simulate_site_trait( n_sites = 30, n_species = 6, mean_species_per_site = 5, spatial_range = 0.4, sigma2_spa = rep(0.3, 6), seed = 1 ) mesh <- make_mesh(sim$data, c("lon", "lat"), cutoff = 0.1) fit <- gllvmTMB( value ~ 0 + trait + spatial_latent(0 + trait | site, d = 2, mesh = mesh), data = sim$data, trait = "trait", unit = "site" )
spatial_scalar(formula, coords = NULL, mesh = NULL)spatial_scalar(formula, coords = NULL, mesh = NULL)
formula |
|
coords |
Character; the column-name pair of spatial coordinates
in |
mesh |
An |
spatial_scalar() is soft-deprecated compatibility syntax in gllvmTMB
0.2.0. Write
spatial_indep(0 + trait | coords, common = TRUE) in new code. The two
spellings fit the same SPDE / GMRF Matérn model with
one shared variance across all traits and a shared
range parameter . Each trait carries its own independent
field draw on the mesh, but every trait's field has the same marginal
variance and the same correlation length. Implementation: the same
SPDE engine as spatial_indep(), with the per-trait
log_tau_spde parameters tied via TMB's map mechanism so they
collapse to a single estimable scalar.
Use spatial_indep(..., common = TRUE) when domain knowledge (or parsimony)
says all traits should share the same amount of spatial structure. Omit
common = TRUE for separate trait-specific spatial variances, or use
spatial_latent() for a reduced-rank cross-trait decomposition.
A formula marker; never evaluated.
The canonical orientation is 0 + trait | coords (parallel to
latent() / indep() and glmmTMB's spatial keywords). The
earlier orientation coords | trait is accepted as a deprecated
alias and emits a one-shot lifecycle::deprecate_warn() per session
(introduced at gllvmTMB 0.1.4).
spatial_indep(), spatial_latent(), spde() (deprecated alias).
sim <- simulate_site_trait( n_sites = 20, n_species = 4, mean_species_per_site = 4, spatial_range = 0.4, sigma2_spa = rep(0.3, 4), seed = 1 ) mesh <- make_mesh(sim$data, c("lon", "lat"), cutoff = 0.1) fit <- gllvmTMB( value ~ 0 + trait + spatial_indep(0 + trait | site, mesh = mesh, common = TRUE), data = sim$data, trait = "trait", unit = "site" )sim <- simulate_site_trait( n_sites = 20, n_species = 4, mean_species_per_site = 4, spatial_range = 0.4, sigma2_spa = rep(0.3, 4), seed = 1 ) mesh <- make_mesh(sim$data, c("lon", "lat"), cutoff = 0.1) fit <- gllvmTMB( value ~ 0 + trait + spatial_indep(0 + trait | site, mesh = mesh, common = TRUE), data = sim$data, trait = "trait", unit = "site" )
spatial_unique(0 + trait | coords)
spatial_unique(formula, coords = NULL, mesh = NULL)spatial_unique(formula, coords = NULL, mesh = NULL)
formula |
|
coords |
Character; the column-name pair of spatial coordinates
in |
mesh |
An |
spatial_unique() is soft-deprecated as compatibility syntax in gllvmTMB
0.2.0. Use spatial_indep() for standalone marginal diagonal spatial
fields. Paired explicit-Psi use remains accepted; new decomposition
code can use spatial_latent() with unique = TRUE.
Canonical name for the SPDE / GMRF Matern spatial random field with
one independent field per trait (per-trait variance
, shared range parameter ). This is the
"unique-rank" cell of the spatial column of the API grid.
Formerly spde(0 + trait | coords) and spatial(0 + trait | coords)
– same engine, new name. See spde() for the technical details on
the Matern kernel.
Compare to spatial_indep() with common = TRUE (one shared variance
across traits) and spatial_latent() (K-dim spatial latent factors with a
loading matrix).
A formula marker; never evaluated.
The canonical orientation is 0 + trait | coords (LHS = trait factor,
RHS = the coords placeholder), parallel to latent(),
unique(), and glmmTMB's gau(0 + trait | pos) / exp(0 + trait | pos).
The earlier orientation coords | trait is accepted as a deprecated
alias: it emits a one-shot lifecycle::deprecate_warn() per session
(introduced at gllvmTMB 0.1.4) and is internally normalised to the
canonical orientation, so existing fits remain byte-identical.
spatial_indep(), spatial_latent(), spde() (deprecated alias).
sim <- simulate_site_trait( n_sites = 20, n_species = 4, mean_species_per_site = 4, spatial_range = 0.4, sigma2_spa = rep(0.3, 4), seed = 1 ) mesh <- make_mesh(sim$data, c("lon", "lat"), cutoff = 0.1) fit <- gllvmTMB( value ~ 0 + trait + spatial_indep(0 + trait | site, mesh = mesh), data = sim$data, trait = "trait", unit = "site" )sim <- simulate_site_trait( n_sites = 20, n_species = 4, mean_species_per_site = 4, spatial_range = 0.4, sigma2_spa = rep(0.3, 4), seed = 1 ) mesh <- make_mesh(sim$data, c("lon", "lat"), cutoff = 0.1) fit <- gllvmTMB( value ~ 0 + trait + spatial_indep(0 + trait | site, mesh = mesh), data = sim$data, trait = "trait", unit = "site" )
gllvmTMB() computes a TMB TMB::sdreport() at fitting time unless it is
told not to (control = gllvmTMBcontrol(se = FALSE)). Skipping it is
substantially faster, but it used to be a one-way door: the only way to get
standard errors afterwards was to fit the model again. standard_errors()
removes that door by computing the same sdreport() on demand from the
fitted object.
standard_errors(fit)standard_errors(fit)
fit |
A fitted multivariate model returned by |
Fits made with non-unit likelihood weights use a weighted objective rather
than an ordinary likelihood. standard_errors() refuses those fits because
the package does not yet provide sandwich-calibrated uncertainty for them;
point estimates remain available from the fitted model.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
## Fit fast, decide later whether standard errors are needed. fit <- gllvmTMB( traits(y1, y2) ~ 1 + latent(1 | site, d = 1), data = dat, family = gaussian(), control = gllvmTMBcontrol(se = FALSE) ) fit <- standard_errors(fit) summary(fit)
The same fit, with its sd_report field populated (and
sdreport_error cleared). Standard-error consumers such as
summary.gllvmTMB_multi(), getLV(), getREsd(), and
confint(method = "wald") work on the returned object.
R semantics matter here: the fit is returned, not modified in place.
Assign the result – fit <- standard_errors(fit) – or the standard
errors are discarded.
If the fit already has an sd_report, it is returned unchanged; the
calculation is not repeated.
The saving is real but bounded: it is worth using when a workflow fits many
models and needs standard errors for only some of them (model selection,
simulation loops, a grid of candidate structures). For a single fit whose
summary you intend to read, the default se = TRUE is simpler and costs
nothing extra.
A TMB ADFun object holds external pointers into compiled memory. Those
pointers do not survive saveRDS() / readRDS() or a new R session, so
standard_errors() can only be called on a fit made in the current
session. This is a pre-existing property of every function in this package
that reuses fit$tmb_obj (profiling, bootstrapping, getLV()), not a new
restriction – but this function names it with a clear error rather than
failing obscurely. If you need standard errors from a saved fit, refit it
with se = TRUE.
Exactly the ones the fit would have produced at fitting time – the same
TMB sdreport() call, on the same converged parameter vector, to the bit.
Deferring the calculation does not change it, and does not change any
honesty caveat that already applies to Wald standard errors from this
package.
gllvmTMBcontrol() for the se argument that defers the
calculation; getREsd() and getLV() for accessors that read the
resulting sd_report.
lambda_constraint matrix for a reduced-rank fitProduces a default constraint matrix M for the reduced-rank loadings
Lambda in a fit returned by gllvmTMB() or in a formula-data pair that will
be passed to gllvmTMB(). The matrix is returned in the format expected by
the lambda_constraint argument: NA in free entries, numeric values in
pinned entries. This function supplies an identification convention – an
orientation/scale scaffold that resolves rotational ambiguity – and does
not discover biological factor structure; a low or pinned loading is a
constraint choice or a result to interpret, not by itself grounds to
exclude a trait.
suggest_lambda_constraint( fit_or_formula, data = NULL, level = "unit", convention = c("lower_triangular", "pin_top_one", "none", "varimax_threshold", "wald_retention", "profile_retention"), trait = "trait", unit = "site", threshold = 0.3, retention_prob = 0.9, sigma_d2 = 1, site = NULL )suggest_lambda_constraint( fit_or_formula, data = NULL, level = "unit", convention = c("lower_triangular", "pin_top_one", "none", "varimax_threshold", "wald_retention", "profile_retention"), trait = "trait", unit = "site", threshold = 0.3, retention_prob = 0.9, sigma_d2 = 1, site = NULL )
fit_or_formula |
Either a fitted multivariate model returned by
|
data |
A data frame. Required when |
level |
Which loading matrix to constrain: |
convention |
One of:
|
trait, unit
|
Name of the trait and unit (site) columns. Forwarded when
|
threshold |
Salience threshold on the standardised loading
|
retention_prob |
Retention probability for
Must be a finite scalar in |
sigma_d2 |
Deprecated compatibility argument; ignored by the
standardised-loading conventions. Their denominators now come from the
model-implied total variance returned by |
site |
Deprecated alias for |
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
sug <- suggest_lambda_constraint( value ~ 0 + trait + latent(0 + trait | site, d = 2), data = my_data ) fit <- gllvmTMB( value ~ 0 + trait + latent(0 + trait | site, d = 2), data = my_data, trait = "trait", unit = "site", lambda_constraint = list(unit = sug$constraint) )
A list with components:
constraintA T x K matrix with NA in free entries and
0 (or 1, for "pin_top_one") in pinned entries. Has trait names
as rownames and "f1", ..., "fK" as colnames.
conventionThe chosen convention.
dThe number of factors K.
n_pinsNumber of pinned entries.
noteA short explanation of what was pinned and why.
usage_hintAn example call as a string showing how to use
the returned matrix with gllvmTMB().
lambda_constraint suggestionsRuns suggest_lambda_constraint() under several conventions and returns a
compact comparison table. This is the convenience layer for exploratory
loading workflows where the analyst wants to see the cheap point-threshold
suggestion beside uncertainty-aware Wald or profile-retention suggestions
before choosing the matrix to pass to lambda_constraint.
suggest_lambda_constraints( fit_or_formula, data = NULL, level = "unit", methods = c("varimax_threshold", "wald_retention"), trait = "trait", unit = "site", threshold = 0.3, retention_prob = 0.9, sigma_d2 = 1, site = NULL )suggest_lambda_constraints( fit_or_formula, data = NULL, level = "unit", methods = c("varimax_threshold", "wald_retention"), trait = "trait", unit = "site", threshold = 0.3, retention_prob = 0.9, sigma_d2 = 1, site = NULL )
fit_or_formula |
Either a fitted multivariate model returned by
|
data |
A data frame. Required when |
level |
Which loading matrix to constrain: |
methods |
Character vector of conventions to run. Accepted values are
the same as the |
trait, unit
|
Name of the trait and unit (site) columns. Forwarded when
|
threshold |
Salience threshold on the standardised loading
|
retention_prob |
Retention probability for
Must be a finite scalar in |
sigma_d2 |
Deprecated compatibility argument; ignored by the
standardised-loading conventions. Their denominators now come from the
model-implied total variance returned by |
site |
Deprecated alias for |
The default compares varimax_threshold and wald_retention. Add
"profile_retention" to methods when you want the likelihood-ratio
version; it is slower because it refits once per testable loading entry.
Template for an existing fitted model or data set; replace the named inputs with your own objects before running this code.
cmp <- suggest_lambda_constraints(
fit,
methods = c("varimax_threshold", "wald_retention"),
threshold = 0.30,
retention_prob = 0.90
)
cmp$summary
fit_con <- gllvmTMB(
value ~ 0 + trait + latent(0 + trait | site, d = 2),
data = df,
family = binomial(),
lambda_constraint = list(unit = cmp$recommended$constraint)
)
# Expensive: one likelihood-ratio refit per testable loading.
cmp_profile <- suggest_lambda_constraints(
fit,
methods = "profile_retention"
)
A list of class gllvmTMB_lambda_constraint_suggestions with:
summaryA data frame with one row per method and columns for decision basis, cost, number of pinned/free entries, and the helper note.
suggestionsA named list of the full
suggest_lambda_constraint() return objects, one per method.
recommended_methodThe highest-evidence method among the
requested methods. By default this is "wald_retention"; if
"profile_retention" is requested, it is recommended because it uses
likelihood-ratio refits rather than the baseline Hessian.
recommendedThe full suggestion object for
recommended_method.
Returns a tibble (or data.frame) of either the fixed-effect coefficient
table, the random-effects variance / covariance terms, or the ordinal
threshold cutpoints. Mirrors the tidy.sdmTMB() API but augmented for
the additional covstructs and the gllvmTMB-native ordinal_probit()
family.
## S3 method for class 'gllvmTMB_multi' tidy( x, effects = c("fixed", "ran_pars", "cutpoint"), conf.int = FALSE, conf.level = 0.95, ... )## S3 method for class 'gllvmTMB_multi' tidy( x, effects = c("fixed", "ran_pars", "cutpoint"), conf.int = FALSE, conf.level = 0.95, ... )
x |
A fit returned by |
effects |
One of |
conf.int |
Whether to add |
conf.level |
Confidence level for the CI. |
... |
Currently unused. |
A data.frame. effect = "fixed" rows include a link column
reporting each trait's link function ("identity", "probit",
"log", "logit", …). effect = "cutpoint" rows carry the
ordinal-probit thresholds. For a non-unit weighted objective, fixed-effect
point estimates remain available but std.error is NA, an
inference_status column explains the boundary, and conf.int = TRUE
fails because no sandwich interval is certified.
gllvmTMB() formula LHSFormula-LHS marker that lets gllvmTMB() accept a wide data frame
(one row per unit, one column per trait) without making the user
pivot first to one row per (unit, trait) observation.
traits(...)traits(...)
... |
Column-selection expression(s) passed verbatim to
|
The package teaches two shapes, long or wide data-frame:
long: gllvmTMB(value ~ ..., data = df_long, ...) – one
row per (unit, trait) observation.
wide data frame: gllvmTMB(traits(t1, t2, ...) ~ ..., data = df_wide, ...) – one row per unit, one column per trait,
with compact formula syntax.
The soft-deprecated gllvmTMB_wide(Y, ...) wrapper remains exported
for legacy matrix-wrapper workflows, but new examples should use
traits(...) through gllvmTMB().
Both taught shapes reach the same stacked-trait model after internal stacking; the user picks whichever shape matches their data on disk.
Because the LHS already names the response traits, the RHS can use a
compact wide shorthand. 1 expands to the trait-specific intercepts
0 + trait; ordinary predictors such as env_temp expand to
(0 + trait):env_temp; and latent(1 | individual) expands to
the long covariance syntax latent(0 + trait | individual).
Ordinary latent() includes its companion by
default. The same 1 | group shorthand is recognised for indep(),
dep(), bar-style phylo_indep() / phylo_dep(), and the
spatial_*() keywords. Species-axis phylogenetic keywords such as
phylo_latent(species, d = K) already name their phylogenetic axis
and pass through unchanged. Ordinary random-intercept terms such as
(1 | batch) also pass through unchanged.
gllvmTMB(
traits(sleep, mass, lifespan, brain) ~ 1 + env_temp +
latent(1 | individual, d = 2),
data = wide_df,
unit = "individual",
family = gaussian()
)
Internally traits() is implemented as a tidyr::pivot_longer()
pre-pass: the wide data is pivoted to long format with trait as a
factor column (levels in the order the user supplied to traits())
and .y_wide_ as the response column; the LHS of the formula is
rewritten from traits(...) to .y_wide_; and the compact RHS is
expanded to the trait-stacked long syntax before dispatch. The
explicit long RHS remains accepted, so existing calls that already
write 0 + trait and latent(0 + trait | group) keep working.
Tidyselect verbs are supported because traits() forwards its
arguments to tidyr::pivot_longer(cols = ...):
traits(all_of(cols)), traits(starts_with("sp")),
traits(matches("^y[0-9]+$")), traits(any_of(c("a", "b"))), and
bare names all work.
Cells with NA responses are, by default, dropped via
pivot_longer(values_drop_na = TRUE) – the canonical complete-case
behaviour. Passing missing = miss_control(response = "include") to
gllvmTMB() instead keeps every (unit, trait) cell and masks the
NA cells out of the likelihood (the observed-response mask), preserving
per-cell identity and original-row accounting in fit$missing_data. Users
who want strict listwise drop should pre-filter the wide data before
calling.
Mixed-family fits (family = list(...) keyed by trait) use the same
family handling after internal stacking; traits() does not
intercept the family argument. Per-row weight vectors of length
nrow(data) are also replicated across traits automatically. For
per-cell weight matrices, pivot to long format and pass a weights
column aligned with (unit, trait) rows. The legacy matrix wrapper
gllvmTMB_wide() still accepts matrix weights for migration code.
A formula marker; never evaluated as a function call. The
parser recognises traits(...) on the LHS of a gllvmTMB()
formula and dispatches to the wide-format pivot pre-pass.
gllvmTMB() for model fitting. The legacy
matrix wrapper gllvmTMB_wide(Y, ...) is soft-deprecated in
0.2.0; wide-data examples now use the traits(...) LHS through
gllvmTMB().
# Wide format: one column per trait; traits() stacks them internally. # The LHS *is* the trait spec, so no `trait =` argument is needed. set.seed(1) df_wide <- data.frame( unit = factor(paste0("u", 1:40)), t1 = rnorm(40), t2 = rnorm(40), t3 = rnorm(40) ) fit <- gllvmTMB( traits(t1, t2, t3) ~ 1 + latent(1 | unit, d = 2), data = df_wide, unit = "unit" ) extract_Sigma(fit)# Wide format: one column per trait; traits() stacks them internally. # The LHS *is* the trait spec, so no `trait =` argument is needed. set.seed(1) df_wide <- data.frame( unit = factor(paste0("u", 1:40)), t1 = rnorm(40), t2 = rnorm(40), t3 = rnorm(40) ) fit <- gllvmTMB( traits(t1, t2, t3) ~ 1 + latent(1 | unit, d = 2), data = df_wide, unit = "unit" ) extract_Sigma(fit)