# ackwards 0.2.0

New publication-figure controls for `autoplot()`, secondary correlation edges
in the pruned view, a near-redundant band in `prune()`, and cleaner layouts for
deep hierarchies. Also fixes an error on degenerate hierarchies, and a crash in
the package's own test suite — both of which surfaced only on particular
numerical platforms after 0.1.1 was published.

* **Publication-figure controls for `autoplot()`.** Three additions make
  hand-tuned figures easier. `show_items = TRUE` lists the salient items (top
  `n_items` by `|loading|`, via the same extraction as `top_items()`) beneath
  each deepest-level factor box, so a figure shows what each most-granular factor
  is made of. `node_width` / `node_height` now also accept a **named vector**
  keyed by factor ID, sizing individual boxes to fit long manual labels. And
  `order =` (also on `ba_layout()`) fixes the deepest level's left-to-right order
  by hand — every factor above stays centred over its primary children, so any
  arrangement of the hierarchy is reachable from the leaf order. All are opt-in;
  defaults are unchanged. The visualization vignette demonstrates each.

* **`autoplot(drop_pruned = TRUE)` can now draw secondary correlation edges.**
  A new `show_secondary = TRUE` adds the between-level correlations the pruned
  view otherwise hides — every kept cross-level factor pair with
  `|r| >= cut_show` that is not the single strongest-ancestor primary edge,
  including a factor's weaker second parents *and* direct skip-level
  correlations (a skip-level `|r|` is its own non-transitive fact). They render
  in a channel deliberately distinct from the primary edges — dimmed and thinner,
  drawn beneath them — while still inheriting the sign encoding (`sign_by`), so
  the sign color/linetype is never conflated with the secondary channel. Default
  `FALSE` leaves the pruned view unchanged. The visualization vignette
  illustrates the new argument.

* **`prune(x, "artifact")` now reports a near-redundant band.** A new
  `x$prune$near_redundant` table flags cross-level factor pairs that sit *just
  below* the redundancy thresholds — where `prune(x, "redundant")` drops full
  redundancy (`|r| >= redundancy_r`), the band surfaces the messier candidates a
  hair under it (e.g. `|r| = 0.89` / `phi = 0.94`), which Forbes (2023) treats as
  the main use of the artifact flags. A pair is flagged when its direct
  (skip-level) `|r|` **or** its Tucker `phi` falls within the new `near_margin`
  argument (default `0.1`) below the corresponding threshold, and the pair is not
  itself fully redundant. The band is report-only — no factor is dropped on its
  basis. Under EFA/ESEM, `redundancy_phi` now auto-resolves in artifact mode too
  (announced via cli), so the `phi` band is active; under PCA only the `|r|` band
  applies. The Forbes-extension vignette's artifact example now illustrates a
  genuinely near-redundant pair rather than one redundancy already drops.

* **Cleaner deep-hierarchy diagrams (`k >= 10`).** `ba_layout()` now orders each
  level by a traversal of the primary-parent forest, laying every subtree out as
  a contiguous block. This drives primary-tree edge crossings to zero in deep
  hierarchies (the "bent levels" the previous single-pass ordering left behind —
  e.g. 3 crossings down to 0 on the 155-variable AMH example at `k_max = 10`),
  with no change to shallow layouts and the primary-child x-placement unchanged.
  Separately, `autoplot(show_r = TRUE)` now dodges overlapping edge-correlation
  labels apart so dense diagrams stay legible. The layout stays fully
  deterministic.

* **`print()` for `suggest_k()` now renders an aligned criteria table.** The
  per-criterion evidence prints as a column-aligned grid (one row per k, one
  column per requested criterion) with a header row and a glyph legend, instead
  of the previous per-k concatenated lines. Numeric columns are right-aligned so
  the optimal-k star never shifts the decimals, and the "retained" tick is drawn
  in text presentation so terminals that render it as a wide emoji keep the
  columns aligned. The returned object and all criteria values are unchanged;
  only the printed layout is new.

* **Clarified documentation (no behavior change).** The `?prune` help and the
  bass-ackward vignette now explain the default `redundancy_criterion = "direct"`
  as a *star anchored on the chain's deepest factor* — each ancestor correlates
  directly with that leaf — distinct from adjacent-hop chaining and from an
  all-pairs screen. The `?ackwards` rationale for varimax now states the reason
  correctly: the between-level edge algebra is exact for any linear scoring, so
  orthogonality is an interpretive choice (it keeps within-level factors
  uncorrelated, so between-level edges are not confounded by within-level factor
  intercorrelation), not a numerical necessity. The artifact-mode discussion now
  frames automated flags as *removing* investigator degrees of freedom, leaving
  only the substantive drop decision to the researcher.

* **Fixed an error when a level of the hierarchy is degenerate.** If an
  extraction produced a factor whose scores carry no variance — reachable when
  `k_max` exceeds what the data can identify — `ackwards()` aborted with an
  opaque `invalid subscript type 'list'` message instead of handling it. Such a
  level is now recorded, warned about, and skipped, with every level above it
  kept, which is how a non-converged level has always been treated. The failure
  was platform-dependent: the same fit is merely ill-conditioned under most
  numerical libraries and fully degenerate under others, so it surfaced only on
  some check platforms (ATLAS and no-long-double builds). A hierarchy left with
  fewer than two usable levels now reports that directly.

* The package's own tests no longer require two identically seeded
  `boot_edges()` runs to agree bit-for-bit. A multi-threaded linear-algebra
  library may reassociate its reductions between runs, which moves aggregated
  bootstrap quantities in the last unit of precision (~1e-16, seen on ATLAS
  builds); the test now allows that while still holding the seed to a far
  tighter standard than any real reproducibility failure would pass. Nothing
  user-facing changed.

* Fixed a crash in the package's own test suite on macOS. Two tests set a
  *forking* parallel plan (`future::multicore`) to verify that parallel and
  serial fits agree exactly. Forking does not mix with the multi-threaded
  numerical libraries R's linear algebra runs through — `?mclapply` warns against
  the combination — and R segfaulted inside the forked worker on one of CRAN's
  macOS check flavours. Both tests now skip macOS and continue to run everywhere
  else. Nothing user-facing changed: `ackwards()` and `boot_edges()` never set a
  parallel plan themselves — the default plan is sequential, and choosing one is
  the caller's business.

# ackwards 0.1.1

CRAN resubmission of the first release, addressing reviewer feedback on the
0.1.0 submission; also picks up everything added since that submission.

* **Fixed compatibility with lavaan 0.7.** lavaan 0.7 renamed its
  sample-statistics slot argument (breaking the ESEM engine's multi-level
  reuse of anchor-level sample statistics — every level beyond k = 1 failed
  to build) and now requires an explicit `ordered = FALSE` to use WLSMV/ULSMV
  with continuous data. The ESEM engine detects the installed lavaan's
  argument vocabulary and works with both lavaan >= 0.7 and >= 0.6-13.

* **New vignette: "Reproducing Forbes (2023): The AMH Applied Example"**
  (`vignette("ackwards-forbes2023")`). A full worked reproduction of the
  paper's 155-variable applied example on the bundled `forbes2023` dataset:
  the 10-level hierarchy, skip-level correlations, the redundancy chase
  (including where the default `direct` criterion and the `adjacent` opt-in
  disagree), and the pruned-factor diagram in the paper's publication style.

* Corrected the Forbes (2023) article title in the `forbes2023` help page
  ("bass-ackward method", per the published title).

* **Corrected the `n_obs` advisory for PCA on correlation-matrix input.** The
  message (and the `n_obs` help text) claimed supplying `n_obs` would enable
  chi-square/RMSEA/TLI, but the PCA engine's level fit is eigenvalue-based and
  never computes them; `n_obs` is recorded in the result metadata and feeds
  the N-based sampling-adequacy checks only. The message now says so. No
  behavior changes.

* Completed the Goldberg (2006) reference to its full published title ("Doing
  it all Bass-Ackwards: The development of hierarchical factor structures from
  the top down") in the `ackwards()` help page and four vignettes.

* **Corrected the historical citations for `comparability()`'s split-half
  benchmarks.** The `.90` replication threshold traces to Everett (1983) and
  to its use in Goldberg's lexical research program by Saucier (1997) and
  Saucier, Georgiades, Tsaousis, and Goldberg (2005) — not to Goldberg
  (1990), which contains no split-half analyses and is no longer cited for
  this purpose. The `.95` reference line is now sourced to Lorenzo-Seva and
  ten Berge (2006). Affects the roxygen help page, `print()`/`autoplot()`
  footer text, the README, and the replicability-workflow vignette; no
  behavior changes.

* **Sourced `suggest_k()`'s k-selection guidance.** The consensus-range stance
  now cites Lim and Jahng (2019) with Achim's (2021) counterpoint, and the
  "PA-PC tends to overextract" note cites Saucier (1997) alongside Forbes
  (2023), in both the help page and the suggest-k vignette; no behavior changes.

* **`label_template()` now returns its scaffold visibly (behavior change).**
  The named character vector carries class `"ackwards_labels"`, and the
  editable `c(...)` literal is rendered by its `print()` method instead of
  being written to the console unconditionally. A top-level call looks the
  same as before; assigning the result (`labs <- label_template(x)`) or
  passing it inline (e.g. inside `autoplot()`) is now silent, per CRAN
  policy on console output.

* **DESCRIPTION** now spells out principal component analysis (PCA),
  exploratory factor analysis (EFA), and exploratory structural equation
  modeling (ESEM) per CRAN feedback.

* **New bundled dataset `forbes2023`.** The 155-variable "Assessing Mental Health"
  Spearman correlation matrix that forms Forbes's (2023) applied example is now
  exported, so `ackwards(forbes2023, k_max = 10)` reproduces her worked hierarchy
  directly. It joins `bfi25` and `sim16`. The matrix is redistributed under
  CC-BY 4.0 with attribution to M. K. Forbes (see `LICENSE.note`).

* **`prune("redundant")` gains `redundancy_criterion`, defaulting to `"direct"`
  (behavior change).** Redundancy chains are now traced by the **direct
  (skip-level)** correlation between a factor and each ancestor level — Forbes's
  (2023) actual `ChaseCorrPaths` rule — rather than the previous adjacent-hop
  walk. Because correlation is non-transitive, the two can differ in deep
  (many-level) hierarchies: on shallow ones (e.g. the bundled `sim16`) results
  are unchanged, but a factor can now be flagged redundant with an ancestor it
  correlates with directly even if an intermediate step is weak (and vice
  versa). Pass `redundancy_criterion = "adjacent"` for the old behavior. This
  makes `prune("redundant")` reproduce Forbes's published applied example
  exactly. `print()` and `summary()` name the active criterion. Note that under
  `"direct"`, a chain's `r_to_prev` column reports the adjacent-level
  correlation and can sit below `redundancy_r` (membership is set by the direct
  skip-level link; see `?prune`).

* **Validation.** The Forbes (2023) fidelity suite now also reproduces her
  155-variable "Assessing Mental Health" applied example (`k_max = 10`), not just
  the three simulation studies: between-level correlations match her reference
  implementation to 1.3e-14 across all 45 level-pairs, loading congruences agree
  within her two-decimal rounding, and her redundancy chase is reproduced for all
  54 components. The published matrix ships as a test fixture under CC-BY 4.0
  (see `LICENSE.note`).

* `suggest_k()` now reads Comparison Data (CD) results from **EFAtools >= 0.8.0**,
  which restructured `CD()`'s return value (the per-iteration RMSE matrix moved
  from the top-level `RMSE_eigenvalues` field into `results[[1]]$rmse_eigenvalues`).
  Without this, the CD criterion and its `autoplot()` panel silently dropped out
  when a current EFAtools was installed. Older EFAtools versions still work.

* **Console output consistency.** `summary()`'s per-level fit-index pass/fail
  mark is now the same terminal-adaptive `cli` glyph as `print()`'s convergence
  mark (a tick/cross that degrades to `v`/`x` in a non-UTF-8 console), replacing
  a hard-coded Unicode `✔`/`✘`. And `print()`'s cumulative-variance percentages
  now carry a fixed single decimal (e.g. `20.0%`, where a whole-number value
  previously printed as `20%`), matching `summary()`. Cosmetic only; the
  reported values are unchanged.

# ackwards 0.1.0

First public release. `ackwards` implements Goldberg's (2006) bass-ackwards
method and its modern extensions: it fits factor solutions at every level from 1
to `k` and characterizes the hierarchy through the between-level factor-score
correlations that connect them. Initial features, roughly in order of importance:

* **`ackwards()`** — fit the hierarchy with a PCA, EFA, or ESEM engine;
  between-level edges from Waller's (2007) exact `W′RW` algebra.
* **`suggest_k()`** — bracket a plausible depth range from five retention
  criteria (parallel analysis, MAP, VSS, Comparison Data).
* **`comparability()`** — gate hierarchy depth on split-half replicability
  (Everett 1983; Goldberg 1990).
* **`factorability()`** — screen a dataset (or correlation matrix) before you
  fit: Kaiser-Meyer-Olkin sampling adequacy (overall and per item), Bartlett's
  test of sphericity, the `N:p` ratio, and the Ledermann bound on identifiable
  factors, reported as numbers-and-bands rather than pass/fail. `ackwards()`
  runs a light version internally and warns only at the consequential extreme
  (`k_max` above the Ledermann bound for EFA/ESEM, or poor sampling adequacy).
* **Forbes (2023) extension** — `pairs = "all"` skip-level edges, `prune()` for
  redundant/artifactual factors, and `boot_edges()` bootstrap edge CIs.
* **Ordinal data** — `cor = "polychoric"` (WLSMV for ESEM), `check_items()`
  pre-analysis screening, and near-singularity diagnostics.
* **Interpretation & scoring** — `top_items()` for reading factors;
  `label_template()`/`set_factor_labels()`/`factor_labels()` to attach persistent
  substantive names shown across `print()`, `summary()`, `autoplot()`, `tidy()`,
  and `top_items()`; `augment()`/`predict()` for factor scores, in and out of
  sample.
* **Output** — `autoplot()` hierarchy diagrams and `tidy()`/`glance()`/`summary()`.
* **Bundled data** — `bfi25` (ordinal Big Five) and `sim16` (continuous), plus
  eight vignettes.

Beyond base R, `psych` is the only hard dependency; `lavaan`, `ggplot2`, and
others are optional.
