# Migrating to the efa_* interface

`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 migration with the largest change to the argument list: `EFA()` to `efa_fit()`.

    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:

    cor_mat <- test_models$baseline$cormat

    # Recommended name -- exactly the same arguments as KMO():
    efa_kmo(cor_mat)
    #>
    #> ── Kaiser-Meyer-Olkin criterion (KMO) ──────────────────────────────────────────
    #>
    #> ✔ The overall KMO value for your data is marvellous.
    #> These data are probably suitable for factor analysis.
    #>
    #> Overall: 0.916
    #>
    #> For each variable:
    #>    V1    V2    V3    V4    V5    V6    V7    V8    V9   V10   V11   V12   V13
    #> 0.900 0.914 0.924 0.932 0.923 0.891 0.928 0.919 0.916 0.892 0.928 0.908 0.922
    #>   V14   V15   V16   V17   V18
    #> 0.905 0.924 0.934 0.907 0.923

    # The old name still works and returns the same result:
    identical(KMO(cor_mat)$KMO, efa_kmo(cor_mat)$KMO)
    #> [1] TRUE

## Migrating from `EFA()` to `efa_fit()`

`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 — the data, factor count, sample size, estimator, rotation, and a few others — as top-level arguments (see `?efa_fit` for the full list), 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:

    # 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)
    #> [1] TRUE

An individual knob is set on the control it belongs to. A flat `EFA(..., type = "SPSS", max_iter = 500, k = 3)` becomes:

    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:

    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 — collecting flat estimation knobs into `estimate_control()` — applies to the other functions that fit a model 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()`. `efa_average()` is the exception: it keeps its flat estimation and rotation arguments, and only renames `P_type` to `p_type`.

## What the `type` Presets Replicate

The `type` presets are more than shorthand for a bundle of defaults. `"SPSS"` and `"psych"` follow how SPSS’s FACTOR procedure and `psych::fa()` implement principal axis factoring and promax rotation, and `"EFAtools"`, the default, combines the settings that performed best in a systematic comparison of the three (Grieder and Steiner, 2022). There is one place a preset alone is not enough: because every preset keeps EFAtools’ Kaiser normalization, `"psych"` also needs `normalize = FALSE` to reproduce `psych::fa()`’s promax — and even then a small residual difference remains, from ordinary convergence slack between the two implementations.

## 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.

    class(efa_new)
    #> [1] "efa" "EFA"
    inherits(efa_new, "EFA")
    #> [1] TRUE

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 vignette; the individual help pages document each function’s arguments in full.