Nothing
test_that("bfi25 has the expected shape and structure", {
expect_equal(dim(bfi25), c(1000L, 25L))
expect_equal(
colnames(bfi25),
c(
paste0("A", 1:5), paste0("C", 1:5), paste0("E", 1:5),
paste0("N", 1:5), paste0("O", 1:5)
)
)
expect_true(all(vapply(bfi25, is.integer, logical(1))))
expect_true(anyNA(bfi25))
expect_true(all(bfi25 >= 1L | is.na(bfi25)))
expect_true(all(bfi25 <= 6L | is.na(bfi25)))
})
test_that("bfi25 ships public-domain IPIP labels that flow into top_items()", {
skip_if_not_installed("psych")
# Every column carries a non-empty scalar "label" attribute.
labs <- vapply(
bfi25,
function(col) attr(col, "label", exact = TRUE) %||% NA_character_,
character(1)
)
expect_equal(sum(!is.na(labs)), 25L)
expect_true(all(nzchar(labs)))
expect_identical(unname(labs[["E4"]]), "Make friends easily")
# Fit on the raw dataset (labels survive; NAs handled by `missing`) -> the
# labels are captured and surfaced by top_items() with zero user setup.
x <- suppressWarnings(ackwards(bfi25, k_max = 3))
expect_length(x$meta$item_labels, 25L)
ti <- top_items(x, level = 3, cut = 0.4)
expect_true("label" %in% names(ti$data))
expect_true(any(ti$data$label == "Make friends easily", na.rm = TRUE))
})
test_that("bfi25 labels are captured regardless of the missing-data route (M50)", {
skip_if_not_installed("psych")
# Capture happens on the supplied data before any missing handling, so both
# the pairwise (default) and listwise routes carry the labels through.
x_pair <- suppressWarnings(ackwards(bfi25, k_max = 2, missing = "pairwise"))
x_list <- suppressWarnings(ackwards(bfi25, k_max = 2, missing = "listwise"))
expect_length(x_pair$meta$item_labels, 25L)
expect_length(x_list$meta$item_labels, 25L)
})
test_that("base row-subsetting drops bfi25's plain label attributes (M50 contract)", {
# Documented caveat: plain `label` attributes do not survive base `[`, unlike
# class-based labelled/haven vectors, so na.omit() / row-indexing strip them
# and top_items() then falls back to bare codes. Guards the roxygen guidance
# to fit the dataset directly (with `missing =`) rather than pre-filtering.
expect_null(attr(na.omit(bfi25)$E4, "label", exact = TRUE))
expect_null(attr(bfi25[1:100, ]$E4, "label", exact = TRUE))
# Column selection (not row subsetting) keeps the attribute.
expect_identical(
attr(bfi25[, "E4"], "label", exact = TRUE),
"Make friends easily"
)
})
test_that("sim16 has the expected shape and structure", {
expect_equal(dim(sim16), c(1000L, 16L))
expect_equal(colnames(sim16), paste0("i", 1:16))
expect_true(all(vapply(sim16, is.double, logical(1))))
expect_false(anyNA(sim16))
# Continuous, not ordinal-looking: far more than 7 distinct values per column.
expect_true(all(vapply(sim16, function(v) length(unique(v)) > 7L, logical(1))))
})
test_that("sim16 triggers no ordinal-detection warning under pca or efa", {
skip_if_not_installed("psych")
x_pca <- suppressMessages(ackwards(sim16, k_max = 4, engine = "pca"))
x_efa <- suppressMessages(ackwards(sim16, k_max = 4, engine = "efa"))
expect_false(x_pca$meta$ordinal_warned)
expect_false(x_efa$meta$ordinal_warned)
})
test_that("sim16 recovers its known 1 -> 2 -> 4 hierarchy under efa", {
skip_if_not_installed("psych")
x <- suppressMessages(ackwards(sim16, k_max = 4, engine = "efa"))
.primary_groups <- function(level) {
load_mat <- x$levels[[as.character(level)]]$loadings
unname(apply(abs(load_mat), 1, which.max))
}
# k = 1: a single general factor spans all 16 items.
expect_equal(length(unique(.primary_groups(1))), 1L)
# k = 2: splits exactly along the metatrait line (i1-i8 vs. i9-i16).
g2 <- .primary_groups(2)
expect_equal(length(unique(g2[1:8])), 1L)
expect_equal(length(unique(g2[9:16])), 1L)
expect_false(g2[1] == g2[9])
# k = 4: recovers the 4 true group factors (i1-4, i5-8, i9-12, i13-16) exactly.
g4 <- .primary_groups(4)
true_groups <- rep(1:4, each = 4)
expect_equal(length(unique(g4)), 4L)
for (grp in 1:4) {
idx <- which(true_groups == grp)
expect_equal(length(unique(g4[idx])), 1L)
}
})
test_that("suggest_k(sim16) converges on the true k = 4 (idealized case)", {
skip_if_not_installed("psych")
skip_if_not_installed("EFAtools")
sk <- suggest_k(sim16, n_iter = 5L, seed = 1L)
# sim16 is deliberately the *idealized* teaching case: a strong, clean planted
# signal on which the criteria converge (contrast bfi25, where the same
# criteria span k = 4-6). We assert that convergence robustly rather than
# pinning all six criteria to 4 exactly -- PA-PC/PA-FA/CD resample, so their
# exact value can wobble +/-1 across platforms/RNG even under a fixed seed.
expect_equal(sk$k_map, 4L) # MAP is deterministic -> a stable anchor on k = 4
ks <- c(
sk$k_parallel_pc, sk$k_parallel_fa, sk$k_map,
sk$k_vss1, sk$k_vss2, sk$k_cd
)
# The majority of criteria (the "consensus") lands on the true k = 4.
expect_equal(as.integer(names(which.max(table(ks)))), 4L)
})
test_that("sim16 at k_max = 5 guarantees a redundant chain and an artifact signal", {
skip_if_not_installed("psych")
x <- suppressMessages(
ackwards(sim16, k_max = 5, engine = "efa") |>
prune(c("redundant", "artifact"))
)
# Redundancy: the true (non-splitting) factors persist across levels, so at
# least one adjacent-level chain is flagged (|r| >= .9 and phi >= .95).
expect_gte(sum(x$prune$nodes$pruned), 1L)
expect_gte(nrow(subset(x$prune$phi, abs_phi >= 0.95)), 1L)
# Artifact: k = 5 has no real 5th dimension, so the extra factor is an
# orphan with too few primary-loading items.
structural <- x$prune$structural
expect_gte(sum(structural$few_items | structural$orphan), 1L)
# Stronger: the artefact factor is a primary loader for *zero* items (the
# doc's "orphan with zero primary-loading items"), and that same factor is
# exactly the one flagged both few_items and orphan.
load5 <- x$levels[["5"]]$loadings
primary5 <- colnames(load5)[apply(abs(load5), 1, which.max)]
counts5 <- table(factor(primary5, levels = colnames(load5)))
expect_true(any(counts5 == 0L))
orphan_ids <- names(counts5)[counts5 == 0L]
flagged_ids <- subset(structural, few_items & orphan)$id
expect_true(all(orphan_ids %in% flagged_ids))
})
test_that("forbes2023 is a valid 155x155 correlation matrix with symptom labels", {
expect_true(is.matrix(forbes2023))
expect_true(is.numeric(forbes2023))
expect_equal(dim(forbes2023), c(155L, 155L))
# Symmetric with a unit diagonal -- a genuine correlation matrix.
expect_true(isSymmetric(unname(forbes2023)))
expect_equal(diag(forbes2023), rep(1, 155L), ignore_attr = TRUE)
expect_true(all(abs(forbes2023) <= 1 + 1e-12))
# Row and column names are the (identical, non-empty, unique) symptom labels.
expect_false(is.null(rownames(forbes2023)))
expect_identical(rownames(forbes2023), colnames(forbes2023))
expect_true(all(nzchar(rownames(forbes2023))))
expect_false(anyDuplicated(rownames(forbes2023)) > 0L)
})
test_that("data-raw/sim16.R regenerates sim16 identically (fixed-seed reproducibility)", {
script_path <- testthat::test_path("..", "..", "data-raw", "sim16.R")
skip_if(!file.exists(script_path), "data-raw/sim16.R not reachable from this test context")
# Drop the trailing usethis::use_data() call (writes to disk; not what this
# test is checking) without referencing usethis:: literally -- R CMD check's
# unstated-dependency scan flags any pkg:: token in tests/, even in a string.
exprs <- parse(script_path)
is_use_data_call <- function(e) {
is.call(e) && grepl("use_data", deparse(e[[1]])[1], fixed = TRUE)
}
exprs <- Filter(Negate(is_use_data_call), exprs)
env <- new.env(parent = globalenv())
for (e in exprs) eval(e, envir = env)
expect_equal(env$sim16, sim16, ignore_attr = TRUE)
})
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.