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.