Skip to contents

Use Xcoef_fixed when a direct predictor effect is fixed at zero for some responses but estimated for others. This is an exact equality constraint on the mean model—not shrinkage, variable selection, or evidence that an estimated effect is null.

You can impose named zero constraints in maximum-likelihood fits. Non-zero fixed values and REML = TRUE are not supported.

Decide whether zero is structural

For unit (i) and response (t), consider

ηit=𝐱it𝖳𝛃t+𝛌t𝖳𝐮i. \eta_{it} = \mathbf x_{it}^{\mathsf T}\boldsymbol\beta_t + \boldsymbol\lambda_t^{\mathsf T}\mathbf u_i.

Xcoef_fixed pins one named entry of the direct mean coefficient 𝛃t\boldsymbol\beta_t. It does not directly constrain the loadings, latent scores, Psi, or other covariance parameters—although their fitted estimates can change after the mean restriction changes.

Use an exact zero only when it is justified before seeing the fitted estimate, for example by experimental design, temporal ordering, measurement definition, preregistration, or a defensible structural model.

  • Known by design that the direct coefficient is zero: use Xcoef_fixed.
  • Want to learn whether the coefficient is near zero: leave it free and report its uncertainty or compare prespecified restricted and unrestricted models.
  • Want shrinkage or data-driven selection: use a method designed for that purpose.
  • Want to remove a response: change the response set.
  • Want to constrain which traits load on an LV: use the separate loading- constraint interface; that changes 𝚲\boldsymbol\Lambda, not 𝛃\boldsymbol\beta.

Pinning a false zero can move the omitted mean pattern into other fixed effects, residual variation, or covariance components.

Worked example

Suppose treatment is assigned after a baseline measurement. By design, the treatment cannot cause the baseline response, while its effects on later growth and colour responses remain free. We encode that prespecified temporal zero.

library(gllvmTMB)

set.seed(20260623)
n_unit <- 36
site <- factor(seq_len(n_unit))
treatment <- rnorm(n_unit)

dat <- expand.grid(
  site = site,
  trait = factor(c("baseline", "growth", "colour")),
  KEEP.OUT.ATTRS = FALSE
)
dat$treatment <- treatment[as.integer(dat$site)]
dat$eta <- with(dat, ifelse(
  trait == "baseline", 0.3,
  ifelse(trait == "growth", 0.4 + 0.8 * treatment,
         -0.2 - 0.5 * treatment)
))
dat$y <- dat$eta + rnorm(nrow(dat), sd = 0.2)

Inspect the expanded coefficient names

Constraint names must match the expanded fixed-effect design, not a guess based on the raw formula. Factor contrasts, interactions, transformations, and factor level order can all change these names.

fixed_formula <- ~ 0 + trait + (0 + trait):treatment
colnames(model.matrix(fixed_formula, dat))
#> [1] "traitbaseline"           "traitcolour"            
#> [3] "traitgrowth"             "traitbaseline:treatment"
#> [5] "traitcolour:treatment"   "traitgrowth:treatment"

The baseline treatment coefficient is named traitbaseline:treatment, so the constraint is:

fit_long <- gllvmTMB(
  y ~ 0 + trait + (0 + trait):treatment,
  data = dat,
  trait = "trait",
  unit = "site",
  family = gaussian(),
  Xcoef_fixed = c("traitbaseline:treatment" = 0),
  silent = TRUE
)

long_fixed <- tidy(fit_long, "fixed")
long_fixed[grepl(":treatment$", long_fixed$term),
           c("term", "estimate", "std.error", "status")]
#>                      term   estimate  std.error    status
#> 4 traitbaseline:treatment  0.0000000         NA     fixed
#> 5   traitcolour:treatment -0.4548902 0.03013778 estimated
#> 6   traitgrowth:treatment  0.8203414 0.03013778 estimated

The zero is imposed, not estimated. Therefore estimate = 0, std.error = NA, and status = "fixed" are expected. The missing standard error does not indicate a failed Hessian calculation. Inference for all other parameters is conditional on the zero restriction being correct, and the model degrees of freedom decrease by one for each pinned coefficient.

The same constraint from wide data

The equivalent traits(...) call reaches the same stacked design and therefore uses the same expanded coefficient name.

dat_wide <- stats::reshape(
  dat[c("site", "trait", "treatment", "y")],
  idvar = c("site", "treatment"),
  timevar = "trait",
  direction = "wide"
)
names(dat_wide) <- sub("^y\\.", "", names(dat_wide))

fit_wide <- gllvmTMB(
  traits(baseline, growth, colour) ~ 1 + treatment,
  data = dat_wide,
  unit = "site",
  family = gaussian(),
  Xcoef_fixed = c("traitbaseline:treatment" = 0),
  silent = TRUE
)

wide_fixed <- tidy(fit_wide, "fixed")
comparison <- merge(
  long_fixed[grepl(":treatment$", long_fixed$term),
             c("term", "estimate", "status")],
  wide_fixed[grepl(":treatment$", wide_fixed$term),
             c("term", "estimate", "status")],
  by = "term",
  suffixes = c("_long", "_wide")
)
comparison
#>                      term estimate_long status_long estimate_wide status_wide
#> 1 traitbaseline:treatment     0.0000000       fixed     0.0000000       fixed
#> 2   traitcolour:treatment    -0.4548902   estimated    -0.4548902   estimated
#> 3   traitgrowth:treatment     0.8203414   estimated     0.8203416   estimated

Equivalent designs and contrast settings produce the same names and estimates; arbitrarily different codings need not.

To pin several prespecified direct effects, supply several uniquely named entries, for example:

Xcoef_fixed = c(
  "traitbaseline:treatment" = 0,
  "traitcolour:temperature" = 0
)

Errors and next actions

  • A name is not recognized: inspect colnames(model.matrix(...)) for the exact expanded design and check factor levels and contrasts.
  • A name is duplicated: provide each expanded coefficient once.
  • A non-zero fixed value is requested: only structural zero is implemented; leave the coefficient free if zero is not the intended value.
  • REML = TRUE errors: fixed-coefficient pinning is currently ML-only.
  • A fixed row has an unavailable SE or CI: this is expected because the value was imposed rather than estimated.
  • The scientific question is whether the effect equals zero: do not impose the answer with Xcoef_fixed; fit an appropriate unrestricted comparison.

Xcoef_fixed acts on direct response-specific mean coefficients. It is different from: