Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 3 additions & 70 deletions R/confirmatoryfactoranalysis.R
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,10 @@ confirmatoryFactorAnalysisInternal <- function(jaspResults, dataset, options, ..
if (options[["dataType"]] == "varianceCovariance") {
.quitAnalysis(gettext("Bootstrapping is not available for variance-covariance matrix input."))
}

cfaResult[["lav"]] <- jaspSem::lavBootstrap(cfaResult[["lav"]], options$bootstrapSamples,
standard = options[["standardized"]] != "none", typeStd = type)

}

# Save cfaResult as state so it's available even when opts don't change
Expand Down Expand Up @@ -990,7 +992,7 @@ confirmatoryFactorAnalysisInternal <- function(jaspResults, dataset, options, ..
}

# Intercepts ----
if (options$meanStructure) {
if (options$meanStructure || options$group != "") {

Comment on lines +995 to 996
Copy link

Copilot AI Jul 22, 2025

Choose a reason for hiding this comment

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

[nitpick] The condition has been expanded to include group analysis. Consider adding a comment explaining why intercepts are needed when group analysis is performed, as this may not be immediately obvious to future maintainers.

Suggested change
if (options$meanStructure || options$group != "") {
# Intercepts are included when group analysis is performed (options$group != "")
# to account for group-level differences in the model. This ensures that the
# model properly estimates group-specific means for the latent variables.
if (options$meanStructure || options$group != "") {

Copilot uses AI. Check for mistakes.
if (options$group != "") {

Expand Down Expand Up @@ -1559,75 +1561,6 @@ confirmatoryFactorAnalysisInternal <- function(jaspResults, dataset, options, ..

}

# delete once jaspSem is merged
lavBootstrap <- function(fit, samples = 1000, standard = FALSE, typeStd = NULL) {
# Run bootstrap, track progress with progress bar
# Notes: faulty runs are simply ignored
# recommended: add a warning if not all boot samples are successful
# fit <- lavBootstrap(fit, samples = 1000)
# if (nrow(fit@boot$coef) < 1000)
# tab$addFootnote(gettextf("Not all bootstrap samples were successful: CI based on %.0f samples.", nrow(fit@boot$coef)),
# "<em>Note.</em>")


coef_with_callback <- function(lav_object) {
# Progress bar is ticked every time coef() is evaluated, which happens once on the main object:
# https://github.com/yrosseel/lavaan/blob/77a568a574e4113245e2f6aff1d7c3120a26dd90/R/lav_bootstrap.R#L107
# and then every time on a successful bootstrap:
# https://github.com/yrosseel/lavaan/blob/77a568a574e4113245e2f6aff1d7c3120a26dd90/R/lav_bootstrap.R#L375
# i.e., samples + 1 times
progressbarTick()

return(lavaan::coef(lav_object))
}

coef_with_callback_std <- function(lav_object, typeStd) {
std <- lavaan::standardizedSolution(lav_object, type = typeStd)
out <- std$est.std

progressbarTick()

return(out)
}

startProgressbar(samples + 1)

if (!standard) {
bootres <- lavaan::bootstrapLavaan(object = fit, R = samples, FUN = coef_with_callback)
} else {
bootres <- lavaan::bootstrapLavaan(object = fit, R = samples, FUN = coef_with_callback_std, typeStd = typeStd)
}

# Add the bootstrap samples to the fit object
fit@boot <- list(coef = bootres)
fit@Options$se <- "bootstrap"

# exclude error bootstrap runs
err_id <- attr(fit@boot$coef, "error.idx")
if (length(err_id) > 0L) {
fit@boot$coef <- fit@boot$coef[-err_id, , drop = FALSE]
}

# we actually need the SEs from the bootstrap not the SEs from ML or something
N <- nrow(fit@boot$coef)

# we multiply the var by (n-1)/n because lavaan actually uses n for the variance instead of n-1
if (!standard) {
# for unstandardized
fit@ParTable$se[fit@ParTable$free != 0] <- apply(fit@boot$coef, 2, sd) * sqrt((N-1)/N)
} else {
fit@ParTable$se <- apply(fit@boot$coef, 2, sd) * sqrt((N-1)/N)
# the standardized solution gives all estimates not only the unconstrained, so we need to change
# the free prameters in the partable and also change the estimate
fit@ParTable$free <- seq_len(ncol(fit@boot$coef))
std <- lavaan::standardizedSolution(fit, type = typeStd)
fit@ParTable$est <- std$est.std
}


return(fit)
}


.cfaAddScoresToData <- function(jaspResults, options, cfaResult, dataset) {

Expand Down
1 change: 1 addition & 0 deletions R/exploratoryfactoranalysis.R
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ exploratoryFactorAnalysisInternal <- function(jaspResults, dataset, options, ...
dataset <- .pcaAndEfaHandleData(dataset, options, ready)
.pcaAndEfaDataCovarianceCheck(dataset, options, ready)


if (ready)
.pcaCheckErrors(dataset, options, method = "efa")

Expand Down
21 changes: 13 additions & 8 deletions R/principalcomponentanalysis.R
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ principalComponentAnalysisInternal <- function(jaspResults, dataset, options, ..
dataset[] <- lapply(dataset, function(x) as.numeric(as.character(x))) # the psych-package wants data to be numeric
return(dataset)
} else {
dataset[] <- lapply(dataset, function(x) as.numeric(as.character(x)))
return(dataset)
return(.readDataSetToEnd(columns.as.numeric = unlist(options$variables)))
Copy link

Copilot AI Jul 22, 2025

Choose a reason for hiding this comment

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

This line appears to be duplicated logic from the .pcaAndEfaHandleData function. Consider consolidating this data reading logic to avoid code duplication.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it is already in a common function

}
} else { # if variance covariance matrix as input
columnIndices <- sapply(options$variables, jaspBase:::columnIndexInData) + 1 # cpp starts at 0
Expand All @@ -90,19 +89,23 @@ principalComponentAnalysisInternal <- function(jaspResults, dataset, options, ..
return(dataset)
}

# it seems the column names are sorted alphabetically when all columns are read
# so we need to sort the column names to match the order of the variables
sortedIndices <- sort(as.numeric(gsub(".*_(\\d+)_.*", "\\1", colnames(dataset))))
sortedNames <- paste0("JaspColumn_", sortedIndices, "_Encoded")
dataset <- dataset[, sortedNames]
Comment on lines +92 to +96
Copy link

Copilot AI Jul 22, 2025

Choose a reason for hiding this comment

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

This comment and the following sorting logic is duplicated from .pcaAndEfaDataCovarianceCheck function. Consider extracting this into a shared utility function to reduce code duplication.

Suggested change
# it seems the column names are sorted alphabetically when all columns are read
# so we need to sort the column names to match the order of the variables
sortedIndices <- sort(as.numeric(gsub(".*_(\\d+)_.*", "\\1", colnames(dataset))))
sortedNames <- paste0("JaspColumn_", sortedIndices, "_Encoded")
dataset <- dataset[, sortedNames]
dataset <- .sortDatasetColumns(dataset, options$variables)

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

the code is already in a common function shared between PCA and EFA.


# possible data matrix?
if ((nrow(dataset) != ncol(dataset)))
.quitAnalysis(gettext("Input data does not seem to be a square matrix! Please check the format of the input data."))

if (!all(dataset[lower.tri(dataset)] == t(dataset)[lower.tri(dataset)]))
.quitAnalysis(gettext("Input data does not seem to be a symmetric matrix! Please check the format of the input data."))

if (cfa) {
if (options[["group"]] != "") .quitAnalysis(gettext("Grouping variable not supported for covariance matrix input"))
if (options[["meanStructure"]]) .quitAnalysis(gettext("Mean structure not supported for covariance matrix input"))
}
usedvars <- unlist(options[["variables"]])
var_idx <- match(usedvars, colnames(dataset))

mat <- try(as.matrix(dataset))
mat <- try(as.matrix(dataset[var_idx, var_idx]))
if (inherits(mat, "try-error"))
.quitAnalysis(gettext("All cells must be numeric."))

Expand All @@ -115,7 +118,9 @@ principalComponentAnalysisInternal <- function(jaspResults, dataset, options, ..
.quitAnalysis("Not enough valid columns to run this analysis")
}
}
return()


return(mat)
}

.pcaCheckErrors <- function(dataset, options, method = "pca") {
Expand Down
2 changes: 1 addition & 1 deletion jaspFactor.Rproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Version: 1.0
ProjectId: 5cc9ddfa-69dc-44cb-b072-af7608690805
ProjectId: ec780552-3ce8-46f5-9c6d-e55f97eb08ff

RestoreWorkspace: Default
SaveWorkspace: Default
Expand Down
Loading
Loading