prune() never removes anything from an ackwards object – it only
annotates factors with pruned/prune_reason flags in x$prune$nodes
(flag-only, never removes; the object keeps every level). Because it is a
separate, cheap step from extraction, you can re-prune with new thresholds
without re-running ackwards():
x <- ackwards(bfi25, k_max = 6, engine = "esem") # expensive
x |> prune("redundant") # cheap, repeatable
x |> prune("redundant", redundancy_r = 0.95) # no re-extractionprune() is an S3 generic (rather than a plain function) so it coexists
with the prune generics already defined by recursive-partitioning
packages (e.g. rpart::prune) regardless of package load order.
Usage
prune(x, ...)
# S3 method for class 'ackwards'
prune(
x,
rules = "none",
manual = NULL,
redundancy_r = 0.9,
redundancy_phi = NULL,
redundancy_criterion = c("direct", "adjacent"),
min_items = 3L,
orphan_r = 0.5,
near_margin = 0.1,
...
)Arguments
- x
An
ackwardsobject.- ...
Reserved for future methods/arguments.
- rules
Character vector controlling which auto-rules run. Default
"none"(no auto rule; combine withmanualfor pure manual pruning, or callprune(x)with no arguments to clear any existing pruning). Options:"redundant"– identify chains of factors connected by score correlations|r| >= redundancy_r(and optionallyphi > redundancy_phi), usingredundancy_criterion(default"direct", faithful to Forbes). Applies Forbes's (2023) retention rule: keep the bottom node when the chain reaches levelk_max(most specific); keep the top node otherwise. Flagged nodes getpruned = TRUEandprune_reason = "redundant"inx$prune$nodes."artifact"(or the alias"artefact", normalized to"artifact") – compute Tucker's congruence coefficient (phi) for all cross-level factor pairs and store inx$prune$phi, structural signals (few_items/orphan/split_merge) inx$prune$structural, and the near-redundant band (near_margin, see Details) inx$prune$near_redundant. No factors are auto-flagged; artifact identification requires judgment (Forbes, 2023; Wicherts et al., 2016).
- manual
Character vector of factor labels (e.g.
c("m4f3", "m4f4")) to flag directly, in addition to or instead of an auto rule. Standalone manual pruning is supported:prune(x, manual = c("m4f3")). Unknown labels error. A node already flagged by an auto rule keeps thatprune_reason; only otherwise-unflagged manual nodes getprune_reason = "manual".- redundancy_r
Scalar in
(0, 1]. Score-correlation|r|threshold for redundancy chains. Default0.9(Forbes, 2023).- redundancy_phi
Scalar in
(0, 1],NULL(default, auto), orNA(explicit opt-out). WhenNULL:x$engine == "pca"– no phi filter. Component scores are determinate (exact linear functions of the data, with no factor-score indeterminacy), so the score correlation|r|is the true correlation between the components themselves; phi adds nothing that|r|does not already capture.x$engineis"efa"or"esem"– automatically set to0.95(Lorenzo-Seva & ten Berge, 2006). Factor-score indeterminacy off-PCA means|r|-alone is liberal; the conjunctive phi criterion is the conservative default. A cli message announces the resolved value. PassNAto disable phi filtering regardless of engine. Pass a numeric value to override on any engine.
- redundancy_criterion
How redundancy chains are traced. One of:
"direct"(default) – chase upward via the direct (skip-level) correlation between a factor and each ancestor level, continuing while|r| >= redundancy_rcontiguously. This is Forbes's (2023) publishedChaseCorrPathsrule and the honest operationalization of "the same construct" (two factor scores share>= redundancy_r^2variance directly). It reproduces her AMH applied example exactly."adjacent"– trace adjacent primary-parent links only (each consecutive level|r| >= redundancy_r). This was the pre-M53 default; because correlation is non-transitive it can both over- and under-flag versus"direct"in deep (many-level) hierarchies, so it is retained only as an opt-in. On shallow/transitive hierarchies the two agree.
- min_items
Minimum number of items for which a factor must be the primary loader (highest
|loading|). Factors with fewer thanmin_itemsprimary items are flaggedfew_items = TRUEinx$prune$structural. Only used whenrulesincludes"artifact". Default3L– a factor defined by one or two items is under-identified and frequently an extraction artifact rather than a replicable construct (the classic "three-indicator rule"; Forbes, 2023, Fig. 2).- orphan_r
Threshold for the
orphanstructural signal. A factor whose maximum adjacent-level|r|(to the immediately shallower and deeper levels) falls beloworphan_ris flaggedorphan = TRUEinx$prune$structural– it does not connect to the neighbouring solutions and so does not replicate across the hierarchy. Only used whenrulesincludes"artifact". Default0.5– a moderate correlation; a factor that shares less than a quarter of its variance with every neighbour is a structural outlier worth inspecting.- near_margin
Scalar in
(0, 1]. Width of the near-redundant band reported by"artifact"mode (see Details). A cross-level pair is flagged near-redundant when its direct|r|sits in[redundancy_r - near_margin, redundancy_r)or its Tuckerphisits in[redundancy_phi - near_margin, redundancy_phi)– i.e. withinnear_marginbelow a redundancy threshold – while the pair is not itself fully redundant. Only used whenrulesincludes"artifact". Default0.1.
Details
The "direct" criterion is a star anchored on the leaf, not a walk.
Take a three-level chain candidate with deepest leaf m3f1 and shallower
factors m2f1 (level 2) and m1f1 (level 1). Under the default "direct"
criterion the chain m1f1 -> m2f1 -> m3f1 forms when both direct-to-leaf
correlations |r(m1f1, m3f1)| and |r(m2f1, m3f1)| meet redundancy_r –
every member is judged by its own direct correlation to the same deepest
factor (a star centred on the leaf). It does not require the adjacent hop
|r(m1f1, m2f1)| to meet the threshold (that is the "adjacent" criterion),
and it does not screen every ancestor pair against every other (there is
no all-pairs test). So an ancestor can join on a strong direct link to the
leaf even where its adjacent hop to the next chain member is weak – which is
exactly why r_to_prev (below) can sit under redundancy_r.
Reading x$prune$chains under redundancy_criterion = "direct". The
r_to_prev and phi_to_prev columns report the adjacent-level
correlation and congruence between consecutive chain members (for continuity
of display), but chain membership is decided by the direct (skip-level)
correlation to the chain's deepest factor. A direct chain can therefore
legitimately contain a link whose r_to_prev is below redundancy_r – the
stronger direct link is what justified it. The endpoint_r column gives the
direct root-to-leaf correlation as an at-a-glance cross-check. Under
redundancy_criterion = "adjacent", r_to_prev is the criterion and always
meets redundancy_r.
The near-redundant band ("artifact" mode). prune("redundant") drops
full redundancy – pairs at or above the thresholds. Forbes (2023) uses the
artifact flags mainly for the messier band just below them: a pair that
correlates, say, |r| = 0.89 and shares a loading pattern phi = 0.93 is not
quite redundant but is a candidate re-rotation worth a second look. Artifact
mode surfaces this as x$prune$near_redundant – a data frame of every
cross-level pair that is not itself fully redundant yet has its direct
(skip-level) |r| or its Tucker phi within near_margin below the
corresponding threshold (redundancy_r / redundancy_phi). Columns:
from, to, level_from, level_to, r (direct score correlation), phi
(loading congruence), and the logical band flags near_r / near_phi. Under
the PCA engine redundancy_phi is NULL (component scores are determinate),
so only the |r| band applies; under EFA/ESEM redundancy_phi auto-resolves
to 0.95 (announced via cli) and the phi band is active too. Like phi and
the structural signals, the band is report-only – it never drops or
mutates the kept node set.
References
Forbes, M. K. (2023). Improving hierarchical models of individual differences: An extension of Goldberg's bass-ackward method. Psychological Methods. doi:10.1037/met0000546
Lorenzo-Seva, U., & ten Berge, J. M. F. (2006). Tucker's congruence coefficient as a meaningful index of factor similarity. Methodology, 2(2), 57–64. doi:10.1027/1614-2241.2.2.57
See also
ackwards(), tidy.ackwards() (what = "nodes"),
autoplot.ackwards() (drop_pruned)
Examples
# sim16 has a planted redundant chain + overextraction artifact at k = 5,
# so the prune rules always have a finding to show (and no ordinal warning).
x <- ackwards(sim16, k_max = 5)
xp <- prune(x, "redundant")
#> ℹ Redundancy pruning (direct criterion, |r| ≥ 0.9) flagged 7 nodes.
#> ℹ Nodes are retained in the object; inspect with `x$prune$nodes` and
#> `x$prune$chains`.
xp$prune$nodes
#> id level pruned prune_reason
#> 1 m1f1 1 FALSE <NA>
#> 2 m2f1 2 FALSE <NA>
#> 3 m2f2 2 FALSE <NA>
#> 4 m3f1 3 TRUE redundant
#> 5 m3f2 3 TRUE redundant
#> 6 m3f3 3 TRUE redundant
#> 7 m4f1 4 TRUE redundant
#> 8 m4f2 4 TRUE redundant
#> 9 m4f3 4 TRUE redundant
#> 10 m4f4 4 TRUE redundant
#> 11 m5f1 5 FALSE <NA>
#> 12 m5f2 5 FALSE <NA>
#> 13 m5f3 5 FALSE <NA>
#> 14 m5f4 5 FALSE <NA>
#> 15 m5f5 5 FALSE <NA>
# Re-prune with a new threshold -- no re-extraction needed
prune(x, "redundant", redundancy_r = 0.95)
#> ℹ Redundancy pruning (direct criterion, |r| ≥ 0.95) flagged 6 nodes.
#> ℹ Nodes are retained in the object; inspect with `x$prune$nodes` and
#> `x$prune$chains`.
#>
#> ── Bass-Ackwards Analysis (ackwards) ───────────────────────────────────────────
#> Engine: pca
#> Rotation: varimax
#> Basis: pearson
#> n: 1,000
#> k (max): 5
#>
#> ── Levels ──
#>
#> ✔ k = 1: 1 factor, 28.2% variance
#> ✔ k = 2: 2 factors, 46.5% variance
#> ✔ k = 3: 3 factors, 57.5% variance
#> ✔ k = 4: 4 factors, 67.7% variance
#> ✔ k = 5: 5 factors, 70.8% variance
#>
#> ── Edges ──
#>
#> 13 of 40 edges have |r| ≥ 0.3
#>
#> ── Pruning ──
#>
#> Redundancy (direct, |r| ≥ 0.95): 6 nodes flagged
#> ────────────────────────────────────────────────────────────────────────────────
#> Note: Pruning is interpretive relabeling, not re-estimation. Flagged nodes
#> remain in the object; all edges are preserved. Inspect with `x$prune$nodes` and
#> `tidy(x, what = "nodes")`.
#> 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.
# Manual pruning: standalone, or mixed with an auto rule
prune(x, manual = "m4f2")
#>
#> ── Bass-Ackwards Analysis (ackwards) ───────────────────────────────────────────
#> Engine: pca
#> Rotation: varimax
#> Basis: pearson
#> n: 1,000
#> k (max): 5
#>
#> ── Levels ──
#>
#> ✔ k = 1: 1 factor, 28.2% variance
#> ✔ k = 2: 2 factors, 46.5% variance
#> ✔ k = 3: 3 factors, 57.5% variance
#> ✔ k = 4: 4 factors, 67.7% variance
#> ✔ k = 5: 5 factors, 70.8% variance
#>
#> ── Edges ──
#>
#> 13 of 40 edges have |r| ≥ 0.3
#>
#> ── Pruning ──
#>
#> Manual: 1 node explicitly flagged (m4f2)
#> ────────────────────────────────────────────────────────────────────────────────
#> Note: Pruning is interpretive relabeling, not re-estimation. Flagged nodes
#> remain in the object; all edges are preserved. Inspect with `x$prune$nodes` and
#> `tidy(x, what = "nodes")`.
#> 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.
prune(x, "redundant", manual = "m4f2")
#> ℹ Redundancy pruning (direct criterion, |r| ≥ 0.9) flagged 7 nodes.
#> ℹ Nodes are retained in the object; inspect with `x$prune$nodes` and
#> `x$prune$chains`.
#>
#> ── Bass-Ackwards Analysis (ackwards) ───────────────────────────────────────────
#> Engine: pca
#> Rotation: varimax
#> Basis: pearson
#> n: 1,000
#> k (max): 5
#>
#> ── Levels ──
#>
#> ✔ k = 1: 1 factor, 28.2% variance
#> ✔ k = 2: 2 factors, 46.5% variance
#> ✔ k = 3: 3 factors, 57.5% variance
#> ✔ k = 4: 4 factors, 67.7% variance
#> ✔ k = 5: 5 factors, 70.8% variance
#>
#> ── Edges ──
#>
#> 13 of 40 edges have |r| ≥ 0.3
#>
#> ── Pruning ──
#>
#> Redundancy (direct, |r| ≥ 0.9): 7 nodes flagged
#> Manual: 1 node explicitly flagged (m4f2)
#> ────────────────────────────────────────────────────────────────────────────────
#> Note: Pruning is interpretive relabeling, not re-estimation. Flagged nodes
#> remain in the object; all edges are preserved. Inspect with `x$prune$nodes` and
#> `tidy(x, what = "nodes")`.
#> 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.
