Weighted Least Squares (WLS) in umx

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 6,
  fig.height = 5
)
library(umx)
library(OpenMx)
umx_set_auto_plot(FALSE)

Introduction

Most Structural Equation Models (SEMs) are fitted using Maximum Likelihood (ML), which assumes that the manifest variables are continuous and multivariate normal. In psychology and related fields, indicators are often ordinal (binary or Likert). Standard ML on integer codes is biased; weighted least squares (WLS / DWLS) fits thresholds and polychoric structure instead.

This vignette shows a compact DWLS example on three binary visual tests from HSwls. Full WLS (dense weight matrix) is slower and is left as an optional local chunk.


1. Data: three ordinal visual items

HSwls stores x1–x9 as ordered factors. Here we use only the three visual items (x1–x3, two levels each):

data(HSwls, package = "umx")
items = c("x1", "x2", "x3")
str(HSwls[, items])

2. Estimators in umxRAM

| type | Role | |--------|------| | "FIML" / "Auto" | ML (continuous; not appropriate for ordered factors as-is) | | "WLS" | Full WLS (dense useWeight) — large models need big $N$ | | "DWLS" | Diagonally weighted least squares (default practical choice) |

Live demo: one-factor DWLS on three binary items

Binary indicators under DWLS need means fixed at 0 for identification (thresholds free). One clean mean path per indicator — no duplicates.

mDWLS = umxRAM("Visual_1f_DWLS", data = HSwls, type = "DWLS",
    umxPath("visual", to = items),
    umxPath(var = items),
    umxPath(var = "visual", fixedAt = 1),
    # identification for binary: latent residual means fixed at 0
    umxPath("one", to = items, fixedAt = 0)
)
umxSummary(mDWLS, std = TRUE, refModels = FALSE)

Nested comparison

Drop one loading and compare with the Satorra–Bentler (2010) path when Jacobians are available (GenomicMx OpenMx):

# Free loading label is typically visual_to_x1 (umx path naming)
labs = names(omxGetParameters(mDWLS))
dropLab = labs[grepl("visual_to_x1|x1$", labs)][1]
mNested = umxModify(mDWLS, update = dropLab, name = "drop_x1", tryHard = "no")
umxCompare(mDWLS, mNested)

Optional: full WLS (local only)

Full WLS builds a dense weight matrix and is slower; not run during package check:

mWLS = umxRAM("Visual_1f_WLS", data = HSwls, type = "WLS",
    umxPath("visual", to = items),
    umxPath(var = items),
    umxPath(var = "visual", fixedAt = 1),
    umxPath("one", to = items, fixedAt = 0)
)
umxSummary(mWLS, std = TRUE, refModels = FALSE)

3. Practical notes

Sample size

Convergence

Reporting

  1. State the estimator (e.g. DWLS with robust / SB adjustments).
  2. Prefer SRMR for absolute residual fit under continuous WLS guidance; use SB nested tests via umxCompare() rather than raw $\Delta\chi^2$ of unadjusted statistics.
  3. Conventional CFI/TLI cutoffs do not transfer cleanly to continuous WLS (see ?umxCompare).

References

See ?umxRAM, ?umxSummary, ?umxCompare, and the GenomicMx OpenMx WLS/Jacobian notes for engine requirements.



Try the umx package in your browser

Any scripts or data that you put into this service are public.

umx documentation built on Oct. 11, 2026, 5:07 p.m.