Skip to contents

Formula-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.

Usage

traits(...)

Arguments

...

Column-selection expression(s) passed verbatim to tidyr::pivot_longer(cols = ...). Bare names or any tidyselect verb (all_of(), starts_with(), matches(), etc.) are accepted.

Value

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.

Details

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 \(\boldsymbol\Psi\) 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.

See also

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().

Examples

if (FALSE) { # \dontrun{
# 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)
} # }