Nothing
# test_SupervisedWriteup.R
# ::rtemis::
# 2026- EDG rtemis.org
# %% Models ----
set.seed(2026)
.bin <- iris[51:150, ]
.bin[["Species"]] <- droplevels(.bin[["Species"]])
.idx <- sample(100, 70)
.mod_bin <- train(
.bin[.idx, ],
dat_test = .bin[-.idx, ],
hyperparameters = setup_CART(maxdepth = tune_over(1L, 2L, 3L)),
verbosity = 0L
)
.mod_reg <- train(
mtcars[1:22, ],
dat_test = mtcars[23:32, ],
hyperparameters = setup_GLM(),
verbosity = 0L
)
.mod_res <- train(
iris,
hyperparameters = setup_CART(),
outer_resampling_config = setup_KFold(n_resamples = 5L),
verbosity = 0L
)
.mods <- list(bin = .mod_bin, reg = .mod_reg, res = .mod_res)
.wus <- lapply(.mods, writeup)
# Paragraph templates of a writeup.
.templates <- function(w) unlist(lapply(w@sections, function(s) s@paragraphs))
# All templates of a writeup as one string.
.text <- function(w) paste(.templates(w), collapse = " ")
# The value of a token.
.value <- function(w, key) w@values[["value"]][match(key, w@values[["key"]])]
.value_text <- function(w, key) {
w@values[["text"]][match(key, w@values[["key"]])]
}
# A section's templates, by heading.
.section <- function(w, heading) {
s <- Find(function(s) s@heading == heading, w@sections)
if (is.null(s)) NULL else paste(s@paragraphs, collapse = " ")
}
# Text that may carry digits without being a number: tokens, code spans, and
# the metric name F1.
.strip_names <- function(text) {
text <- gsub(WRITEUP_REF_TOKEN, "", text)
text <- gsub(WRITEUP_VALUE_TOKEN, "", text)
text <- gsub("`[^`]*`", "", text)
gsub("\\bF1\\b", "", text)
}
# %% Numbers come from the values table ----
test_that("templates hold no digit outside a token or a name", {
for (w in .wus) {
expect_false(
any(grepl("[0-9]", .strip_names(.templates(w)))),
info = w@algorithm
)
}
})
test_that("every token names a row, and every row is rendered", {
for (w in .wus) {
tokens <- regmatches(.text(w), gregexpr(WRITEUP_VALUE_TOKEN, .text(w)))[[
1L
]]
keys <- unique(substr(tokens, 2L, nchar(tokens) - 1L))
expect_true(all(keys %in% w@values[["key"]]))
# Rows not in a paragraph are cells of the performance table or of the
# hyperparameter tables.
table_only <- setdiff(w@values[["key"]], keys)
expect_true(all(grepl(
"^table_(training|training_sd|test|test_sd|lower|upper|hp)_",
table_only
)))
cells <- c(w@hyperparameters[["value"]], w@hyperparameters[["tried"]])
cell_tokens <- unlist(regmatches(
cells,
gregexpr(WRITEUP_VALUE_TOKEN, cells)
))
expect_true(all(
substr(cell_tokens, 2L, nchar(cell_tokens) - 1L) %in% w@values[["key"]]
))
expect_false(anyDuplicated(w@values[["key"]]) > 0L)
}
})
test_that("the performance table renders only values-table cells", {
w <- .wus[["bin"]]
rows <- writeup_table_rows(w)
cells <- as.vector(rows[-1L, -1L])
cells <- unlist(strsplit(cells[nzchar(cells)], "\u2013", fixed = TRUE))
expect_true(all(cells %in% w@values[["text"]]))
expect_identical(
rows[1L, ],
c(
"Metric",
"Training",
"Test",
paste0(.value_text(w, "confidence_percent"), "% CI")
)
)
wr <- .wus[["res"]]
expect_match(
writeup_table_caption(wr),
"mean (SD) over 5 resamples",
fixed = TRUE
)
})
test_that("values equal the quantities they name, computed independently", {
w <- .wus[["bin"]]
y <- .mod_bin@y_test
predicted <- .mod_bin@predicted_test
expect_identical(.value(w, "n_training"), 70)
expect_identical(.value(w, "n_test"), 30)
expect_identical(.value(w, "n_predictors"), 4)
# Sensitivity of the positive class (the second level) and its
# Clopper-Pearson interval.
positive <- levels(y)[[2L]]
hits <- sum(predicted == positive & y == positive)
n_positive <- sum(y == positive)
expect_equal(.value(w, "table_test_sensitivity"), hits / n_positive)
ci <- stats::binom.test(hits, n_positive)[["conf.int"]]
expect_equal(.value(w, "table_lower_sensitivity"), ci[[1L]])
expect_equal(.value(w, "table_upper_sensitivity"), ci[[2L]])
expect_identical(.value_text(w, "positive_class"), positive)
counts <- as.vector(table(.mod_bin@y_training))
expect_identical(
c(.value(w, "count_training_1"), .value(w, "count_training_2")),
as.numeric(counts)
)
expect_identical(
.value(w, "selected_maxdepth"),
as.numeric(.mod_bin@tuner@best_hyperparameters[["maxdepth"]])
)
# Candidates come from the space the tuner searched.
expect_identical(.value(w, "grid_maxdepth_3"), 3)
expect_identical(
w@values[["source"]][match("grid_maxdepth_3", w@values[["key"]])],
"tuner.hyperparameters.maxdepth[3]"
)
wr <- .wus[["res"]]
expect_identical(.value(wr, "n_cases"), 150)
expect_identical(.value(wr, "outer_resamples"), 5)
fold_ba <- vapply(
.mod_res@models,
function(m) m@metrics_test@metrics[["overall"]][["balanced_accuracy"]],
numeric(1L)
)
expect_equal(.value(wr, "table_test_balanced_accuracy"), mean(fold_ba))
expect_equal(
.value(wr, "table_test_sd_balanced_accuracy"),
stats::sd(fold_ba)
)
})
test_that("versions are those recorded when the model was trained", {
w <- .wus[["bin"]]
si <- .mod_bin@session_info
expect_identical(
.value_text(w, "version_r"),
paste(si[["R.version"]][["major"]], si[["R.version"]][["minor"]], sep = ".")
)
rpart_version <- (si[["otherPkgs"]][["rpart"]] %||%
si[["loadedOnly"]][["rpart"]])[["Version"]]
expect_identical(.value_text(w, "version_rpart"), rpart_version)
})
test_that("formatting follows the value kind", {
expect_identical(fmt_writeup_value(1234L, "count"), "1,234")
expect_identical(fmt_writeup_value(1234L, "integer"), "1234")
expect_identical(fmt_writeup_value(0.91666, "metric"), "0.917")
expect_identical(fmt_writeup_value(0.0304885, "quantity"), "0.0305")
expect_identical(fmt_writeup_value(0.1, "quantity"), "0.1")
expect_identical(fmt_writeup_value(0.0004, "p_value"), "p < 0.001")
expect_identical(fmt_writeup_value(0.0123, "p_value"), "p = 0.012")
expect_identical(fmt_writeup_value(0.95, "percent"), "95")
expect_identical(fmt_writeup_value(0.975, "percent"), "97.5")
# A setting is written in full.
expect_identical(
fmt_writeup_value(0.123456789012345678, "exact"),
"0.123456789012346"
)
expect_identical(fmt_writeup_value(1e-08, "exact"), "1e-08")
expect_identical(fmt_writeup_value(0.05, "exact"), "0.05")
})
# %% Rendering ----
test_that("rendering replaces tokens and numbers references by first citation", {
values <- data.frame(
key = "n",
value = 3,
kind = "count",
text = "3",
source = "here"
)
refs <- c(a = 1L, b = 2L)
expect_identical(
render_writeup_template("{n} cases {ref:b,a}.", values, refs),
"3 cases [1, 2]."
)
expect_error(
render_writeup_template("{m} cases.", values, refs),
"names no row",
class = "rtemis_value_error"
)
expect_error(
render_writeup_template("x {ref:c}.", values, refs),
"no reference",
class = "rtemis_value_error"
)
for (w in .wus) {
tokens <- regmatches(.text(w), gregexpr(WRITEUP_REF_TOKEN, .text(w)))[[1L]]
first <- unique(trimws(unlist(strsplit(
substr(tokens, 6L, nchar(tokens) - 1L),
","
))))
expect_identical(w@references[["key"]], first)
}
})
test_that("the rendered text states the numbers of the values table", {
text <- repr(.wus[["bin"]], output_type = "plain")
expect_match(
text,
"comprised 70 training cases and 30 test cases, with 4 predictors.",
fixed = TRUE
)
expect_false(grepl("{", text, fixed = TRUE))
expect_match(text, "Clopper-Pearson method [", fixed = TRUE)
})
# %% Content ----
test_that("a single split states its statistics, a resampled model does not", {
bin <- .text(.wus[["bin"]])
expect_match(bin, "exact McNemar test", fixed = TRUE)
expect_match(bin, "Clopper-Pearson", fixed = TRUE)
res <- .text(.wus[["res"]])
expect_match(
res,
"no confidence intervals or tests were computed",
fixed = TRUE
)
expect_false(grepl("McNemar", res, fixed = TRUE))
expect_match(res, "{outer_resamples}-fold cross-validation", fixed = TRUE)
expect_match(res, "stratified by outcome class", fixed = TRUE)
})
test_that("the statistics paragraph names intervals it could not obtain", {
rv <- review(.mod_bin)
rv@performance[["lower"]][rv@performance[["metric"]] == "auc"] <- NA_real_
ctx <- writeup_context(.mod_bin, rv)
text <- writeup_statistics_single(ctx, writeup_collector(), "{p}")
expect_false(grepl("DeLong", text, fixed = TRUE))
expect_match(
text,
"No confidence interval could be computed for AUC",
fixed = TRUE
)
})
test_that("tuning is described from the tuner", {
bin <- .section(.wus[["bin"]], "Hyperparameter tuning")
expect_match(bin, "grid search over `maxdepth`", fixed = TRUE)
expect_match(bin, "highest mean balanced accuracy", fixed = TRUE)
expect_null(.section(.wus[["res"]], "Hyperparameter tuning"))
})
test_that("a randomized search reports the combinations evaluated", {
set.seed(5)
w <- writeup(train(
iris,
hyperparameters = setup_CART(maxdepth = tune_over(1L, 2L, 3L)),
tuner_config = setup_GridSearch(
search_type = "randomized",
randomize_p = 0.5
),
verbosity = 0L
))
expect_match(
.section(w, "Hyperparameter tuning"),
"A random {grid_evaluated} of the {grid_eligible} combinations were evaluated.",
fixed = TRUE
)
expect_identical(.value(w, "grid_evaluated"), 2)
expect_identical(.value(w, "grid_eligible"), 3)
})
test_that("a hyperparameter set is described by its configurations", {
set.seed(7)
single <- writeup(train(
iris,
hyperparameters = list(
a = setup_CART(cp = 0.001, maxdepth = 2L),
b = setup_CART(cp = 0.1, maxdepth = tune_over(4L, 6L))
),
tuner_config = setup_GridSearch(
resampler_config = setup_KFold(n_resamples = 2L)
),
verbosity = 0L
))
text <- .section(single, "Hyperparameter tuning")
expect_match(
text,
"`{variant_a}` (`cp` = {variant_a_cp_1}, `maxdepth` = {variant_a_maxdepth_1})",
fixed = TRUE
)
expect_match(
text,
"`{variant_b}` (`cp` = {variant_b_cp_1}, `maxdepth` {variant_b_maxdepth_1} or {variant_b_maxdepth_2})",
fixed = TRUE
)
expect_identical(.value(single, "variant_b_cp_1"), 0.1)
resampled <- writeup(train(
iris,
hyperparameters = list(
shallow = setup_CART(maxdepth = 2L),
deep = setup_CART(maxdepth = 6L)
),
tuner_config = setup_GridSearch(
resampler_config = setup_KFold(n_resamples = 2L)
),
outer_resampling_config = setup_KFold(n_resamples = 3L),
verbosity = 0L
))
expect_match(
.text(resampled),
"`{variant_shallow}` (`maxdepth` = {variant_shallow_maxdepth_1}) and `{variant_deep}` (`maxdepth` = {variant_deep_maxdepth_1})",
fixed = TRUE
)
expect_identical(.value(resampled, "variant_deep_maxdepth_1"), 6)
})
test_that("decomposition reports the components the fit kept", {
model <- train(
iris,
hyperparameters = setup_CART(),
decomposition_config = setup_PCA(k = 3L, tol = 0.9),
verbosity = 0L
)
w <- writeup(model)
expect_identical(
.value(w, "decomposition_components"),
as.numeric(NCOL(model@decomposition@transformed))
)
# Without a test set, nothing is applied to test cases.
text <- .section(w, "Preprocessing")
expect_match(
text,
"Decomposition was fitted on the training cases.",
fixed = TRUE
)
expect_false(grepl("test cases", text, fixed = TRUE))
})
test_that("a model without a test set is described as evaluated on training cases", {
w <- writeup(train(iris, hyperparameters = setup_CART(), verbosity = 0L))
expect_match(.text(w), "evaluated on the training cases only", fixed = TRUE)
expect_match(
.text(w),
"Performance on the training cases is shown",
fixed = TRUE
)
expect_null(.section(w, "Comparison with a baseline"))
expect_false("confidence_percent" %in% w@values[["key"]])
})
test_that("leave-one-out and user-defined resampling are described", {
loocv <- writeup(train(
mtcars[1:12, ],
hyperparameters = setup_GLM(),
outer_resampling_config = setup_LOOCV(),
verbosity = 0L
))
expect_match(
.section(loocv, "Evaluation"),
"leave-one-out cross-validation",
fixed = TRUE
)
custom <- writeup(train(
iris,
hyperparameters = setup_CART(),
outer_resampling_config = setup_Custom(list(
c(1:40, 51:90, 101:140),
c(11:50, 61:100, 111:150)
)),
verbosity = 0L
))
expect_match(
.section(custom, "Evaluation"),
"{outer_resamples} user-defined resamples",
fixed = TRUE
)
})
test_that("resampled baseline counts use the resamples in which both were defined", {
text <- .section(.wus[["res"]], "Comparison with a baseline")
expect_match(
text,
"of the {baseline_accuracy_resamples_compared}",
fixed = TRUE
)
b <- .wus[["res"]]@review@baseline
expect_identical(
b[["resamples_compared"]],
rep(5L, NROW(b))
)
})
test_that("unrecorded information is listed", {
expect_true("How the test set was selected." %in% .wus[["bin"]]@not_reported)
expect_false("How the test set was selected." %in% .wus[["res"]]@not_reported)
mars <- writeup(train(mtcars, hyperparameters = setup_MARS(), verbosity = 0L))
expect_true(any(grepl(
"How MARS selects values inside each fit",
mars@not_reported
)))
})
test_that("internal selection is described for GLMNET, LightGBM, HAL and LINAD", {
ctx <- list(
tuner = .mod_bin@tuner,
space_root = "tuner.hyperparameters",
grid = data.frame(param_combo_id = 1L),
fits = list(.mod_bin)
)
w <- writeup_collector()
out <- writeup_selection(setup_GLMNET(), ctx, w)
expect_match(out[["tuning"]], "`cv.glmnet`", fixed = TRUE)
expect_match(out[["tuning"]], "within one standard error", fixed = TRUE)
expect_identical(out[["collected"]], "lambda")
out <- writeup_selection(setup_GLMNET(which_lambda_cv = "lambda.min"), ctx, w)
expect_match(
out[["tuning"]],
"minimizing the cross-validated error",
fixed = TRUE
)
expect_identical(
writeup_selection(setup_GLMNET(lambda = 0.1), ctx, w),
list()
)
out <- writeup_selection(setup_LightGBM(), ctx, w)
expect_match(
out[["tuning"]],
"early stopping on the inner validation cases",
fixed = TRUE
)
out <- writeup_selection(setup_LightGBM(boosting = "dart"), ctx, w)
expect_match(
out[["tuning"]],
"without early stopping, which DART boosting does not support",
fixed = TRUE
)
expect_match(
writeup_selection(setup_HAL(), ctx, w)[["model"]],
"`hal9001`",
fixed = TRUE
)
expect_match(
writeup_selection(setup_LINAD(), ctx, w)[["model"]],
"keeps every leaf",
fixed = TRUE
)
ctx[["grid"]] <- data.frame(param_combo_id = 1L, best_n_leaves = 3L)
expect_identical(
writeup_selection(setup_LINAD(), ctx, w)[["collected"]],
"best_n_leaves"
)
expect_null(writeup_selection(setup_CART(), ctx, w))
})
test_that("each algorithm's declared backends are the packages its train_ method checks", {
for (algorithm in names(SUPERVISED_BACKENDS)) {
packages <- setdiff(SUPERVISED_BACKENDS[[algorithm]], "stats")
if (length(packages) == 0L) {
next
}
cls <- get(paste0(algorithm, "Hyperparameters"))
body <- paste(deparse(S7::method(train_, cls)), collapse = "\n")
for (p in packages) {
expect_match(
body,
paste0("\"", p, "\""),
fixed = TRUE,
info = paste(algorithm, p)
)
}
}
})
# %% Stratification ----
test_that("stratification groups a categorical variable by its observed levels", {
y <- factor(rep(letters[1:6], each = 10L))
folds <- kfold(y, k = 5L, seed = 1L, verbosity = 0L)
for (f in folds) {
expect_identical(as.vector(table(y[-f])), rep(2L, 6L))
}
subsamples <- strat_sub(y, n_resamples = 3L, train_p = 0.5, seed = 1L)
for (s in subsamples) {
expect_identical(as.vector(table(y[s])), rep(5L, 6L))
}
# Unused levels and character values form one stratum per observed value.
sparse <- factor(
rep(c("a", "b", "c", "j"), each = 10L),
levels = letters[1:10]
)
for (f in kfold(sparse, k = 5L, seed = 1L, verbosity = 0L)) {
expect_identical(as.vector(table(droplevels(sparse[-f]))), rep(2L, 4L))
}
chr <- rep(c("x", "y", "z"), each = 10L)
for (f in kfold(chr, k = 5L, seed = 1L, verbosity = 0L)) {
expect_identical(as.vector(table(chr[-f])), rep(2L, 3L))
}
})
test_that("k-fold records the intervals a numeric outcome was cut into", {
set.seed(9)
dat <- data.frame(x = rnorm(30), y = rep(c(1, 2), 15))
w <- writeup(train(
dat,
hyperparameters = setup_GLM(),
outer_resampling_config = setup_KFold(n_resamples = 3L),
verbosity = 0L
))
expect_identical(.value(w, "outer_strat_bins"), 2)
})
# %% Output ----
test_that("write_text writes a writeup as Markdown and refuses to overwrite", {
path <- tempfile(fileext = ".md")
write_text(.wus[["bin"]], path, verbosity = 0L)
md <- readLines(path)
expect_identical(md[[1L]], "## Methods")
expect_true("## Results" %in% md)
expect_true("## References" %in% md)
expect_true(any(grepl("^\\| Metric \\| Training \\| Test \\|", md)))
expect_false(any(grepl("{", md, fixed = TRUE)))
expect_error(
write_text(.wus[["bin"]], path, verbosity = 0L),
"exists",
class = "rtemis_value_error"
)
write_text(.wus[["bin"]], path, overwrite = TRUE, verbosity = 0L)
})
# %% Contract ----
test_that("a writeup record validates against its published schema", {
testthat::skip_if_not_installed("jsonvalidate")
doc <- record_object(.wus[["bin"]])
schema_of <- function(cls) {
schema <- S7_to_JSONSchema(
cls,
id = paste0("https://schema.rtemis.org/test/", tolower(cls@name), ".json")
)
schema[["$id"]] <- NULL
schema[["properties"]][["$schema"]] <- NULL
schema
}
targets <- list(
reviewfinding = function() schema_of(ReviewFinding),
writeupsection = function() schema_of(WriteupSection),
supervisedreview = function() inline(schema_of(SupervisedReview))
)
inline <- function(node) {
if (!is.list(node)) {
return(node)
}
ref <- node[["$ref"]]
if (is.character(ref) && length(ref) == 1L && startsWith(ref, "https://")) {
for (slug in names(targets)) {
if (grepl(paste0("/", slug, "/"), ref, fixed = TRUE)) {
return(targets[[slug]]())
}
}
stop("unexpected reference: ", ref)
}
lapply(node, inline)
}
as_json <- function(d) {
jsonlite::toJSON(
d,
auto_unbox = TRUE,
null = "null",
na = "null",
digits = NA
)
}
validate <- jsonvalidate::json_validator(
as_json(inline(schema_of(SupervisedWriteup))),
engine = "ajv"
)
expect_true(validate(as_json(doc), verbose = TRUE))
# Negative cases: a value kind and an algorithm outside their vocabularies.
bad <- doc
bad[["values"]][["kind"]][[1L]] <- "number"
expect_false(validate(as_json(bad)))
bad <- doc
bad[["algorithm"]] <- "NotAnAlgorithm"
expect_false(validate(as_json(bad)))
bad <- doc
bad[["hyperparameters"]][["source"]][[1L]] <- "guessed"
expect_false(validate(as_json(bad)))
})
# %% Hyperparameter tables ----
.hp_row <- function(w, name) {
w@hyperparameters[w@hyperparameters[["name"]] == name, , drop = FALSE]
}
.render_cell <- function(w, cell) {
render_writeup_template(cell, w@values, writeup_ref_numbers(w))
}
# Sources of the tokens of a cell.
.cell_sources <- function(w, cell) {
tokens <- regmatches(cell, gregexpr(WRITEUP_VALUE_TOKEN, cell))[[1L]]
keys <- substr(tokens, 2L, nchar(tokens) - 1L)
w@values[["source"]][match(keys, w@values[["key"]])]
}
# One fit's entry for writeup_hp_cell().
.fit_value <- function(value, source = "default", applies = TRUE) {
list(
value = value,
identity = as.character(jsonlite::toJSON(value, null = "null")),
source = source,
applies = applies
)
}
test_that("table cells hold no number outside a token", {
for (w in .wus) {
cells <- c(w@hyperparameters[["value"]], w@hyperparameters[["tried"]])
stripped <- gsub(WRITEUP_VALUE_TOKEN, "", cells)
expect_false(any(grepl("[0-9]", stripped)), info = w@algorithm)
}
})
test_that("the main table lists primary, tuned and specified hyperparameters", {
w <- .wus[["bin"]]
maxdepth <- .hp_row(w, "maxdepth")
expect_true(maxdepth[["main"]])
expect_identical(maxdepth[["source"]], "tuned")
expect_identical(
.render_cell(w, maxdepth[["value"]]),
as.character(.mod_bin@hyperparameters[["maxdepth"]])
)
expect_identical(.render_cell(w, maxdepth[["tried"]]), "1; 2; 3")
expect_identical(
unique(.cell_sources(w, maxdepth[["tried"]])),
"derived: distinct values of tuner.tuning_results.param_grid.maxdepth"
)
expect_identical(
.cell_sources(w, maxdepth[["value"]]),
"hyperparameters.maxdepth"
)
# Primary for CART, at its default.
cp <- .hp_row(w, "cp")
expect_true(cp[["main"]])
expect_identical(cp[["source"]], "default")
# Neither primary, tuned nor specified.
expect_false(.hp_row(w, "maxcompete")[["main"]])
# GLM declares no primary hyperparameter and was given none.
expect_false(any(.wus[["reg"]]@hyperparameters[["main"]]))
model <- .section(.wus[["bin"]], "Model")
expect_match(model, "Table {table_hyperparameters} lists", fixed = TRUE)
expect_false(grepl("default", model))
})
test_that("a value the backend chose is the fitted backend's", {
skip_if_not_installed("ranger")
set.seed(3)
mod <- train(
iris,
hyperparameters = setup_Ranger(num_trees = 20L),
verbosity = 0L
)
w <- writeup(mod)
mtry <- .hp_row(w, "mtry")
expect_identical(mtry[["source"]], "resolved")
expect_identical(.cell_sources(w, mtry[["value"]]), "hyperparameters.mtry")
# A value the user supplied.
expect_identical(.hp_row(w, "num_trees")[["source"]], "specified")
expect_identical(
.render_cell(w, mtry[["value"]]),
as.character(mod@model[["mtry"]])
)
})
test_that("hyperparameters that do not apply are omitted and named", {
skip_if_not_installed("lightgbm")
mod <- train(
mtcars,
hyperparameters = setup_LightGBM(force_nrounds = 10L),
verbosity = 0L
)
w <- writeup(mod)
drop_rate <- .hp_row(w, "drop_rate")
expect_false(drop_rate[["applies"]])
expect_false("drop_rate" %in% writeup_hp_table_rows(w, main = FALSE)[, 1L])
expect_match(
writeup_hp_table_caption(w, main = FALSE),
"Not applicable under this configuration:.*drop_rate"
)
})
test_that("an unset hyperparameter reads unset, with its declared meaning", {
w <- .wus[["bin"]]
prune_cp <- .hp_row(w, "prune_cp")
expect_identical(prune_cp[["source"]], "unset")
rows <- writeup_hp_table_rows(w, main = FALSE)
expect_identical(unname(rows[rows[, 1L] == "prune_cp", 2L]), "unset")
expect_true(
paste0("prune_cp: ", unset_meaning(CARTHyperparameters, "prune_cp")) %in%
attr(rows, "notes")
)
})
test_that("a value that differs between resamples is listed with its count", {
render <- function(fit_values, ...) {
w <- writeup_collector()
cell <- writeup_hp_cell(
w,
"table_hp_x",
fit_values,
"models[].hyperparameters.x",
...
)
render_writeup_template(cell, writeup_values_table(w), integer())
}
expect_identical(
render(list(.fit_value(2L), .fit_value(3L), .fit_value(2L))),
"2 (2); 3 (1)"
)
# One value chosen two ways, and a resample in which it had no effect.
expect_identical(
render(list(
.fit_value(20L, "default"),
.fit_value(1L, "specified"),
.fit_value(20L, "default"),
.fit_value(NULL, "unset", applies = FALSE)
)),
"20 (2, default); 1 (1, specified); not applicable (1)"
)
# Configurations that share a summary are told apart by their settings.
kfold <- function(n) {
v <- setup_KFold(n_resamples = n)
list(
value = v,
identity = as.character(jsonlite::toJSON(S7_to_list(v), null = "null")),
source = "specified",
applies = TRUE
)
}
configs <- render(list(kfold(2L), kfold(3L)), object_valued = TRUE)
parts <- strsplit(configs, "; ", fixed = TRUE)[[1L]]
expect_length(parts, 2L)
expect_false(identical(parts[[1L]], parts[[2L]]))
expect_match(parts[[1L]], "\\(1\\)$")
})
test_that("values tried are those the evaluated configurations gave", {
set.seed(5)
mod <- train(
iris,
hyperparameters = setup_CART(maxdepth = tune_over(1L, 2L, 3L)),
tuner_config = setup_GridSearch(
search_type = "randomized",
randomize_p = 0.5
),
verbosity = 0L
)
w <- writeup(mod)
tried <- strsplit(
.render_cell(w, .hp_row(w, "maxdepth")[["tried"]]),
"; ",
fixed = TRUE
)[[1L]]
expect_setequal(
tried,
as.character(unique(mod@tuner@tuning_results[["param_grid"]][["maxdepth"]]))
)
# A hyperparameter set: the value each evaluated member gives, unset
# included.
set_mod <- train(
iris,
hyperparameters = list(
a = setup_CART(prune_cp = NULL),
b = setup_CART(prune_cp = 0.1)
),
verbosity = 0L
)
ws <- writeup(set_mod)
prune_cp <- .hp_row(ws, "prune_cp")
expect_setequal(
strsplit(.render_cell(ws, prune_cp[["tried"]]), "; ", fixed = TRUE)[[1L]],
c("unset", "0.1")
)
expect_true(
"config.hyperparameters.variants.b.prune_cp" %in%
.cell_sources(ws, prune_cp[["tried"]])
)
# The value is that of the member the fit came from.
winner <- set_mod@hyperparameters@variant
expect_identical(
prune_cp[["source"]],
if (identical(winner, "a")) "unset" else "specified"
)
})
test_that("a resampled model's cell counts every resample's value", {
set.seed(6)
mod <- train(
iris,
hyperparameters = setup_CART(maxdepth = tune_over(1L, 2L, 3L)),
tuner_config = setup_GridSearch(
resampler_config = setup_KFold(n_resamples = 2L)
),
outer_resampling_config = setup_KFold(n_resamples = 3L),
verbosity = 0L
)
w <- writeup(mod)
selected <- vapply(
mod@models,
function(m) m@hyperparameters[["maxdepth"]],
integer(1L)
)
counts <- table(selected)
expected <- if (length(counts) == 1L) {
names(counts)
} else {
paste0(names(counts), " (", as.integer(counts), ")")
}
rendered <- .render_cell(w, .hp_row(w, "maxdepth")[["value"]])
expect_setequal(strsplit(rendered, "; ", fixed = TRUE)[[1L]], expected)
})
# Hyperparameter rows of a resampled model whose first fit is altered, and a
# writeup holding them, for the aggregation and rendering cases a single
# search does not produce.
.altered_rows <- function(alter) {
x <- .mod_res
ctx <- writeup_context(x, review(x))
ctx[["fits"]][[1L]]@hyperparameters <- alter(
ctx[["fits"]][[1L]]@hyperparameters
)
w <- writeup_collector()
rows <- writeup_hyperparameters(ctx, w)
wu <- .wus[["res"]]
wu@values <- writeup_values_table(w)
wu@hyperparameters <- rows
list(rows = rows, writeup = wu)
}
test_that("a value chosen differently in one resample makes the row varied", {
out <- .altered_rows(function(hp) {
hp@cp <- 0.5
hp
})
cp <- out[["rows"]][out[["rows"]][["name"]] == "cp", ]
expect_identical(cp[["source"]], "varied")
expect_setequal(
strsplit(.render_cell(out[["writeup"]], cp[["value"]]), "; ")[[1L]],
c("0.5 (1, resolved during fitting)", "0.01 (4, default)")
)
# An unset value beside a set one: each reads as itself, and the meaning is
# listed under the table.
out <- .altered_rows(function(hp) {
hp@prune_cp <- 0.1
hp
})
rows <- writeup_hp_table_rows(out[["writeup"]], main = FALSE)
cell <- unname(rows[rows[, 1L] == "prune_cp", 2L])
expect_setequal(
strsplit(cell, "; ")[[1L]],
c("0.1 (1, resolved during fitting)", "unset (4, unset)")
)
expect_true(
paste0("prune_cp: ", unset_meaning(CARTHyperparameters, "prune_cp")) %in%
attr(rows, "notes")
)
})
test_that("an unset alternative tuning evaluated has its meaning listed", {
set.seed(5)
mod <- train(
iris,
hyperparameters = list(
a = setup_CART(prune_cp = 0.01),
b = setup_CART(prune_cp = NULL)
),
tuner_config = setup_GridSearch(resampler_config = setup_KFold(2L)),
execution_config = setup_SerialExecution(seed = 5L),
verbosity = 0L
)
# The selected member sets prune_cp, so only the tried cell holds unset.
expect_identical(mod@hyperparameters@variant, "a")
w <- writeup(mod)
prune_cp <- .hp_row(w, "prune_cp")
expect_match(.render_cell(w, prune_cp[["tried"]]), "unset", fixed = TRUE)
expect_true(
paste0("prune_cp: ", unset_meaning(CARTHyperparameters, "prune_cp")) %in%
attr(writeup_hp_table_rows(w, main = FALSE), "notes")
)
})
test_that("one value chosen two ways is listed once per way", {
w <- writeup_collector()
cell <- writeup_hp_cell(
w,
"table_hp_x",
list(.fit_value(2L, "default"), .fit_value(2L, "specified")),
"models[].hyperparameters.x"
)
expect_identical(
render_writeup_template(cell, writeup_values_table(w), integer()),
"2 (1, default); 2 (1, specified)"
)
})
test_that("a set's pairing uses the member the fit came from", {
set.seed(5)
mod <- train(
iris,
hyperparameters = list(a = setup_CART(cp = 1), b = setup_CART(cp = 0.01)),
tuner_config = setup_GridSearch(resampler_config = setup_KFold(2L)),
verbosity = 0L
)
# cp = 1 grows no split, so b wins.
expect_identical(mod@hyperparameters@variant, "b")
w <- writeup(mod)
# 0.01 is CART's default; compared with member a it would read resolved.
expect_identical(.hp_row(w, "cp")[["source"]], "default")
# Without a config, the set is read from the tuner.
bare <- mod
bare@config <- NULL
wb <- writeup(bare)
expect_true(all(startsWith(
.cell_sources(wb, .hp_row(wb, "cp")[["tried"]]),
"tuner.searched_set.variants."
)))
})
test_that("a set of config-valued hyperparameters is written up", {
sl <- function(n) {
setup_SuperLearner(
base_learners = list(cart = setup_CART(), glm = setup_GLM()),
meta_learner = setup_NNLS(),
inner_resampling_config = setup_KFold(n_resamples = n)
)
}
set.seed(8)
# Few predictors, so the GLM is full rank on the inner folds' cases.
mod <- train(
mtcars[, c("wt", "hp", "mpg")],
hyperparameters = list(two = sl(2L), three = sl(3L)),
tuner_config = setup_GridSearch(resampler_config = setup_KFold(2L)),
verbosity = 0L
)
w <- writeup(mod)
tried <- strsplit(
.render_cell(w, .hp_row(w, "inner_resampling_config")[["tried"]]),
"; ",
fixed = TRUE
)[[1L]]
# Two configurations that share the summary KFold, told apart by settings.
expect_length(tried, 2L)
expect_false(identical(tried[[1L]], tried[[2L]]))
# The same learners in both members: no values tried.
expect_identical(.hp_row(w, "base_learners")[["tried"]], "")
# Grouping in the value cell is by configuration, not by summary: a second
# fit with the other inner resampler.
ctx <- writeup_context(mod, review(mod))
other <- ctx[["fits"]][[1L]]
n <- other@hyperparameters@inner_resampling_config@n_resamples
other@hyperparameters@inner_resampling_config <- setup_KFold(
n_resamples = if (n == 2L) 3L else 2L
)
ctx[["fits"]] <- c(ctx[["fits"]], list(other))
cw <- writeup_collector()
rows <- writeup_hyperparameters(ctx, cw)
cell <- render_writeup_template(
rows[["value"]][rows[["name"]] == "inner_resampling_config"],
writeup_values_table(cw),
integer()
)
parts <- strsplit(cell, "; ", fixed = TRUE)[[1L]]
expect_length(parts, 2L)
expect_false(identical(parts[[1L]], parts[[2L]]))
})
test_that("a tuned unset hidden_units makes the width settings apply", {
skip_if_not_installed("torch")
skip_if_not(torch::torch_is_installed())
mod <- train(
iris,
hyperparameters = setup_MLP(
hidden_units = tune_over(NULL, c(8L, 4L)),
max_epochs = 1L,
batch_size = 32L
),
tuner_config = setup_GridSearch(resampler_config = setup_KFold(2L)),
execution_config = setup_SerialExecution(seed = 5L),
verbosity = 0L
)
w <- writeup(mod)
widths_generated <- is.null(mod@tuner@best_hyperparameters[["hidden_units"]])
for (nm in c("shape", "shape_layers", "shape_max_units")) {
expect_identical(.hp_row(w, nm)[["applies"]], widths_generated, info = nm)
}
})
test_that("applicability follows the backend's rules as well as the gates", {
# MLP widths: generated only when hidden_units is unset.
given <- setup_MLP(hidden_units = c(8L, 4L))
expect_false(hyperparameter_applies(given, "shape", given@hyperparameters))
generated <- setup_MLP()
expect_true(
hyperparameter_applies(generated, "shape", generated@hyperparameters)
)
skip_if_not_installed("lightgbm")
regression <- train(
mtcars,
hyperparameters = setup_LightGBM(force_nrounds = 5L),
verbosity = 0L
)
expect_false(.hp_row(writeup(regression), "sigmoid")[["applies"]])
expect_true(.hp_row(writeup(regression), "reg_sqrt")[["applies"]])
})
test_that("include_hyperparameters replaces the primary list", {
w <- writeup(.mod_bin, include_hyperparameters = "minsplit")
main <- w@hyperparameters[["name"]][w@hyperparameters[["main"]]]
# The tuned hyperparameter stays; cp, primary by declaration, leaves.
expect_setequal(main, c("minsplit", "maxdepth"))
none <- writeup(.mod_bin, include_hyperparameters = character())
expect_identical(
none@hyperparameters[["name"]][none@hyperparameters[["main"]]],
"maxdepth"
)
expect_false(none@primary_listed)
expect_null(none@include_hyperparameters)
expect_match(
writeup_hp_table_caption(none),
"^Table 1\\. Every hyperparameter that was tuned or specified"
)
expect_match(
writeup_hp_table_caption(.wus[["bin"]]),
"^Table 1\\. The primary hyperparameters of the algorithm"
)
expect_error(
writeup(.mod_bin, include_hyperparameters = "not_a_hyperparameter"),
"names no hyperparameter",
class = "rtemis_value_error"
)
})
test_that("the Markdown carries both hyperparameter tables", {
path <- tempfile(fileext = ".md")
write_text(.wus[["bin"]], path, verbosity = 0L)
md <- readLines(path)
expect_true(any(grepl("^Table 1\\. The primary hyperparameters", md)))
expect_true(any(grepl("^Table 2\\. Performance", md)))
expect_true("## Supplementary material" %in% md)
expect_true(any(grepl("^Table S1\\.", md)))
expect_true(any(grepl("^- prune_cp: Unset prunes nothing", md)))
# The unrecorded items, as print shows them.
expect_true("## Not recorded by the model" %in% md)
expect_true(all(
paste0("- ", .wus[["bin"]]@not_reported) %in% md
))
})
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.