Complete continuous-data models

Scope

This guide covers models with continuous, complete data. Normal-theory maximum likelihood is the most thoroughly studied part of semTests and offers the widest menu of test options. Complete-data GLS and ULS are supported too, and their results have been checked against a separate implementation.

library("semTests")
library("lavaan")

model <- "
  visual  =~ x1 + x2 + x3
  textual =~ x4 + x5 + x6
  speed   =~ x7 + x8 + x9
"
data <- HolzingerSwineford1939

Overall goodness of fit

Ask lavaan for a robust test when fitting the model. This stores the extra sample information that semTests needs.

fit_ml <- cfa(model, data, estimator = "MLM")
pvalues(fit_ml)
#>    peba4_rls 
#> 5.165712e-07 
#> estimator: ML (MLM) | data: continuous | information: expected | df: 24

The default uses penalized eigenvalue block averaging. You can also request a small collection of tests in one call:

pvalues(
  fit_ml,
  c("STD_ML", "SB_ML", "SS_ML", "ALL_ML", "PEBA4_ML")
)
#>       std_ml        sb_ml        ss_ml       all_ml     peba4_ml 
#> 8.502553e-09 4.416190e-08 2.746479e-07 2.141358e-06 1.529609e-07 
#> estimator: ML (MLM) | data: continuous | information: expected | df: 24

The names let you choose three ingredients:

The unbiased gamma and RLS statistic are available for continuous, complete-data fits from lavaan’s ML family, including MLM and MLR fits:

pvalues(
  fit_ml,
  c("SB_UG_ML", "PEBA4_UG_ML", "PEBA4_RLS", "PEBA4_UG_RLS")
)
#>     sb_ug_ml  peba4_ug_ml    peba4_rls peba4_ug_rls 
#> 5.962072e-08 2.006756e-07 5.165712e-07 6.669703e-07 
#> estimator: ML (MLM) | data: continuous | information: expected | df: 24

Reading the result

The printed footer records what was actually used. The same information is available programmatically:

result <- pvalues(fit_ml, "PEBA4_ML")
attr(result, "semtests")
#> $estimator
#> [1] "ML"
#> 
#> $estimator_requested
#> [1] "MLM"
#> 
#> $lavaan_test
#> [1] "standard"        "satorra.bentler"
#> 
#> $information
#> [1] "expected" "expected"
#> 
#> $data_type
#> [1] "continuous"
#> 
#> $missing
#> [1] "listwise"
#> 
#> $df
#> [1] 24
#> 
#> $nested
#> [1] FALSE
#> 
#> $method
#> [1] NA
#> 
#> $requested_tests
#> [1] "PEBA4_ML"
#> 
#> $statistic
#> PEBA4_ML 
#>     "ml" 
#> 
#> $gamma
#> PEBA4_ML 
#> "biased"

That record is useful when several kinds of models end up in the same analysis.

Nested comparison

The constrained model goes first. Here two loading equalities are tested jointly:

constrained <- "
  visual  =~ x1 + a*x2 + a*x3
  textual =~ x4 + b*x5 + b*x6
  speed   =~ x7 + x8 + x9
"

m1 <- cfa(model, data, estimator = "MLM")
m0 <- cfa(constrained, data, estimator = "MLM")

pvalues_nested(m0, m1)
#>  pall_ug_ml 
#> 0.003479121 
#> estimator: ML (MLM) | data: continuous | information: expected | df: 2 | nested (method 2000)

Satorra’s 2000 reduction is the method used for nested comparison:

pvalues_nested(m0, m1, method = "2000", tests = c("SB_ML", "PALL_ML"))
#>       sb_ml     pall_ml 
#> 0.002887682 0.003285123 
#> estimator: ML (MLM) | data: continuous | information: expected | df: 2 | nested (method 2000)

PALL is the recommended default for nested comparison. The Satorra–Bentler 2001 construction has been withdrawn because it performs poorly, so method = "2001" points you back to "2000".

GLS and ULS

GLS supplies the required sample information directly. For ULS, ask lavaan for a robust test so that it computes the same ingredient:

fit_gls <- cfa(model, data, estimator = "GLS")
fit_uls <- cfa(
  model, data,
  estimator = "ULS", test = "satorra.bentler"
)

pvalues(fit_gls, "PEBA4")
#>     peba4_ml 
#> 8.854857e-07 
#> estimator: GLS | data: continuous | information: expected | df: 24
pvalues(fit_uls, "PEBA4")
#>     peba4_ml 
#> 2.527237e-08 
#> estimator: ULS | data: continuous | information: expected | df: 24

For GLS and ULS, use the standard statistic and the usual biased gamma. RLS and UG are limited to the complete-data ML setting.

Practical checks

A quick check on these points catches most avoidable problems:

  1. Inspect convergence and admissibility. semTests refuses nonconvergence and warns when lavaan’s post-estimation check fails.
  2. Request a robust lavaan test whenever the fit would otherwise omit Gamma.
  3. Report the test name, statistic, and gamma choice.
  4. Keep the semtests attribute with saved results.
  5. Make sure nested models really are nested. semTests checks that the fits use comparable data and options, while the model logic is still yours.

See vignette("categorical-data", package = "semTests") and vignette("fiml-missing-data", package = "semTests") before carrying this workflow to categorical or missing data.

References

Browne, M. W. (1974). Generalized least squares estimators in the analysis of covariance structures. South African Statistical Journal, 8, 1–24.

Du, H., & Bentler, P. M. (2022). 40-Year Old Unbiased Distribution Free Estimator Reliably Improves SEM Statistics for Nonnormal Data. Structural Equation Modeling, 29(6), 872–887. https://doi.org/10.1080/10705511.2022.2063870

Foldnes, N., Moss, J., & Grønneberg, S. (2025). Improved goodness of fit procedures for structural equation models. Structural Equation Modeling: A Multidisciplinary Journal, 32(1), 1–13. https://doi.org/10.1080/10705511.2024.2372028

Foldnes, N., Grønneberg, S., & Moss, J. (2026). Penalized eigenvalue block averaging: Extension to nested model comparison and Monte Carlo evaluations. Behavior Research Methods, 58, article 107. https://doi.org/10.3758/s13428-026-02968-4

Satorra, A. (2000). Scaled and adjusted restricted tests in multi-sample analysis of moment structures. In R. D. H. Heijmans, D. S. G. Pollock, & A. Satorra (Eds.), Innovations in Multivariate Statistical Analysis (pp. 233–247). Kluwer Academic. https://doi.org/10.1007/978-1-4615-4603-0_17

Satorra, A., & Bentler, P. M. (2001). A scaled difference chi-square test statistic for moment structure analysis. Psychometrika, 66(4), 507–514. https://doi.org/10.1007/BF02296192