semTests computes robust p-values for
structural equation models fit with lavaan. The standard
chi-square test of model fit is anti-conservative under non-normality,
and the usual corrections (Satorra–Bentler and friends) only partly fix
it. The eigenvalue-based tests in this package target the limiting
null law of the test statistic directly: that law is a weighted sum
of independent chi-squares, \(\sum_j \lambda_j
\chi^2_1\), whose weights \(\lambda_j\) are the eigenvalues of a matrix
built from the model and the asymptotic covariance of the sample
moments. Because this holds for any minimum-discrepancy estimator, the
tests are not specific to normal-theory maximum likelihood.
The penalized eigenvalue block-averaging (pEBA) and
penalized regression (pOLS) methods were introduced by
Foldnes, Moss, & Grønneberg (2024) and extended to nested model
comparison by Foldnes, Grønneberg, & Moss (2026).
Fit a model with lavaan, then call
pvalues(). Fit with a robust test (e.g.
estimator = "MLM" or "MLR") so that lavaan
exposes the asymptotic moment covariance the correction needs.
model <- "visual =~ x1 + x2 + x3
textual =~ x4 + x5 + x6
speed =~ x7 + x8 + x9"
fit <- cfa(model, HolzingerSwineford1939, estimator = "MLM")
pvalues(fit)
#> peba4_rls
#> 5.165737e-07
#> estimator: ML | data: continuous | information: expected | df: 24The result is a named vector that also prints a one-line provenance
footer recording the estimator, data type, information matrix, and
degrees of freedom actually used. The full option record is available
with attr(x, "semtests").
Each requested test is a string of the form
(test)_(ug?)_(ml|rls):
pEBA<j>
(penalized block-averaging, the recommended default; j is
the number of blocks, typically 2–6), pOLS<gamma>
(penalized regression), pall/all,
eba<j>, or a traditional statistic std,
sb (Satorra–Bentler), ss (scaled-and-shifted),
sf (scaled F).UG to use the unbiased
Du–Bentler (2022) gamma instead of the standard biased one.ML
(normal-theory discrepancy) or RLS (Browne’s reweighted
least squares). When omitted, a fit-appropriate default is chosen.Several tests can be requested at once:
pvalues(fit, c("SB_ML", "pEBA4_ML", "pOLS2_ML"))
#> sb_ml peba4_ml pols2_ml
#> 4.416190e-08 1.529685e-07 1.519741e-07
#> estimator: ML | data: continuous | information: expected | df: 24The UG gamma and the RLS statistic are
defined only for the classical case (continuous, complete data,
estimator = "ML"); off that case they are refused with an
informative error rather than silently degrading.
To compare two nested models, fit both and pass them to
pvalues_nested() with the constrained
model first. Here we test weak invariance of the textual loadings:
constrained <- "visual =~ x1 + x2 + x3
textual =~ a*x4 + a*x5 + a*x6
speed =~ x7 + x8 + x9"
m1 <- cfa(model, HolzingerSwineford1939, estimator = "MLM")
m0 <- cfa(constrained, HolzingerSwineford1939, estimator = "MLM")
pvalues_nested(m0, m1)
#> pall_ug_ml
#> 0.0166158
#> estimator: ML | data: continuous | information: expected | df: 2 | nested (method 2000)pall is the recommended default for nested comparison
(Foldnes, Grønneberg, & Moss, 2026). The reduction
method defaults to "2000" (Satorra 2000);
"2001" is also available for complete-data fits.
The same machinery applies to other estimators. The non-ML paths
below are experimental as of this release; the
classical ML path is stable. See ?semTests-support for the
authoritative matrix of what is supported.
GLS and ULS work directly (ULS needs a robust test for the asymptotic covariance):
gls <- cfa(model, HolzingerSwineford1939, estimator = "GLS")
pvalues(gls, "pEBA4")
#> peba4_ml
#> 8.854857e-07
#> estimator: GLS | data: continuous | information: expected | df: 24
uls <- cfa(model, HolzingerSwineford1939, estimator = "ULS",
test = "satorra.bentler")
pvalues(uls, "pEBA4")
#> peba4_ml
#> 2.527109e-08
#> estimator: ULS | data: continuous | information: expected | df: 24Missing data is handled through full-information maximum likelihood.
Fit with missing = "fiml" and pass the fit to
pvalues() as usual. FIML uses the biased gamma and the
standard statistic, so the UG/RLS suffixes do
not apply; the default suffix-free tests are the right choice.
HS <- HolzingerSwineford1939
set.seed(1)
HS$x1[sample(nrow(HS), 60)] <- NA
fit_fiml <- cfa(model, HS, missing = "fiml", estimator = "MLR")
pvalues(fit_fiml)
#> peba4_ml
#> 2.845788e-08
#> estimator: ML (FIML) | data: continuous | information: observed | df: 24FIML inference rests on the observed information under MAR (lavaan’s
default for missing = "fiml"); a fit using expected
information triggers a warning, since that is valid only under the
stronger MCAR assumption.
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. (2024). Improved goodness of fit procedures for structural equation models. Structural Equation Modeling: A Multidisciplinary Journal, 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. https://doi.org/10.3758/s13428-026-02968-4