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