Nothing
# Guard tests for vignette idioms introduced in M24:
# wide + delta comparison tables in engines/ordinal/forbes vignettes.
# Helper: primary loading per item at a given level
.primary_loading <- function(x, level_k, items) {
d <- tidy(x, what = "loadings")
d <- d[d$level == level_k & d$item %in% items, ]
do.call(rbind, lapply(split(d, d$item), function(r) r[which.max(abs(r$loading)), ]))
}
# The bfi25 fits below are shared across several tests; polychoric correlation
# on 25 items is the dominant cost, and the same fit was previously recomputed
# up to 3x. Each distinct fit is computed once via the suite-wide cached()
# memo in helper-data.R (M48; it replaced this file's former `.vfit` local
# memo — nothing here mutates the returned objects, so sharing is safe).
.bfi_cc <- na.omit(bfi25)
test_that("engines vignette: loadings wide-pivot produces expected columns and shape", {
# Guards loadings-compare chunk in ackwards-engines.Rmd
skip_if_not_installed("psych")
x_pca <- cached(ackwards(.bfi_cc, k_max = 3, cor = "polychoric"))
x_efa <- cached(ackwards(.bfi_cc, k_max = 3, engine = "efa", cor = "polychoric"))
anchors <- c("N1", "N2", "E1", "E2", "C1", "C2")
pca_p <- .primary_loading(x_pca, 3, anchors)
efa_p <- .primary_loading(x_efa, 3, anchors)
# Alignment assertion must pass
pca_s <- pca_p[order(pca_p$item), ]
efa_s <- efa_p[order(efa_p$item), ]
expect_equal(pca_s$factor, efa_s$factor)
expect_equal(sign(pca_s$loading), sign(efa_s$loading))
wide <- merge(
pca_p[, c("factor", "item", "loading")],
efa_p[, c("item", "loading")],
by = "item", suffixes = c("_pca", "_efa")
)
wide$delta <- abs(wide$loading_efa) - abs(wide$loading_pca)
expect_named(wide, c("item", "factor", "loading_pca", "loading_efa", "delta"),
ignore.order = TRUE
)
expect_equal(nrow(wide), length(anchors))
expect_true(all(wide$delta < 0),
info = "EFA |loading| should be uniformly smaller than PCA |loading| for these anchor items"
)
})
test_that("engines vignette: edges wide-pivot produces expected columns and no NA mismatch", {
# Guards edges-compare chunk in ackwards-engines.Rmd
skip_if_not_installed("psych")
x_pca <- cached(ackwards(.bfi_cc, k_max = 3, cor = "polychoric"))
x_efa <- cached(ackwards(.bfi_cc, k_max = 3, engine = "efa", cor = "polychoric"))
pca_e <- tidy(x_pca, what = "edges", primary_only = TRUE)
efa_e <- tidy(x_efa, what = "edges", primary_only = TRUE)
wide_e <- merge(
pca_e[, c("from", "to", "r")],
efa_e[, c("from", "to", "r")],
by = c("from", "to"), all = TRUE, suffixes = c("_pca", "_efa")
)
wide_e$delta <- abs(wide_e$r_efa) - abs(wide_e$r_pca)
expect_named(wide_e, c("from", "to", "r_pca", "r_efa", "delta"), ignore.order = TRUE)
expect_true(nrow(wide_e) > 0L)
expect_true(all(is.finite(wide_e$delta)))
# On well-structured data, primary parents should agree between engines
expect_false(anyNA(wide_e),
info = "Expect no engine disagreement on primary parents for BFI at k=3"
)
})
test_that("ordinal vignette: loadings wide-pivot produces expected columns and positive deltas", {
# Guards load-compare chunk in ackwards-ordinal.Rmd
skip_if_not_installed("psych")
x_pearson <- cached(suppressWarnings(ackwards(.bfi_cc, k_max = 5)))
x_poly <- cached(ackwards(.bfi_cc, k_max = 5, cor = "polychoric"))
n_items <- c("N1", "N2", "N3", "N4", "N5")
pear_p <- .primary_loading(x_pearson, 5, n_items)
poly_p <- .primary_loading(x_poly, 5, n_items)
# Alignment assertion must pass
pear_s <- pear_p[order(pear_p$item), ]
poly_s <- poly_p[order(poly_p$item), ]
expect_equal(pear_s$factor, poly_s$factor)
expect_equal(sign(pear_s$loading), sign(poly_s$loading))
wide <- merge(
pear_p[, c("factor", "item", "loading")],
poly_p[, c("item", "loading")],
by = "item", suffixes = c("_pearson", "_poly")
)
wide$delta <- abs(wide$loading_poly) - abs(wide$loading_pearson)
expect_named(wide, c("item", "factor", "loading_pearson", "loading_poly", "delta"),
ignore.order = TRUE
)
expect_equal(nrow(wide), length(n_items))
expect_true(all(wide$delta > 0),
info = "Polychoric |loading| should be uniformly larger for Neuroticism items"
)
})
test_that("ordinal vignette: edges wide-pivot produces expected columns and no NA mismatch", {
# Guards edge-compare chunk in ackwards-ordinal.Rmd
skip_if_not_installed("psych")
x_pearson <- cached(suppressWarnings(ackwards(.bfi_cc, k_max = 5)))
x_poly <- cached(ackwards(.bfi_cc, k_max = 5, cor = "polychoric"))
pear_e <- tidy(x_pearson, what = "edges", primary_only = TRUE)
poly_e <- tidy(x_poly, what = "edges", primary_only = TRUE)
wide_e <- merge(
pear_e[, c("from", "to", "r")],
poly_e[, c("from", "to", "r")],
by = c("from", "to"), all = TRUE, suffixes = c("_pearson", "_poly")
)
wide_e$delta <- abs(wide_e$r_poly) - abs(wide_e$r_pearson)
expect_named(wide_e, c("from", "to", "r_pearson", "r_poly", "delta"), ignore.order = TRUE)
expect_true(nrow(wide_e) > 0L)
expect_true(all(is.finite(wide_e$delta)))
expect_false(anyNA(wide_e),
info = "Expect no basis disagreement on primary parents for BFI at k=5"
)
# Magnitude delta keeps the "positive = stronger connection" reading correct
# even for the negatively-signed primary edge (m2f2 -> m3f2), which strengthens
# under the polychoric basis. A signed delta would render that row negative.
neg_edge <- wide_e[wide_e$r_pearson < 0 & wide_e$r_poly < 0, ]
if (nrow(neg_edge) > 0L) {
stronger <- abs(neg_edge$r_poly) > abs(neg_edge$r_pearson)
expect_true(all(neg_edge$delta[stronger] > 0),
info = "A strengthening negative edge must show a positive magnitude delta"
)
}
})
test_that("forbes vignette: prune-nodes table idiom returns expected columns and n_redundant", {
# Guards prune-nodes chunk in ackwards-forbes.Rmd. prune() is cheap and
# re-runnable without re-extraction (M34), so this reuses the shared
# "poly5_all" base fit (see the pairs="all" test below) rather than
# re-fitting: only prune() itself needs to run here.
skip_if_not_installed("psych")
x_base <- cached(ackwards(.bfi_cc, k_max = 5, cor = "polychoric", pairs = "all"))
x_prune <- suppressMessages(prune(x_base, "redundant"))
nodes <- tidy(x_prune, what = "nodes")
n_redundant <- sum(nodes$pruned)
expect_named(nodes, c("id", "level", "pruned", "prune_reason"), ignore.order = TRUE)
expect_true(n_redundant > 0L, info = "Expect some redundant nodes for BFI at k=5")
expect_true(all(nodes$prune_reason[nodes$pruned] == "redundant"),
info = "Flagged nodes should carry prune_reason = 'redundant'"
)
expect_true(all(is.na(nodes$prune_reason[!nodes$pruned])),
info = "Unflagged nodes should have NA prune_reason"
)
# Inline-derivation helpers used in the prose (flagged_ids + lvl_summary)
flagged_ids <- nodes$id[nodes$pruned]
expect_equal(length(flagged_ids), n_redundant)
lvl_summary <- paste(
vapply(
sort(unique(nodes$level[nodes$pruned])),
function(L) {
ids_L <- nodes$id[nodes$pruned & nodes$level == L]
if (length(ids_L) == sum(nodes$level == L)) {
paste0("the entire k = ", L, " level")
} else {
paste0(length(ids_L), " factor at k = ", L)
}
},
character(1)
),
collapse = ", "
)
expect_type(lvl_summary, "character")
expect_true(nzchar(lvl_summary))
# On BFI k=5 the whole k=4 level is flagged, so the phrase must appear
expect_match(lvl_summary, "the entire k = 4 level", fixed = TRUE)
})
test_that("forbes vignette: prune-artifact table idiom returns expected columns", {
# Guards the prune-artifact-nodes chunk in ackwards-forbes.Rmd. Reuses the
# shared "poly5_all" base fit (see note in the prune-nodes test above).
skip_if_not_installed("psych")
x_base <- cached(ackwards(.bfi_cc, k_max = 5, cor = "polychoric", pairs = "all"))
x_art <- suppressMessages(prune(x_base, "artifact"))
art_nodes <- tidy(x_art, what = "nodes")
n_artifact <- sum(art_nodes$pruned)
expect_named(art_nodes, c("id", "level", "pruned", "prune_reason"), ignore.order = TRUE)
expect_type(n_artifact, "integer")
# Artifact is never auto-flagged (DECISIONS D-012); BFI flags nothing
expect_equal(n_artifact, 0L)
})
test_that("forbes vignette: skip-edge inline-R values are computed correctly", {
# Guards the edge-counts and top-skip-edge inline R in ackwards-forbes.Rmd
skip_if_not_installed("psych")
x_adj <- cached(ackwards(.bfi_cc, k_max = 5, cor = "polychoric"))
x_all <- cached(ackwards(.bfi_cc, k_max = 5, cor = "polychoric", pairs = "all"))
n_adj <- nrow(tidy(x_adj, what = "edges"))
n_all <- nrow(tidy(x_all, what = "edges"))
expect_true(n_adj > 0L)
expect_true(n_all > n_adj)
edges <- tidy(x_all, what = "edges", sort = "strength")
skip <- edges[
abs(edges$level_to - edges$level_from) > 1 & abs(edges$r) >= 0.5,
c("from", "to", "level_from", "level_to", "r")
]
expect_true(nrow(skip) > 0L)
top_r <- round(skip$r[1], 2)
expect_true(abs(top_r) >= 0.5)
expect_true(abs(skip$level_to[1] - skip$level_from[1]) > 1)
})
test_that("vignette table idiom: knitr::kable fallback renders without gt", {
# Guards the `else { knitr::kable(...) }` branch of every comparison chunk.
# gt is installed in dev/CI so that branch never runs during vignette builds;
# this exercises the fallback idiom directly so it cannot rot unnoticed.
skip_if_not_installed("psych")
x_pca <- cached(ackwards(.bfi_cc, k_max = 3, cor = "polychoric"))
x_efa <- cached(ackwards(.bfi_cc, k_max = 3, engine = "efa", cor = "polychoric"))
pca_e <- tidy(x_pca, what = "edges", primary_only = TRUE)
efa_e <- tidy(x_efa, what = "edges", primary_only = TRUE)
wide_e <- merge(pca_e[, c("from", "to", "r")], efa_e[, c("from", "to", "r")],
by = c("from", "to"), all = TRUE, suffixes = c("_pca", "_efa")
)
wide_e$delta <- abs(wide_e$r_efa) - abs(wide_e$r_pca)
out <- knitr::kable(
wide_e,
digits = 2, row.names = FALSE,
col.names = c("From", "To", "PCA", "EFA", "Δ (|EFA| - |PCA|)")
)
expect_s3_class(out, "knitr_kable")
expect_true(length(out) > 0L)
})
test_that("vignette edges idiom: disagreeing primary parents surface as NA in the merge", {
# Guards the documented behaviour behind the edge-table footnotes: when two
# engines/bases pick a different primary parent for the same child, the
# all = TRUE merge yields NA rather than silently dropping the row. BFI never
# triggers this, so demonstrate it on synthetic edge frames.
a <- data.frame(from = c("m1f1", "m1f1"), to = c("m2f1", "m2f2"), r = c(0.9, 0.4))
b <- data.frame(from = c("m1f1", "m1f2"), to = c("m2f1", "m2f2"), r = c(0.8, 0.5))
wide <- merge(a, b,
by = c("from", "to"), all = TRUE, suffixes = c("_a", "_b")
)
wide$delta <- abs(wide$r_b) - abs(wide$r_a)
# m2f2 has from = m1f1 in `a` but from = m1f2 in `b`: two rows, each half-NA
disagreement <- wide[wide$to == "m2f2", ]
expect_equal(nrow(disagreement), 2L)
expect_true(anyNA(disagreement$r_a))
expect_true(anyNA(disagreement$r_b))
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.