| Title: | (Bifactor) ESEM with Continuous (MLR) or Ordered (WLSMV) Data |
|---|---|
| Description: | Fits bifactor Exploratory Structural Equation Models (B-ESEM), together with standard ESEM and Confirmatory Factor Analysis (CFA), for continuous and ordered-categorical data. Continuous models use 'lavaan' native 'efa()' blocks (MLR). Ordered-categorical ESEM defaults to 'lavaan' WLSMV; ordered B-ESEM uses a custom diagonally weighted least squares (DWLS) path with polychoric correlations from 'psych', rotation-delta standard errors via 'numDeriv', and a mean- and variance-adjusted chi-square. Target, geomin, and oblimin rotations use 'GPArotation'; the bifactor ESEM approach follows Morin, Arens and Marsh (2016) <doi:10.1080/10705511.2014.961800>. Additional features include multi-group measurement invariance (configural through strict, with partial invariance), ESEM-within-CFA conversion, McDonald's omega reliability suite, and the Mehrvarz and Rouder (2026) <doi:10.31234/osf.io/95enc_v3> alignment ratio check for ICM-CFA misspecification. An optional 'MplusAutomation' interface allows side-by-side comparison with 'Mplus' output. |
| Authors: | Leon T. De Beer [aut, cre] |
| Maintainer: | Leon T. De Beer <[email protected]> |
| License: | AGPL-3 |
| Version: | 0.5.0 |
| Built: | 2026-07-12 08:05:55 UTC |
| Source: | https://github.com/leondebeer/bifactory |
Rotates a fitted solution's standardized loadings by an orthogonal Q so they match a reference matrix (Procrustes) or a deterministic canonical orientation. Useful when comparing bifactor solutions across software, or when you want a reproducible orientation across reruns.
align_loadings( x, target = "canonical", level = "configural", se_method = c("approx", "none") )align_loadings( x, target = "canonical", level = "configural", se_method = c("approx", "none") )
x |
An |
target |
One of:
|
level |
Only used for |
se_method |
|
Multi-group fits and esem_invariance objects are aligned per group.
Item communalities (), reliability indices (omega,
ECV), and model fit are invariant under orthogonal rotation, so alignment
affects only the per-loading partition, not substantive conclusions.
An object of class aligned_loadings: a list with one
element per group, each containing
aligned standardized loading matrix (items x factors)
aligned SEs (matching shape) or NULL
the orthogonal rotation matrix applied
max / mean abs residual vs target (only when an external reference was supplied)
## Not run: # Canonical orientation (no external reference, deterministic) inv <- esem_invariance(spec, model = "besem") aligned <- align_loadings(inv, target = "canonical", level = "configural") print(aligned) # Align to Mplus output via MplusAutomation library(MplusAutomation) mp <- readModels("besem_inv_configural.out") tgt <- extract_mplus_loadings(mp) aligned <- align_loadings(inv, target = tgt, level = "configural") # Within-fit: align all groups to group 1's orientation aligned <- align_loadings(inv, target = "group1", level = "configural") ## End(Not run)## Not run: # Canonical orientation (no external reference, deterministic) inv <- esem_invariance(spec, model = "besem") aligned <- align_loadings(inv, target = "canonical", level = "configural") print(aligned) # Align to Mplus output via MplusAutomation library(MplusAutomation) mp <- readModels("besem_inv_configural.out") tgt <- extract_mplus_loadings(mp) aligned <- align_loadings(inv, target = tgt, level = "configural") # Within-fit: align all groups to group 1's orientation aligned <- align_loadings(inv, target = "group1", level = "configural") ## End(Not run)
Tests whether a standard CFA (ICM-CFA) is appropriately specified by computing alignment ratios from the manifest correlation matrix. Based on Mehrvarz & Rouder (2026), who prove that in a correctly specified ICM-CFA, alignment ratios must be invariant across all admissible item quadruples, with the common value equal to the squared latent correlation phi^2.
alignment_check( data, clusters, cfa_fit = NULL, is_cor = FALSE, min_within_r = 0.05, log_sd_thresholds = c(slight = 0.64, moderate = 1.6) )alignment_check( data, clusters, cfa_fit = NULL, is_cor = FALSE, min_within_r = 0.05, log_sd_thresholds = c(slight = 0.64, moderate = 1.6) )
data |
A |
clusters |
A named character vector mapping item names to factor names.
Example: |
cfa_fit |
Optional. A fitted |
is_cor |
Logical. Is |
min_within_r |
Numeric. Alignment ratios whose denominator contains a
within-cluster correlation below this value are excluded (near-zero
within-cluster correlations make ratios numerically unstable). Default
|
log_sd_thresholds |
Named numeric vector of log-scale SD cutoffs for
the slight / moderate / high misalignment classification. Defaults to the
empirical terciles reported by Mehrvarz & Rouder (2026, p.24):
|
## Interpreting the log-scale dispersion (sd of log Q)
is the primary diagnostic. The log scale is
natural because alignment ratios are multiplicative objects. Mehrvarz &
Rouder (2026, p.24) report empirical terciles of from their bifactor
misalignment simulation:
s < 0.64 – Slight misalignment. ICM-CFA is approximately consistent with the data; latent correlations are roughly trustworthy.
s 0.64-1.6 – Moderate misalignment. ICM-CFA is partially misspecified; interpret latent correlations with caution.
s > 1.6 – High misalignment (top tercile of the paper's simulation, up to ~4.4). ICM-CFA is substantially misspecified and latent correlations are likely inflated. Consider ESEM.
These cutpoints are the 33rd and 66th percentiles of observed by
Mehrvarz & Rouder across a broad range of simulated loading configurations
(with ), not hard decision
boundaries; adjust log_sd_thresholds if your application calls for
stricter or looser cutoffs.
## Types of misspecification detected
Misassignment: items are assigned to the wrong cluster. Mehrvarz & Rouder prove this necessarily inflates phi. The alignment ratios split into three plateaus at phi^2, 1, and 1/phi^2.
Misalignment: cluster assignments are correct but cross-loadings exist (or the proportionality constraint is violated in a bifactor-like DGP). The ratios are dispersed rather than invariant.
## Number of alignment ratios per pair
Mehrvarz & Rouder (2026, p.14) count one alignment ratio per admissible
index quadruple: . Because each
unordered quadruple admits two
between-cluster matchings ( and
), both of which equal under
correctly specified ICM-CFA but diverge under misspecification, this
function records both matchings per quadruple – yielding ratios in all_ratios – to maximise the
information available for the dispersion diagnostic.
An object of class "alignment_check" (a list) containing:
pair_resultsA data.frame with one row per factor
pair, giving: geometric mean of alignment ratios, log-scale SD, implied phi,
CFA-estimated phi (if supplied), inflation percentage, and verdict.
all_ratiosA named list of raw alignment ratio vectors, one element per factor pair.
recommendationCharacter string: overall recommendation.
cor_matrixThe manifest correlation matrix used.
clustersResolved cluster assignments (named list).
callThe matched call.
For any two items in cluster A and two items
in cluster B, the alignment ratio is:
Under a correctly specified ICM-CFA, all such ratios equal phi^2 and lie in
(Mehrvarz & Rouder, 2026, Eq. 5). Dispersion in the ratios –
measured by their log-scale standard deviation
– signals misspecification: either misassignment (items in the wrong
cluster) or misalignment (cross-loadings exist but are fixed to zero).
Mehrvarz, M., & Rouder, J. N. (2026). The geometry and brittleness of latent correlations in confirmatory factor analysis.
esem for the recommended follow-up under moderate or
high misalignment.
## Not run: # Vector form of cluster assignment clusters <- c( batEX1 = "EX", batEX2 = "EX", batEX3 = "EX", batMD1 = "MD", batMD2 = "MD", batMD3 = "MD", batCC1 = "CI", batCC2 = "CI", batCC3 = "CI" ) # Run alignment check (data only) check <- alignment_check(mydata, clusters) print(check) # Run with a fitted CFA to quantify inflation library(lavaan) cfa_model <- " EX =~ batEX1 + batEX2 + batEX3 MD =~ batMD1 + batMD2 + batMD3 CI =~ batCC1 + batCC2 + batCC3 " cfa_fit <- cfa(cfa_model, data = mydata, std.lv = TRUE) check <- alignment_check(mydata, clusters, cfa_fit = cfa_fit) print(check) # Visualise sorted alignment ratios for a specific pair plot(check, pair = "EX-MD") ## End(Not run)## Not run: # Vector form of cluster assignment clusters <- c( batEX1 = "EX", batEX2 = "EX", batEX3 = "EX", batMD1 = "MD", batMD2 = "MD", batMD3 = "MD", batCC1 = "CI", batCC2 = "CI", batCC3 = "CI" ) # Run alignment check (data only) check <- alignment_check(mydata, clusters) print(check) # Run with a fitted CFA to quantify inflation library(lavaan) cfa_model <- " EX =~ batEX1 + batEX2 + batEX3 MD =~ batMD1 + batMD2 + batMD3 CI =~ batCC1 + batCC2 + batCC3 " cfa_fit <- cfa(cfa_model, data = mydata, std.lv = TRUE) check <- alignment_check(mydata, clusters, cfa_fit = cfa_fit) print(check) # Visualise sorted alignment ratios for a specific pair plot(check, pair = "EX-MD") ## End(Not run)
Fits a Bifactor ESEM model – a general factor (G) loading on all
indicators plus domain-specific factors each targeting a subset of
indicators. All factors are orthogonal (uncorrelated), matching the
Mplus B-ESEM specification on **continuous** data via lavaan's efa()
block. With ordered indicators, the call routes to
besem_ordered (default custom DWLS/WLSMV path; see
?besem_ordered).
besem( data, specific_factors, indicators = NULL, g_name = "G", estimator = "MLR", std.lv = TRUE, ordered = NULL, group = NULL, group_equal = NULL, missing = "listwise", n_starts = 30L, ... )besem( data, specific_factors, indicators = NULL, g_name = "G", estimator = "MLR", std.lv = TRUE, ordered = NULL, group = NULL, group_equal = NULL, missing = "listwise", n_starts = 30L, ... )
data |
A |
specific_factors |
A named list mapping specific factor names to
their primary indicator names. The general factor G is added
automatically.
Example: |
indicators |
Optional character vector of all indicator names. If
|
g_name |
Character. Name for the general factor. Default |
estimator |
Character. Default |
std.lv |
Logical. Fix factor variances to 1. Default |
ordered |
Character vector of ordered-categorical item names.
When non- |
group |
Character. Grouping variable for multi-group B-ESEM. |
group_equal |
Character vector of lavaan equality constraints. |
missing |
Character. Missing data handling for **continuous** B-ESEM.
Default |
n_starts |
Integer. Number of random orthogonal starting matrices for
the target rotation (forwarded as |
... |
Additional arguments passed to |
## What makes B-ESEM different from ESEM
| | ESEM | B-ESEM | |—|—|—| | Factor structure | k oblique specific factors | 1 general + k orthogonal specific | | Factor correlations | Freely estimated | All fixed to zero (orthogonal) | | Cross-loadings | Estimated via rotation | Estimated via orthogonal target rotation | | G factor | None | Loads freely on all items | | Rotation | Oblique target/geomin | Orthogonal target |
## Orthogonality
B-ESEM uses orthogonal target rotation ("targetT" in lavaan),
which constrains all factors to be uncorrelated. This means:
G is uncorrelated with EX, MD, CI
EX, MD, CI are uncorrelated with each other
This matches Mplus ROTATION = TARGET (orthogonal).
## Interpreting results
**G loadings**: variance shared across all items regardless of domain
**Specific loadings**: domain-specific variance after accounting for G
**omega_h (omega hierarchical)**: reliability of G (use psych::omega())
**omega_s (omega specific)**: reliability of each specific factor
## Target matrix structure
G EX MD CI
y1 1 1 0 0 <- G free on all; EX primary; MD/CI targeted to 0
y2 1 1 0 0
y6 1 0 1 0 <- MD item
y9 1 0 0 1 <- CI item
An object of class c("besem_fit", "esem_fit") with the
same structure as esem, plus:
g_nameName of the general factor.
specific_factorsNamed list of specific factor assignments.
Mplus continuous / ordered target syntax: ROTATION = TARGET (orthogonal); MODEL: G BY batEX1-batCI5 (*1); EX BY batEX1~1 ... batMD1~0 ... (*1); MD BY batEX1~0 ... batMD1~1 ... (*1); CI BY batEX1~0 ... batCI1~1 ... (*1);
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.
Defaults differ by entry point; pass missing explicitly when fitting ESEM and
B-ESEM separately on the same ordered dataset.
specify_model / run_comparison: "pairwise"
for ordered data, "listwise" for continuous.
esem_ordered: "pairwise".
besem_ordered: "listwise" (pipeline still passes
missing from the spec when used via run_comparison).
For besem_ordered(method = "rotation") and
esem_ordered(method = "rotation"), $lavaan_fit is often an
**auxiliary** one-factor WLSMV CFA used only to extract DWLS weight matrices—not
the fitted ESEM/B-ESEM model. Use std_loadings, parameters,
and fitMeasures(x) on the esem_fit wrapper;
fitMeasures(x) reads wlsmv_stats when present.
Do not interpret summary(x$lavaan_fit), modindices(x), or
coef(x) as the rotated solution unless you know the fit used the lavaan
efa() path (method = "lavaan" or method = "set-esem" for B-ESEM).
esem for standard oblique ESEM,
besem_ordered for ordered-categorical B-ESEM,
make_bifactor_target for the target matrix,
generate_mplus_besem_syntax for Mplus comparison.
## Not run: fit_b <- besem( data = Rdata, specific_factors = list( EX = c("batEX1","batEX2","batEX3","batEX4", "batEX5","batEX6","batEX7","batEX8"), MD = c("batMD1","batMD2","batMD3","batMD4","batMD5"), CI = c("batCI1","batCI2","batCI3","batCI4","batCI5") ) ) summary(fit_b, fit.measures = TRUE, standardized = TRUE) std_loadings(fit_b) # rows = items, cols = G + specific factors factor_correlations(fit_b) # should all be ~0 (orthogonal) # Compare B-ESEM vs standard ESEM fit_esem <- esem(Rdata, nfactors = 3, ...) lavaan::fitMeasures(lavaan_fit(fit_b), c("cfi","rmsea","aic")) lavaan::fitMeasures(lavaan_fit(fit_esem), c("cfi","rmsea","aic")) ## End(Not run)## Not run: fit_b <- besem( data = Rdata, specific_factors = list( EX = c("batEX1","batEX2","batEX3","batEX4", "batEX5","batEX6","batEX7","batEX8"), MD = c("batMD1","batMD2","batMD3","batMD4","batMD5"), CI = c("batCI1","batCI2","batCI3","batCI4","batCI5") ) ) summary(fit_b, fit.measures = TRUE, standardized = TRUE) std_loadings(fit_b) # rows = items, cols = G + specific factors factor_correlations(fit_b) # should all be ~0 (orthogonal) # Compare B-ESEM vs standard ESEM fit_esem <- esem(Rdata, nfactors = 3, ...) lavaan::fitMeasures(lavaan_fit(fit_b), c("cfi","rmsea","aic")) lavaan::fitMeasures(lavaan_fit(fit_esem), c("cfi","rmsea","aic")) ## End(Not run)
Fits B-ESEM on ordered-categorical indicators. Called from besem
when ordered is set. For Mplus-aligned loadings and fit indices use
method = "rotation" (default; also used by run_comparison).
besem_ordered( data, specific_factors, indicators = NULL, g_name = "G", method = c("rotation", "set-esem"), n_starts = 30L, r_obs_override = NULL, group = NULL, group_equal = NULL, missing = "listwise", std.lv = TRUE, ... )besem_ordered( data, specific_factors, indicators = NULL, g_name = "G", method = c("rotation", "set-esem"), n_starts = 30L, r_obs_override = NULL, group = NULL, group_equal = NULL, missing = "listwise", std.lv = TRUE, ... )
data |
A |
specific_factors |
Named list of specific factor -> item assignments. |
indicators |
Character vector of all indicator names. If |
g_name |
Character. General factor name. Default |
method |
Character. |
n_starts |
Integer. Random rotation starts. Default |
r_obs_override |
Optional observed correlation matrix to use instead of polychoric estimation. |
group |
Character. Grouping variable for multi-group models. |
group_equal |
Character vector of lavaan equality constraints. |
missing |
Character. Missing data handling. Default |
std.lv |
Logical. Default |
... |
Additional arguments passed to |
An object of class c("besem_fit_ordered","besem_fit","esem_fit").
For method = "rotation", lavaan_fit is auxiliary (one-factor CFA);
use std_rotated_loadings, wlsmv_stats, std_loadings,
and fitMeasures(x). For method = "set-esem",
lavaan_fit is the fitted bifactor CFA.
"set-esem" vs "rotation"
rotation estimates cross-loadings (targeted toward zero), then rotates—
same estimand as Mplus B-ESEM WLSMV. set-esem **fixes** non-primary
specific loadings at zero in lavaan; G and primary loadings are re-optimized
under that harder constraint, so loadings and fit differ from Mplus.
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.
Defaults differ by entry point; pass missing explicitly when fitting ESEM and
B-ESEM separately on the same ordered dataset.
specify_model / run_comparison: "pairwise"
for ordered data, "listwise" for continuous.
esem_ordered: "pairwise".
besem_ordered: "listwise" (pipeline still passes
missing from the spec when used via run_comparison).
For besem_ordered(method = "rotation") and
esem_ordered(method = "rotation"), $lavaan_fit is often an
**auxiliary** one-factor WLSMV CFA used only to extract DWLS weight matrices—not
the fitted ESEM/B-ESEM model. Use std_loadings, parameters,
and fitMeasures(x) on the esem_fit wrapper;
fitMeasures(x) reads wlsmv_stats when present.
Do not interpret summary(x$lavaan_fit), modindices(x), or
coef(x) as the rotated solution unless you know the fit used the lavaan
efa() path (method = "lavaan" or method = "set-esem" for B-ESEM).
esem_ordered, besem,
run_comparison
## Not run: fit_b_ord <- besem_ordered( data = Rdata, specific_factors = list( EX = items_EX, MD = items_MD, CI = items_CI ) ) summary(fit_b_ord, fit.measures = TRUE, standardized = TRUE) ## End(Not run)## Not run: fit_b_ord <- besem_ordered( data = Rdata, specific_factors = list( EX = items_EX, MD = items_MD, CI = items_CI ) ) summary(fit_b_ord, fit.measures = TRUE, standardized = TRUE) ## End(Not run)
Opens the shipped analysis template in your editor, or copies it to a
destination of your choice. The template walks through the full bifactory
pipeline (CFA / ESEM / B-ESEM comparison, reliability, ESEM-within-CFA,
factor scores, and optional multi-group invariance) using
psych::bfi as the demo dataset.
bifactory_template(to = NULL, overwrite = FALSE)bifactory_template(to = NULL, overwrite = FALSE)
to |
Optional file path. If supplied, the template is copied there
(with overwrite protection). If |
overwrite |
Logical. Overwrite an existing file at |
Invisibly returns the path to the template (or the destination
when to is supplied).
# Find the template path system.file("templates", "template.R", package = "bifactory") ## Not run: # Open in editor bifactory_template() # Copy to the working directory bifactory_template(to = "my_analysis.R") ## End(Not run)# Find the template path system.file("templates", "template.R", package = "bifactory") ## Not run: # Open in editor bifactory_template() # Copy to the working directory bifactory_template(to = "my_analysis.R") ## End(Not run)
Extracts the per-group contribution to the WLSMV (or ML) chi-square at each
invariance level of an esem_invariance object. Optionally compares
against Mplus's Chi-Square Contribution From Each Group table, which
is useful for pinpointing whether a basin difference between R and Mplus is
driven by a single group or distributed across all of them.
chisq_decomp(x, mplus_dir = NULL, levels = NULL)chisq_decomp(x, mplus_dir = NULL, levels = NULL)
x |
An |
mplus_dir |
Optional directory containing Mplus output files named
|
levels |
Character vector of levels to include. Defaults to all levels
present in |
lavaan's stat.group is the raw (unscaled) per-group chi-square; Mplus
prints the scaled per-group contribution that sums to the scaled
total. To compare on the same scale, R per-group values are rescaled by the
global ratio chisq.scaled / chisq from fitMeasures.
A data frame of class "chisq_decomp" with columns
level, group, chisq_R (rescaled to scaled scale), and
when mplus_dir is supplied, mplus_group, chisq_M,
delta.
## Not run: inv <- esem_invariance(spec, model = "besem") chisq_decomp(inv) chisq_decomp(inv, mplus_dir = "validation/_bfi_g4_mplus_inv") ## End(Not run)## Not run: inv <- esem_invariance(spec, model = "besem") chisq_decomp(inv) chisq_decomp(inv, mplus_dir = "validation/_bfi_g4_mplus_inv") ## End(Not run)
Forwards to lavaan on lavaan_fit(x). On custom WLSMV rotation paths,
use parameters or std_loadings instead; see
The lavaan_fit slot on custom WLSMV fits.
## S3 method for class 'esem_fit' coef(object, standardized = FALSE, ...)## S3 method for class 'esem_fit' coef(object, standardized = FALSE, ...)
object |
An |
standardized |
Logical. Return standardized estimates? Default |
... |
Passed to |
A data.frame of parameter estimates from
lavaan::parameterEstimates(), or the standardized solution from
lavaan::standardizedsolution() when standardized = TRUE.
Appends fit indices from a fit_ewc result to the pipeline
comparison table, producing a unified data frame with CFA, ESEM, B-ESEM,
and EWC columns side by side.
compare_ewc(results, ewc)compare_ewc(results, ewc)
results |
An |
ewc |
An |
An object of class c("ewc_comparison","data.frame").
Print with print() for a formatted table.
Builds a long-format data frame with STDYX loadings, standard errors,
z-scores, p-values, and R Mplus differences for every loading
(primary and cross) across CFA, ESEM, and B-ESEM. Mplus columns are
NA when no Mplus results are present in the pipeline object.
compare_loadings(results)compare_loadings(results)
results |
An |
A data frame with columns:
modelCFA, ESEM, or BESEM.
factorFactor name (lowercase).
itemItem name (lowercase).
loading_type"primary" or "cross".
R_std, R_se, R_z, R_p
R estimates.
Mplus_std, Mplus_se, Mplus_z, Mplus_p
Mplus estimates (NA if unavailable).
diff_stdR Mplus standardised loading difference (NA if unavailable).
## Not run: results <- run_comparison(spec, mplus_folder = "path/to/mplus") lc <- compare_loadings(results) head(lc) # Primary loadings only lc[lc$loading_type == "primary", ] # Largest absolute R - Mplus differences lc[order(abs(lc$diff_std), decreasing = TRUE), ] ## End(Not run)## Not run: results <- run_comparison(spec, mplus_folder = "path/to/mplus") lc <- compare_loadings(results) head(lc) # Primary loadings only lc[lc$loading_type == "primary", ] # Largest absolute R - Mplus differences lc[order(abs(lc$diff_std), decreasing = TRUE), ] ## End(Not run)
Computes McDonald's omega reliability indices for all three models in a pipeline result, following the bifactor reporting framework recommended by Morin, Arens & Marsh (2016) and Rodriguez, Reise & Haviland (2016).
compute_indices(results)compute_indices(results)
results |
An |
For the **total composite** (all items):
omega_total – total reliability (rotation-invariant for
orthogonal models).
omega_H – hierarchical omega: G factor's contribution to
total-score reliability (B-ESEM only).
ECV – explained common variance: G's share of all common
variance (B-ESEM only).
H(G) – construct replicability of G (B-ESEM only).
For each **subscale** (items of specific factor ):
omega_S – specific factor's unique contribution to subscale
reliability: .
Uses target loadings and item residuals only; G does not enter the
denominator because already has G partialled out.
omega_sub – total reliability of the subscale sum score
(G + specific factor combined).
omega_H_sub – G's contribution to subscale reliability.
ECV_s – G's share of common variance within the subscale.
H(Fs) – construct replicability of the specific factor.
An object of class "reliability_indices" – a named list with
elements cfa, esem, besem, each containing the
computed indices for that model, plus alpha (Cronbach's alpha per
subscale and for G). Pass to print() for a formatted table.
McDonald, R. P. (1999). Test theory: A unified treatment. Erlbaum.
Rodriguez, A., Reise, S. P., & Haviland, M. G. (2016). Evaluating bifactor models: Calculating and interpreting statistical indices. Psychological Methods, 21(2), 137-150.
Morin, A. J. S., Arens, A. K., & Marsh, H. W. (2016). A bifactor exploratory structural equation modeling framework for the identification of distinct sources of construct-relevant psychometric multidimensionality. Structural Equation Modeling, 23(1), 116-139.
## Not run: spec <- specify_model(EX = paste0("batEX", 1:8), MD = paste0("batMD", 1:5), CI = paste0("batCI", 1:5), data = mydata, ordered = TRUE) results <- run_comparison(spec) indices <- compute_indices(results) print(indices) ## End(Not run)## Not run: spec <- specify_model(EX = paste0("batEX", 1:8), MD = paste0("batMD", 1:5), CI = paste0("batCI", 1:5), data = mydata, ordered = TRUE) results <- run_comparison(spec) indices <- compute_indices(results) print(indices) ## End(Not run)
compute_indices insteadDeprecated: use compute_indices instead
compute_omega(results)compute_omega(results)
results |
An |
A reliability_indices object.
Fits ESEM using lavaan's native efa() block. For **continuous**
data (default), estimation and rotation are integrated in lavaan's ML
optimiser—the same single-step approach as Mplus (*1) syntax. For
**ordered** indicators, supply ordered and the call is routed to
esem_ordered (set-ESEM / WLSMV; not identical to continuous
esem()).
esem( data, nfactors, indicators = NULL, rotation = "geomin", target = NULL, estimator = "MLR", std.lv = TRUE, ordered = NULL, group = NULL, group_equal = NULL, missing = "listwise", factor_names = NULL, rotation_args = list(), heywood_fix = TRUE, ... )esem( data, nfactors, indicators = NULL, rotation = "geomin", target = NULL, estimator = "MLR", std.lv = TRUE, ordered = NULL, group = NULL, group_equal = NULL, missing = "listwise", factor_names = NULL, rotation_args = list(), heywood_fix = TRUE, ... )
data |
A |
nfactors |
Integer. Number of latent factors. |
indicators |
Character vector of item names to include. If |
rotation |
Character. Rotation criterion. Passed to
The full list of lavaan-supported rotations is in
|
target |
A numeric matrix (items x factors) for target rotation.
Create with |
estimator |
Character. lavaan estimator. Default |
std.lv |
Logical. Fix factor variances to 1 for identification?
Default |
ordered |
Character vector of ordered-categorical item names.
When non- |
group |
Character. Name of a grouping variable in |
group_equal |
Character vector of lavaan parameter labels to constrain
equal across groups, e.g. |
missing |
Character. Missing data handling for **continuous** fits.
Default |
factor_names |
Optional character vector of length |
rotation_args |
Named list of extra arguments passed to the rotation
function (e.g. |
heywood_fix |
Logical. Retry rotation with Cholesky unrotation if a
standardised loading exceeds 1 (single-group continuous fits only).
Default |
... |
Additional arguments passed to |
## Mplus equivalence
| Mplus syntax | bifactory equivalent | |—|—| | 'F1-F3 BY y1-y15 (*1);' | 'esem(data, nfactors = 3)' | | '(*1)' with geomin (default) | 'rotation = "geomin"' | | '(*1)' with target | 'rotation = "target", target = tgt' | | 'GROUPING = g;' | 'group = "g"' | | Metric invariance | 'group_equal = "loadings"' | | Scalar invariance | 'group_equal = c("loadings", "intercepts")' |
## Why not automate cross-loadings from a CFA? Adding cross-loadings stepwise from modification indices is a different (exploratory CFA) approach and is not recommended: it capitalises on chance, inflates Type I error, and produces a different model on every dataset. ESEM instead estimates all cross-loadings simultaneously, with rotation acting as a mathematical penalty for complexity – giving a reproducible, theory-neutral solution.
An object of class "esem_fit" containing:
lavaan_fitThe lavaan fit object for continuous ESEM.
For ordered / custom WLSMV paths see esem_ordered and
The lavaan_fit slot on custom WLSMV fits.
syntaxThe lavaan model string that was estimated.
nfactorsNumber of factors.
rotationRotation method used.
factor_namesFactor names.
indicatorsItem names included in the model.
callThe matched call.
Many R implementations run EFA first, extract the loading matrix, then paste
it into a CFA as starting values. That is an approximation. bifactory
instead uses lavaan's efa() block syntax (available since
lavaan 0.6-12), which estimates the rotation and the SEM parameters
simultaneously – exactly as Mplus does with the (*1) syntax.
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.
Defaults differ by entry point; pass missing explicitly when fitting ESEM and
B-ESEM separately on the same ordered dataset.
specify_model / run_comparison: "pairwise"
for ordered data, "listwise" for continuous.
esem_ordered: "pairwise".
besem_ordered: "listwise" (pipeline still passes
missing from the spec when used via run_comparison).
When heywood_fix = TRUE (default on single-group esem and
esem_ordered), std_loadings may reflect a post-hoc
rotation correction while lavaan_fit still holds the pre-correction lavaan
solution. Prefer std_loadings(x) for reported loadings in that case.
make_target for target matrices,
esem_ordered for ordered-categorical data,
esem_compare for ESEM vs CFA comparison,
std_loadings for the standardised loading matrix.
data("HolzingerSwineford1939", package = "lavaan") d <- HolzingerSwineford1939[, paste0("x", 1:9)] # Basic ESEM: 3 factors, geomin rotation (Mplus default) fit <- esem(d, nfactors = 3) round(std_loadings(fit), 2) lavaan::fitMeasures(lavaan_fit(fit), c("cfi", "tli", "rmsea", "srmr")) # Named factors fit2 <- esem(d, nfactors = 3, factor_names = c("Visual", "Textual", "Speed")) # Target rotation tgt <- make_target(list(Vis = 1:3, Txt = 4:6, Spd = 7:9), nitems = 9) fit3 <- esem(d, nfactors = 3, rotation = "target", target = tgt) # Multi-group configural ESEM fit_mg <- esem(HolzingerSwineford1939, nfactors = 3, indicators = paste0("x", 1:9), group = "sex")data("HolzingerSwineford1939", package = "lavaan") d <- HolzingerSwineford1939[, paste0("x", 1:9)] # Basic ESEM: 3 factors, geomin rotation (Mplus default) fit <- esem(d, nfactors = 3) round(std_loadings(fit), 2) lavaan::fitMeasures(lavaan_fit(fit), c("cfi", "tli", "rmsea", "srmr")) # Named factors fit2 <- esem(d, nfactors = 3, factor_names = c("Visual", "Textual", "Speed")) # Target rotation tgt <- make_target(list(Vis = 1:3, Txt = 4:6, Spd = 7:9), nitems = 9) fit3 <- esem(d, nfactors = 3, rotation = "target", target = tgt) # Multi-group configural ESEM fit_mg <- esem(HolzingerSwineford1939, nfactors = 3, indicators = paste0("x", 1:9), group = "sex")
Fits a user-specified CFA model on the same data and compares it against an
esem_fit object using fit indices and, where applicable, a chi-square
difference test (when models are nested).
esem_compare(esem_model, cfa_model, data = NULL, estimator = NULL, ...)esem_compare(esem_model, cfa_model, data = NULL, estimator = NULL, ...)
esem_model |
An |
cfa_model |
A lavaan model string for the comparison CFA. All items must be the same as in the ESEM. |
data |
A |
estimator |
Estimator for the CFA model. Defaults to the same estimator
used in |
... |
Additional arguments passed to |
A list of class "esem_comparison" with:
fit_tableA data.frame of fit indices for both models.
esem_fitThe original esem_fit object.
cfa_fitThe fitted lavaan CFA object.
lavtestOutput of lavaan::lavTestLRT() if models are nested, else NULL.
## Not run: # Fit ESEM esem_result <- esem(mydata, nfactors = 3) # Define comparison CFA (no cross-loadings) cfa_model <- " F1 =~ y1 + y2 + y3 + y4 + y5 F2 =~ y6 + y7 + y8 + y9 + y10 F3 =~ y11 + y12 + y13 + y14 + y15 " comparison <- esem_compare(esem_result, cfa_model, data = mydata) print(comparison) ## End(Not run)## Not run: # Fit ESEM esem_result <- esem(mydata, nfactors = 3) # Define comparison CFA (no cross-loadings) cfa_model <- " F1 =~ y1 + y2 + y3 + y4 + y5 F2 =~ y6 + y7 + y8 + y9 + y10 F3 =~ y11 + y12 + y13 + y14 + y15 " comparison <- esem_compare(esem_result, cfa_model, data = mydata) print(comparison) ## End(Not run)
Tests configural, weak (metric), strong (scalar), and strict invariance for an ESEM model across groups. 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, ... )esem_invariance( spec, model = c("esem", "besem"), missing = NULL, verbose = TRUE, ... )
spec |
An |
model |
Character. Which model type to test: |
missing |
Character. Missing data handling passed to |
verbose |
Logical. Print progress messages. Default |
... |
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:
configural, weak, strong, strict.
NULL entries indicate a model that failed to fit.
lrtNamed list of lavTestLRT() outputs:
weak, strong, strict.
specThe original model specification.
modelCharacter: "esem" or "besem".
fallback_from, fallback_note
If 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
(max ,
, df match). At with
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 |
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.
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
## Not run: spec <- specify_model( EX = items_EX, MD = items_MD, CI = items_CI, data = Rdata, group = "sex", label = "Burnout Battery" ) 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) spec_ord <- specify_model( EX = items_EX, MD = items_MD, CI = items_CI, data = Rdata, group = "sex", ordered = TRUE, label = "Burnout Battery (Ordered)" ) inv_ord <- esem_invariance(spec_ord) # B-ESEM invariance (ordered, WLSMV + orthogonal target rotation) # Matches Mplus ROTATION = TARGET(ORTHOGONAL) multi-group proof inv_besem <- esem_invariance(spec_ord, model = "besem") print(inv_besem) ## End(Not run)## Not run: spec <- specify_model( EX = items_EX, MD = items_MD, CI = items_CI, data = Rdata, group = "sex", label = "Burnout Battery" ) 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) spec_ord <- specify_model( EX = items_EX, MD = items_MD, CI = items_CI, data = Rdata, group = "sex", ordered = TRUE, label = "Burnout Battery (Ordered)" ) inv_ord <- esem_invariance(spec_ord) # B-ESEM invariance (ordered, WLSMV + orthogonal target rotation) # Matches Mplus ROTATION = TARGET(ORTHOGONAL) multi-group proof inv_besem <- esem_invariance(spec_ord, model = "besem") print(inv_besem) ## End(Not run)
Fits ESEM on ordered-categorical indicators (WLSMV / Theta parameterization).
Called automatically from esem when ordered is set.
esem_ordered( data, nfactors, indicators = NULL, rotation = "target", target = NULL, factor_names = NULL, method = c("lavaan", "rotation"), n_starts = 100L, r_obs_override = NULL, std.lv = TRUE, missing = "pairwise", group = NULL, group_equal = NULL, heywood_fix = TRUE, n_obs = NULL, ... )esem_ordered( data, nfactors, indicators = NULL, rotation = "target", target = NULL, factor_names = NULL, method = c("lavaan", "rotation"), n_starts = 100L, r_obs_override = NULL, std.lv = TRUE, missing = "pairwise", group = NULL, group_equal = NULL, heywood_fix = TRUE, n_obs = NULL, ... )
data |
A |
nfactors |
Integer. Number of latent factors. |
indicators |
Character vector of item names. If |
rotation |
Character. Rotation method. Default |
target |
A target matrix from |
factor_names |
Optional character vector of factor names. |
method |
Character. |
n_starts |
Integer. Number of random rotation starts when
|
r_obs_override |
Optional observed correlation matrix to use instead of polychoric estimation (for reproducibility / testing). |
std.lv |
Logical. Fix factor variances to 1. Default |
missing |
Character. Missing data handling passed to lavaan.
Default |
group |
Character. Grouping variable name for multi-group models. |
group_equal |
Character vector of lavaan equality constraints. |
heywood_fix |
Logical. Retry rotation with Cholesky unrotation when a
standardised loading exceeds 1. Default |
n_obs |
Ignored (kept for backward compatibility). |
... |
Additional arguments passed to |
**Default** (method = "lavaan"): lavaan's efa() block with WLSMV
(unrestricted model, post-hoc rotation, delta-method SEs)—aligned with Mplus
ESTIMATOR = WLSMV; ROTATION = TARGET (oblique).
**Alternate** (method = "rotation"): custom DWLS pipeline (polychoric
correlations via psych, GPArotation, sandwich + rotation Jacobian SEs).
## Mplus equivalence
Mplus:
ANALYSIS:
ESTIMATOR = WLSMV;
ROTATION = TARGET;
PARAMETERIZATION = THETA;
MODEL:
EX MD CI BY item1-item18 (*1);
R (this function):
esem_ordered(data, nfactors = 3, rotation = "target", target = tgt,
factor_names = c("EX","MD","CI"))
## Algorithm (method = "lavaan")
lavaan's efa() block fits an unrestricted k-factor model under WLSMV,
applies post-hoc rotation, and propagates standard errors through the rotation
transformation (Asparouhov & Muthen, 2009).
An object of class c("esem_fit_ordered", "esem_fit"). For
method = "lavaan", lavaan_fit is the fitted ESEM model. For
method = "rotation", lavaan_fit is an auxiliary one-factor CFA;
use std_rotated_loadings, wlsmv_stats, and
std_loadings. Slot estimator is "WLSMV" or
"DWLS" depending on path.
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.
Defaults differ by entry point; pass missing explicitly when fitting ESEM and
B-ESEM separately on the same ordered dataset.
specify_model / run_comparison: "pairwise"
for ordered data, "listwise" for continuous.
esem_ordered: "pairwise".
besem_ordered: "listwise" (pipeline still passes
missing from the spec when used via run_comparison).
For besem_ordered(method = "rotation") and
esem_ordered(method = "rotation"), $lavaan_fit is often an
**auxiliary** one-factor WLSMV CFA used only to extract DWLS weight matrices—not
the fitted ESEM/B-ESEM model. Use std_loadings, parameters,
and fitMeasures(x) on the esem_fit wrapper;
fitMeasures(x) reads wlsmv_stats when present.
Do not interpret summary(x$lavaan_fit), modindices(x), or
coef(x) as the rotated solution unless you know the fit used the lavaan
efa() path (method = "lavaan" or method = "set-esem" for B-ESEM).
When heywood_fix = TRUE (default on single-group esem and
esem_ordered), std_loadings may reflect a post-hoc
rotation correction while lavaan_fit still holds the pre-correction lavaan
solution. Prefer std_loadings(x) for reported loadings in that case.
Asparouhov, T., & Muthen, B. (2009). Exploratory structural equation modeling. Structural Equation Modeling, 16(3), 397–438.
esem for continuous data,
besem_ordered for bifactor ordered ESEM.
## Not run: tgt <- make_target( list(EX = items_EX, MD = items_MD, CI = items_CI), item_names = all_items ) fit_ord <- esem_ordered( data = Rdata, nfactors = 3, indicators = all_items, rotation = "target", target = tgt, factor_names = c("EX","MD","CI") ) summary(fit_ord, fit.measures = TRUE, standardized = TRUE) std_loadings(fit_ord) factor_correlations(fit_ord) ## End(Not run)## Not run: tgt <- make_target( list(EX = items_EX, MD = items_MD, CI = items_CI), item_names = all_items ) fit_ord <- esem_ordered( data = Rdata, nfactors = 3, indicators = all_items, rotation = "target", target = tgt, factor_names = c("EX","MD","CI") ) summary(fit_ord, fit.measures = TRUE, standardized = TRUE) std_loadings(fit_ord) factor_correlations(fit_ord) ## End(Not run)
Converts a fitted ESEM solution into explicit lavaan CFA syntax following
the ESEM-within-CFA approach (Marsh et al. 2014). The syntax uses
unstandardised ESEM loadings as starting values (start(v)*item) or
fixed values (v*item) depending on the referent scheme.
ewc_syntax(esem_fit, spec, referents = NULL, var_fixed = TRUE)ewc_syntax(esem_fit, spec, referents = NULL, var_fixed = TRUE)
esem_fit |
An |
spec |
An |
referents |
Named character vector of referent items, one per factor.
Names = factor names; values = item names. If |
var_fixed |
Logical. Identification mode:
|
A character string of lavaan model syntax. Use cat() to
inspect, or pass directly to fit_ewc via
custom_syntax.
Marsh, H. W., Morin, A. J. S., Parker, P. D., & Kaur, G. (2014). Exploratory structural equation modeling. Annual Review of Clinical Psychology, 10, 85-110.
Convenience helper for use as the target of
align_loadings(): pulls the STDYX-standardized loadings from a
MplusAutomation::readModels() result and returns one matrix per
group, in items-by-factors form with names matching common conventions.
extract_mplus_loadings( mp_out, standardized = "stdyx.standardized", items = NULL, factors = NULL )extract_mplus_loadings( mp_out, standardized = "stdyx.standardized", items = NULL, factors = NULL )
mp_out |
An object returned by |
standardized |
Which Mplus section to read. Default
|
items |
Optional character vector. If supplied, the returned matrices use this row order; otherwise the order from Mplus is used. |
factors |
Optional character vector. If supplied, the returned matrices use this column order. |
A named list of matrices, one per group, each with rownames (items) and colnames (factors).
Extracts the estimated correlation matrix among latent factors.
factor_correlations(x, digits = 3)factor_correlations(x, digits = 3)
x |
An |
digits |
Integer. Number of decimal places. Default 3. |
A symmetric matrix of factor correlations.
Returns a data frame of estimated latent factor scores, one column per
factor and one row per observation. Works with any esem_fit,
plain lavaan object, or esem_invariance result.
factor_scores( x, method = c("regression", "bartlett"), level = "auto", align = NULL, dCFI_cutoff = -0.01, ... )factor_scores( x, method = c("regression", "bartlett"), level = "auto", align = NULL, dCFI_cutoff = -0.01, ... )
x |
An |
method |
Character. Estimation method: |
level |
Character. Only used when |
align |
Optional alignment of the score columns.
Internally calls |
dCFI_cutoff |
Numeric. The |
... |
Reserved for future use. |
A data.frame with one column per latent factor (named by
the model's factor labels) and one row per observation. A group
column is prepended automatically for multi-group models. Rows for
missing observations contain NA in all factor columns.
## Not run: # Single fit fit <- esem(mydata, nfactors = 3) scores <- factor_scores(fit) head(scores) # Merge back to original data mydata_with_scores <- cbind(mydata, factor_scores(fit)) # Invariance result -- auto-selects most constrained passing level inv <- esem_invariance(spec) scores <- factor_scores(inv) # Force a specific invariance level scores <- factor_scores(inv, level = "weak") # Bartlett method scores <- factor_scores(fit, method = "bartlett") ## End(Not run)## Not run: # Single fit fit <- esem(mydata, nfactors = 3) scores <- factor_scores(fit) head(scores) # Merge back to original data mydata_with_scores <- cbind(mydata, factor_scores(fit)) # Invariance result -- auto-selects most constrained passing level inv <- esem_invariance(spec) scores <- factor_scores(inv) # Force a specific invariance level scores <- factor_scores(inv, level = "weak") # Bartlett method scores <- factor_scores(fit, method = "bartlett") ## End(Not run)
For each factor, selects the primary indicator with the largest absolute unstandardised loading from the ESEM solution. This is the item that will anchor that factor's identification in the EWC model.
find_ewc_referents(esem_fit, spec)find_ewc_referents(esem_fit, spec)
esem_fit |
An |
spec |
An |
Referents are chosen solely by maximum |loading| on the primary factor.
You can override automatic selection by supplying a custom named vector
to the referents argument of ewc_syntax or
fit_ewc.
A named character vector: names are factor names, values are the
selected referent item names (in the original case from spec).
Generates EWC lavaan syntax from a fitted ESEM solution and estimates it as
a standard lavaan::cfa() model – no rotation required.
fit_ewc( esem_fit, spec, referents = NULL, var_fixed = TRUE, missing = NULL, custom_syntax = NULL, ... )fit_ewc( esem_fit, spec, referents = NULL, var_fixed = TRUE, missing = NULL, custom_syntax = NULL, ... )
esem_fit |
An |
spec |
An |
referents |
Named character vector of referent items or |
var_fixed |
Logical. Identification mode. Default |
missing |
Character. Missing data handling. Defaults to
|
custom_syntax |
Character or |
... |
Additional arguments forwarded to |
An object of class "ewc_fit":
lavaan_fitlavaan fit object; all lavaan generics work on it.
syntaxlavaan model string used.
referentsNamed vector of referent items.
var_fixedIdentification mode used.
estimatorEstimator ("MLR" for continuous, "DWLS" for ordered).
specThe spec object.
ewc_syntax, find_ewc_referents,
compare_ewc
Returns CFI, TLI, RMSEA (with 90% CI) and SRMR formatted as a named
character vector. Uses the .scaled variants when the fit was
estimated with MLR/WLSMV, matching Mplus's default output. For B-ESEM /
ESEM WLSMV fits that carry pre-computed Mplus-matched values in
$wlsmv_stats, those values are used directly and the RMSEA CI is
computed from the scaled chi-square via non-central chi-squared inversion
(MacCallum, Browne & Sugawara, 1996).
fit_indices(fit)fit_indices(fit)
fit |
An |
A named character vector with elements "CFI", "TLI",
"RMSEA [90% CIs]", and "SRMR". Stack rows with
rbind() (wrapped in noquote()) to build a comparison
table.
## Not run: noquote(rbind( ESEM = fit_indices(results$fit_esem), BESEM = fit_indices(results$fit_besem) )) ## End(Not run)## Not run: noquote(rbind( ESEM = fit_indices(results$fit_esem), BESEM = fit_indices(results$fit_besem) )) ## End(Not run)
Forwards to lavaan::fitMeasures() when the fit used lavaan's efa()
path. When wlsmv_stats is present (custom DWLS/WLSMV from
besem_ordered(method = "rotation") or
esem_ordered(method = "rotation")), returns those indices instead.
## S3 method for class 'esem_fit' fitMeasures( object, fit.measures = c("cfi", "tli", "rmsea", "rmsea.ci.lower", "rmsea.ci.upper", "srmr", "aic", "bic"), ... )## S3 method for class 'esem_fit' fitMeasures( object, fit.measures = c("cfi", "tli", "rmsea", "rmsea.ci.lower", "rmsea.ci.upper", "srmr", "aic", "bic"), ... )
object |
An |
fit.measures |
Character vector of fit index names. Default returns a standard set: CFI, TLI, RMSEA, SRMR, AIC, BIC. |
... |
Passed to |
A named numeric vector of fit indices (by default CFI, TLI, RMSEA with its confidence interval, SRMR, and, on lavaan paths, AIC and BIC).
Generates the correct Mplus syntax for Bifactor ESEM with orthogonal target rotation, using separate BY statements per factor with (*1).
generate_mplus_besem_syntax( specific_factors, all_indicators, g_name = "G", cfa_factors = NULL, regressions = NULL, covariances = NULL, data_file = "mydata.dat", missing_code = 999, output_path = NULL )generate_mplus_besem_syntax( specific_factors, all_indicators, g_name = "G", cfa_factors = NULL, regressions = NULL, covariances = NULL, data_file = "mydata.dat", missing_code = 999, output_path = NULL )
specific_factors |
Named list of specific factor -> item assignments. |
all_indicators |
Character vector of all ESEM indicator names. |
g_name |
Character. General factor name. Default |
cfa_factors |
Optional named list of additional CFA factors. |
regressions |
Optional character vector of regression statements. |
covariances |
Optional character vector of covariance statements. |
data_file |
Character. Data file name. Default |
missing_code |
Numeric. Missing value code. Default |
output_path |
Character. Path to write .inp file. Returns syntax
invisibly if |
Mplus syntax string (invisibly). Writes file if output_path
supplied.
Generates a complete Mplus .inp file for ESEM with target rotation, using the correct Mplus syntax: separate BY statements per factor, each with its own (*1) label. Primary items always appear first, followed by cross-loading items targeted to zero.
generate_mplus_syntax( factors, cfa_factors = NULL, regressions = NULL, covariances = NULL, data_file = "mydata.dat", missing_code = 999, estimator = "MLR", output_path = NULL )generate_mplus_syntax( factors, cfa_factors = NULL, regressions = NULL, covariances = NULL, data_file = "mydata.dat", missing_code = 999, estimator = "MLR", output_path = NULL )
factors |
Named list mapping factor names to their primary indicator
names. Example: |
cfa_factors |
Optional named list of additional CFA factors. |
regressions |
Optional character vector of regression statements. |
covariances |
Optional character vector of covariance statements. |
data_file |
Character. Name of the data file. Default |
missing_code |
Numeric. Missing value code. Default |
estimator |
Character. Default |
output_path |
Character. Full path to write the .inp file. Returns
syntax string invisibly if |
Correct output format:
EX BY batEX1 batEX2 ... batEX8 <- primary items first batMD1~0 ... batCI5~0 (*1); <- cross-loadings after MD BY batMD1 batMD2 ... batMD5 <- primary items first batEX1~0 ... batCI5~0 (*1); <- cross-loadings after
The Mplus syntax as a character string (invisibly). Writes file
if output_path is supplied.
## Not run: syntax <- generate_mplus_syntax( factors = list( EX = c("batEX1","batEX2","batEX3","batEX4", "batEX5","batEX6","batEX7","batEX8"), MD = c("batMD1","batMD2","batMD3","batMD4","batMD5"), CI = c("batCI1","batCI2","batCI3","batCI4","batCI5") ), cfa_factors = list(Burnout = c("mbiEX1z","mbiEX2z","mbiEX3z", "mbiEX4z","mbiEX5z")), regressions = c("Burnout ON EX MD CI", "EX MD CI ON sex"), covariances = c("mbiEX1z WITH mbiEX2z"), output_path = file.path(tempdir(), "esem_model.inp") ) cat(syntax) ## End(Not run)## Not run: syntax <- generate_mplus_syntax( factors = list( EX = c("batEX1","batEX2","batEX3","batEX4", "batEX5","batEX6","batEX7","batEX8"), MD = c("batMD1","batMD2","batMD3","batMD4","batMD5"), CI = c("batCI1","batCI2","batCI3","batCI4","batCI5") ), cfa_factors = list(Burnout = c("mbiEX1z","mbiEX2z","mbiEX3z", "mbiEX4z","mbiEX5z")), regressions = c("Burnout ON EX MD CI", "EX MD CI ON sex"), covariances = c("mbiEX1z WITH mbiEX2z"), output_path = file.path(tempdir(), "esem_model.inp") ) cat(syntax) ## End(Not run)
Extract the Generated lavaan Syntax
get_syntax(x, cat = TRUE)get_syntax(x, cat = TRUE)
x |
An |
cat |
Logical. If |
The lavaan model string (invisibly).
Returns the raw lavaan object stored in x$lavaan_fit. For
continuous ESEM/B-ESEM and method = "lavaan" ordered fits, this is the
fitted model. For besem_ordered(method = "rotation") (and
esem_ordered(method = "rotation")), it is often an **auxiliary**
one-factor CFA used for DWLS weights only—see
The lavaan_fit slot on custom WLSMV fits.
Use lavaan::lavTestScore(), lavaan::lavInspect(), etc. only when
that slot represents the model you intend to analyse.
lavaan_fit(x)lavaan_fit(x)
x |
An |
A lavaan object.
## Not run: fit <- esem(mydata, nfactors = 3) lav <- lavaan_fit(fit) # Use semPlot semPlot::semPaths(lav, whatLabels = "std", layout = "tree") # Use lavaan::lavInspect lavaan::lavInspect(lav, "cor.lv") # factor correlations ## End(Not run)## Not run: fit <- esem(mydata, nfactors = 3) lav <- lavaan_fit(fit) # Use semPlot semPlot::semPaths(lav, whatLabels = "std", layout = "tree") # Use lavaan::lavInspect lavaan::lavInspect(lav, "cor.lv") # factor correlations ## End(Not run)
Builds the target loading matrix for B-ESEM. The general factor G has target 1 for all items (free to load everywhere). Each specific factor has target 1 for its primary items and 0 for all others.
make_bifactor_target(specific_factors, indicators, g_name = "G")make_bifactor_target(specific_factors, indicators, g_name = "G")
specific_factors |
Named list of specific factor -> item assignments. |
indicators |
Character vector of all item names. |
g_name |
Character. Name for the general factor. Default |
A numeric matrix (items x factors) of class "esem_target".
Column order: G first, then specific factors in list order.
tgt <- make_bifactor_target( specific_factors = list(EX = c("y1","y2","y3"), MD = c("y4","y5","y6")), indicators = paste0("y", 1:6) ) print(tgt)tgt <- make_bifactor_target( specific_factors = list(EX = c("y1","y2","y3"), MD = c("y4","y5","y6")), indicators = paste0("y", 1:6) ) print(tgt)
Constructs a target matrix suitable for use with rotation = "target"
in esem. Items listed in keys are assigned a target
value of 1 for their primary factor; all other cells are set to
0 (penalised to be near zero) or NA (free, no penalty).
make_target(keys, nitems = NULL, item_names = NULL, cross_loading_value = 0)make_target(keys, nitems = NULL, item_names = NULL, cross_loading_value = 0)
keys |
A named list where each element is a vector of item indices
(integers) or item names (characters) that are hypothesised to load
primarily on that factor. Names become factor names.
Example: |
nitems |
Integer. Total number of items. Required when |
item_names |
Optional character vector of item names of length
|
cross_loading_value |
Numeric or |
## Target vs. Soft Target Rotation
- **Hard target** (cross_loading_value = 0): Cross-loadings are
penalised toward zero. Use this when you have strong theory.
- **Soft target** (cross_loading_value = NA): Only primary loadings
are targeted; cross-loadings are completely free. Use this when you are
less certain about the zero pattern.
## Items Loading on Multiple Factors
An item can appear in multiple keys entries if it is expected to have
meaningful loadings on more than one factor. In that case its target value
will be 1 for both factors and cross_loading_value elsewhere.
A numeric matrix (items x factors) with row names set to item names
and column names set to factor names from keys.
# Simple 15-item, 3-factor target (integer keys) tgt <- make_target( keys = list(Extrav = 1:5, Agree = 6:10, Open = 11:15), nitems = 15 ) # Named items tgt2 <- make_target( keys = list(F1 = c("y1", "y2", "y3"), F2 = c("y4", "y5", "y6")), item_names = paste0("y", 1:6) ) # Soft target (cross-loadings free) tgt_soft <- make_target( keys = list(F1 = 1:5, F2 = 6:10), nitems = 10, cross_loading_value = NA )# Simple 15-item, 3-factor target (integer keys) tgt <- make_target( keys = list(Extrav = 1:5, Agree = 6:10, Open = 11:15), nitems = 15 ) # Named items tgt2 <- make_target( keys = list(F1 = c("y1", "y2", "y3"), F2 = c("y4", "y5", "y6")), item_names = paste0("y", 1:6) ) # Soft target (cross-loadings free) tgt_soft <- make_target( keys = list(F1 = 1:5, F2 = 6:10), nitems = 10, cross_loading_value = NA )
Forwards to lavaan::modindices() on lavaan_fit(x). For custom
WLSMV paths where lavaan_fit is an auxiliary one-factor CFA, indices
refer to that auxiliary model, not the rotated ESEM/B-ESEM solution; see
The lavaan_fit slot on custom WLSMV fits.
## S3 method for class 'esem_fit' modindices(object, sort. = TRUE, maximum.number = 20, ...)## S3 method for class 'esem_fit' modindices(object, sort. = TRUE, maximum.number = 20, ...)
object |
An |
sort. |
Logical. Sort by modification index value? Default |
maximum.number |
Integer. Maximum number of indices to return. Default 20. |
... |
Passed to |
A data.frame of modification indices, as returned by
lavaan::modindices().
Prints a formatted parameter table – standardized loadings, standard errors, z-values, and p-values – for one fit object or all three models in a pipeline. Analogous to the STDYX section of Mplus output.
parameters( x, model = "all", type = "loadings", suppress = 0, digits = 3, highlight_primary = TRUE )parameters( x, model = "all", type = "loadings", suppress = 0, digits = 3, highlight_primary = TRUE )
x |
An |
model |
Character. When |
type |
Character. |
suppress |
Numeric. Hide loadings with |z| below this value. Default 0 (show all). |
digits |
Integer. Decimal places. Default 3. |
highlight_primary |
Logical. Colour the target (primary) loadings using
ANSI codes. Supported in RStudio and most terminals; falls back silently to
plain output when colour is unavailable. Default |
Invisibly returns the parameter table as a data frame.
Reads a Mplus .out file and assembles the polychoric (tetrachoric)
correlation matrix that Mplus prints under SAMPLE STATISTICS when
SAMPSTAT is requested in the OUTPUT section.
parse_mplus_polychoric(out_file)parse_mplus_polychoric(out_file)
out_file |
Character. Full path to a Mplus |
The matrix can be passed directly to besem_ordered via the
r_obs_override argument so that R's DWLS optimisation uses the
same polychoric estimates as Mplus, eliminating rotational discrepancies
caused by differences in the polychoric estimators.
A named, symmetric numeric matrix of polychoric correlations with 1s on the diagonal. Row and column names are the item names as reported by Mplus (typically upper-case).
## Not run: R_mplus <- parse_mplus_polychoric( "path/to/besem_measurement.out" ) dim(R_mplus) # 18 x 18 # Use as input to besem_ordered so R rotates from the same matrix as Mplus fit_b <- besem_ordered( data = mydata, specific_factors = factor_items, r_obs_override = R_mplus ) ## End(Not run)## Not run: R_mplus <- parse_mplus_polychoric( "path/to/besem_measurement.out" ) dim(R_mplus) # 18 x 18 # Use as input to besem_ordered so R rotates from the same matrix as Mplus fit_b <- besem_ordered( data = mydata, specific_factors = factor_items, r_obs_override = R_mplus ) ## End(Not run)
Given a failed invariance level, uses a greedy score-test loop
(lavTestScore + group.partial) to identify and free the
minimum set of non-invariant equality constraints needed to restore
acceptable fit (DCFI >= -0.010). Downstream invariance levels are then
re-tested under the same partial constraints.
partial_invariance( inv, level, max_free = 10L, delta_cfi_cutoff = -0.01, verbose = TRUE )partial_invariance( inv, level, max_free = 10L, delta_cfi_cutoff = -0.01, verbose = TRUE )
inv |
An |
level |
Character. The invariance level to partially free:
|
max_free |
Integer. Maximum parameters to free before stopping. Default 10. Byrne et al. (1989) recommend freeing the minimum number – in practice >5 rarely recovers invariance. |
delta_cfi_cutoff |
Numeric. Stopping criterion: loop stops when DCFI >= this value. Default -0.010 (Cheung & Rensvold, 2002). |
verbose |
Logical. Print progress messages. Default |
An object of class "esem_partial_invariance" with:
$freed_paramsData frame: round, label, group_name, score (LM), delta_cfi (baseline-relative), converged.
$partial_fitesem_fit at level with freed params.
$downstreamNamed list of esem_fit for levels above target.
$downstream_lrtlavTestLRT() comparisons among downstream.
$tableCombined fit table (original + partial + downstream).
$convergedLogical: did DCFI pass cutoff?
$group_partialCharacter vector: final group.partial labels.
B-ESEM models are not supported. Use
lavaan::lavTestScore(inv$models[[level]]$lavaan_fit) directly.
Produces a sorted alignment ratio plot (as in Figure 2 of Mehrvarz & Rouder 2026) for one or all factor pairs. Flat horizontal spread = invariant (ICM-CFA tenable). Dispersion = misspecification.
## S3 method for class 'alignment_check' plot(x, pair = NULL, ...)## S3 method for class 'alignment_check' plot(x, pair = NULL, ...)
x |
An |
pair |
Character. Name of a specific factor pair (e.g., |
... |
Ignored. |
Called for its side effect of drawing the alignment-ratio plot;
invisibly returns NULL.
Print Method for alignment_check
## S3 method for class 'alignment_check' print(x, ...)## S3 method for class 'alignment_check' print(x, ...)
x |
An |
... |
Ignored. |
Invisibly returns x; called for the side effect of printing
the alignment-ratio summary.
Print Method for besem_fit
## S3 method for class 'besem_fit' print(x, ...)## S3 method for class 'besem_fit' print(x, ...)
x |
A |
... |
Ignored. |
Invisibly returns x; called for the side effect of printing
a formatted B-ESEM model overview to the console.
Print Method for esem_comparison
## S3 method for class 'esem_comparison' print(x, ...)## S3 method for class 'esem_comparison' print(x, ...)
x |
An |
... |
Ignored. |
Invisibly returns x; called for the side effect of printing
the comparison table.
Print Method for esem_comparison_pipeline
## S3 method for class 'esem_comparison_pipeline' print(x, hints = TRUE, ...)## S3 method for class 'esem_comparison_pipeline' print(x, hints = TRUE, ...)
x |
An |
hints |
Logical. Print a short help block listing the accessors
available on the object (e.g. |
... |
Ignored. |
Invisibly returns x; called for the side effect of printing
the comparison-pipeline overview.
Print Method for esem_fit
## S3 method for class 'esem_fit' print(x, ...)## S3 method for class 'esem_fit' print(x, ...)
x |
An |
... |
Ignored. |
Invisibly returns x; called for the side effect of printing
a formatted model overview to the console.
Print Method for esem_invariance
## S3 method for class 'esem_invariance' print(x, ...)## S3 method for class 'esem_invariance' print(x, ...)
x |
An |
... |
Ignored. |
Invisibly returns x; called for the side effect of printing
the invariance comparison table.
Print Method for esem_partial_invariance
## S3 method for class 'esem_partial_invariance' print(x, ...)## S3 method for class 'esem_partial_invariance' print(x, ...)
x |
An |
... |
Ignored. |
Invisibly returns x; called for the side effect of printing
the partial-invariance summary.
Print an esem_spec Object
## S3 method for class 'esem_spec' print(x, ...)## S3 method for class 'esem_spec' print(x, ...)
x |
An |
... |
Ignored. |
Invisibly returns x; called for the side effect of printing
the model specification.
Displays a make_target() result in a compact, readable format,
marking primary loadings (1), penalised cells (0), and
free cells (NA).
## S3 method for class 'esem_target' print(x, ...)## S3 method for class 'esem_target' print(x, ...)
x |
An |
... |
Ignored. |
Invisibly returns x; called for the side effect of printing
the target loading matrix.
Displays the fit-index comparison table returned by compare_ewc.
## S3 method for class 'ewc_comparison' print(x, ...)## S3 method for class 'ewc_comparison' print(x, ...)
x |
An |
... |
Ignored. |
Invisibly returns x.
Compact fit summary (CFI/TLI/RMSEA/SRMR and ) for an
ESEM-within-CFA fit.
## S3 method for class 'ewc_fit' print(x, ...)## S3 method for class 'ewc_fit' print(x, ...)
x |
An |
... |
Ignored. |
Invisibly returns x.
Displays McDonald's omega and Cronbach's alpha indices following the Morin / Rodriguez et al. reporting framework.
## S3 method for class 'reliability_indices' print(x, ...) ## S3 method for class 'omega_result' print(x, ...)## S3 method for class 'reliability_indices' print(x, ...) ## S3 method for class 'omega_result' print(x, ...)
x |
A |
... |
Ignored. |
Invisibly returns x; called for the side effect of printing
the reliability table.
The bifactor target rotation criterion surface has many local minima. R and Mplus sometimes converge to different ones, leading to different partitioning of variance between G and specific factors (while fit indices remain identical because they are rotation-invariant).
refine_rotation(results)refine_rotation(results)
results |
An |
This function uses orthogonal Procrustes rotation to compute the rotation
matrix that maps R's unrotated loading matrix toward the
Mplus STDYX solution, then evaluates whether the target criterion at that
is lower than R's best random-start criterion. If so, the
better rotation is adopted. This is methodologically sound because:
The same published criterion function is still being minimised.
Procrustes only provides a well-informed starting point; the final
solution is the converged GPArotation::targetT optimum from that
start.
The approach is analogous to Mansolf and Reise's (2016) recommendation to use Schmid-Leiman solutions as warm starts.
The same results object with fit_besem updated if a
better rotation was found, otherwise unchanged. A message reports whether
the criterion improved. The comparison_table is also updated.
Mansolf, M., and Reise, S. P. (2016). Exploratory bifactor analysis: The Schmid-Leiman orthogonalization and Jennrich-Bentler analytic rotations. Multivariate Behavioral Research, 51(5), 698–717.
## Not run: results <- run_comparison(spec, mplus_folder = "path/to/mplus") results <- refine_rotation(results) omega <- compute_omega(results) print(omega) ## End(Not run)## Not run: results <- run_comparison(spec, mplus_folder = "path/to/mplus") results <- refine_rotation(results) omega <- compute_omega(results) print(omega) ## End(Not run)
Takes a model specification from specify_model and
automatically fits CFA, ESEM, and B-ESEM in R, optionally runs all
three in Mplus, and returns a comparison table plus all fitted objects.
run_comparison( spec, mplus_folder = NULL, mplus_command = "Mplus", run_alignment = TRUE, group_equal = NULL, n_starts = 30L )run_comparison( spec, mplus_folder = NULL, mplus_command = "Mplus", run_alignment = TRUE, group_equal = NULL, n_starts = 30L )
spec |
An |
mplus_folder |
Character. Path to a folder for Mplus files. If
|
mplus_command |
Character. Path or command used to invoke Mplus.
Default |
run_alignment |
Logical. Run alignment check? Default |
group_equal |
Character vector of lavaan equality constraints
(e.g. |
n_starts |
Integer. Random rotation starts for the B-ESEM rotation
search. Default |
An object of class "esem_comparison_pipeline" containing:
specThe original model specification.
fit_cfalavaan CFA fit object.
fit_esemesem_fit object.
fit_besembesem_fit object.
alignmentalignment_check result (if run).
comparison_tableData frame of fit indices.
mplus_resultsList of Mplus readModels results (if run).
## Not run: spec <- specify_model( EX = c("batEX1","batEX2","batEX3","batEX4", "batEX5","batEX6","batEX7","batEX8"), MD = c("batMD1","batMD2","batMD3","batMD4","batMD5"), CI = c("batCI1","batCI2","batCI3","batCI4","batCI5"), data = Rdata, label = "Burnout Battery" ) # R only results <- run_comparison(spec) print(results) # R + Mplus results <- run_comparison( spec, mplus_folder = "path/to/mplus_output" ) print(results) # Access individual fits summary(results$fit_esem, fit.measures = TRUE, standardized = TRUE) std_loadings(results$fit_besem, suppress = 0.10) factor_correlations(results$fit_esem) plot(results$alignment) ## End(Not run)## Not run: spec <- specify_model( EX = c("batEX1","batEX2","batEX3","batEX4", "batEX5","batEX6","batEX7","batEX8"), MD = c("batMD1","batMD2","batMD3","batMD4","batMD5"), CI = c("batCI1","batCI2","batCI3","batCI4","batCI5"), data = Rdata, label = "Burnout Battery" ) # R only results <- run_comparison(spec) print(results) # R + Mplus results <- run_comparison( spec, mplus_folder = "path/to/mplus_output" ) print(results) # Access individual fits summary(results$fit_esem, fit.measures = TRUE, standardized = TRUE) std_loadings(results$fit_besem, suppress = 0.10) factor_correlations(results$fit_esem) plot(results$alignment) ## End(Not run)
Creates complete Mplus .inp files for configural, weak, strong, and
strict invariance, runs them via MplusAutomation, then prints a
side-by-side comparison of fit statistics against the R results from
esem_invariance.
run_mplus_besem_invariance( inv, output_folder, mplus_command = "mplus", group_labels = NULL, missing_code = -999, difftest = TRUE )run_mplus_besem_invariance( inv, output_folder, mplus_command = "mplus", group_labels = NULL, missing_code = -999, difftest = TRUE )
inv |
An |
output_folder |
Character. Folder where |
mplus_command |
Character. Full path to the Mplus executable.
Default |
group_labels |
Named character vector mapping group values to Mplus
labels, e.g. |
missing_code |
Numeric. Missing value sentinel written to the data
file. Default |
difftest |
Logical. Generate |
A data frame (invisibly) with _R, _Mplus, and
delta_ columns for each fit statistic. Printed as a table.
## Not run: inv <- esem_invariance(spec, model = "besem") cmp <- run_mplus_besem_invariance( inv, output_folder = "mplus_inv/", mplus_command = "C:/Program Files/Mplus/mplus.exe", group_labels = c("1" = "MALE", "2" = "FEMALE") ) ## End(Not run)## Not run: inv <- esem_invariance(spec, model = "besem") cmp <- run_mplus_besem_invariance( inv, output_folder = "mplus_inv/", mplus_command = "C:/Program Files/Mplus/mplus.exe", group_labels = c("1" = "MALE", "2" = "FEMALE") ) ## End(Not run)
Writes all standard output files to output_folder:
<label>_fit_indices.csv – CFI/TLI/RMSEA/SRMR for all models.
<label>_CFA_loadings.csv – standardised loading matrix (wide).
<label>_ESEM_loadings.csv – standardised loading matrix (wide).
<label>_BESEM_loadings.csv – standardised loading matrix (wide).
<label>_loadings_comparison.csv – R vs Mplus loadings, long
format with SEs, z-scores, p-values, and diff_std.
<label>_omega.csv – Reliability indices (if
indices is supplied).
<label>_results.xlsx – Single xlsx workbook with all results
(when xlsx = TRUE).
save_results(results, omega = NULL, output_folder, label = NULL, xlsx = FALSE)save_results(results, omega = NULL, output_folder, label = NULL, xlsx = FALSE)
results |
An |
omega |
Optional. A |
output_folder |
Character. Path to the folder where files are written. Created if it does not exist. |
label |
Character. Prefix for output file names. Defaults to
|
xlsx |
Logical. If |
Invisibly returns a character vector of file paths written.
## Not run: results <- run_comparison(spec) indices <- compute_indices(results) save_results(results, indices, output_folder = file.path(tempdir(), "esem_results")) ## End(Not run)## Not run: results <- run_comparison(spec) indices <- compute_indices(results) save_results(results, indices, output_folder = file.path(tempdir(), "esem_results")) ## End(Not run)
Convenience function to set specific cells of a target matrix after
initial construction with make_target.
set_target(target, items, factors, value)set_target(target, items, factors, value)
target |
An |
items |
Integer indices or character names of items to modify. |
factors |
Integer indices or character names of factors to modify. |
value |
New value: |
The modified target matrix.
Creates a model specification object that flows automatically through all subsequent steps – CFA, ESEM, B-ESEM, alignment check, Mplus syntax, and comparison tables. Define your factor structure once at the top; everything else is derived automatically.
specify_model( ..., data, label = "ESEM Model", ordered = FALSE, group = NULL, estimator = NULL, missing = NULL )specify_model( ..., data, label = "ESEM Model", ordered = FALSE, group = NULL, estimator = NULL, missing = NULL )
... |
Named character vectors, one per factor. The name becomes the
factor name and the vector contains the indicator names.
Example: |
data |
A |
label |
Optional character string labelling the model (used in
output headers and Mplus titles). Default |
ordered |
Logical or character vector. If |
group |
Character. Name of a grouping variable in |
estimator |
Character. Override the auto-selected lavaan estimator.
If |
missing |
Character. Missing-data handling. If |
An object of class "esem_spec" containing:
factorsNamed list of factor -> indicator assignments.
factor_namesCharacter vector of factor names.
all_itemsCharacter vector of all indicators in order.
nfactorsNumber of specific factors.
dataThe supplied data frame.
labelModel label.
cfa_syntaxReady-to-use lavaan CFA model string.
targetTarget matrix for ESEM target rotation.
bifactor_targetTarget matrix for B-ESEM.
data("HolzingerSwineford1939", package = "lavaan") # Continuous indicators (MLR estimator by default) spec <- specify_model( Visual = c("x1", "x2", "x3"), Textual = c("x4", "x5", "x6"), Speed = c("x7", "x8", "x9"), data = HolzingerSwineford1939, label = "Holzinger-Swineford 3-factor" ) print(spec) # Multi-group spec (for invariance testing) spec_mg <- specify_model( Visual = c("x1", "x2", "x3"), Textual = c("x4", "x5", "x6"), Speed = c("x7", "x8", "x9"), data = HolzingerSwineford1939, group = "sex", label = "HS 3-factor (multi-group)" ) # Run the CFA/ESEM/B-ESEM comparison from a spec results <- run_comparison(spec) print(results)data("HolzingerSwineford1939", package = "lavaan") # Continuous indicators (MLR estimator by default) spec <- specify_model( Visual = c("x1", "x2", "x3"), Textual = c("x4", "x5", "x6"), Speed = c("x7", "x8", "x9"), data = HolzingerSwineford1939, label = "Holzinger-Swineford 3-factor" ) print(spec) # Multi-group spec (for invariance testing) spec_mg <- specify_model( Visual = c("x1", "x2", "x3"), Textual = c("x4", "x5", "x6"), Speed = c("x7", "x8", "x9"), data = HolzingerSwineford1939, group = "sex", label = "HS 3-factor (multi-group)" ) # Run the CFA/ESEM/B-ESEM comparison from a spec results <- run_comparison(spec) print(results)
Returns the standardized factor loading matrix. Uses std_rotated_loadings
when present (custom WLSMV rotation path or Heywood-corrected loadings);
otherwise extracts from lavaan_fit(x). Analogous to Mplus STDYX loadings.
std_loadings(x, digits = 3, suppress = 0)std_loadings(x, digits = 3, suppress = 0)
x |
An |
digits |
Integer. Rounding digits. Default 3. |
suppress |
Numeric. Loadings with absolute value below this threshold
are replaced with |
A matrix of standardized loadings (items x factors).
Prints a full summary for an esem_fit. For models fit through the
DWLS-from-scratch path (B-ESEM ordered, or esem_ordered(method =
"rotation")), the underlying lavaan_fit slot holds an auxiliary
1-factor CFA used only for weight-matrix extraction; in that case we print
a self-contained bifactory summary built from the stored rotated
loadings, standard errors, and WLSMV statistics. Otherwise the call is
forwarded to lavaan::summary().
## S3 method for class 'esem_fit' summary(object, fit.measures = TRUE, standardized = TRUE, rsquare = FALSE, ...)## S3 method for class 'esem_fit' summary(object, fit.measures = TRUE, standardized = TRUE, rsquare = FALSE, ...)
object |
An |
fit.measures |
Logical. Include fit indices? Default |
standardized |
Logical. Include standardized solution? Default |
rsquare |
Logical. Include R-squared for endogenous variables? Default |
... |
Additional arguments passed to |
Invisibly returns object; called for the side effect of
printing the model summary.
Forwards to lavaan::lavaan-class summary on the underlying
lavaan fit, so you do not need to library(lavaan) separately.
## S3 method for class 'ewc_fit' summary( object, fit.measures = TRUE, standardized = TRUE, show_loadings = TRUE, ... )## S3 method for class 'ewc_fit' summary( object, fit.measures = TRUE, standardized = TRUE, show_loadings = TRUE, ... )
object |
An |
fit.measures |
Logical. Include fit indices. Default |
standardized |
Logical. Include standardised estimates. Default |
show_loadings |
Logical. After the lavaan summary, also print the
|
... |
Additional arguments passed to lavaan's summary method. |
Invisibly returns the lavaan summary object.