Skip to content

ESEM (feature request)#137

Open
LSLindeloo wants to merge 2 commits intojasp-stats:masterfrom
LSLindeloo:ESEM
Open

ESEM (feature request)#137
LSLindeloo wants to merge 2 commits intojasp-stats:masterfrom
LSLindeloo:ESEM

Conversation

@LSLindeloo
Copy link
Contributor

@LSLindeloo
Copy link
Contributor Author

Hi @Kucharssim,

What do you think about this analysis, would you recommend fundamental changes, e.g. to the qml layout?

The analysis runs fine in R, however when i run the analysis in JASP, the "engine crashes", do you know what might be the problem?

Thanks in advance!

@juliuspfadt juliuspfadt self-requested a review July 1, 2025 09:42
@codecov-commenter
Copy link

Codecov Report

❌ Patch coverage is 0% with 658 lines in your changes missing coverage. Please review.
✅ Project coverage is 55.95%. Comparing base (8e4cbc4) to head (2131aae).

Files with missing lines Patch % Lines
R/esem.R 0.00% 658 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #137      +/-   ##
==========================================
- Coverage   61.67%   55.95%   -5.72%     
==========================================
  Files          13       14       +1     
  Lines        6437     7095     +658     
==========================================
  Hits         3970     3970              
- Misses       2467     3125     +658     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new ESEM analysis to the SEM module (new QML UI + R backend) to address feature request jasp-issues#1866.

Changes:

  • Register new analysis entry in inst/Description.qml.
  • Add new UI definition inst/qml/ESEM.qml with model/EFA-block inputs and estimation/output options.
  • Add new backend implementation R/esem.R and export ESEM in NAMESPACE.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 17 comments.

File Description
inst/qml/ESEM.qml New ESEM UI (models + EFA blocks, estimation/output, multigroup controls).
inst/Description.qml Registers “Exploratory Structural Equation Modeling” analysis pointing to ESEM.qml / ESEM.
R/esem.R New ESEM analysis backend (model building, lavaan fitting, tables/plots).
NAMESPACE Exports ESEM.

"dependentCorrelation", "threshold", "scalingParameter", "efaConstrained", "standardizedVariable", "naAction", "estimator", "modelTest",
"errorCalculationMethod", "informationMatrix", "emulation", "group", "equalLoading", "equalIntercept",
"equalResidual", "equalResidualCovariance", "equalMean", "equalThreshold", "equalRegression",
"equalVariance", "equalLatentCovariance", "dataType", "sampleSize", "freeParameters", "rotation"))
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dependency list uses equalVariance, but the QML option is equalLatentVariance. This will prevent correct cache invalidation when the latent-variance equality constraint is toggled.

Suggested change
"equalVariance", "equalLatentCovariance", "dataType", "sampleSize", "freeParameters", "rotation"))
"equalLatentVariance", "equalLatentCovariance", "dataType", "sampleSize", "freeParameters", "rotation"))

Copilot uses AI. Check for mistakes.
options[["equalRegression"]],
options[["equalResidual"]],
options[["equalResidualCovariance"]],
options[["equalVariance"]],
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

equality_constraints reads options[["equalVariance"]], but the QML checkbox is equalLatentVariance. This means the latent-variance constraint won’t be applied (and can error if the option key is missing).

Suggested change
options[["equalVariance"]],
isTRUE(options[["equalVariance"]]) || isTRUE(options[["equalLatentVariance"]]),

Copilot uses AI. Check for mistakes.
Comment on lines +404 to +411
if (options[["group"]] != "") {
lavopts[["group"]] <- .v(options[["group"]])
}

# sampling weights
if (options[["samplingWeights"]] != "") {
lavopts[["sampling.weights"]] <- .v(options[["samplingWeights"]])
}
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.v() is called here but is never defined in this file/package scope. Use the raw option strings (as SEM does) or define .v <- I explicitly before use.

Copilot uses AI. Check for mistakes.
Comment on lines +111 to +113
# edit options[["models"]]
options[["models"]] <- lapply(options[["models"]], .esemSyntax)
return(options)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

options[["models"]] <- lapply(..., .esemSyntax) can inject NULL entries because .esemSyntax() uses bare return() in several branches. That will make later code (which indexes options[["models"]][[i]]) fail. Consider filtering/removing empty models explicitly (like .semPrepOpts() does) instead of returning NULL from the mapper.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +31
Analysis
{
title: qsTr("Exploratory Structural Equation Modeling")
qml: "ESEM.qml"
func: "ESEM"
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New analysis added, but there are no testthat tests covering it (this repo has comprehensive tests for SEM-family analyses, e.g. tests/testthat/test-sem.R). Please add at least a smoke test that runs ESEM and checks key outputs are produced.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +284 to +285
modelContainer$setError(paste0("Error in model \"", options[["models"]][[i]][["name"]], "\" - ",
.decodeVarsInMessage(names(dataset), errmsg)))
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error text is user-visible but isn’t wrapped in gettext()/gettextf(), so it won’t be translated. Please wrap the full message (including the model name) for i18n consistency with the rest of the module.

Suggested change
modelContainer$setError(paste0("Error in model \"", options[["models"]][[i]][["name"]], "\" - ",
.decodeVarsInMessage(names(dataset), errmsg)))
fullErrMsg <- gettextf(
"Error in model \"%1$s\" - %2$s",
options[["models"]][[i]][["name"]],
.decodeVarsInMessage(names(dataset), errmsg)
)
modelContainer$setError(fullErrMsg)

Copilot uses AI. Check for mistakes.
.esemComputeResults <- function(modelContainer, dataset, options) {
#' create result list from options
# find reusable results
if (!options[["estimator"]] %in% c("default", "ml", "mlr", "mlf") && options[["naAction"]] == "fiml") return()
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning NULL here can break callers like .esemFitTab() which assume a list of fits. Prefer setting a user-visible error on modelContainer and returning, or make callers handle NULL safely.

Suggested change
if (!options[["estimator"]] %in% c("default", "ml", "mlr", "mlf") && options[["naAction"]] == "fiml") return()
if (!options[["estimator"]] %in% c("default", "ml", "mlr", "mlf") && options[["naAction"]] == "fiml") {
modelContainer$setError(gettext("Full information maximum likelihood (FIML) is only available for ML-based estimators."))
return()
}

Copilot uses AI. Check for mistakes.
overtitle = gettextf("%s%% Confidence Interval", options$ciLevel * 100))


efatab$addFootnote(message = gettextf("Applied rotation method is <i>%s</i>. Different EFA blocks are rotated seperately.", options[["rotation"]]))
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling: "seperately" → "separately".

Suggested change
efatab$addFootnote(message = gettextf("Applied rotation method is <i>%s</i>. Different EFA blocks are rotated seperately.", options[["rotation"]]))
efatab$addFootnote(message = gettextf("Applied rotation method is <i>%s</i>. Different EFA blocks are rotated separately.", options[["rotation"]]))

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +79
.esemEditOptions <- function(dataset, options) {
indicators <- unlist(unique(lapply(options[["models"]], function(x) {
parsed <- lavaan::lavParseModelString(x[["syntax"]][["model"]], TRUE)
return(unique(parsed[parsed$op == "=~",]$rhs))
})))
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-detecting ordinal indicators here only parses the CFA/regression syntax and ignores indicators that are only included via EFA blocks. That can lead to choosing an ML-type estimator even when EFA indicators are ordinal. Consider adding EFA block variables to indicators before checking is.ordered.

Copilot uses AI. Check for mistakes.
Comment on lines +118 to +124
cfaAndRegressionSyntax <- model[["syntax"]][["model"]]
if(cfaAndRegressionSyntax == "") return()

efaBlocks <- model[["efaBlock"]]
efaSyntax <- ""
for (i in 1:length(efaBlocks)) {
if (length(efaBlocks[[i]][["variables"]]) < 1) return()
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.esemSyntax() returns NULL if the CFA/regression syntax is empty, which prevents EFA-only models and also creates NULL entries in options[["models"]]. Consider allowing empty CFA/regression syntax (generate model from EFA blocks only) and only skipping specific empty EFA blocks rather than aborting the whole model.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request]: Exploratory Structural Equation Modeling (ESEM)

4 participants