Conversation
|
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! |
Codecov Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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.qmlwith model/EFA-block inputs and estimation/output options. - Add new backend implementation
R/esem.Rand exportESEMinNAMESPACE.
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")) |
There was a problem hiding this comment.
Dependency list uses equalVariance, but the QML option is equalLatentVariance. This will prevent correct cache invalidation when the latent-variance equality constraint is toggled.
| "equalVariance", "equalLatentCovariance", "dataType", "sampleSize", "freeParameters", "rotation")) | |
| "equalLatentVariance", "equalLatentCovariance", "dataType", "sampleSize", "freeParameters", "rotation")) |
| options[["equalRegression"]], | ||
| options[["equalResidual"]], | ||
| options[["equalResidualCovariance"]], | ||
| options[["equalVariance"]], |
There was a problem hiding this comment.
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).
| options[["equalVariance"]], | |
| isTRUE(options[["equalVariance"]]) || isTRUE(options[["equalLatentVariance"]]), |
| if (options[["group"]] != "") { | ||
| lavopts[["group"]] <- .v(options[["group"]]) | ||
| } | ||
|
|
||
| # sampling weights | ||
| if (options[["samplingWeights"]] != "") { | ||
| lavopts[["sampling.weights"]] <- .v(options[["samplingWeights"]]) | ||
| } |
There was a problem hiding this comment.
.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.
| # edit options[["models"]] | ||
| options[["models"]] <- lapply(options[["models"]], .esemSyntax) | ||
| return(options) |
There was a problem hiding this comment.
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.
| Analysis | ||
| { | ||
| title: qsTr("Exploratory Structural Equation Modeling") | ||
| qml: "ESEM.qml" | ||
| func: "ESEM" |
There was a problem hiding this comment.
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.
| modelContainer$setError(paste0("Error in model \"", options[["models"]][[i]][["name"]], "\" - ", | ||
| .decodeVarsInMessage(names(dataset), errmsg))) |
There was a problem hiding this comment.
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.
| 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) |
| .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() |
There was a problem hiding this comment.
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.
| 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() | |
| } |
| 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"]])) |
There was a problem hiding this comment.
Spelling: "seperately" → "separately".
| 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"]])) |
| .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)) | ||
| }))) |
There was a problem hiding this comment.
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.
| cfaAndRegressionSyntax <- model[["syntax"]][["model"]] | ||
| if(cfaAndRegressionSyntax == "") return() | ||
|
|
||
| efaBlocks <- model[["efaBlock"]] | ||
| efaSyntax <- "" | ||
| for (i in 1:length(efaBlocks)) { | ||
| if (length(efaBlocks[[i]][["variables"]]) < 1) return() |
There was a problem hiding this comment.
.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.
fixes jasp-stats/jasp-issues#1866