--- title: "Migrating to the efa_* interface" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Migrating to the efa_* interface} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` `EFAtools` exposes its functionality through a set of lowercase `efa_*` functions: `efa_screen()`, `efa_retain()`, `efa_fit()`, and so on. Earlier releases used uppercase names instead (`EFA()`, `N_FACTORS()`, `OMEGA()`, ...). The uppercase names have **not** been removed — they still work — but the `efa_*` functions are now the recommended interface. This vignette explains the change, gives the full old-to-new mapping, and walks through the one migration that involves more than a rename: `EFA()` to `efa_fit()`. ```{r} library(EFAtools) ``` ## Why the efa_* Interface The `efa_*` names are the interface we recommend for new code. They read consistently, share a common prefix that groups them in tab-completion and the documentation index, and they are where the package's development now happens. The uppercase names are **superseded**, not deprecated. In practice that means: * They keep working with their **original arguments**, and calling them emits **no warning or message**. Existing scripts and saved analyses continue to run byte-for-byte unchanged. * Their argument lists are **frozen**. New capabilities are added only to the `efa_*` functions, so an uppercase name will never grow a new argument. So there is no urgency to rewrite working code. Migrate a script when you want the new functions' additional features (or simply for consistency); until then the old calls remain valid. ## The Old-to-New Mapping Every uppercase function has a lowercase counterpart. For most of them the migration is a pure rename: the arguments are identical and only the name changes. | Old name | Recommended name | |---|---| | `EFA()` | `efa_fit()` | | `N_FACTORS()` | `efa_retain()` | | `EFA_AVERAGE()` | `efa_average()` | | `EFA_POOLED()` | `efa_mi()` | | `COMPARE()` | `efa_compare()` | | `SL()` | `efa_schmid_leiman()` | | `OMEGA()` | `efa_reliability()` | | `FACTOR_SCORES()` | `efa_scores()` | | `PROCRUSTES()` | `efa_procrustes()` | | `BARTLETT()` | `efa_bartlett()` | | `KMO()` | `efa_kmo()` | | `PARALLEL()` | `efa_parallel()` | | `EKC()` | `efa_ekc()` | | `KGC()` | `efa_kgc()` | | `HULL()` | `efa_hull()` | | `SCREE()` | `efa_scree()` | | `MAP()` | `efa_map()` | | `NEST()` | `efa_nest()` | | `SMT()` | `efa_smt()` | | `CD()` | `efa_cd()` | `efa_reliability()` and `efa_scores()` are broader than the `OMEGA()` and `FACTOR_SCORES()` they replace, but they cover the same use cases and are the recommended way to obtain those quantities going forward. Alongside the renamed functions, the package ships tools that have **no uppercase predecessor** — they are new, and available only under the `efa_*` interface (and the two control constructors): | New function | Purpose | |---|---| | `efa_screen()` | Data screening and factorability diagnostics in one report | | `efa_group()` | Multigroup EFA with factor congruence | | `efa_simulate()` | Simulate data from a factor model | | `efa_power()` | Power analysis for EFA | | `estimate_control()` | Bundle the estimation tuning knobs (see below) | | `rotate_control()` | Bundle the rotation tuning knobs (see below) | For a plain rename, the call is unchanged apart from the name. For example, the Kaiser-Meyer-Olkin criterion: ```{r} cor_mat <- test_models$baseline$cormat # Recommended name -- exactly the same arguments as KMO(): efa_kmo(cor_mat) # The old name still works and returns the same result: identical(KMO(cor_mat)$KMO, efa_kmo(cor_mat)$KMO) ``` ## Migrating from `EFA()` to `efa_fit()` `EFA()` to `efa_fit()` is the only migration that changes an argument list. `EFA()` exposed every estimation and rotation setting as a flat argument, which made for a long and somewhat unwieldy signature. `efa_fit()` keeps the **primary** choices as top-level arguments — `x`, `n_factors`, `N`, `estimator`, `rotation`, `se`, `cor_method`, `use`, `b_boot`, `ci`, and `seed` — and collects the **tuning** knobs into two small control objects built by `estimate_control()` and `rotate_control()`. Each flat `EFA()` tuning argument now lives in one of the two controls: | Control object | Arguments it holds | |---|---| | `estimate_control()` | `type`, `init_comm`, `criterion`, `criterion_type`, `max_iter`, `abs_eigen`, `start_method` | | `rotate_control()` | `type`, `normalize`, `precision`, `order_type`, `varimax_type`, `p_type`, `k`, `random_starts` | A few points make the translation mechanical: * **The `type` preset feeds both controls.** `EFA()` had a single `type` (`"EFAtools"`, `"psych"`, `"SPSS"`, or `"none"`) that governed both estimation and rotation. Pass the same `type` to each constructor to reproduce it. Because the two controls are independent, you *can* now give them different presets, but you do not have to. * **Three arguments were renamed.** The estimator is selected with `estimator` instead of `method` — passing `method` to `efa_fit()` is an error that points to the new name, while `EFA()` keeps its `method` argument. `P_type` is now `p_type`, and `randomStarts` is now `random_starts`; `EFA()` still accepts those old spellings silently. * **Rotation-engine extras** — the criterion-specific arguments a rotation may take, such as `maxit`, `gam` (oblimin), or `delta` (geomin) — are passed through `rotate_control()`'s `...` (or, equivalently, `efa_fit()`'s own `...`). Both validate the names: an extra the selected rotation's engine cannot consume (for example `gamma`, a misspelling of oblimin's `gam`) is rejected with an error, where `EFA()` silently ignores it. The side-by-side below reproduces an SPSS-style analysis. The old flat call and the new control-based call give identical results: ```{r} # Old flat interface efa_old <- EFA(cor_mat, n_factors = 3, N = 500, type = "SPSS") # New interface: the SPSS preset travels through the two control objects efa_new <- efa_fit(cor_mat, n_factors = 3, N = 500, estimate_control = estimate_control(type = "SPSS"), rotate_control = rotate_control(type = "SPSS")) # Identical numerical result all.equal(efa_old$rot_loadings, efa_new$rot_loadings) ``` An individual knob is set on the control it belongs to. A flat `EFA(..., type = "SPSS", max_iter = 500, k = 3)` becomes: ```{r, eval = FALSE} efa_fit(cor_mat, n_factors = 3, N = 500, rotation = "promax", estimate_control = estimate_control(type = "SPSS", max_iter = 500), rotate_control = rotate_control(type = "SPSS", k = 3)) ``` One behavioural difference is worth knowing about. `efa_fit()` **rejects** a tuning knob passed directly, rather than silently ignoring it. This turns a common and previously invisible mistake into an immediate, informative error that names the constructor the knob belongs to: ```{r, eval = FALSE} efa_fit(cor_mat, n_factors = 3, N = 500, max_iter = 500) #> Error: `max_iter` cannot be passed to `efa_fit()` directly. #> i The estimation and rotation tuning knobs live in `estimate_control()` and `rotate_control()`. #> i For example: `efa_fit(x, ..., estimate_control = estimate_control(max_iter = 500))`. ``` The same move — flat estimation knobs into `estimate_control()` — applies to the other functions that run factor extractions internally. `efa_retain()`, `efa_schmid_leiman()`, and the retention criteria that fit a model or use EFA-based eigenvalues (`efa_parallel()`, `efa_kgc()`, `efa_scree()`, `efa_hull()`, `efa_nest()`, and `efa_smt()`) all take an `estimate_control()` object in place of the loose estimation arguments; and `efa_average()` takes `p_type` in place of `P_type`. ## Returned Objects Keep Their Legacy Classes Migrating a call does not break code that inspects or dispatches on the result. The renamed functions attach the **legacy class alongside the new one**, so `inherits()` checks and S3 methods written against the old classes keep working — including on objects saved from earlier sessions. ```{r} class(efa_new) inherits(efa_new, "EFA") ``` The same holds for the other direct renames: `efa_retain()` objects still inherit `"N_FACTORS"`, `efa_bartlett()` still inherits `"BARTLETT"`, and likewise for `efa_kmo()`, `efa_schmid_leiman()`, `efa_compare()`, `efa_average()`, and `efa_mi()`. The retention criteria (`efa_parallel()`, `efa_ekc()`, and the rest) all share the single `efa_retention` class they have returned since EFAtools 0.8.0. And the uppercase functions themselves keep working unchanged: an `EFA()` call returns the same result as before, now additionally classed `efa` so that it picks up the shared methods — `inherits()` checks against `"EFA"` are unaffected. ## Where to Next That is the whole migration: rename the function, and — for `EFA()` only — move the tuning knobs into `estimate_control()` and `rotate_control()`. For an overview of the full analysis workflow under the `efa_*` interface, see the [EFAtools](EFAtools.html) vignette; the individual help pages document each function's arguments in full.