Package {semTests}


Type: Package
Title: Robust Test Statistics for Structural Equation Models
Description: Supports penalized eigenvalue block-averaging and penalized regression p-values (Foldnes, Moss, Grønneberg, 2024) <doi:10.1080/10705511.2024.2372028>, including their extension to nested model comparison (Foldnes, Grønneberg, Moss, 2026) <doi:10.3758/s13428-026-02968-4>, as well as traditional p-values such as Satorra-Bentler. All p-values can be calculated using unbiased or biased gamma estimates (Du, Bentler, 2022) <doi:10.1080/10705511.2022.2063870> and two choices of chi square statistics. The tests apply to any minimum-discrepancy estimator – ML, GLS, ULS, and categorical WLSMV/DWLS – with experimental support for full-information maximum-likelihood (FIML) fits under missing data.
Version: 0.9.0
License: GPL (≥ 3)
Encoding: UTF-8
URL: https://github.com/JonasMoss/semTests
Depends: R (≥ 3.5.0)
Imports: lavaan (≥ 0.6-16), methods
Suggests: testthat (≥ 3.0.0), covr, xml2, knitr, rmarkdown
VignetteBuilder: knitr
Config/testthat/edition: 3
Config/roxygen2/version: 8.0.0
NeedsCompilation: no
Packaged: 2026-06-09 12:18:33 UTC; jonas
Author: Jonas Moss ORCID iD [aut, cre], Njål Foldnes ORCID iD [ctb], Steffen Grønneberg ORCID iD [ctb]
Maintainer: Jonas Moss <jonas.moss.statistics@gmail.com>
Repository: CRAN
Date/Publication: 2026-06-09 14:00:02 UTC

Reject anything that is not a fitted lavaan object.

Description

The class gate for pvalues() / pvalues_nested(). It must run before any @-slot access so a NULL / data.frame / list argument fails with a readable message instead of a cryptic S4 "no slot of name ..." error – the nested entry point in particular reads m0@test to compute the degrees of freedom before the support gate runs.

Usage

check_lavaan(x, arg = "object")

Arguments

x

The object passed by the user.

arg

The argument name, used in the message (e.g. "object", "m0").

Value

x, invisibly.


Reject a fit whose configuration is outside the supported surface.

Description

The single entry-level gate for pvalues(). It admits exactly the configurations documented in semTests-support – the supported continuous and categorical estimators, complete data, and single-group FIML – and stops with a pointer to ?semTests-support otherwise. Statistic- and gamma-specific rejections (the normal-theory-only RLS statistic and UG gamma) depend on the parsed test string and stay with the code that consumes them (make_chisqs(), gamma()); this gate is purely fit-shape.

Usage

check_supported(fit)

Arguments

fit

A fitted lavaan object.

Value

fit, invisibly.


Reject a nested pair whose configuration is outside the supported surface.

Description

The single entry-level gate for pvalues_nested(): categorical nesting is deferred, missing-data nesting requires both fits to be FIML, and FIML nesting supports method = "2000" only. Each fit is also run through check_supported(). The UG-gamma rejection for FIML stays in the p-value engine because it depends on the parsed test string.

Usage

check_supported_nested(m0, m1, method, A.method = "exact")

Arguments

m0, m1

Two nested lavaan objects (canonical order: m0 constrained).

method

The nested reduction method, "2000" or "2001".

A.method

The FIML restriction map, "exact" or "delta" (validated upstream; accepted here for signature completeness).

Value

TRUE, invisibly.


Common default value of 2.

Description

Common default value of 2.

Usage

default(x)

Calculate the jth eba pvalue.

Description

Calculate the jth eba pvalue.

Usage

eba_pvalue(chisq, lambdas, j)

FIML goodness-of-fit eigenvalues in saturated eta-space.

Description

FIML goodness-of-fit eigenvalues in saturated eta-space.

Usage

fiml_lambdas(fit, df)

FIML Satorra-2000 nested restriction eigenvalues.

Description

FIML Satorra-2000 nested restriction eigenvalues.

Usage

fiml_lambdas_nested(m0, m1, df, A.method = c("exact", "delta"))

Saturated observed-data FIML eta-space pieces.

Description

Saturated observed-data FIML eta-space pieces.

Usage

fiml_saturated_moments(fit)

Provenance of a semTests_pvalues object.

Description

Records the fit-level options actually used to compute the p-values, so the returned object is self-describing across estimators and data types.

Usage

fit_provenance(fit, nested, method = NA, A.method = NA, df = NULL)

Get gamma from a model.

Description

Get gamma from a model.

Usage

gamma(m1, unbiased = 1, m0 = NULL)

Arguments

m1

Model to extract gamma from.

unbiased

Biased (1), unbiased (2), or both (3).

m0

Optional second model, used if m1 does not work.

Value

List of (un)biased gammas.


Calculate unbiased gamma from gamma and object.

Description

Calculate unbiased gamma from gamma and object.

Usage

gamma_to_gamma_unbiased(gammas, object)

Arguments

gammas

List of gammas for each group.

object

lavaan object that corresponds to gamma.

Value

List of unbiased gammas.


Upper tail of a linear combination of chi-square_1 variables

Description

Computes P(Q > q) for Q = \sum_j \lambda_j Z_j^2 with independent Z_j \sim N(0,1). Drop-in replacement for CompQuadForm::imhof(q, lambda)$Qq: the Imhof integral in the body of the distribution, a Lugannani-Rice saddlepoint in the far tail where the integral degrades.

Usage

imhof_pvalue(q, lambda)

Arguments

q

Numeric scalar (or vector) of thresholds; the observed chi-square.

lambda

Numeric vector of eigenvalues (may be mixed sign).

Value

Numeric vector of upper-tail probabilities, length length(q).


Is this the classical normal-theory, complete-data, ML case?

Description

The Du-Bentler unbiased gamma and the RLS (browne.residual.nt.model) statistic are only defined here; off this case the eigenvalue machinery uses the biased gamma and the estimator's own (uncorrected) statistic.

Usage

is_classic_nt(fit)

Is this a continuous FIML/missing-data lavaan fit?

Description

Is this a continuous FIML/missing-data lavaan fit?

Usage

is_fiml(fit)

Reference spectrum of the nested test, without forming the full UGamma.

Description

For Satorra's (2000) method the nonzero eigenvalues are those of the ⁠m x m⁠ matrix ⁠C^{-1} D' Gamma D⁠ (see nested_factor_2000); the ⁠q x q⁠ UGamma and its eigendecomposition are avoided. The (2001) method has no such reduction, so the top-df eigenvalues of the full ⁠(U0 - U1) Gamma⁠ are returned.

Usage

lambdas_nested(m0, m1, method = c("2000", "2001"), unbiased = 1, df)

Arguments

m0, m1

Two nested lavaan objects.

method

Either "2000" or "2001".

unbiased

Biased (1), unbiased (2), or both (3) gamma.

df

Number of restrictions (degrees-of-freedom difference).

Value

A list of eigenvalue vectors, one per gamma estimate.


Calculate nested ugamma.

Description

This can also be used with restrictions.

Usage

lav_ugamma_nested_2000(m0, m1, gamma, a = NULL, method = "delta")

Arguments

m0, m1

Two nested lavaan objects.

gamma

Gamma weighted by groups.

a

The A matrix. If if NULL, gets calculated by lavaan:::lav_test_diff_A with method = method.

method

Method passed to lavaan:::lav_test_diff_A.

Value

Ugamma for nested object.


Calculate the scaled and shifted / the mean-variance adjusted p-value

Description

Calculate the scaled and shifted / the mean-variance adjusted p-value

Usage

scaled_and_shifted(chisq, lambdas)

Arguments

chisq

Chi-square fit value from a lavaan object.

lambdas

Eigenvalues of UG matrix.

Value

The scaled and shifted p-value or the mean-variance adjusted p-value.


Returns if not NA; else converts NA to NULL.

Description

Returns if not NA; else converts NA to NULL.

Usage

nanull(x)

Gamma-free factors of the reduced nested spectrum (Satorra 2000).

Description

Returns the restriction-space factor D and companion C such that the m nonzero eigenvalues of the full ⁠q x q⁠ UGamma equal those of ⁠C^{-1} D' Gamma D⁠, an ⁠m x m⁠ problem with m the number of restrictions. This is the materialised reduction of Moss (2026): the full ⁠q x q⁠ U matrix and its eigendecomposition are never formed. With ⁠U = D C^+ D'⁠ one has ⁠D = V Delta P^+ A'⁠ and ⁠C = A P^+ A'⁠, where V is the (group-weighted) weight, Delta the Jacobian, ⁠P^+⁠ the inverted information, and A the restriction matrix.

Usage

nested_factor_2000(m0, m1, a = NULL)

Calculate the jth pall pvalue.

Description

Calculate the jth pall pvalue.

Usage

pall(chisq, lambdas)

Calculate the jth eba pvalue.

Description

Calculate the jth eba pvalue.

Usage

peba_pvalue(chisq, lambdas, j)

Calculate penalized OLS pvalue.

Description

Calculate penalized OLS pvalue.

Usage

pols_pvalue(chisq, lambdas, gamma)

Print method for p-values from pvalues() / pvalues_nested().

Description

Prints the p-values, then a one-line provenance footer (estimator, data type, information, df) recording the options used.

Usage

## S3 method for class 'semTests_pvalues'
print(x, ...)

Arguments

x

A semTests_pvalues object.

...

Passed to the default print method.

Value

x, invisibly.


Calculate the jth all pvalue.

Description

Calculate the jth all pvalue.

Usage

pvalue_all(chisq, lambdas)

P value function for one and two arguments.

Description

P value function for one and two arguments.

Usage

pvalues_(
  m0,
  m1,
  unbiased,
  trad,
  eba,
  peba,
  pols,
  chisq = c("ml", "rls"),
  extras = FALSE,
  method,
  A.method = "exact"
)

Value

pvalues.


Calculate p-values for one or two lavaan objects.

Description

Calculate p-values for a lavaan object using several methods, including penalized eigenvalue block-averaging and penalized regression estimators. The recommended choices of p-values are included as default values. Multiple p-values can be returned simultaneously.

Usage

pvalues(object, tests = if (is_classic_nt(object)) "pEBA4_RLS" else "pEBA4")

pvalues_nested(
  m0,
  m1,
  method = c("2000", "2001"),
  tests = if (is_classic_nt(m0)) "PALL_UG_ML" else "PALL",
  A.method = c("exact", "delta")
)

Arguments

object, m0, m1

One or two lavaan objects. pvalues does goodness-of-fit testing on one object, pvalues_nested does hypothesis testing on two nested models.

tests

A list of tests to evaluate on the form "(test)_(ug?)_(rls?)"; see the default arguments and details below. The defaults are the recommended options.

method

For nested models, choose between 2000 and 2001. Note: 2001 and Satorra-Bentler will not correspond with the variant in the paper.

A.method

For nested FIML models, choose "exact" for the literal parameter restriction map or "delta" for the local moment-tangent restriction map.

Details

The test argument is a list of character strings on the form (test)(ug?)(ml?), for instance, SB_UG_RLS.

The peba method is the recommended default. It partitions the eigenvalues into j equally sized sets (if not possible, the smallest set is incomplete), shrinks them towards their common mean, and averages within each set. Provide a list of integers j to partition with respect to; the best choices are typically about 26. It was introduced by Foldnes, Moss, & Grønneberg (2024).

pols is a penalized regression method with a penalization term ranging from 0 to infinity. Foldnes, Moss, & Grønneberg (2024) studied pols=2, which has good performance in a variety of contexts.

pall penalizes all eigenvalues in ugamma, while all uses all eigenvalues without penalization. pall is the recommended option for nested models, for which the penalized methods were extended and evaluated by Foldnes, Grønneberg, & Moss (2026).

The eba method is the unpenalized predecessor of peba (Foldnes & Grønneberg, 2018): it averages within the eigenvalue blocks without shrinkage. It is generally outperformed by peba and is kept mainly for comparison; eba with j=2j=4 tends to work best.

In addition, you may specify a

The unbiased argument is TRUE if the unbiased estimator of the fourth order moment matrix (Du, Bentler, 2022) is used. If FALSE, the standard biased matrix is used. There is no simple relationship between p-value performance and the choice of unbiased.

The chisq argument controls which basic test statistic is used. The ml choice uses the chi square based on the normal discrepancy function (Bollen, 2014). The rls choice uses the reweighted least squares statistic of Browne (1974).

Estimators and data types

The authoritative list of supported estimators, data types, and configurations – the matrix this function is validated against – is semTests-support (?semTests-support). In brief:

The limiting null law of the test statistic is a weighted sum of chi-squares for any minimum-discrepancy estimator, so these tests are not specific to normal-theory ML. pvalues() supports ML/MLM/MLR, GLS, ULS, FIML (missing data), and categorical WLSMV/DWLS, with single- and multi-group continuous and categorical fits; pvalues_nested() supports the continuous estimators (nested categorical is not yet implemented). The model must be fit so that lavaan exposes the asymptotic moment covariance – fit with a robust test such as test = "satorra.bentler", or with estimator = "MLM"/"MLR"/"DWLS". Off the classical continuous-complete-data ML case, the RLS statistic (browne.residual.nt.model) and the unbiased (UG) Du-Bentler gamma are undefined and are refused; the standard statistic and the biased gamma are used instead. ADF/WLS is the degenerate exception, where the test equals the ordinary chi-square and the correction adds nothing.

Support beyond classical normal-theory ML – GLS, ULS, categorical WLSMV/DWLS, FIML missing data, and nested FIML comparison – is experimental as of 0.9.0; see the Stability note in semTests-support.

The information matrix (expected vs observed) is taken from the fit; to control it, fit the lavaan model with information = "expected" or "observed". The returned object records the estimator, statistic, information type, data type and degrees of freedom actually used; see its printed footer and attr(x, "semtests").

Value

A named numeric vector of p-values, of class semTests_pvalues, carrying an "semtests" attribute that records the options used (estimator, statistic, information type, gamma type, data type, and degrees of freedom).

References

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

Satorra, A., & Bentler, P. M. (1994). Corrections to test statistics and standard errors in covariance structure analysis. https://psycnet.apa.org/record/1996-97111-016

Asparouhov, & Muthén. (2010). Simple second order chi-square correction. Mplus Technical Appendix. https://www.statmodel.com/download/WLSMV_new_chi21.pdf

Wu, H., & Lin, J. (2016). A Scaled F Distribution as an Approximation to the Distribution of Test Statistics in Covariance Structure Analysis. Structural Equation Modeling. https://doi.org/10.1080/10705511.2015.1057733

Foldnes, N., & Grønneberg, S. (2018). Approximating Test Statistics Using Eigenvalue Block Averaging. Structural Equation Modeling, 25(1), 101-114. https://doi.org/10.1080/10705511.2017.1373021

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

Bollen, K. A. (2014). Structural Equations with Latent Variables (Vol. 210). John Wiley & Sons. https://doi.org/10.1002/9781118619179

Browne. (1974). Generalized least squares estimators in the analysis of covariance structures. South African Statistical Journal. https://doi.org/10.10520/aja0038271x_175

See Also

semTests-support for the full list of supported configurations.

Examples

library("semTests")
library("lavaan")
model <- "visual  =~ x1 + x2 + x3
          textual =~ x4 + x5 + x6
          speed   =~ x7 + x8 + x9"
object <- cfa(model, HolzingerSwineford1939, estimator = "MLM")
pvalues(object)

# For the pEBA6 method with biased gamma and ML chisq statistic:
pvalues(object, "pEBA6_ML")

# Nested model comparison (constrain the textual loadings to be equal):
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)


Renormalise the eigenvalue spectrum under missing data.

Description

For complete data, lavaan's UGamma spectrum has mean equal to the robust scaling factor, so the reference law ⁠sum lambda_j chi^2_1⁠ has the right first moment. Under missing data (FIML) lavaan's UGamma is mis-scaled (its mean is off by a constant, ~1.8x in practice), which makes the eigenvalue p-values badly anti-conservative. The spectrum shape is correct, so we rescale it to mean = chisq.scaling.factor. This is a no-op for complete data (where the mean already equals the scaling factor) and requires a robust test, which FIML fits always have.

Usage

rescale_missing(lambdas_list, fit, df)

Calculate the scaled_f p-value.

Description

Calculate the scaled_f p-value.

Usage

scaled_f(chisq, eig)

Arguments

chisq

Chi-square fit value from a lavaan object.

eig

eig of UG matrix.

Value

scaled f p-value.


Supported estimators, data types, and configurations

Description

The authoritative list of what pvalues() and pvalues_nested() support. The eigenvalue-based p-values target the limiting null law of the test statistic – a weighted sum of chi-squares – which holds for any minimum-discrepancy estimator, so the tests are not specific to normal-theory ML. This page is the single source of truth: anything not listed here is refused at the entry point (see check_supported()), and the test suite is written against it.

Details

Single-model (pvalues())

Estimator Data Groups Missing Status
ML / MLM / MLR continuous single or multi complete supported
GLS continuous single or multi complete supported
ULS continuous single or multi complete supported
ML / MLR (FIML) continuous single only FIML supported
WLSMV / DWLS categorical single or multi complete supported
WLS (ADF) continuous single complete degenerate (no-op)

Nested (pvalues_nested())

Estimators Data Groups Missing Method A.method Status
ML/MLM/MLR, GLS, ULS continuous single or multi complete 2000 or 2001 -- supported
ML / MLR (FIML) continuous single only FIML (both fits) 2000 only exact or delta supported
WLSMV / DWLS categorical any any -- -- rejected
any continuous -- mixed / non-FIML -- -- rejected

Stability

The classical normal-theory ML path (continuous, complete data) is the mature, paper-backed core and is stable. Support for the other estimators (GLS, ULS, categorical WLSMV/DWLS), for FIML missing-data fits, and for nested comparison under FIML is experimental as of 0.9.0: the methodology rests on the references but the implementation surface is newer and less Monte-Carlo-vetted, so the API and numerical details may change. Everything in the tables above is validated and tested; configurations outside them are refused at the entry point.

Statistic and gamma options

Test names have the form ⁠(test)_(ug?)_(ml|rls?)⁠ (e.g. "SB_UG_RLS"); see pvalues() for the test families. Two of the options are defined only for the classical case:

Why ADF/WLS is degenerate

For full WLS (ADF) the model test statistic already uses the asymptotically-correct weight matrix, so its null distribution is the exact chi-square and the eigenvalue correction collapses to the identity: every p-value equals the ordinary 1 - pchisq(chisq, df). It is accepted but adds nothing.

See Also

pvalues(), pvalues_nested()


Split string into options.

Description

Split string into options.

Usage

split_input(string)

Arguments

string

Input string


Calculate traditional pvalues.

Description

Calculate traditional pvalues.

Usage

trad_pvalue(
  df,
  chisq,
  lambdas,
  type = c("std", "sf", "ss", "sb", "pall", "all")
)

Arguments

df, chisq, lambdas, type

Parameters needed to calculate the p-values.

Value

Traditional p-values.


Calculate non-nested gamma

Description

Calculate non-nested gamma

Usage

ugamma(object, unbiased = 1)

Calculate nested gamma

Description

Calculate nested gamma

Usage

ugamma_nested(m0, m1, method = c("2000", "2001"), unbiased = 1)

Warn when a FIML fit uses expected information.

Description

Under missing data the expected (Fisher) information is consistent only under MCAR; under MAR – the regime FIML is adopted for – the observed information is required for valid inference (Kenward & Molenberghs, 1998). lavaan defaults to observed information for missing = "ml", but it silently accepts information = "expected", which would make the eigenvalue p-values rest on the stronger MCAR assumption. This emits a warning (not an error: expected is still valid under MCAR).

Usage

warn_fiml_information(fit)

Arguments

fit

A fitted lavaan object.

Details

lavaan stores information as a length-2 vector, so we test its first entry.

Value

fit, invisibly.