---
title: "Reproducing Forbes (2023): The AMH Applied Example"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Reproducing Forbes (2023): The AMH Applied Example}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

<!-- Generated by vignettes/precompute.R; DO NOT EDIT BY HAND.
     Edit ackwards-forbes2023.Rmd.orig and re-run `Rscript vignettes/precompute.R`.
     Freshness is enforced by tools/check-vignette-freshness.R. -->
<!-- precompute-stamp: source=ackwards-forbes2023.Rmd.orig md5=335c0654afcd73834d1c1ff786bb027c -->



Forbes (2023) introduced the extended bass-ackwards method — all-pairs
between-level correlations plus redundancy pruning — and demonstrated it on a
real, deep hierarchy: the **Assessing Mental Health (AMH)** study, 155
mental-health symptom variables spanning 18 DSM disorders, measured in an
Australian general-population sample (N = 3,175; Forbes et al., 2021). Her
paper cites this package as the reference implementation of the extension.

The Spearman correlation matrix from that applied example ships with the
package as the `forbes2023` dataset (redistributed from Forbes's OSF project
under CC-BY 4.0; see `?forbes2023`). This vignette reproduces her published
analysis end-to-end: the 10-level hierarchy, the skip-level correlations, the
redundancy chase, and the pruned-factor diagram.

If the *concepts* here are new — what skip-level edges are, how `prune()`
decides what to flag — read `vignette("ackwards-forbes")` first: it teaches
the method extension on a small didactic dataset. This vignette runs the real
thing.

## The data

`forbes2023` is a 155 × 155 Spearman correlation matrix, not raw data:


``` r
library(ackwards)
dim(forbes2023)
#> [1] 155 155
forbes2023[1:3, 1:3]
#>                        Impulsivity Reckless_behaviour__i_  Blurting
#> Impulsivity              1.0000000              0.5415115 0.6172762
#> Reckless_behaviour__i_   0.5415115              1.0000000 0.4783731
#> Blurting                 0.6172762              0.4783731 1.0000000
```

A correlation matrix carries no sample size, so we supply the AMH study's
N explicitly with `n_obs = 3175`. (For the PCA engine used here nothing
downstream depends on N; supplying it is still good practice — it is recorded
in the object and enables N-dependent fit statistics under the factor
engines.)

## Fitting the 10-level hierarchy

Forbes's applied example extracts 1 through 10 components with all
between-level pairs:


``` r
x <- ackwards(forbes2023, k_max = 10, pairs = "all", n_obs = 3175)
x
#> 
#> ── Bass-Ackwards Analysis (ackwards) ───────────────────────────────────────────
#> Engine: pca
#> Rotation: varimax
#> Basis: (user-supplied matrix)
#> n: 3,175
#> k (max): 10
#> 
#> ── Levels ──
#> 
#> ✔ k = 1: 1 factor, 31.4% variance
#> ✔ k = 2: 2 factors, 36.8% variance
#> ✔ k = 3: 3 factors, 41.1% variance
#> ✔ k = 4: 4 factors, 44.9% variance
#> ✔ k = 5: 5 factors, 48.2% variance
#> ✔ k = 6: 6 factors, 50.8% variance
#> ✔ k = 7: 7 factors, 52.9% variance
#> ✔ k = 8: 8 factors, 54.6% variance
#> ✔ k = 9: 9 factors, 56.2% variance
#> ✔ k = 10: 10 factors, 57.6% variance
#> 
#> ── Edges ──
#> 
#> 312 of 1320 edges have |r| ≥ 0.3
#> ────────────────────────────────────────────────────────────────────────────────
#> Note: This is a series of linked solutions, not a fitted hierarchical model.
#> Cross-level edges are descriptive score correlations. Per-level fit indices
#> (EFA/ESEM) describe how well a k-factor model fits the items at that level --
#> they do not validate the edges or the hierarchy itself.
```



The hierarchy unfolds a general factor of psychopathology at the top
(k = 1, ~31% of variance) down to ten fine-grained components
(cumulatively ~58%). With `pairs = "all"`, the edge table holds
1320 correlations across all 45 level pairs — every one of
which is matched against Forbes's own reference implementation by the package
regression test (see *Fidelity*, below).

**Label mapping.** Forbes labels components by level letter and position
within the level: `a1` for the single component at k = 1, `b1`–`b2` at k = 2,
down to `j1`–`j10` at k = 10. `ackwards` uses `m{k}f{j}` for the same
positions, so her `d4` is `m4f4` and her `j4` is `m10f4`. Signs may differ
component-by-component (the package aligns each factor to its primary parent;
see `vignette("ackwards-intro")`), which leaves every correlation identical in
absolute value.

## Skip-level structure



The deep hierarchy is where skip-level correlations earn their keep. Among
the non-adjacent pairs alone, 48 correlations exceed |r| = 0.9 —
components that persist essentially unchanged across two or more levels:

<!--html_preserve--><div id="zpgymzpzyw" style="padding-left:0px;padding-right:0px;padding-top:10px;padding-bottom:10px;overflow-x:auto;overflow-y:auto;width:auto;height:auto;">
<style>#zpgymzpzyw table {
  font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

#zpgymzpzyw thead, #zpgymzpzyw tbody, #zpgymzpzyw tfoot, #zpgymzpzyw tr, #zpgymzpzyw td, #zpgymzpzyw th {
  border-style: none;
}

#zpgymzpzyw p {
  margin: 0;
  padding: 0;
}

#zpgymzpzyw .gt_table {
  display: table;
  border-collapse: collapse;
  line-height: normal;
  margin-left: auto;
  margin-right: auto;
  color: #333333;
  font-size: 16px;
  font-weight: normal;
  font-style: normal;
  background-color: #FFFFFF;
  width: auto;
  border-top-style: solid;
  border-top-width: 2px;
  border-top-color: #A8A8A8;
  border-right-style: none;
  border-right-width: 2px;
  border-right-color: #D3D3D3;
  border-bottom-style: solid;
  border-bottom-width: 2px;
  border-bottom-color: #A8A8A8;
  border-left-style: none;
  border-left-width: 2px;
  border-left-color: #D3D3D3;
}

#zpgymzpzyw .gt_caption {
  padding-top: 4px;
  padding-bottom: 4px;
}

#zpgymzpzyw .gt_title {
  color: #333333;
  font-size: 125%;
  font-weight: initial;
  padding-top: 4px;
  padding-bottom: 4px;
  padding-left: 5px;
  padding-right: 5px;
  border-bottom-color: #FFFFFF;
  border-bottom-width: 0;
}

#zpgymzpzyw .gt_subtitle {
  color: #333333;
  font-size: 85%;
  font-weight: initial;
  padding-top: 3px;
  padding-bottom: 5px;
  padding-left: 5px;
  padding-right: 5px;
  border-top-color: #FFFFFF;
  border-top-width: 0;
}

#zpgymzpzyw .gt_heading {
  background-color: #FFFFFF;
  text-align: center;
  border-bottom-color: #FFFFFF;
  border-left-style: none;
  border-left-width: 1px;
  border-left-color: #D3D3D3;
  border-right-style: none;
  border-right-width: 1px;
  border-right-color: #D3D3D3;
}

#zpgymzpzyw .gt_bottom_border {
  border-bottom-style: solid;
  border-bottom-width: 2px;
  border-bottom-color: #D3D3D3;
}

#zpgymzpzyw .gt_col_headings {
  border-top-style: solid;
  border-top-width: 2px;
  border-top-color: #D3D3D3;
  border-bottom-style: solid;
  border-bottom-width: 2px;
  border-bottom-color: #D3D3D3;
  border-left-style: none;
  border-left-width: 1px;
  border-left-color: #D3D3D3;
  border-right-style: none;
  border-right-width: 1px;
  border-right-color: #D3D3D3;
}

#zpgymzpzyw .gt_col_heading {
  color: #333333;
  background-color: #FFFFFF;
  font-size: 100%;
  font-weight: normal;
  text-transform: inherit;
  border-left-style: none;
  border-left-width: 1px;
  border-left-color: #D3D3D3;
  border-right-style: none;
  border-right-width: 1px;
  border-right-color: #D3D3D3;
  vertical-align: bottom;
  padding-top: 5px;
  padding-bottom: 6px;
  padding-left: 5px;
  padding-right: 5px;
  overflow-x: hidden;
}

#zpgymzpzyw .gt_column_spanner_outer {
  color: #333333;
  background-color: #FFFFFF;
  font-size: 100%;
  font-weight: normal;
  text-transform: inherit;
  padding-top: 0;
  padding-bottom: 0;
  padding-left: 4px;
  padding-right: 4px;
}

#zpgymzpzyw .gt_column_spanner_outer:first-child {
  padding-left: 0;
}

#zpgymzpzyw .gt_column_spanner_outer:last-child {
  padding-right: 0;
}

#zpgymzpzyw .gt_column_spanner {
  border-bottom-style: solid;
  border-bottom-width: 2px;
  border-bottom-color: #D3D3D3;
  vertical-align: bottom;
  padding-top: 5px;
  padding-bottom: 5px;
  overflow-x: hidden;
  display: inline-block;
  width: 100%;
}

#zpgymzpzyw .gt_spanner_row {
  border-bottom-style: hidden;
}

#zpgymzpzyw .gt_group_heading {
  padding-top: 8px;
  padding-bottom: 8px;
  padding-left: 5px;
  padding-right: 5px;
  color: #333333;
  background-color: #FFFFFF;
  font-size: 100%;
  font-weight: initial;
  text-transform: inherit;
  border-top-style: solid;
  border-top-width: 2px;
  border-top-color: #D3D3D3;
  border-bottom-style: solid;
  border-bottom-width: 2px;
  border-bottom-color: #D3D3D3;
  border-left-style: none;
  border-left-width: 1px;
  border-left-color: #D3D3D3;
  border-right-style: none;
  border-right-width: 1px;
  border-right-color: #D3D3D3;
  vertical-align: middle;
  text-align: left;
}

#zpgymzpzyw .gt_empty_group_heading {
  padding: 0.5px;
  color: #333333;
  background-color: #FFFFFF;
  font-size: 100%;
  font-weight: initial;
  border-top-style: solid;
  border-top-width: 2px;
  border-top-color: #D3D3D3;
  border-bottom-style: solid;
  border-bottom-width: 2px;
  border-bottom-color: #D3D3D3;
  vertical-align: middle;
}

#zpgymzpzyw .gt_from_md > :first-child {
  margin-top: 0;
}

#zpgymzpzyw .gt_from_md > :last-child {
  margin-bottom: 0;
}

#zpgymzpzyw .gt_row {
  padding-top: 8px;
  padding-bottom: 8px;
  padding-left: 5px;
  padding-right: 5px;
  margin: 10px;
  border-top-style: solid;
  border-top-width: 1px;
  border-top-color: #D3D3D3;
  border-left-style: none;
  border-left-width: 1px;
  border-left-color: #D3D3D3;
  border-right-style: none;
  border-right-width: 1px;
  border-right-color: #D3D3D3;
  vertical-align: middle;
  overflow-x: hidden;
}

#zpgymzpzyw .gt_stub {
  color: #333333;
  background-color: #FFFFFF;
  font-size: 100%;
  font-weight: initial;
  text-transform: inherit;
  border-right-style: solid;
  border-right-width: 2px;
  border-right-color: #D3D3D3;
  padding-left: 5px;
  padding-right: 5px;
}

#zpgymzpzyw .gt_stub_row_group {
  color: #333333;
  background-color: #FFFFFF;
  font-size: 100%;
  font-weight: initial;
  text-transform: inherit;
  border-right-style: solid;
  border-right-width: 2px;
  border-right-color: #D3D3D3;
  padding-left: 5px;
  padding-right: 5px;
  vertical-align: top;
}

#zpgymzpzyw .gt_row_group_first td {
  border-top-width: 2px;
}

#zpgymzpzyw .gt_row_group_first th {
  border-top-width: 2px;
}

#zpgymzpzyw .gt_summary_row {
  color: #333333;
  background-color: #FFFFFF;
  text-transform: inherit;
  padding-top: 8px;
  padding-bottom: 8px;
  padding-left: 5px;
  padding-right: 5px;
}

#zpgymzpzyw .gt_first_summary_row {
  border-top-style: solid;
  border-top-color: #D3D3D3;
}

#zpgymzpzyw .gt_first_summary_row.thick {
  border-top-width: 2px;
}

#zpgymzpzyw .gt_last_summary_row {
  padding-top: 8px;
  padding-bottom: 8px;
  padding-left: 5px;
  padding-right: 5px;
  border-bottom-style: solid;
  border-bottom-width: 2px;
  border-bottom-color: #D3D3D3;
}

#zpgymzpzyw .gt_grand_summary_row {
  color: #333333;
  background-color: #FFFFFF;
  text-transform: inherit;
  padding-top: 8px;
  padding-bottom: 8px;
  padding-left: 5px;
  padding-right: 5px;
}

#zpgymzpzyw .gt_first_grand_summary_row {
  padding-top: 8px;
  padding-bottom: 8px;
  padding-left: 5px;
  padding-right: 5px;
  border-top-style: double;
  border-top-width: 6px;
  border-top-color: #D3D3D3;
}

#zpgymzpzyw .gt_last_grand_summary_row_top {
  padding-top: 8px;
  padding-bottom: 8px;
  padding-left: 5px;
  padding-right: 5px;
  border-bottom-style: double;
  border-bottom-width: 6px;
  border-bottom-color: #D3D3D3;
}

#zpgymzpzyw .gt_striped {
  background-color: rgba(128, 128, 128, 0.05);
}

#zpgymzpzyw .gt_table_body {
  border-top-style: solid;
  border-top-width: 2px;
  border-top-color: #D3D3D3;
  border-bottom-style: solid;
  border-bottom-width: 2px;
  border-bottom-color: #D3D3D3;
}

#zpgymzpzyw .gt_footnotes {
  color: #333333;
  background-color: #FFFFFF;
  border-bottom-style: none;
  border-bottom-width: 2px;
  border-bottom-color: #D3D3D3;
  border-left-style: none;
  border-left-width: 2px;
  border-left-color: #D3D3D3;
  border-right-style: none;
  border-right-width: 2px;
  border-right-color: #D3D3D3;
}

#zpgymzpzyw .gt_footnote {
  margin: 0px;
  font-size: 90%;
  padding-top: 4px;
  padding-bottom: 4px;
  padding-left: 5px;
  padding-right: 5px;
}

#zpgymzpzyw .gt_sourcenotes {
  color: #333333;
  background-color: #FFFFFF;
  border-bottom-style: none;
  border-bottom-width: 2px;
  border-bottom-color: #D3D3D3;
  border-left-style: none;
  border-left-width: 2px;
  border-left-color: #D3D3D3;
  border-right-style: none;
  border-right-width: 2px;
  border-right-color: #D3D3D3;
}

#zpgymzpzyw .gt_sourcenote {
  font-size: 90%;
  padding-top: 4px;
  padding-bottom: 4px;
  padding-left: 5px;
  padding-right: 5px;
}

#zpgymzpzyw .gt_left {
  text-align: left;
}

#zpgymzpzyw .gt_center {
  text-align: center;
}

#zpgymzpzyw .gt_right {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

#zpgymzpzyw .gt_font_normal {
  font-weight: normal;
}

#zpgymzpzyw .gt_font_bold {
  font-weight: bold;
}

#zpgymzpzyw .gt_font_italic {
  font-style: italic;
}

#zpgymzpzyw .gt_super {
  font-size: 65%;
}

#zpgymzpzyw .gt_footnote_marks {
  font-size: 75%;
  vertical-align: 0.4em;
  position: initial;
}

#zpgymzpzyw .gt_asterisk {
  font-size: 100%;
  vertical-align: 0;
}

#zpgymzpzyw .gt_indent_1 {
  text-indent: 5px;
}

#zpgymzpzyw .gt_indent_2 {
  text-indent: 10px;
}

#zpgymzpzyw .gt_indent_3 {
  text-indent: 15px;
}

#zpgymzpzyw .gt_indent_4 {
  text-indent: 20px;
}

#zpgymzpzyw .gt_indent_5 {
  text-indent: 25px;
}

#zpgymzpzyw .katex-display {
  display: inline-flex !important;
  margin-bottom: 0.75em !important;
}

#zpgymzpzyw div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after {
  height: 0px !important;
}
</style>
<table class="gt_table" data-quarto-disable-processing="false" data-quarto-bootstrap="false">
  <thead>
    <tr class="gt_heading">
      <td colspan="5" class="gt_heading gt_title gt_font_normal" style>Strongest skip-level edges in the AMH hierarchy</td>
    </tr>
    <tr class="gt_heading">
      <td colspan="5" class="gt_heading gt_subtitle gt_font_normal gt_bottom_border" style>Top 10 non-adjacent pairs by |r|</td>
    </tr>
    <tr class="gt_col_headings">
      <th class="gt_col_heading gt_columns_bottom_border gt_left" rowspan="1" colspan="1" scope="col" id="from">From</th>
      <th class="gt_col_heading gt_columns_bottom_border gt_left" rowspan="1" colspan="1" scope="col" id="to">To</th>
      <th class="gt_col_heading gt_columns_bottom_border gt_right" rowspan="1" colspan="1" scope="col" id="level_from">Level (from)</th>
      <th class="gt_col_heading gt_columns_bottom_border gt_right" rowspan="1" colspan="1" scope="col" id="level_to">Level (to)</th>
      <th class="gt_col_heading gt_columns_bottom_border gt_right" rowspan="1" colspan="1" scope="col" id="r">r</th>
    </tr>
  </thead>
  <tbody class="gt_table_body">
    <tr><td headers="from" class="gt_row gt_left">m6f6</td>
<td headers="to" class="gt_row gt_left">m8f7</td>
<td headers="level_from" class="gt_row gt_right">6</td>
<td headers="level_to" class="gt_row gt_right">8</td>
<td headers="r" class="gt_row gt_right">1.000</td></tr>
    <tr><td headers="from" class="gt_row gt_left">m7f6</td>
<td headers="to" class="gt_row gt_left">m9f5</td>
<td headers="level_from" class="gt_row gt_right">7</td>
<td headers="level_to" class="gt_row gt_right">9</td>
<td headers="r" class="gt_row gt_right">0.999</td></tr>
    <tr><td headers="from" class="gt_row gt_left">m6f6</td>
<td headers="to" class="gt_row gt_left">m9f5</td>
<td headers="level_from" class="gt_row gt_right">6</td>
<td headers="level_to" class="gt_row gt_right">9</td>
<td headers="r" class="gt_row gt_right">0.998</td></tr>
    <tr><td headers="from" class="gt_row gt_left">m5f5</td>
<td headers="to" class="gt_row gt_left">m8f7</td>
<td headers="level_from" class="gt_row gt_right">5</td>
<td headers="level_to" class="gt_row gt_right">8</td>
<td headers="r" class="gt_row gt_right">0.998</td></tr>
    <tr><td headers="from" class="gt_row gt_left">m5f5</td>
<td headers="to" class="gt_row gt_left">m7f6</td>
<td headers="level_from" class="gt_row gt_right">5</td>
<td headers="level_to" class="gt_row gt_right">7</td>
<td headers="r" class="gt_row gt_right">0.998</td></tr>
    <tr><td headers="from" class="gt_row gt_left">m8f7</td>
<td headers="to" class="gt_row gt_left">m10f5</td>
<td headers="level_from" class="gt_row gt_right">8</td>
<td headers="level_to" class="gt_row gt_right">10</td>
<td headers="r" class="gt_row gt_right">0.998</td></tr>
    <tr><td headers="from" class="gt_row gt_left">m7f6</td>
<td headers="to" class="gt_row gt_left">m10f5</td>
<td headers="level_from" class="gt_row gt_right">7</td>
<td headers="level_to" class="gt_row gt_right">10</td>
<td headers="r" class="gt_row gt_right">0.998</td></tr>
    <tr><td headers="from" class="gt_row gt_left">m6f6</td>
<td headers="to" class="gt_row gt_left">m10f5</td>
<td headers="level_from" class="gt_row gt_right">6</td>
<td headers="level_to" class="gt_row gt_right">10</td>
<td headers="r" class="gt_row gt_right">0.997</td></tr>
    <tr><td headers="from" class="gt_row gt_left">m5f5</td>
<td headers="to" class="gt_row gt_left">m9f5</td>
<td headers="level_from" class="gt_row gt_right">5</td>
<td headers="level_to" class="gt_row gt_right">9</td>
<td headers="r" class="gt_row gt_right">0.996</td></tr>
    <tr><td headers="from" class="gt_row gt_left">m7f5</td>
<td headers="to" class="gt_row gt_left">m9f4</td>
<td headers="level_from" class="gt_row gt_right">7</td>
<td headers="level_to" class="gt_row gt_right">9</td>
<td headers="r" class="gt_row gt_right">0.996</td></tr>
  </tbody>
  
</table>
</div><!--/html_preserve-->

Correlations this close to 1.0 across multiple levels are the signature of
redundancy: the intermediate solutions are relabeling a stable dimension, not
refining it. That is exactly what the redundancy chase formalizes.

## The redundancy chase

Forbes's `ChaseCorrPaths()` procedure walks each component upward through the
hierarchy, at every ancestor level taking the component with the largest
**direct** (skip-level) correlation, and continuing while |r| ≥ 0.9. Chains of
near-identical components are then collapsed to a single retained node.
`prune(x, "redundant")` implements the same rule as its default
(`redundancy_criterion = "direct"`):


``` r
xp <- prune(x, "redundant")
#> ℹ Redundancy pruning (direct criterion, |r| ≥ 0.9) flagged 37 nodes.
#> ℹ Nodes are retained in the object; inspect with `x$prune$nodes` and
#>   `x$prune$chains`.
```



Of the 55 components in the full hierarchy, **37 are
flagged as redundant**, leaving 18 distinct constructs — the same
decomposition reported in Forbes (2023). As always, `prune()` only
*annotates*: nothing is removed from the object, and every flagged component
keeps its loadings, scores, and edges.

### A chain, worked

Forbes's chase traces the chain `d4 → e4 → f5 → g5 → h5 → i4 → j4`: one
dimension surfacing at k = 4 and persisting, near-unchanged, to k = 10. In
`ackwards` labels, that chain is:


``` r
ch <- xp$prune$chains
d4 <- ch[ch$chain_id == ch$chain_id[ch$id == "m10f4"], c("id", "level", "retain")]
d4
#>       id level retain
#> 14  m4f4     4  FALSE
#> 15  m5f4     5  FALSE
#> 16  m6f5     6  FALSE
#> 17  m7f5     7  FALSE
#> 18  m8f5     8  FALSE
#> 19  m9f4     9  FALSE
#> 20 m10f4    10   TRUE
```

Because the chain reaches the deepest level, the retention rule keeps its
**bottom** node — `m10f4` (her `j4`), the most specific, best-defined
manifestation — and flags the six components above it as redundant. A chain
that stops short of k = 10 instead keeps its *top* node, the broadest
manifestation.

The full retained set (13 chain-retained components, in
level order): m1f1, m4f2, m5f2, m7f7, m10f1, m10f2, m10f3, m10f4, m10f5, m10f6, m10f7, m10f8, m10f9. The other
5 kept components were never part of a |r| ≥ 0.9 chain at all,
so no retention rule applies to them.

### Why the *direct* criterion matters here

Correlation is not transitive: a chain of adjacent links each ≥ 0.9 does not
guarantee the two ends correlate ≥ 0.9 directly. On shallow hierarchies the
distinction rarely bites, but on this 10-level hierarchy it does — Forbes's
direct rule and an adjacent-hop walk disagree on 7 of the 54 chased
components:


``` r
xa <- prune(x, "redundant", redundancy_criterion = "adjacent")
#> ℹ Redundancy pruning (adjacent criterion, |r| ≥ 0.9) flagged 36 nodes.
#> ℹ Nodes are retained in the object; inspect with `x$prune$nodes` and
#>   `x$prune$chains`.
```



The adjacent opt-in flags 36 components (vs 37
under the default) and retains `m3f3`, which the direct rule chases further
up the hierarchy. The default reproduces Forbes's published chase exactly —
all 54 components, including those 7 divergences — so stick with it when your
goal is comparability with her method; `redundancy_criterion = "adjacent"`
remains available for sensitivity analysis.

## The pruned-factor diagram

Dropping the flagged components and bridging each retained component directly
to its strongest kept ancestor yields the pruned-factor diagram, in the same
publication style as Forbes (2023) — black lines, uniform width, no legend,
correlation labels on each spanning arrow:


``` r
autoplot(xp,
  drop_pruned = TRUE, show_r = TRUE,
  color_pos = "black", color_neg = "black",
  edge_linewidth = 0.6, show_arrows = FALSE, legend = FALSE
)
```

![plot of chunk pruned-diagram](assets/ackwards-forbes2023-pruned-diagram-1.png)

Whole levels vanish (every component at those levels was flagged), leaving
deliberate gaps in the y-axis. To compact the layout while keeping the
original level numbers:


``` r
autoplot(xp,
  drop_pruned = TRUE, compress_levels = TRUE,
  color_pos = "black", color_neg = "black",
  edge_linewidth = 0.6, show_arrows = FALSE, legend = FALSE
)
```

![plot of chunk pruned-diagram-compressed](assets/ackwards-forbes2023-pruned-diagram-compressed-1.png)

For the full set of cosmetic controls, see
`vignette("ackwards-visualization")`.

## Fidelity

This reproduction is not merely visual — it is pinned by the package's
regression suite (`tests/testthat/test-forbes-fidelity.R`), which verifies on
every check run that:

- all 45 between-level correlation matrices match the output of Forbes's own
  reference implementation entrywise in absolute value (tolerance 1e-12), and
- the direct redundancy chase lands on her published `ChaseCorrPaths()` result
  for **all 54** chased components.

The expected values were computed with Forbes's reference implementation
(from her OSF project, `https://osf.io/pcwm8/`) on the same md5-pinned matrix
that ships as `forbes2023`, so the dataset and the oracle cannot drift apart.
Only `ackwards()` runs at test time.

## References

Forbes, M. K. (2023). Improving hierarchical models of individual differences:
An extension of Goldberg's bass-ackward method. *Psychological Methods*.
https://doi.org/10.1037/met0000546

Forbes, M. K., Sunderland, M., Rapee, R. M., Batterham, P. J., Calear, A. L.,
Carragher, N., Ruggero, C., Zimmerman, M., Baillie, A. J., Lynch, S. J.,
Mewton, L., Slade, T., & Krueger, R. F. (2021). A detailed hierarchical model
of psychopathology: From individual symptoms up to the general factor of
psychopathology. *Clinical Psychological Science*, *9*(2), 139–168.
https://doi.org/10.1177/2167702620954799

Goldberg, L. R. (2006). Doing it all Bass-Ackwards: The development of
hierarchical factor structures from the top down. *Journal of Research in
Personality*, *40*(4), 347–358. https://doi.org/10.1016/j.jrp.2006.01.001
