--- title: "Weighted Least Squares (WLS) in umx" subtitle: "Fitting and Interpreting Ordinal Factor Models" author: "Timothy C. Bates" date: "`r Sys.Date()`" output: rmarkdown::html_vignette: toc: true vignette: > %\VignetteIndexEntry{Weighted Least Squares (WLS) in umx} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} 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): ```{r load_data} 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. ```{r fit_dwls, message = FALSE, warning = FALSE} 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): ```{r nested_compare, message = FALSE, warning = FALSE} # 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: ```{r fit_wls_optional, eval = FALSE} 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 * **Full WLS:** often needs large $N$ as the asymptotic covariance of residual moments grows with thresholds and correlations. * **DWLS:** uses the diagonal of the weight matrix; more stable at modest $N$. ### Convergence * Empty threshold categories → collapse levels. * Prefer DWLS if full WLS is unstable. ### 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.