An R package for robust tests of overall fit and nested model
comparisons in structural equation models. It works with
lavaan and implements the penalized eigenvalue block
averaging and penalized regression p values of Foldnes, Moss, and
Grønneberg (2025), along with their extension to nested model
comparison in Foldnes, Grønneberg,
and Moss (2026).
It covers ML, GLS, ULS, categorical DWLS and ULS, and continuous FIML
with one or several groups, for both single models and nested
comparisons. Observed predictors are supported under joint random-x
inference. See ?semTests-support for the exact list.
Install the released version from CRAN:
install.packages("semTests")Or try the development version:
# install.packages("remotes")
remotes::install_github("JonasMoss/semTests")Here is a quick CFA:
library("semTests")
model <- "visual =~ x1 + x2 + x3
textual =~ x4 + x5 + x6
speed =~ x7 + x8 + x9"
object <- lavaan::cfa(model, lavaan::HolzingerSwineford1939, estimator = "MLM")
pvalues(object, tests = c("SB", "SS", "PEBA4"))
#> sb_rls ss_rls peba4_rls
#> 1.726558e-07 9.251198e-07 5.165712e-07
#> estimator: ML (MLM) | data: continuous | information: expected | df: 24For a nested comparison, pass the constrained model first:
constrained <- "visual =~ x1 + x2 + x3
textual =~ a*x4 + a*x5 + a*x6
speed =~ x7 + x8 + x9"
m1 <- lavaan::cfa(model, lavaan::HolzingerSwineford1939, estimator = "MLM")
m0 <- lavaan::cfa(constrained, lavaan::HolzingerSwineford1939, estimator = "MLM")
pvalues_nested(m0, m1, tests = c("SB", "SS", "PEBA2"))
#> sb_rls ss_rls peba2_rls
#> 0.01023556 0.01092401 0.01051737
#> estimator: ML (MLM) | data: continuous | information: expected | df: 2 | nested (method 2000)The fits need to use the same data, estimator, and fitting choices.
If they arrive in the wrong order, semTests lets you know
and swaps them.
Missing values are fine too. Fit the model with
missing = "fiml" and call pvalues() as usual.
semTests uses observed information by default, following Savalei (2010). The FIML vignette explains this
choice and shows the other options.
HS <- lavaan::HolzingerSwineford1939
set.seed(313)
HS$x1[sample(nrow(HS), 60)] <- NA
fit_fiml <- lavaan::cfa(model, HS, missing = "fiml", estimator = "MLR")
pvalues(fit_fiml)
#> peba4_ml
#> 3.301286e-07
#> estimator: ML (MLR) (FIML) | data: continuous | information: observed | df: 24 | FIML convention: observedOrdered indicators work in much the same way:
HSord <- lavaan::HolzingerSwineford1939
ordered_names <- paste0("x", 1:9)
HSord[ordered_names] <- lapply(
HSord[ordered_names],
function(x) ordered(cut(x, 3))
)
fit_ordinal <- lavaan::cfa(model, HSord, ordered = ordered_names)
pvalues(fit_ordinal)
#> peba4_ml
#> 7.156389e-06
#> estimator: DWLS (WLSMV) | data: categorical | information: expected | df: 24vignette("continuous-data", package = "semTests")vignette("categorical-data", package = "semTests")vignette("fiml-missing-data", package = "semTests")vignette("latent-growth", package = "semTests")vignette("measurement-invariance", package = "semTests")vignette("semTests", package = "semTests")The help page ?semTests-support gives the exact
supported combinations.
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.
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.
Savalei, V. (2010). Expected versus observed information in SEM with incomplete normal and nonnormal data. Psychological Methods, 15(4), 352–367.
Found a bug or have an idea? Open a GitHub issue. Pull requests are welcome too.