besem_ordered: Bifactor ESEM for Ordinal Data

View source: R/ordered_esem.R

besem_orderedR Documentation

Bifactor ESEM for Ordinal Data

Description

Fits B-ESEM on ordinal indicators. Called from besem when ordered is set. For Mplus-aligned loadings and fit indices use method = "rotation" (default; also used by run_comparison).

Usage

besem_ordered(
  data,
  specific_factors,
  indicators = NULL,
  g_name = "G",
  method = c("rotation", "set-esem"),
  n_starts = 30L,
  r_obs_override = NULL,
  group = NULL,
  group_equal = NULL,
  missing = "listwise",
  std.lv = TRUE,
  ...
)

Arguments

data

A data.frame of observed ordered indicators.

specific_factors

Named list of specific factor -> item assignments.

indicators

Character vector of all indicator names. If NULL, derived from specific_factors.

g_name

Character. General factor name. Default "G".

method

Character. "rotation" (default): custom DWLS/WLSMV + orthogonal targetT rotation (Mplus-aligned B-ESEM). "set-esem": lavaan WLSMV bifactor CFA with non-primary specific loadings fixed at 0* and starts from polychoric EFA—does not match Mplus B-ESEM loadings (restricted model; use only for debugging or when you want zero specific-factor cross-loadings).

n_starts

Integer. Random rotation starts. Default 30L (matches Mplus).

r_obs_override

Optional observed correlation matrix to fit instead of lavaan's polychoric correlations. The weight matrix still comes from lavaan, so a matrix that differs from lavaan's by more than 0.001 triggers a warning.

group

Character. Grouping variable for multi-group models.

group_equal

Character vector of lavaan equality constraints.

missing

Character. Missing data handling. Default "listwise". run_comparison passes "pairwise" from specify_model when fitting via the pipeline. Set explicitly when comparing with esem_ordered (default "pairwise").

std.lv

Logical. Default TRUE.

...

Additional arguments passed to lavaan::cfa().

Value

An object of class c("besem_fit_ordered","besem_fit","esem_fit"). For method = "rotation", lavaan_fit is auxiliary (one-factor CFA); use std_rotated_loadings, wlsmv_stats, std_loadings, and fitMeasures(x). For method = "set-esem", lavaan_fit is the fitted bifactor CFA.

Method "set-esem" vs "rotation"

rotation estimates cross-loadings (targeted toward zero), then rotates— same estimand as Mplus B-ESEM WLSMV. set-esem fixes non-primary specific loadings at zero in lavaan; G and primary loadings are re-optimized under that harder constraint, so loadings and fit differ from Mplus.

Estimator paths

Continuous ESEM / B-ESEM

esem and besem use lavaan::cfa() with a native efa() block (integrated MLR estimation and rotation).

Ordered ESEM

esem(ordered = ...) calls esem_ordered. Default method = "lavaan" (lavaan WLSMV, efa() block, post-hoc rotation). method = "rotation" uses a custom DWLS pipeline (polychoric correlations + GPArotation).

Ordered B-ESEM

besem(ordered = ...) calls besem_ordered. Default method = "rotation" (custom DWLS/WLSMV + orthogonal targetT; this is what run_comparison uses for ordered data). method = "set-esem" fits a lavaan WLSMV bifactor CFA with non-primary specific loadings fixed at zero; it does not match Mplus B-ESEM loadings.

Multi-group invariance

esem_invariance uses lavaan multi-group efa() models plus explicit syntax patches for ordered B-ESEM. See that help page for scope limits.

Missing-data defaults

Defaults differ by entry point; pass missing explicitly when fitting ESEM and B-ESEM separately on the same ordered dataset.

  • specify_model / run_comparison: "pairwise" for ordered data, "listwise" for continuous.

  • esem_ordered: "pairwise".

  • besem_ordered: "listwise" (pipeline still passes missing from the spec when used via run_comparison).

  • esem / besem (continuous): "listwise".

The lavaan_fit slot on custom WLSMV fits

For besem_ordered(method = "rotation") and esem_ordered(method = "rotation"), $lavaan_fit is often an auxiliary one-factor WLSMV CFA used only to extract DWLS weight matrices—not the fitted ESEM/B-ESEM model. Use std_loadings, parameters, and fitMeasures(x) on the esem_fit wrapper; fitMeasures(x) reads wlsmv_stats when present. Do not interpret summary(x$lavaan_fit), modindices(x), or coef(x) as the rotated solution unless you know the fit used the lavaan efa() path (method = "lavaan" or method = "set-esem" for B-ESEM).

See Also

esem_ordered, besem, run_comparison

Examples

data("HolzingerSwineford1939", package = "lavaan")

# Derive ordered (5-category Likert) versions of the 9 continuous items
items <- paste0("x", 1:9)
ord <- as.data.frame(lapply(HolzingerSwineford1939[, items], function(v) {
  as.integer(cut(v, breaks = quantile(v, probs = seq(0, 1, 0.2)),
                 include.lowest = TRUE))
}))
names(ord) <- items


fit_b_ord <- besem_ordered(
  data = ord,
  specific_factors = list(
    Visual  = c("x1", "x2", "x3"),
    Textual = c("x4", "x5", "x6"),
    Speed   = c("x7", "x8", "x9")
  ),
  n_starts = 5L
)
summary(fit_b_ord, fit.measures = TRUE, standardized = TRUE)



bifactory documentation built on Oct. 28, 2026, 1:06 a.m.