
Confirmatory Factor Analysis Optimization with AI Assistance
optimal_cfa_with_ai.RdAutomatically refines a user-specified Confirmatory Factor Analysis (CFA) model by iteratively evaluating fit and modification indices. At each step, the function:
1. Fits the CFA model and computes the scaled RMSEA.
2. If RMSEA <= rmsea_threshold, stops.
3. Otherwise, identifies all modification indices (MI) > mi_threshold.
4. Selects the largest MI and compares the standardized loadings for the two involved items; removes the item with the smaller loading.
5. Records the modification, RMSEA and MI, updates the model syntax by dropping the removed item, and repeats until RMSEA is acceptable, no MI exceed threshold, or max_steps is reached.
Optionally, when analyze_removed = TRUE, it uses the OpenAI API to generate concise justifications for exclusion or retention of each item, based on provided item_definitions and factor_definitions.
Usage
optimal_cfa_with_ai(
initial_model,
data,
rmsea_threshold = 0.08,
mi_threshold = 3.84,
max_steps = 10,
verbose = TRUE,
debug = FALSE,
filter_expr = NULL,
exclude_items = character(0),
analyze_removed = FALSE,
api_key = NULL,
item_definitions = NULL,
domain_name = "Dominio por Defecto",
scale_title = "Título de la Escala por Defecto",
construct_definition = "",
model_name = "Modelo CFA",
gpt_model = "gpt-3.5-turbo",
factor_definitions = NULL,
...
)Arguments
- initial_model
A character string with the lavaan-style CFA model syntax.
- data
A
data.framecontaining the observed variables.- rmsea_threshold
Numeric. Maximum acceptable scaled RMSEA to stop refinement.
- mi_threshold
Numeric. Minimum modification index value to consider.
- max_steps
Integer. Maximum number of refinement iterations.
- verbose
Logical. If
TRUE, prints progress messages.- debug
Logical. If
TRUE, prints additional debugging information.- filter_expr
An expression to subset
databefore fitting (optional).- exclude_items
Character vector of items to never remove.
- analyze_removed
Logical. If
TRUE, performs AI-driven conceptual analysis.- api_key
String. OpenAI API key (required if
analyze_removed = TRUE).- item_definitions
Named list mapping each item to its textual content for AI prompts.
- domain_name
Character. Domain or factor label used in AI prompts.
- scale_title
Character. Scale title used in AI prompts.
- construct_definition
Character. Brief construct definition for AI context.
- model_name
Character. Label for the CFA model in AI prompts.
- gpt_model
Character. Name of the ChatGPT model (e.g.,
"gpt-3.5-turbo").- factor_definitions
Named list of factor descriptions, indexed by factor name.
- ...
Additional arguments passed to
cfa.
Details
The function proceeds through these phases:
Initialization: Parses the initial model syntax into factor and item lists.
Filtering: Optionally subsets
dataviafilter_expr.Iterative refinement (up to
max_steps):Fit the current CFA model.
Compute scaled RMSEA; if <=
rmsea_threshold, stop.Extract all MI >
mi_threshold; if none, stop.Select the single largest MI, compare the two involved items’ standardized loadings, and remove the item with the lower loading.
Update the model syntax by dropping that item and log the step.
Final fit: Stores the last fitted
lavaanobject and final RMSEA/CFI.Conceptual analysis (optional): For each removed and retained item, calls the OpenAI API to generate justification text.
Value
A list with elements:
- final_model
Character. The refined CFA model syntax.
- final_fit
A
lavaanobject of the last fitted model.- log
Data frame recording each refinement step (
step,modification,mi_value,rmsea).- removed_items
Character vector of items removed.
- alternative_rmsea
Numeric. Final scaled RMSEA.
- final_cfi
Numeric. Final scaled CFI.
- conceptual_analysis
List with sublists
removedandkeptcontaining AI-generated texts, orNULLif not performed.
Examples
data(Data_Personality)
model <- '
F1 =~ PPTQ1 + PPTQ2 + PPTQ3 + PPTQ4 + PPTQ5
F2 =~ PPTQ6 + PPTQ7 + PPTQ8 + PPTQ9 + PPTQ10
F3 =~ PPTQ11 + PPTQ12 + PPTQ13 + PPTQ14 + PPTQ15
'
# Run the optimization without AI
res_cfa <- optimal_cfa_with_ai(
initial_model = model,
data = Data_Personality,
rmsea_threshold = 0.08,
max_steps = 5
)
#> Paso 1
#> Warning: lavaan->lavaan():
#> the first indicator of the following latent variable(s) is a poor item;
#> switching to another marker item (to set the metric) to avoid convergence
#> problems; use bad.marker.crit = 0 to switch off this behavior: F2 (PPTQ6
#> -> PPTQ10)
#> Warning: lavaan->lav_object_post_check():
#> covariance matrix of latent variables is not positive definite ; use
#> lavInspect(fit, "cov.lv") to investigate.
#> Warning: lavaan->lav_start_check_cov():
#> starting values imply a correlation larger than 1; variables involved are:
#> F1 F2
#> Warning: lavaan->lav_start_check_cov():
#> starting values imply a correlation larger than 1; variables involved are:
#> F1 F3
#> Warning: lavaan->lav_start_check_cov():
#> starting values imply a correlation larger than 1; variables involved are:
#> F2 F3
#> Eliminando F1 (carga 0 < 0.366).
#> Paso 2
#> Warning: lavaan->lavaan():
#> the first indicator of the following latent variable(s) is a poor item;
#> switching to another marker item (to set the metric) to avoid convergence
#> problems; use bad.marker.crit = 0 to switch off this behavior: F2 (PPTQ6
#> -> PPTQ10)
#> Warning: lavaan->lav_object_post_check():
#> covariance matrix of latent variables is not positive definite ; use
#> lavInspect(fit, "cov.lv") to investigate.
#> Warning: lavaan->lav_start_check_cov():
#> starting values imply a correlation larger than 1; variables involved are:
#> F1 F2
#> Warning: lavaan->lav_start_check_cov():
#> starting values imply a correlation larger than 1; variables involved are:
#> F1 F3
#> Warning: lavaan->lav_start_check_cov():
#> starting values imply a correlation larger than 1; variables involved are:
#> F2 F3
#> Eliminando F1 (carga 0 < 0.366).
#> Paso 3
#> Warning: lavaan->lavaan():
#> the first indicator of the following latent variable(s) is a poor item;
#> switching to another marker item (to set the metric) to avoid convergence
#> problems; use bad.marker.crit = 0 to switch off this behavior: F2 (PPTQ6
#> -> PPTQ10)
#> Warning: lavaan->lav_object_post_check():
#> covariance matrix of latent variables is not positive definite ; use
#> lavInspect(fit, "cov.lv") to investigate.
#> Warning: lavaan->lav_start_check_cov():
#> starting values imply a correlation larger than 1; variables involved are:
#> F1 F2
#> Warning: lavaan->lav_start_check_cov():
#> starting values imply a correlation larger than 1; variables involved are:
#> F1 F3
#> Warning: lavaan->lav_start_check_cov():
#> starting values imply a correlation larger than 1; variables involved are:
#> F2 F3
#> Eliminando F1 (carga 0 < 0.366).
#> Paso 4
#> Warning: lavaan->lavaan():
#> the first indicator of the following latent variable(s) is a poor item;
#> switching to another marker item (to set the metric) to avoid convergence
#> problems; use bad.marker.crit = 0 to switch off this behavior: F2 (PPTQ6
#> -> PPTQ10)
#> Warning: lavaan->lav_object_post_check():
#> covariance matrix of latent variables is not positive definite ; use
#> lavInspect(fit, "cov.lv") to investigate.
#> Warning: lavaan->lav_start_check_cov():
#> starting values imply a correlation larger than 1; variables involved are:
#> F1 F2
#> Warning: lavaan->lav_start_check_cov():
#> starting values imply a correlation larger than 1; variables involved are:
#> F1 F3
#> Warning: lavaan->lav_start_check_cov():
#> starting values imply a correlation larger than 1; variables involved are:
#> F2 F3
#> Eliminando F1 (carga 0 < 0.366).
#> Paso 5
#> Warning: lavaan->lavaan():
#> the first indicator of the following latent variable(s) is a poor item;
#> switching to another marker item (to set the metric) to avoid convergence
#> problems; use bad.marker.crit = 0 to switch off this behavior: F2 (PPTQ6
#> -> PPTQ10)
#> Warning: lavaan->lav_object_post_check():
#> covariance matrix of latent variables is not positive definite ; use
#> lavInspect(fit, "cov.lv") to investigate.
#> Warning: lavaan->lav_start_check_cov():
#> starting values imply a correlation larger than 1; variables involved are:
#> F1 F2
#> Warning: lavaan->lav_start_check_cov():
#> starting values imply a correlation larger than 1; variables involved are:
#> F1 F3
#> Warning: lavaan->lav_start_check_cov():
#> starting values imply a correlation larger than 1; variables involved are:
#> F2 F3
#> Eliminando F1 (carga 0 < 0.366).
res_cfa$removed_items
#> [1] "F1" "F1" "F1" "F1" "F1"
res_cfa$log
#> step modification mi_value rmsea
#> 1 1 F1 =~ PPTQ15 15.5407 0.08866544
#> 2 2 F1 =~ PPTQ15 15.5407 0.08866544
#> 3 3 F1 =~ PPTQ15 15.5407 0.08866544
#> 4 4 F1 =~ PPTQ15 15.5407 0.08866544
#> 5 5 F1 =~ PPTQ15 15.5407 0.08866544
cat(res_cfa$final_model, "\n")
#> F1 =~ PPTQ1 + PPTQ2 + PPTQ3 + PPTQ4 + PPTQ5
#> F2 =~ PPTQ6 + PPTQ7 + PPTQ8 + PPTQ9 + PPTQ10
#> F3 =~ PPTQ11 + PPTQ12 + PPTQ13 + PPTQ14 + PPTQ15
if (FALSE) { # \dontrun{
# Run with AI-driven analysis (requires an OpenAI-compatible API key)
res_cfa_ai <- optimal_cfa_with_ai(
initial_model = model,
data = Data_Personality,
analyze_removed = TRUE,
api_key = Sys.getenv("OPENAI_API_KEY"),
item_definitions = as.list(stats::setNames(
paste("Item", 1:15, "content"), paste0("PPTQ", 1:15))),
factor_definitions = list(F1 = "Factor 1 description",
F2 = "Factor 2 description",
F3 = "Factor 3 description")
)
res_cfa_ai$conceptual_analysis
} # }