| esem_invariance | R Documentation |
Tests configural, weak (metric), strong (scalar), and strict invariance for
an ESEM model across groups, optionally followed by latent
variance/covariance and latent mean invariance (through). Returns a
formatted table of fit indices and chi-square difference tests, analogous
to Mplus's multi-group output.
esem_invariance(
spec,
model = c("esem", "besem"),
missing = NULL,
verbose = TRUE,
through = c("strict", "varcov", "means"),
cores = 2L,
...
)
spec |
An |
model |
Character. Which model type to test: |
missing |
Character. Missing data handling passed to |
verbose |
Logical. Print progress messages. Default |
through |
Character. Last level of the sequence to fit.
|
cores |
Integer. Worker processes for the two starting-value fits of
each level (lavaan's default start and |
... |
Additional arguments passed to the underlying fit function. Do not
pass |
An object of class "esem_invariance" containing:
tableData frame with fit indices and D-statistics.
print() renders it formatted.
modelsNamed list of fit objects, one per fitted level:
configural, weak, strong, strict, and, with
through, varcov and means.
NULL entries indicate a model that failed to fit.
lrtNamed list of lavTestLRT() outputs, one per
fitted level after configural (each compared with the level before it).
specThe original model specification.
modelCharacter: "esem" or "besem".
notesNamed list, one character vector per level that raised a
lavaan warning while being fitted or compared (singular information matrix
for the robust test statistic, non-positive-definite latent or residual
covariance matrix, negative variance). The warnings are caught, reworded
and shown by print() instead of being raised, so a run that
concludes does not end with a lavaan warning. Empty when nothing was raised.
fallback_from, fallback_noteIf B-ESEM configural fails to converge, the result may be from an ESEM fallback (no general factor); not comparable df-for-df to Mplus B-ESEM.
Ordered B-ESEM: Validated against Mplus on BFI for
G \in \{2, 3, 4\} (max |\Delta\mathrm{CFI}| \le 0.001,
|\Delta\mathrm{SRMR}| \le 0.002, df match). At G \ge 5 with
\ge 18 items a warning is issued; verify externally before reporting.
WLSMV SRMR: The invariance table applies an Mplus-style SRMR denominator correction for ordered models where implemented.
B-ESEM configural failure: After retries, may fall back to ESEM
(fallback_from = "besem"). The printed table is structurally different
from B-ESEM; do not compare to Mplus B-ESEM output.
Empty categories: Ordered multi-group fits may recode categories absent in one group (see verbose output).
See system.file("VALIDATION.md", package = "bifactory") for a summary.
| Level | Continuous (MLR) | Ordered (WLSMV/Theta) |
| Configural | free | free |
| Weak | loadings | loadings |
| Strong | loadings + intercepts | loadings + thresholds |
| Strict | + residuals | + residuals |
Latent var/cov (through = "varcov") | + factor variances (1) and covariances | + factor variances (1) and covariances |
Latent means (through = "means") | + factor means (0) | + factor means (0) |
The last two levels follow Morin, Arens and Marsh (2016): the degrees of
freedom grow by k(k+1)/2 (variances and covariances of k
factors) and then by k (means) per non-reference group.
Simple subtraction of scaled chi-square values is not valid for MLR or
WLSMV. lavaan::lavTestLRT() is used, which applies the
Satorra-Bentler (2001) correction for MLR and a mean-variance-adjusted
difference test for WLSMV. WLSMV difference test results may differ
numerically from Mplus's DIFFTEST procedure.
At the configural level std.lv = TRUE fixes factor variances to 1
in all groups. Under weak invariance lavaan automatically frees factor
variances in Group 2+ and keeps them at 1 in Group 1. Under strong
invariance factor means are freed in Group 2+ and fixed to 0 in Group 1.
The optional latent var/cov level fixes the Group 2+ variances back to 1
and equates the covariances; the latent mean level fixes the Group 2+
means back to 0.
With ordered indicators (theta parameterization) the configural model
uses the standard identification in every group: residual variances 1,
factor means 0, all thresholds free. From the weak level on, the
non-reference residual variances and factor means are free and identified
through the equal loadings plus partial threshold equalities (two
thresholds of one referent item per factor, one threshold of every other
item), which is the Mplus convention. The same scheme is not used at the
configural level: with free loadings an item's single equated threshold
is its only link across groups, and when that threshold lies near zero in
a non-reference group the item's residual variance is empirically
unidentified (it ran to several hundred on psych::bfi, with the
chi-square drifting by 14 to 36 units between programs). The standard
identification is the equivalent, well-conditioned model.
Group 1 (the reference group) is the first sorted level of the grouping
variable, spec$group_levels, which is also the reference group in
the Mplus syntax written by run_mplus_besem_invariance. The
fits pass this order to lavaan as group.label; lavaan's own default
would be the order in which the groups appear in the data, and a different
reference group gives an equivalent model with the same fit but differently
oriented loadings and factor covariances.
esem and besem use lavaan::cfa() with a
native efa() block (integrated MLR estimation and rotation).
esem(ordered = ...) calls esem_ordered. Default
method = "lavaan" (lavaan WLSMV, efa() block, post-hoc rotation).
method = "rotation" uses a custom DWLS pipeline (polychoric correlations
+ GPArotation).
besem(ordered = ...) calls besem_ordered. Default
method = "rotation" (custom DWLS/WLSMV + orthogonal targetT; this
is what run_comparison uses for ordered data). method = "set-esem"
fits a lavaan WLSMV bifactor CFA with non-primary specific loadings fixed at
zero; it does not match Mplus B-ESEM loadings.
esem_invariance uses lavaan multi-group efa() models plus
explicit syntax patches for ordered B-ESEM. See that help page for scope limits.
specify_model, esem,
esem_ordered
data("HolzingerSwineford1939", package = "lavaan")
spec <- specify_model(
Visual = c("x1", "x2", "x3"),
Textual = c("x4", "x5", "x6"),
Speed = c("x7", "x8", "x9"),
data = HolzingerSwineford1939,
group = "school",
label = "Holzinger-Swineford"
)
# Continuous ESEM measurement invariance across schools
inv <- esem_invariance(spec)
print(inv)
# Access individual model fits
summary(inv$models$strong, fit.measures = TRUE, standardized = TRUE)
lavaan::lavTestScore(inv$models$strong$lavaan_fit)
# Ordered data (WLSMV) and B-ESEM invariance follow the same pattern:
# add `ordered = TRUE` to specify_model(), then optionally `model = "besem"`.
items <- paste0("x", 1:9)
ord <- as.data.frame(lapply(HolzingerSwineford1939[, items], function(v) {
as.integer(cut(v, breaks = quantile(v, probs = seq(0, 1, 0.2)),
include.lowest = TRUE))
}))
names(ord) <- items
ord$school <- HolzingerSwineford1939$school
spec_ord <- specify_model(
Visual = c("x1", "x2", "x3"), Textual = c("x4", "x5", "x6"),
Speed = c("x7", "x8", "x9"),
data = ord, group = "school", ordered = TRUE
)
inv_besem <- esem_invariance(spec_ord, model = "besem")
print(inv_besem)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.