esem_ordered: ESEM for Ordinal (Likert) Data

View source: R/ordered_esem.R

esem_orderedR Documentation

ESEM for Ordinal (Likert) Data

Description

Fits ESEM on ordinal indicators (WLSMV / Theta parameterization). Called automatically from esem when ordered is set.

Usage

esem_ordered(
  data,
  nfactors,
  indicators = NULL,
  rotation = "target",
  target = NULL,
  factor_names = NULL,
  method = c("lavaan", "rotation"),
  n_starts = 100L,
  r_obs_override = NULL,
  std.lv = TRUE,
  missing = "pairwise",
  group = NULL,
  group_equal = NULL,
  heywood_fix = TRUE,
  n_obs = NULL,
  ...
)

Arguments

data

A data.frame of observed ordered indicators.

nfactors

Integer. Number of latent factors.

indicators

Character vector of item names. If NULL, all columns of data except group are used.

rotation

Character. Rotation method. Default "target". Supports "target" (oblique), "targetT" (orthogonal), "geomin", "geominT" (orthogonal geomin), "oblimin", "varimax".

target

A target matrix from make_target. Required when rotation contains "target". Convention: NA = free (no penalty), 0 = targeted toward zero.

factor_names

Optional character vector of factor names.

method

Character. "lavaan" (default): lavaan efa() + WLSMV. "rotation": custom DWLS + post-hoc target rotation (see The lavaan_fit slot on custom WLSMV fits).

n_starts

Integer. Number of random rotation starts when method = "rotation". Default 100L.

r_obs_override

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

std.lv

Logical. Fix factor variances to 1. Default TRUE.

missing

Character. Missing data handling passed to lavaan. Default "pairwise" (Mplus-style polychoric pairs). Use "listwise" for complete cases. When comparing with besem_ordered on the same data, set missing explicitly on both calls (besem_ordered defaults to "listwise").

group

Character. Grouping variable name for multi-group models.

group_equal

Character vector of lavaan equality constraints.

heywood_fix

Logical. Retry rotation with Cholesky unrotation when a standardised loading exceeds 1. Default TRUE.

n_obs

Ignored (kept for backward compatibility).

...

Additional arguments passed to lavaan::cfa().

Details

Default (method = "lavaan"): lavaan's efa() block with WLSMV (unrestricted model, post-hoc rotation, delta-method SEs)—aligned with Mplus ESTIMATOR = WLSMV; ROTATION = TARGET (oblique).

Alternate (method = "rotation"): custom DWLS pipeline (polychoric correlations, weight matrix and asymptotic covariance from one auxiliary lavaan fit, GPArotation, sandwich + rotation Jacobian SEs).

Mplus equivalence

Mplus:
  ANALYSIS:
    ESTIMATOR = WLSMV;
    ROTATION  = TARGET;
    PARAMETERIZATION = THETA;
  MODEL:
    EX MD CI BY item1-item18 (*1);

R (this function):
  esem_ordered(data, nfactors = 3, rotation = "target", target = tgt,
               factor_names = c("EX","MD","CI"))

Algorithm (method = "lavaan")

lavaan's efa() block fits an unrestricted k-factor model under WLSMV, applies post-hoc rotation, and propagates standard errors through the rotation transformation (Asparouhov & Muthen, 2009).

Value

An object of class c("esem_fit_ordered", "esem_fit"). For method = "lavaan", lavaan_fit is the fitted ESEM model. For method = "rotation", lavaan_fit is an auxiliary one-factor CFA; use std_rotated_loadings, wlsmv_stats, and std_loadings. Slot estimator is "WLSMV" or "DWLS" depending on path.

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

Heywood fix and loadings

When heywood_fix = TRUE (default on single-group esem and esem_ordered), std_loadings may reflect a post-hoc rotation correction while lavaan_fit still holds the pre-correction lavaan solution. Prefer std_loadings(x) for reported loadings in that case.

References

Asparouhov, T., & Muthen, B. (2009). Exploratory structural equation modeling. Structural Equation Modeling, 16(3), 397–438.

See Also

esem for continuous data, besem_ordered for bifactor ordered ESEM.

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

tgt <- make_target(
  list(Visual = c("x1", "x2", "x3"),
       Textual = c("x4", "x5", "x6"),
       Speed = c("x7", "x8", "x9")),
  item_names = items
)


fit_ord <- esem_ordered(
  data         = ord,
  nfactors     = 3,
  indicators   = items,
  rotation     = "target",
  target       = tgt,
  factor_names = c("Visual", "Textual", "Speed"),
  n_starts     = 5L
)

summary(fit_ord, fit.measures = TRUE, standardized = TRUE)
std_loadings(fit_ord)
factor_correlations(fit_ord)



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