Weighted Least Squares (WLS) in umx

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 x1x9 as ordered factors. Here we use only the three visual items (x1x3, two levels each):

data(HSwls, package = "umx")
items = c("x1", "x2", "x3")
str(HSwls[, items])
#> 'data.frame':    301 obs. of  3 variables:
#>  $ x1: Ord.factor w/ 2 levels "1"<"2": 1 2 1 2 1 2 1 2 1 1 ...
#>   ..- attr(*, "mxFactor")= logi TRUE
#>  $ x2: Ord.factor w/ 2 levels "1"<"2": 2 1 1 2 1 1 1 2 1 1 ...
#>   ..- attr(*, "mxFactor")= logi TRUE
#>  $ x3: Ord.factor w/ 2 levels "1"<"2": 1 1 1 2 1 2 1 1 1 1 ...
#>   ..- attr(*, "mxFactor")= logi TRUE

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)
)
#> 
#> 
#> Table: Parameter loadings for model 'Visual_1f_DWLS'
#> 
#> |   |name               | Estimate|SE    |CI                   |type            |
#> |:--|:------------------|--------:|:-----|:--------------------|:---------------|
#> |1  |visual_to_x3       |    0.783|0.23  |0.783 [0.333, 1.234] |Factor loading  |
#> |2  |visual_to_x2       |    0.660|0.18  |0.66 [0.307, 1.013]  |Factor loading  |
#> |3  |visual_to_x1       |    0.862|0.267 |0.862 [0.338, 1.386] |Factor loading  |
#> |7  |visual_with_visual |    1.000|0     |1 [1, 1]             |Factor Variance |
#> |4  |x3_with_x3         |    1.000|0     |1 [1, 1]             |Residual        |
#> |5  |x2_with_x2         |    1.000|0     |1 [1, 1]             |Residual        |
#> |6  |x1_with_x1         |    1.000|0     |1 [1, 1]             |Residual        |
#> 
#> *Statistical Note*: Continuous WLS/DWLS - conventional CFI/TLI/RMSEA cutoffs do not apply. Prefer SRMR for absolute fit; nested comparisons use Strict Satorra-Bentler (2010) Delta chi-square via umxCompare. See ?umxCompare.
#> Algebra'threshMat':
#> 
#> 
#> |     |    x3|    x2|    x1|
#> |:----|-----:|-----:|-----:|
#> |th_1 | 0.143| 0.216| 0.149|
umxSummary(mDWLS, std = TRUE, refModels = FALSE)
#> 
#> 
#> Table: Parameter loadings for model 'Visual_1f_DWLS'
#> 
#> |   |name               | Std.Estimate|Std.SE |CI                |type            |
#> |:--|:------------------|------------:|:------|:-----------------|:---------------|
#> |1  |visual_to_x3       |         0.62|0.11   |0.62 [0.4, 0.84]  |Factor loading  |
#> |2  |visual_to_x2       |         0.55|0.1    |0.55 [0.35, 0.76] |Factor loading  |
#> |3  |visual_to_x1       |         0.65|0.12   |0.65 [0.42, 0.88] |Factor loading  |
#> |7  |visual_with_visual |         1.00|0      |1 [1, 1]          |Factor Variance |
#> |4  |x3_with_x3         |         0.62|0.14   |0.62 [0.35, 0.89] |Residual        |
#> |5  |x2_with_x2         |         0.70|0.12   |0.7 [0.47, 0.92]  |Residual        |
#> |6  |x1_with_x1         |         0.57|0.15   |0.57 [0.28, 0.87] |Residual        |
#> 
#> *Statistical Note*: Continuous WLS/DWLS - conventional CFI/TLI/RMSEA cutoffs do not apply. Prefer SRMR for absolute fit; nested comparisons use Strict Satorra-Bentler (2010) Delta chi-square via umxCompare. See ?umxCompare.
#> Algebra'threshMat':
#> 
#> 
#> |     |    x3|    x2|    x1|
#> |:----|-----:|-----:|-----:|
#> |th_1 | 0.143| 0.216| 0.149|

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")
#> 
#> 
#> Table: Parameter loadings for model 'drop_x1'
#> 
#> |   |name               | Estimate|SE    |CI                  |type            |
#> |:--|:------------------|--------:|:-----|:-------------------|:---------------|
#> |1  |visual_to_x3       |    0.783|0.002 |0.783 [0.78, 0.787] |Factor loading  |
#> |2  |visual_to_x2       |    0.660|0.002 |0.66 [0.655, 0.665] |Factor loading  |
#> |6  |visual_with_visual |    1.000|0     |1 [1, 1]            |Factor Variance |
#> |3  |x3_with_x3         |    1.000|0     |1 [1, 1]            |Residual        |
#> |4  |x2_with_x2         |    1.000|0     |1 [1, 1]            |Residual        |
#> |5  |x1_with_x1         |    1.000|0     |1 [1, 1]            |Residual        |
#> 
#> *Statistical Note*: Continuous WLS/DWLS - conventional CFI/TLI/RMSEA cutoffs do not apply. Prefer SRMR for absolute fit; nested comparisons use Strict Satorra-Bentler (2010) Delta chi-square via umxCompare. See ?umxCompare.
#> Algebra'threshMat':
#> 
#> 
#> |     |    x3|    x2|    x1|
#> |:----|-----:|-----:|-----:|
#> |th_1 | 0.143| 0.216| 0.113|
umxCompare(mDWLS, mNested)
#> 
#> 
#> Table: Table of Model Comparisons
#> 
#> |Model          |Compared_to    | EP|Chi   |   AIC|CFI |delta_CFI |SRMR |delta_SRMR |delta_df |diffFit |p  |
#> |:--------------|:--------------|--:|:-----|-----:|:---|:---------|:----|:----------|:--------|:-------|:--|
#> |Visual_1f_DWLS |<NA>           |  6|0     | 12.00|    |          |0    |           |         |        |   |
#> |drop_x1        |Visual_1f_DWLS |  5|37.98 | 47.98|    |          |0.22 |0.22       |1        |        |   |
#> 
#> *Note*: EP = free parameters; delta_df = change in df.
#>   - Chi: Satorra-Bentler (2010) scaled WLS discrepancy (same as umxSummary; not Browne output$chi).
#>   - SRMR / delta_SRMR: preferred absolute residual summary and its change.
#>   - CFI / delta_CFI: descriptive only (no conventional cutoffs under WLS/GSEM).
#>   - AIC: Chi + 2*EP on the same Chi scale (convenience only for GSEM).
#>   - diffFit: Satorra-Bentler (2010) scaled nested Delta chi-square when Jacobians are available and F is nested-monotone.
#> 
#> *Statistical Note*: Continuous WLS/DWLS - conventional CFI/TLI/RMSEA cutoffs do not apply. Prefer SRMR for absolute fit; nested comparisons use Strict Satorra-Bentler (2010) Delta chi-square (diffFit). See ?umxCompare.

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

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