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.
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])
#> '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 TRUEumxRAMtype |
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) |
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|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.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)umxCompare() rather than raw \(\Delta\chi^2\) of unadjusted
statistics.?umxCompare).See ?umxRAM, ?umxSummary,
?umxCompare, and the GenomicMx OpenMx WLS/Jacobian notes
for engine requirements.