Nothing
# tests/testthat/test-decide-sample-size.R
# Tests for decide_sample_size() — both assurance and conditional modes.
library(testthat)
# ---- Shared synthetic fixture -----------------------------------------------
make_syn <- function() {
syn_summary <- data.frame(
n = rep(c(50, 100, 200), each = 3),
treatment = rep(c(0.2, 0.5, 0.8), 3),
power_direction = c(0.40, 0.65, 0.85,
0.60, 0.82, 0.95,
0.72, 0.90, 0.98),
power_threshold = c(0.30, 0.55, 0.75,
0.50, 0.72, 0.88,
0.62, 0.80, 0.92),
stringsAsFactors = FALSE
)
list(
summary = syn_summary,
settings = list(effect_name = "treatment")
)
}
# Normal design prior weights over the three effect values
make_weights <- function() {
assurance_prior_weights(c(0.2, 0.5, 0.8), dist = "normal",
mean = 0.5, sd = 0.2)
}
# =============================================================================
# (a) Assurance mode: one row per metric, correct structure
# =============================================================================
test_that("assurance mode returns one row per requested metric", {
res <- make_syn()
w <- make_weights()
out <- decide_sample_size(res,
direction = 0.80,
threshold = 0.75,
prior_weights = w)
expect_s3_class(out, "powerbrmsINLA_sample_size")
expect_s3_class(out, "data.frame")
expect_equal(nrow(out), 2L)
expect_setequal(out$metric, c("direction", "threshold"))
expect_named(out, c("metric", "target", "n_recommended",
"assurance_achieved", "prior_description"))
})
test_that("assurance mode: single metric returns single row", {
res <- make_syn()
w <- make_weights()
out <- decide_sample_size(res,
direction = 0.80,
prior_weights = w)
expect_equal(nrow(out), 1L)
expect_equal(out$metric, "direction")
})
test_that("assurance mode: n_recommended is a valid sample size from the grid", {
res <- make_syn()
w <- make_weights()
out <- decide_sample_size(res,
direction = 0.70,
prior_weights = w)
expect_false(is.na(out$n_recommended))
expect_true(out$n_recommended %in% c(50, 100, 200))
expect_true(out$assurance_achieved >= 0.70)
})
test_that("assurance mode: distribution prior (list) works", {
res <- make_syn()
dist_prior <- list(dist = "normal", mean = 0.5, sd = 0.2)
out <- decide_sample_size(res,
direction = 0.70,
prior_weights = dist_prior)
expect_s3_class(out, "powerbrmsINLA_sample_size")
expect_equal(nrow(out), 1L)
expect_false(is.na(out$n_recommended))
})
test_that("assurance mode: prior_description is populated", {
res <- make_syn()
w <- make_weights()
out <- decide_sample_size(res,
direction = 0.70,
prior_weights = w)
expect_true(nchar(out$prior_description) > 0L)
})
# =============================================================================
# (g) Assurance mode: per-metric targets are respected
# =============================================================================
test_that("target column equals the value passed as direction argument", {
res <- make_syn()
w <- make_weights()
out_70 <- decide_sample_size(res, direction = 0.70, prior_weights = w)
out_80 <- decide_sample_size(res, direction = 0.80, prior_weights = w)
expect_equal(out_70$target, 0.70)
expect_equal(out_80$target, 0.80)
})
test_that("target column equals the value passed as threshold argument", {
res <- make_syn()
w <- make_weights()
out <- decide_sample_size(res, threshold = 0.60, prior_weights = w)
expect_equal(out$target, 0.60)
})
test_that("different metrics carry independent targets", {
res <- make_syn()
w <- make_weights()
out <- decide_sample_size(res,
direction = 0.70,
threshold = 0.60,
prior_weights = w)
expect_equal(nrow(out), 2L)
dir_row <- out[out$metric == "direction", ]
thr_row <- out[out$metric == "threshold", ]
expect_equal(dir_row$target, 0.70)
expect_equal(thr_row$target, 0.60)
# assurance_achieved must be >= its own target, not the other metric's target
if (!is.na(dir_row$n_recommended)) {
expect_true(dir_row$assurance_achieved >= 0.70)
}
if (!is.na(thr_row$n_recommended)) {
expect_true(thr_row$assurance_achieved >= 0.60)
}
})
test_that("lower target finds smaller or equal n than higher target", {
res <- make_syn()
w <- make_weights()
out_easy <- decide_sample_size(res, direction = 0.50, prior_weights = w)
out_hard <- decide_sample_size(res, direction = 0.80, prior_weights = w)
# If both are non-NA, the easier target should need no more n
if (!is.na(out_easy$n_recommended) && !is.na(out_hard$n_recommended)) {
expect_true(out_easy$n_recommended <= out_hard$n_recommended)
}
})
test_that("direction = 0.70 gives different result than direction = 0.80", {
res <- make_syn()
w <- make_weights()
out_70 <- decide_sample_size(res, direction = 0.70, prior_weights = w)
out_80 <- decide_sample_size(res, direction = 0.80, prior_weights = w)
# Targets recorded in output must differ
expect_false(identical(out_70$target, out_80$target))
# The n at target=0.80 must be >= n at target=0.70 (when both achievable)
if (!is.na(out_70$n_recommended) && !is.na(out_80$n_recommended)) {
expect_true(out_80$n_recommended >= out_70$n_recommended)
}
})
test_that("print output mentions the correct percentage for the metric target", {
res <- make_syn()
w <- make_weights()
out <- decide_sample_size(res, direction = 0.70, prior_weights = w)
# Should say "70%" not "80%"
expect_output(print(out), regexp = "70%", fixed = TRUE)
})
test_that("print output for threshold = 0.60 mentions 60%", {
res <- make_syn()
w <- make_weights()
out <- decide_sample_size(res, threshold = 0.60, prior_weights = w)
expect_output(print(out), regexp = "60%", fixed = TRUE)
})
test_that("global target fallback is used when targets list has non-numeric", {
# Edge case: targets list with a non-numeric value triggers global fallback
res <- make_syn()
w <- make_weights()
# Use the targets list interface with a proper numeric to verify it works
out_list <- decide_sample_size(res,
targets = list(direction = 0.70),
prior_weights = w)
out_direct <- decide_sample_size(res, direction = 0.70, prior_weights = w)
expect_equal(out_list$target, 0.70)
expect_equal(out_list$n_recommended, out_direct$n_recommended)
})
# =============================================================================
# (b) Conditional mode: one row per effect-size value
# =============================================================================
test_that("conditional mode returns one row per effect-size value", {
res <- make_syn()
out <- decide_sample_size(res, direction = 0.80)
expect_s3_class(out, "powerbrmsINLA_sample_size")
# Three unique treatment values → three rows
expect_equal(nrow(out), 3L)
expect_true("treatment" %in% names(out))
expect_true("n_recommended" %in% names(out))
})
test_that("conditional mode includes conditional power column", {
res <- make_syn()
out <- decide_sample_size(res, direction = 0.80)
expect_true("cond_power_direction" %in% names(out))
# Power at recommended n should be >= target for non-NA rows
ok_rows <- !is.na(out$n_recommended)
expect_true(all(out$cond_power_direction[ok_rows] >= 0.80))
})
test_that("conditional mode with threshold returns cond_power_threshold", {
res <- make_syn()
out <- decide_sample_size(res, threshold = 0.70)
expect_true("cond_power_threshold" %in% names(out))
})
test_that("conditional mode with two metrics returns both power columns", {
res <- make_syn()
out <- decide_sample_size(res, direction = 0.60, threshold = 0.50)
expect_true("cond_power_direction" %in% names(out))
expect_true("cond_power_threshold" %in% names(out))
})
# =============================================================================
# (c) Backward compatibility: no prior_weights still works
# =============================================================================
test_that("calling without prior_weights uses conditional mode", {
res <- make_syn()
out <- decide_sample_size(res, direction = 0.80)
expect_s3_class(out, "powerbrmsINLA_sample_size")
expect_equal(attr(out, "mode"), "conditional")
expect_false("metric" %in% names(out)) # assurance-mode column absent
})
test_that("targets list interface works in conditional mode", {
res <- make_syn()
out_direct <- decide_sample_size(res, direction = 0.80)
out_list <- decide_sample_size(res, targets = list(direction = 0.80))
expect_equal(out_direct$n_recommended, out_list$n_recommended)
})
test_that("plain data.frame input works in conditional mode", {
s <- data.frame(
n = rep(c(50, 100), each = 2),
treatment = rep(c(0.3, 0.7), 2),
power_direction = c(0.55, 0.75, 0.78, 0.92),
stringsAsFactors = FALSE
)
out <- decide_sample_size(s, direction = 0.75)
expect_s3_class(out, "powerbrmsINLA_sample_size")
expect_equal(nrow(out), 2L)
})
test_that("plain data.frame input works in assurance mode", {
s <- data.frame(
n = rep(c(50, 100, 200), each = 3),
treatment = rep(c(0.2, 0.5, 0.8), 3),
power_direction = c(0.40, 0.65, 0.85,
0.60, 0.82, 0.95,
0.72, 0.90, 0.98),
stringsAsFactors = FALSE
)
res <- list(summary = s, settings = list(effect_name = "treatment"))
w <- make_weights()
out <- decide_sample_size(res, direction = 0.70, prior_weights = w)
expect_s3_class(out, "powerbrmsINLA_sample_size")
expect_equal(out$target, 0.70)
})
# =============================================================================
# (d) NA with informative message when no n meets the target
# =============================================================================
test_that("assurance mode returns NA when target is unachievable, with message", {
res <- make_syn()
w <- make_weights()
# direction = 0.999 is the per-metric target; no n achieves it
expect_message(
out <- decide_sample_size(res,
direction = 0.999,
prior_weights = w),
regexp = "no sample size"
)
expect_equal(nrow(out), 1L)
expect_true(is.na(out$n_recommended))
expect_true(is.na(out$assurance_achieved))
# The recorded target is the value we actually passed, not the fallback 0.80
expect_equal(out$target, 0.999)
})
test_that("conditional mode returns NA when target is unachievable, with message", {
res <- make_syn()
expect_message(
out <- decide_sample_size(res, direction = 0.999),
regexp = "no sample size met all targets"
)
expect_true(any(is.na(out$n_recommended)))
})
# =============================================================================
# (e) Sampled-SD aggregation in conditional mode
# =============================================================================
test_that("conditional mode aggregates across sampled_error_sd rows", {
# Simulate a summary that has a sampled_error_sd column (variance uncertainty)
s <- data.frame(
n = rep(c(50, 100), each = 4),
treatment = rep(c(0.3, 0.7), times = 4),
sampled_error_sd = rep(c(0.9, 1.1), each = 4),
power_direction = c(0.55, 0.75, 0.55, 0.75,
0.80, 0.92, 0.80, 0.92),
stringsAsFactors = FALSE
)
# Without aggregation this would yield 4 rows (2 treatments x 2 SD draws)
# With aggregation it should yield 2 rows (one per treatment value)
out <- decide_sample_size(s, direction = 0.75)
expect_equal(nrow(out), 2L)
expect_false("sampled_error_sd" %in% names(out))
})
# =============================================================================
# (f) print methods run without error
# =============================================================================
test_that("print method works in assurance mode", {
res <- make_syn()
w <- make_weights()
out <- decide_sample_size(res, direction = 0.70, prior_weights = w)
expect_no_error(print(out))
expect_identical(print(out), out) # returns invisibly
})
test_that("print method works in conditional mode", {
res <- make_syn()
out <- decide_sample_size(res, direction = 0.80)
expect_no_error(print(out))
expect_identical(print(out), out)
})
test_that("print NA assurance row mentions criterion not achieved", {
res <- make_syn()
w <- make_weights()
suppressMessages(
out <- decide_sample_size(res, direction = 0.999, prior_weights = w)
)
expect_output(print(out), regexp = "No sample size")
})
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.