
EFA-Boosting Optimization
efa_boosting.RdPerforms an iterative, machine-learning-inspired optimization of Exploratory Factor Analysis (EFA).
The algorithm integrates: (1) greedy item-by-item elimination, (2) optional global subset search (multi-item removal), (3) structural rule enforcement (loadings, Heywood / near-Heywood, cross-loadings, minimum items per factor), (4) adaptive minimum interfactor correlation checks, and (5) a composite fit index whose weights adapt dynamically to degrees of freedom and sample size (df x N), following Kenny and McCoach (2003) and Shi, Lee and Maydeu-Olivares (2019).
The function handles ordinal data (WLSMV) and includes robust corrections, adaptive weighting rules, and an optional GPT-based conceptual analysis of removed (and optionally retained) items.
When verbose = TRUE, the function prints a complete
EFA-Boosting Optimization Summary Report including:
(a) iteration diagnostics (RMSEA, SRMR, CFI, df, composite loss), (b) identification and elimination of structural problems, (c) global-search progress bars and decisions, (d) thresholded final structure, (e) interfactor-correlation compliance check, and (f) AI-based conceptual interpretations.
Returns a complete list of results invisibly.
Usage
efa_boosting(
data,
name_items,
item_range = NULL,
n_factors = 3,
n_sample = NULL,
exclude_items = NULL,
thresholds = list(loading = 0.3, min_items_per_factor = 3,
heywood_tol = 1e-06, near_heywood = 0.015,
min_interfactor_correlation = 0.32, min_omega = NULL),
model_config = list(estimator = "WLSMV", rotation = "oblimin"),
performance = list(max_candidates_eval = 12, smart_pruning = TRUE,
timeout_efa = 30, timeout_optimization = 120, use_timeouts = FALSE,
emit_progress = TRUE, fit_target_only = TRUE),
use_global = FALSE,
global_opt = list(max_drop = 2, max_global_combinations = 5000,
verbose = TRUE, progress_bar = TRUE),
fit_config = list(targets = list(rmsea = 0.08, srmr = 0.06, cfi = 0.95),
margins = list(rmsea = 0.03, srmr = 0.03, cfi = 0.03),
base_weights = list(rmsea = 0.5, srmr = 0.25, cfi = 0.25),
small_df_cut = 5,
small_df_weights = list(rmsea = 0.15, srmr = 0.45, cfi = 0.4),
wlsmv_boost = list(rmsea = 0.8, srmr = 1.2, cfi = 1.1),
use_pclose_if_available = TRUE, pclose_bonus = 0.1),
use_ai_analysis = FALSE,
ai_config = list(api_key = NULL, generate_names = FALSE,
only_removed = TRUE, gpt_model = "gpt-3.5-turbo", language = "english",
analysis_detail = "detailed", domain_name = "Default Domain",
scale_title = "Default Scale Title", construct_definition = "",
model_name = "EFA Model", item_definitions = NULL),
verbose = TRUE,
...
)Arguments
- data
Data frame of item responses.
- name_items
Item prefix (e.g.,
"IT"produces IT1, IT2, ...). Used for auto-detection.- item_range
Integer vector
c(start, end). IfNULL, items are auto-detected.- n_factors
Number of factors.
- n_sample
Sample size. If
NULL, auto-detected asnrow(data). Used for df x N adaptive weighting of the composite fit index.- exclude_items
Items excluded before starting optimization.
- thresholds
Rules governing structural decisions:
loading: Minimum acceptable loading.min_items_per_factor: Structural protection rule.heywood_tol,near_heywood: Detection thresholds.min_interfactor_correlation: Minimum acceptable factor correlation.min_omega: Reliability floor. When set (e.g.0.80), an item is removed only if every factor keeps McDonald's omega at or above this value, so the algorithm cannot buy fit at the cost of internal consistency. Omega is a guard rail here, never a term in the loss: reliability is only interpretable once the model fits, so it vetoes individual removals instead of competing with the fit indices. A factor that already sits below the floor is not frozen - its effective bar becomes its current omega, so removals that do not reduce reliability are still allowed. Heywood cases are exempt, since an inadmissible solution must be fixed regardless.NULL(default) disables the check and reproduces the behaviour of versions prior to 1.3.0 exactly.
- model_config
EFA estimation configuration:
estimator = "WLSMV"rotation = "oblimin"
- performance
Performance and timeout settings:
max_candidates_eval: If set, only this many candidates are evaluated in greedy mode.timeout_efa: Timeout per EFA run (requiresR.utils).timeout_optimization: Global timeout.use_timeouts: Enable/disable timeout protection.smart_pruning: Rank candidates by smallest maximum loading before applyingmax_candidates_eval, so the items most likely to be removed are the ones evaluated.emit_progress: Emit amessage()per iteration and per candidate, which is what lets a Shiny app show live progress.fit_target_only: Fit only then_factorssolution while evaluating a candidate (defaultTRUE). The engine used to fit the 1..k-1 solutions as well and discard them, since candidate evaluation reads only the target one. Results are identical, verified onData_PersonalityandData_Expectativasacross structure, removed items, stop reason, RMSEA, omega and the fit table, with 38 percent fewer lavaan fits where the greedy loop evaluates candidates.FALSErestores the previous behaviour.
- use_global
Enable global subset search (multi-item removal).
- global_opt
Configuration for global search:
max_drop: Maximum subset size k.max_global_combinations: Hard cap to avoid explosion.verbose: Print global-search diagnostics.progress_bar: Show a visual progress bar.
- fit_config
Configuration of the adaptive composite fit index:
targets: Target values for RMSEA, SRMR, CFI.margins: Tolerances for each index.base_weights: Default RMSEA/SRMR/CFI weights.critical_weights: df < 5 and N < 200 (RMSEA nearly useless).df_low_n_high_weights: df < 5 and N >= 200.df_mid_n_low_weights: df 5-19 and N < 200.df_mid_n_high_weights: df 5-19 and N >= 200.critical_df_cut,moderate_df_cut: df boundaries.small_n_cut: N-boundary for weighting.wlsmv_boost: Multiplicative corrections for WLSMV.use_pclose_if_available: Enables p-close bonus.pclose_bonus: Amount subtracted from composite loss.
- use_ai_analysis
Enable GPT-based conceptual analysis of removed (and optionally retained) items.
- ai_config
AI analysis configuration:
api_key: OpenAI API key.gpt_model: Model (e.g.,"gpt-4","gpt-3.5-turbo").language: "spanish" or "english".analysis_detail: "brief", "standard", "detailed".domain_name,scale_title,model_name: Metadata included in prompts.construct_definition: Theoretical definition of the latent construct.item_definitions: Named list with item wording.only_removed: If FALSE, retained items are also analyzed.
- verbose
If TRUE, prints the full diagnostic report, global-search bars, item maps, interfactor-correlation warnings, and AI progress bars.
- ...
Additional arguments passed to internal estimation routines.
Details
1. Optimization strategy.
The algorithm follows a strict hierarchical rule system:
Remove Heywood items (\(\psi\) < -tol or |loading| > 1).
Remove near-Heywood items (\(\psi\) ~ 0).
Resolve cross-loadings by removing items with smallest ambiguity gap.
Enforce minimum items per factor.
If structure is acceptable but RMSEA > target: remove the weakest-loading item.
If
use_global = TRUE: evaluate all subsets up tomax_dropusing the composite loss.
2. Adaptive composite loss (df x N).
Weights for RMSEA / SRMR / CFI adapt dynamically according to:
degrees of freedom (df),
sample size (N),
estimator (WLSMV boosters),
p-close >= .05 (bonus).
This allows stable decisions even when RMSEA is unreliable (df < 5).
3. Interfactor correlation rule.
After each iteration and at finalization, factor correlations are checked:
If any \(|\phi_{ij}|\) <
min_interfactor_correlation, a warning is printed.
4. Global subset search.
If enabled:
evaluates all subsets of size 1 ... k,
uses safe-combination caps,
shows a progress bar,
accepts subset removals only if composite loss strictly improves.
5. AI conceptual analysis.
If use_ai_analysis = TRUE:
removed items receive a narrative justification using loadings, ambiguity gaps, h^2, \(\psi\), RMSEA-at-removal, and algorithmic reason,
retained items can also be evaluated,
exponential-backoff retry logic ensures robustness,
a detailed elimination timeline is integrated into the prompt.
Value
A list containing:
final_structure: Thresholded loading matrix.removed_items: Items removed (in chronological order).steps_log: Full elimination log with the following columns:step: Iteration numberremoved_item: Name of the removed itemreason: Reason for removalrmsea: RMSEA value at the moment of removal (scaled when WLSMV is used)srmr: SRMR value at the moment of removalcfi: CFI value at the moment of removal (scaled when WLSMV is used)
iterations: Number of optimization iterations.final_rmsea: Final RMSEA.bondades_original: Fit indices from the final model.stop_reason: Why the loop ended ("all_criteria_met","min_items_per_factor_protected","min_omega_protected","fit_target_reached","max_iterations","not_enough_items","timeout","efa_convergence_failed","fit_zero_no_structural_problem").inter_factor_correlation: Final phi matrix.interfactor_check: Information about violations.omega_final: McDonald's omega per factor in the final solution, computed from the primary loadings of the pattern matrix. Always reported, whether or not the floor is active.omega_check: The floor in force, whether it was met, andblocked_items(items the floor kept from being removed).last_h2,last_psi: Final communalities and uniquenesses.last_flags: Heywood and near-Heywood indicators.conceptual_analysis: GPT-based narrative analyses.config_used: Full configuration list.
References
Kenny, D. A., & McCoach, D. B. (2003). Effect of the number of variables on measures of fit in structural equation modeling. Structural Equation Modeling, 10(3), 333-351. doi:10.1207/S15328007SEM1003_1
Shi, D., Lee, T., & Maydeu-Olivares, A. (2019). Understanding the model size effect on SEM fit indices. Educational and Psychological Measurement, 79(2), 310-334. doi:10.1177/0013164418783530
Examples
# \donttest{
data(Data_Personality)
res <- efa_boosting(
data = Data_Personality,
name_items = "PPTQ",
n_factors = 3,
verbose = TRUE
)
#> Sample size auto-detected: N = 100
#>
#> ╔════════════════════════════════════════════════════════════════╗
#> ║ EFA OPTIMIZER v4.1 (Greedy/Global + Objetivo compuesto) ║
#> ╚════════════════════════════════════════════════════════════════╝
#>
#> Items iniciales: 15 | Factores: 3
#> Targets → RMSEA≤ 0.08 | SRMR≤ 0.06 | CFI≥ 0.95
#> Modo de búsqueda: GREEDY 1×1
#> Min ítems/factor: 3 | Loading umbral: 0.3
#>
#> Boosting iter 1: 15 items activos, 0 eliminados hasta ahora
#> Warning: lavaan->lav_model_vcov():
#> The variance-covariance matrix of the estimated parameters (vcov) does not
#> appear to be positive definite! The smallest eigenvalue (= 8.540726e-18)
#> is close to zero. This may be a symptom that the model is not identified.
#> Warning: lavaan->lav_model_vcov():
#> The variance-covariance matrix of the estimated parameters (vcov) does not
#> appear to be positive definite! The smallest eigenvalue (= 1.397705e-17)
#> is close to zero. This may be a symptom that the model is not identified.
#> Warning: lavaan->lav_model_vcov():
#> The variance-covariance matrix of the estimated parameters (vcov) does not
#> appear to be positive definite! The smallest eigenvalue (= 1.268967e-17)
#> is close to zero. This may be a symptom that the model is not identified.
#>
#> ──────────────────────────────────────────────────────────────────────
#> ITERATION 0 | Loss: 0.010 | RMSEA: 0.056 | SRMR: 0.061 | CFI: 0.952 | df: 63
#> Items per factor: 3 | 4 | 7
#> ──────────────────────────────────────────────────────────────────────
#>
#> Items f1 f2 f3
#> 1 PPTQ9 0.754 0.000 0.000
#> 2 PPTQ4 0.610 0.000 0.000
#> 3 PPTQ14 -0.452 0.347 0.000
#> 4 PPTQ12 0.000 0.668 0.000
#> 5 PPTQ2 0.000 0.627 0.000
#> 6 PPTQ6 0.000 -0.621 0.000
#> 7 PPTQ7 -0.387 0.429 0.000
#> 8 PPTQ5 0.000 0.000 0.799
#> 9 PPTQ10 0.000 0.000 0.661
#> 10 PPTQ15 0.000 0.000 0.639
#> 11 PPTQ3 0.000 0.000 0.566
#> 12 PPTQ1 0.000 -0.302 0.502
#> 13 PPTQ8 0.000 0.000 0.360
#> 14 PPTQ11 0.000 0.000 0.315
#> 15 PPTQ13 0.000 0.000 0.000
#>
#> ❌ Removed PPTQ7 due to: Cross-loading (priority)
#> Boosting iter 2: 14 items activos, 1 eliminados hasta ahora
#> Warning: lavaan->lav_model_vcov():
#> The variance-covariance matrix of the estimated parameters (vcov) does not
#> appear to be positive definite! The smallest eigenvalue (= -1.842400e-17)
#> is smaller than zero. This may be a symptom that the model is not
#> identified.
#> Warning: lavaan->lav_model_vcov():
#> The variance-covariance matrix of the estimated parameters (vcov) does not
#> appear to be positive definite! The smallest eigenvalue (= 2.161542e-17)
#> is close to zero. This may be a symptom that the model is not identified.
#> Warning: lavaan->lav_model_vcov():
#> The variance-covariance matrix of the estimated parameters (vcov) does not
#> appear to be positive definite! The smallest eigenvalue (= 5.176050e-17)
#> is close to zero. This may be a symptom that the model is not identified.
#>
#> ──────────────────────────────────────────────────────────────────────
#> ITERATION 1 | Loss: 0.000 | RMSEA: 0.061 | SRMR: 0.060 | CFI: 0.952 | df: 52
#> Items per factor: 3 | 3 | 7
#> ──────────────────────────────────────────────────────────────────────
#>
#> Items f1 f2 f3
#> 1 PPTQ9 0.699 0.000 0.000
#> 2 PPTQ4 0.597 0.000 0.000
#> 3 PPTQ14 -0.528 0.400 0.000
#> 4 PPTQ12 0.000 0.647 0.000
#> 5 PPTQ2 0.000 0.644 0.000
#> 6 PPTQ6 0.000 -0.641 0.000
#> 7 PPTQ5 0.000 0.000 0.778
#> 8 PPTQ15 0.000 0.000 0.672
#> 9 PPTQ10 0.000 0.000 0.669
#> 10 PPTQ3 0.000 0.000 0.544
#> 11 PPTQ1 0.000 0.000 0.531
#> 12 PPTQ8 0.000 0.000 0.360
#> 13 PPTQ11 0.000 0.000 0.332
#> 14 PPTQ13 0.000 0.000 0.000
#>
#> ⚠ Cross-loadings detected but protected by min_items_per_factor; RMSEA target reached, stopping.
#> 📐 STRATEGY: Structural optimization (non-cross-loading)
#> ❌ Removed PPTQ13 due to: No loading
#> Boosting iter 3: 13 items activos, 2 eliminados hasta ahora
#> Warning: lavaan->lav_model_vcov():
#> The variance-covariance matrix of the estimated parameters (vcov) does not
#> appear to be positive definite! The smallest eigenvalue (= -4.983443e-17)
#> is smaller than zero. This may be a symptom that the model is not
#> identified.
#> Warning: lavaan->lav_model_vcov():
#> The variance-covariance matrix of the estimated parameters (vcov) does not
#> appear to be positive definite! The smallest eigenvalue (= 2.201994e-17)
#> is close to zero. This may be a symptom that the model is not identified.
#> Warning: lavaan->lav_model_vcov():
#> The variance-covariance matrix of the estimated parameters (vcov) does not
#> appear to be positive definite! The smallest eigenvalue (= 5.931251e-17)
#> is close to zero. This may be a symptom that the model is not identified.
#>
#> ──────────────────────────────────────────────────────────────────────
#> ITERATION 2 | Loss: 0.113 | RMSEA: 0.077 | SRMR: 0.060 | CFI: 0.938 | df: 42
#> Items per factor: 3 | 3 | 7
#> ──────────────────────────────────────────────────────────────────────
#>
#> Items f1 f2 f3
#> 1 PPTQ6 -0.671 0.000 0.000
#> 2 PPTQ12 0.645 0.000 0.000
#> 3 PPTQ2 0.640 0.000 0.000
#> 4 PPTQ9 0.000 0.659 0.000
#> 5 PPTQ4 0.000 0.607 0.000
#> 6 PPTQ14 0.364 -0.558 0.000
#> 7 PPTQ5 0.000 0.000 0.779
#> 8 PPTQ10 0.000 0.000 0.690
#> 9 PPTQ15 0.000 0.000 0.671
#> 10 PPTQ3 0.000 0.000 0.542
#> 11 PPTQ1 0.000 0.000 0.520
#> 12 PPTQ8 0.000 0.000 0.355
#> 13 PPTQ11 0.000 0.000 0.333
#>
#> ⚠ Cross-loadings detected but protected by min_items_per_factor; RMSEA target reached, stopping.
#> 📐 STRATEGY: Structural optimization (non-cross-loading)
#>
#> ⚠ Structural issue found (PPTQ14) but protected by min_items_per_factor; RMSEA target reached, stopping.
#>
#> ╔════════════════════════════════════════════════════════════════╗
#> ║ OPTIMIZATION COMPLETED ║
#> ╚════════════════════════════════════════════════════════════════╝
#>
#> Total iterations: 2
#> Items removed: PPTQ7, PPTQ13
#> Final RMSEA: 0.077
#>
#> ⚠️ ADVERTENCIA: No se alcanzó el criterio de correlación mínima entre factores (>= 0.32)
#> Correlaciones que no cumplen el criterio:
#> - f1-f2: 0.178
#> - f1-f3: 0.313
#> - f2-f3: 0.286
#> Correlación mínima encontrada: 0.178
#>
#> Omega por factor: f1=0.690 | f2=0.638 | f3=0.764
#>
#> ✅ Analysis finished successfully.
#>
res$removed_items
#> [1] "PPTQ7" "PPTQ13"
res$final_structure
#> Items f1 f2 f3
#> 1 PPTQ6 -0.6713424 0.0000000 0.0000000
#> 2 PPTQ12 0.6447529 0.0000000 0.0000000
#> 3 PPTQ2 0.6403550 0.0000000 0.0000000
#> 4 PPTQ9 0.0000000 0.6593586 0.0000000
#> 5 PPTQ4 0.0000000 0.6071600 0.0000000
#> 6 PPTQ14 0.3643084 -0.5578913 0.0000000
#> 7 PPTQ5 0.0000000 0.0000000 0.7788308
#> 8 PPTQ10 0.0000000 0.0000000 0.6900500
#> 9 PPTQ15 0.0000000 0.0000000 0.6706673
#> 10 PPTQ3 0.0000000 0.0000000 0.5421446
#> 11 PPTQ1 0.0000000 0.0000000 0.5200078
#> 12 PPTQ8 0.0000000 0.0000000 0.3547691
#> 13 PPTQ11 0.0000000 0.0000000 0.3328500
# }