diff --git a/NEWS.md b/NEWS.md index 20c07b5b..4bb40ad4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,15 @@ # nestedtune 0.0.0.9000 +* Three arguments from tidymodels are new. `nested_workflow_map()` takes + `verbose`, which writes a message as each workflow starts and finishes. + It sits before `...`, where `workflowsets::workflow_map()` has it. So an + unnamed third argument is now read as `verbose`. A value that is not + `TRUE` or `FALSE` there gives an error of class `nestedtune_bad_verbose`, + where it was `nestedtune_bad_dots`. + `collect_inner_metrics()` takes `type = "wide"`, which gives one column + per metric, as `tune::collect_metrics()` does. `autoplot()` on a + workflow-set run takes `id`, which draws the plot of one workflow. + * `nested_tune_bayes()` and `nested_tune_sim_anneal()` now list `eval_time` before `objective` and `initial`, in the order `tune::tune_bayes()` and `finetune::tune_sim_anneal()` use. These arguments come after `...`, so diff --git a/R/checks.R b/R/checks.R index e09c556c..9d89c23b 100644 --- a/R/checks.R +++ b/R/checks.R @@ -2951,6 +2951,37 @@ check_map_fn <- function(fn, call = rlang::caller_env()) { ) } +# The map's `verbose` (M149): one `TRUE` or `FALSE`, as workflowsets' +# `check_bool()` reads it, refused before any workflow runs. It sits in +# `workflow_map()`'s place, before `...`, so a third argument passed by +# position lands here. When the caller did not name `verbose`, the hint +# says the rest are matched by name, which the dots check said before +# `verbose` took the place. +check_map_verbose <- function( + verbose, + named = TRUE, + call = rlang::caller_env() +) { + if (rlang::is_bool(verbose)) { + return(invisible(verbose)) + } + hint <- if (!named) { + c( + i = "Every argument of {.arg fn} must be named, such as \\ + {.code resamples = }." + ) + } + cli::cli_abort( + c( + "{.arg verbose} must be {.code TRUE} or {.code FALSE}.", + x = "Got {.obj_type_friendly {verbose}}.", + hint + ), + class = "nestedtune_bad_verbose", + call = call + ) +} + # What an orchestrator accepts beyond the workflow: its formals other than # `object` (so `resamples` and everything behind the dots), and the `control` # every one of the six takes through `...` (D-042). Read off the function @@ -2967,7 +2998,7 @@ orchestrator_args <- function(fn) { # The map's `...` (M71): every name must be one the orchestrator `fn` names # accepts, so a typo is refused rather than narrowed away for every # workflow; `object` is the set's to bind, never the caller's; everything -# after `fn` is matched by name; and the design is the one argument every +# after `fn` and `verbose` is matched by name; and the design is the one argument every # route needs, so its absence is refused here rather than as a missing # argument two frames down. `dots` is the list `capture_dots()` returns. check_map_dots <- function(dots, fn, call = rlang::caller_env()) { @@ -2978,7 +3009,7 @@ check_map_dots <- function(dots, fn, call = rlang::caller_env()) { c( "Every argument in {.arg ...} must be named.", x = "Got {n_unnamed} unnamed argument{?s}; everything after \\ - {.arg fn} is matched by name." + {.arg fn} and {.arg verbose} is matched by name." ), class = "nestedtune_bad_dots", call = call diff --git a/R/nested-results-collect.R b/R/nested-results-collect.R index 813ee0c6..54927911 100644 --- a/R/nested-results-collect.R +++ b/R/nested-results-collect.R @@ -34,6 +34,12 @@ #' does not identify a candidate across folds. #' #' @inheritParams collect_metrics.nested_results +#' @param type For `collect_inner_metrics()`, `"long"` (the default), one row +#' per fold, candidate and metric, or `"wide"`, one column per metric that +#' holds its `mean`. The wide shape drops `.estimator`, `n` and `std_err`, +#' and a metric that a fold did not score is `NA` in its rows. It +#' gives an error for a metric named like another column, such as a tuning +#' parameter, and for a metric scored with two estimators. #' #' @return A tibble with the fold labels first (`id`, and `id2` on a #' repeated design), then the stacked columns. A fold that lacks a column @@ -97,16 +103,29 @@ collect_inner_metrics.default <- function(x, ...) { #' @rdname collect_selections #' @export -collect_inner_metrics.nested_results <- function(x, ...) { +collect_inner_metrics.nested_results <- function( + x, + ..., + type = c("long", "wide") +) { rlang::check_dots_empty() + type <- check_metrics_type(type) check_any_completed(x, action = "collect") warn_partial_summary(x, noun = "table") - stack_fold_column( + out <- stack_fold_column( x, ".inner_metrics", completed_only = TRUE, call = rlang::current_env() ) + # The wide shape is collect_metrics()'s (M149): the inner table holds + # tune's summarized columns, so the pivot keeps the fold labels, the + # parameters, `.config` and any `.iter` or `.eval_time` as keys, which on + # one fold's rows is the key set tune's own pivot uses (GP1). + if (identical(type, "wide")) { + out <- pivot_metrics_wide(out, call = rlang::current_env()) + } + out } #' @rdname collect_selections diff --git a/R/nested-results-plot.R b/R/nested-results-plot.R index 011b83d6..7d1fd277 100644 --- a/R/nested-results-plot.R +++ b/R/nested-results-plot.R @@ -576,9 +576,24 @@ autoplot.nested_results_set <- function( type = c("parameters", "performance"), metric = NULL, eval_time = NULL, - ... + ..., + id = NULL ) { rlang::check_dots_empty() + # One workflow's plot (M149), as workflowsets' `id` draws one result: the + # single-run method on that row, with every argument it takes. Its + # conditions name the workflow and the user's call, as the set's readers' + # do (`for_workflow()`). + if (!is.null(id)) { + call <- rlang::current_env() + i <- match_set_id(object, id, call = call) + result <- object$result[[i]] + return(for_workflow( + object$wflow_id[[i]], + call, + autoplot(result, type = type, metric = metric, eval_time = eval_time) + )) + } type <- check_plot_type(type) check_plot_filter(type, metric, eval_time) call <- rlang::current_env() diff --git a/R/nested-results-set.R b/R/nested-results-set.R index 05a17850..9a104ca7 100644 --- a/R/nested-results-set.R +++ b/R/nested-results-set.R @@ -27,9 +27,12 @@ #' @param ... Not used, and must be empty. #' @param summarize As on [collect_metrics.nested_results()], and for #' `collect_predictions()` as on [collect_predictions.nested_results()]. -#' @param type For `collect_metrics()`, `"long"` (the default) or `"wide"`. -#' The wide shape is keyed on `wflow_id` too, and a metric that a workflow -#' did not score is `NA` in its rows. +#' @param type For `collect_metrics()` and `collect_inner_metrics()`, +#' `"long"` (the default) or `"wide"`. The wide shape is keyed on +#' `wflow_id` too. A column that a workflow lacks is `NA` in its rows, +#' such as a metric it did not score or a parameter it does not tune. +#' A workflow with no inner candidates, such as a fixed one, has no rows +#' in `collect_inner_metrics()`. #' #' @return A tibble with `wflow_id` first, then the columns that the method #' for one workflow returns, bound in the set's order. A column that one @@ -83,9 +86,19 @@ collect_selections.nested_results_set <- function(x, ...) { #' @rdname collect_metrics.nested_results_set #' @export -collect_inner_metrics.nested_results_set <- function(x, ...) { +collect_inner_metrics.nested_results_set <- function( + x, + ..., + type = c("long", "wide") +) { rlang::check_dots_empty() - stack_set(x, collect_inner_metrics, call = rlang::current_env()) + type <- check_metrics_type(type) + # Stacked long and pivoted once, as collect_metrics() does on a set. + out <- stack_set(x, collect_inner_metrics, call = rlang::current_env()) + if (identical(type, "wide")) { + out <- pivot_metrics_wide(out, call = rlang::current_env()) + } + out } #' @rdname collect_metrics.nested_results_set @@ -269,6 +282,10 @@ stack_set <- function( #' `"performance"`. #' @param metric,eval_time For `type = "performance"`, the metrics and the #' evaluation times to draw, as on [autoplot.nested_results()]. +#' @param id For `autoplot()`, `NULL` (the default) to draw every workflow, +#' or one `wflow_id` to draw that workflow alone. One `wflow_id` gives the +#' plot that [autoplot.nested_results()] draws for that workflow's result. +#' That plot states a failed fold in its subtitle and gives no warning. #' @inheritParams collect_metrics.nested_results_set #' #' @return `summary()` returns a `summary.nested_results_set`, a list of diff --git a/R/nested-workflow-map.R b/R/nested-workflow-map.R index c6d90129..7f44a22f 100644 --- a/R/nested-workflow-map.R +++ b/R/nested-workflow-map.R @@ -56,6 +56,10 @@ #' `"nested_tune_race_anova"`, `"nested_tune_race_win_loss"` and #' `"nested_tune_sim_anneal"`. With `"nested_fit_resamples"`, no workflow #' can have a parameter to tune. +#' @param verbose Whether `nested_workflow_map()` writes a message as each +#' workflow starts and as it finishes. The default is `FALSE`. The finish +#' message gives the time the workflow took. A workflow that gives an +#' error has no finish message. #' @param ... The arguments of `fn`, each one named. `resamples`, the nested #' design, is required, and a `control` goes here as well. A name that `fn` #' does not take is an error. A `metrics` set reaches every workflow, and @@ -83,10 +87,16 @@ #' [collect_metrics.nested_results_set()], [nested_final_fit()], #' [workflowsets::workflow_map()] #' @export -nested_workflow_map <- function(object, fn = "nested_tune_grid", ...) { +nested_workflow_map <- function( + object, + fn = "nested_tune_grid", + verbose = FALSE, + ... +) { dots <- capture_dots(...) check_workflow_set(object) check_map_fn(fn) + check_map_verbose(verbose, named = "verbose" %in% rlang::names2(sys.call())) check_map_dots(dots, fn) call <- rlang::current_env() @@ -141,11 +151,13 @@ nested_workflow_map <- function(object, fn = "nested_tune_grid", ...) { for (i in seq_along(ids)) { assign(".Random.seed", entry_seed, envir = globalenv()) args <- map_args(dots, object$option[[i]], routes[[i]], fn) + started <- log_map_start(verbose, i, ids, routes[[i]]) results[[i]] <- for_workflow( ids[[i]], call, run_orchestrator(routes[[i]], workflows[[i]], args) ) + log_map_end(verbose, i, ids, routes[[i]], started) } new_nested_results_set(ids, workflows, results, fn) } @@ -171,6 +183,40 @@ map_args <- function(dots, option, route, fn) { args[intersect(names(args), orchestrator_args(route))] } +# The `verbose` log (M149), in workflowsets' `log_progress()` form: a line as +# each workflow starts and one, with the time it took, as it finishes. A +# workflow that errors gets no end line, because the error stops the map +# and `for_workflow()` already names the workflow in it. The clock is read +# whether or not the log is on, and reading it draws no random number. +log_map_start <- function(verbose, i, ids, route) { + if (verbose) { + msg <- map_log_text(i, ids, route) + cli::cli_inform(c(i = "{msg}")) + } + proc.time()[["elapsed"]] +} + +log_map_end <- function(verbose, i, ids, route, started) { + if (!verbose) { + return(invisible(NULL)) + } + took <- proc.time()[["elapsed"]] - started + time <- paste0(format(round(took, 1L), nsmall = 1L), "s") + msg <- paste0(map_log_text(i, ids, route), " (", time, ")") + cli::cli_inform(c(v = "{msg}")) +} + +map_log_text <- function(i, ids, route) { + event <- if (identical(route, "nested_fit_resamples")) { + "resampling" + } else { + "tuning" + } + # Pasted, not interpolated, and handed to cli as a value, so a brace in + # an id is not read as cli markup (the M83 lesson). + paste0(i, " of ", length(ids), " ", event, ": ", ids[[i]]) +} + # One orchestrator call, built as a call in an environment that binds the # workflow and every argument to a name (the M05 lesson: a value inlined # into a call is deparsed into every condition raised beneath it, and a diff --git a/cairn/ROADMAP.md b/cairn/ROADMAP.md index b378d3f4..f63b33e8 100644 --- a/cairn/ROADMAP.md +++ b/cairn/ROADMAP.md @@ -4,7 +4,7 @@ _Last hygiene check: 2026-10-18 M148 done. Archived M148, pruned M145's row, add ## Milestones | ID | Title | Status | Depends on | Priority | File/Archive | |---|---|---|---|---|---| -| M149 | Three arguments from tidymodels: verbose on the map, a wide inner table, one workflow's plot | planned | — | normal | milestones/M149-argument-gaps.md | +| M149 | Three arguments from tidymodels: verbose on the map, a wide inner table, one workflow's plot | review | — | normal | milestones/M149-argument-gaps.md | | M148 | Each export and result column is set beside its tidymodels counterpart | done | — | normal | milestones/archive/M148-export-review.md | | M147 | CI runs the tests CRAN runs, with the Suggests and without them | done | — | normal | milestones/archive/M147-cran-subset-ci.md | | M146 | DESCRIPTION's Title and Description say what the package does, in CRAN's form | done | — | normal | milestones/archive/M146-description-text.md | diff --git a/cairn/milestones/M149-argument-gaps.md b/cairn/milestones/M149-argument-gaps.md index 46830264..ea608717 100644 --- a/cairn/milestones/M149-argument-gaps.md +++ b/cairn/milestones/M149-argument-gaps.md @@ -1,13 +1,13 @@ # M149: Three arguments from tidymodels: verbose on the map, a wide inner table, one workflow's plot -- **Status:** planned +- **Status:** review - **Priority:** normal - **Depends on:** — - **Driving RR:** — - **Principles touched:** GP1 - **Resolves:** — - **Surface tier:** user-facing — three new arguments on exported functions -- **Branch/PR:** — +- **Branch/PR:** m149-argument-gaps ## Goal @@ -21,11 +21,11 @@ The three argument gaps that M148's review proposed are closed in the tidymodels ## Acceptance criteria -- [ ] AC1: `nested_workflow_map()` takes `verbose = FALSE` between `fn` and `...`, where `workflowsets::workflow_map()` puts it. With `verbose = TRUE`, a run writes one message as each workflow starts, and one as each workflow finishes without error. Each message names the workflow's position in the set, its route, and its `wflow_id`. The route reads `tuning` or `resampling`. The end message also gives the time the workflow took. A test runs a set of one tuned and one fixed workflow and snapshots the four messages with the time masked. The same test asserts that the run with `verbose = FALSE` signals no message, and that the two runs return identical results. A `verbose` that is `NA`, of length 2, or not logical is refused with class `nestedtune_bad_verbose` before any workflow runs, and a test asserts each of the three. -- [ ] AC2: `collect_inner_metrics()` on a `nested_results` and on a `nested_results_set` takes `type = c("long", "wide")` after `...`, as tune's `collect_metrics()` does. `"long"` returns the table that the call without `type` returns. `"wide"` has one column per metric that holds `mean`. It drops `.metric`, `.estimator`, `mean`, `n`, `std_err` and `.weight`, and keeps every other column as a key. A test rebuilds fold 1's inner `tune::tune_grid()` run and asserts that fold 1's wide rows, less the fold-label columns, equal `tune::collect_metrics(type = "wide")` on that run. A second test asserts that `.iter` stays a key column on a `nested_tune_bayes()` run. A third asserts that the set's wide table carries `wflow_id`. A metric named like a key column is refused with class `nestedtune_wide_collision`, and a `type` outside the two values with class `nestedtune_bad_type`. A test asserts each class on each method. -- [ ] AC3: `autoplot()` on a `nested_results_set` takes `id = NULL` after `...`. With a `wflow_id`, it returns the plot that `autoplot()` on that workflow's `nested_results` returns for the same `type`, `metric` and `eval_time`. A test on a tuned workflow asserts for `type = "parameters"` and for `type = "performance"` that the two plots' `ggplot2::ggplot_build()` data are identical. With `id = NULL`, the three existing set-plot snapshots pass. An `id` other than `NULL` that is not one string naming a row of the set is refused with class `nestedtune_unknown_id`, and a test asserts it. -- [ ] AC4: `cairn/references/export-surface-review.md` marks its three `proposed` rows `added`, and its verdict definitions say what `added` means. Every argument difference that `Rscript benchmarks/export-surface.R` prints at the milestone's head for the three changed items has a row with a verdict. The page also gives a verdict, `kept` or `proposed`, with a reason, for each argument default that differs from its counterpart's. Those defaults are the ones found by comparing the deparsed default of every argument both sides share, over every pair in the script's `pairs` table. No default changes in this milestone. -- [ ] AC5: The `verbose`, `type` and `id` text on each changed help page names the function or method it applies to. `NEWS.md` gains one entry that names the three arguments. `devtools::test()` passes, and `devtools::check()` reports 0 errors, 0 warnings and 0 notes. +- [x] AC1: `nested_workflow_map()` takes `verbose = FALSE` between `fn` and `...`, where `workflowsets::workflow_map()` puts it. With `verbose = TRUE`, a run writes one message as each workflow starts, and one as each workflow finishes without error. Each message names the workflow's position in the set, its route, and its `wflow_id`. The route reads `tuning` or `resampling`. The end message also gives the time the workflow took. A test runs a set of one tuned and one fixed workflow and snapshots the four messages with the time masked. The same test asserts that the run with `verbose = FALSE` signals no message, and that the two runs return identical results. A `verbose` that is `NA`, of length 2, or not logical is refused with class `nestedtune_bad_verbose` before any workflow runs, and a test asserts each of the three. +- [x] AC2: `collect_inner_metrics()` on a `nested_results` and on a `nested_results_set` takes `type = c("long", "wide")` after `...`, as tune's `collect_metrics()` does. `"long"` returns the table that the call without `type` returns. `"wide"` has one column per metric that holds `mean`. It drops `.metric`, `.estimator`, `mean`, `n`, `std_err` and `.weight`, and keeps every other column as a key. A test rebuilds fold 1's inner `tune::tune_grid()` run and asserts that fold 1's wide rows, less the fold-label columns, equal `tune::collect_metrics(type = "wide")` on that run. A second test asserts that `.iter` stays a key column on a `nested_tune_bayes()` run. A third asserts that the set's wide table carries `wflow_id`. A metric named like a key column is refused with class `nestedtune_wide_collision`, and a `type` outside the two values with class `nestedtune_bad_type`. A test asserts each class on each method. +- [x] AC3: `autoplot()` on a `nested_results_set` takes `id = NULL` after `...`. With a `wflow_id`, it returns the plot that `autoplot()` on that workflow's `nested_results` returns for the same `type`, `metric` and `eval_time`. A test on a tuned workflow asserts for `type = "parameters"` and for `type = "performance"` that the two plots' `ggplot2::ggplot_build()` data are identical. With `id = NULL`, the three existing set-plot snapshots pass. An `id` other than `NULL` that is not one string naming a row of the set is refused with class `nestedtune_unknown_id`, and a test asserts it. +- [x] AC4: `cairn/references/export-surface-review.md` marks its three `proposed` rows `added`, and its verdict definitions say what `added` means. Every argument difference that `Rscript benchmarks/export-surface.R` prints at the milestone's head for the three changed items has a row with a verdict. The page also gives a verdict, `kept` or `proposed`, with a reason, for each argument default that differs from its counterpart's. Those defaults are the ones found by comparing the deparsed default of every argument both sides share, over every pair in the script's `pairs` table. No default changes in this milestone. +- [x] AC5: The `verbose`, `type` and `id` text on each changed help page names the function or method it applies to. `NEWS.md` gains one entry that names the three arguments. `devtools::test()` passes, and `devtools::check()` reports 0 errors, 0 warnings and 0 notes. ## Coverage @@ -37,11 +37,11 @@ The three argument gaps that M148's review proposed are closed in the tidymodels ## Tasks -- [ ] T1: `verbose` in `R/nested-workflow-map.R:86`, before `...`. Check it before the per-workflow checks, with class `nestedtune_bad_verbose`. Print with `cli::cli_inform()` at each start and each clean finish, inside the loop at the `run_orchestrator()` call. Tests go in `test-nested-workflow-map-checks.R` and `test-nested-workflow-map-readers.R`, under `skip_if_no_wset_fixture()` (the M101 lesson). Mask the time in the snapshot with `transform`. Update `@param verbose` and the `@details` order sentence. -- [ ] T2: `type` on `collect_inner_metrics.nested_results()` (`R/nested-results-collect.R:100`) and the set method (`R/nested-results-set.R:86`). Read it with `check_metrics_type()` and pivot with `pivot_metrics_wide()` (`R/nested-results.R:836`); the set pivots once after `stack_set()`, as `collect_metrics()` does. Build the fold 1 oracle on `analysis_framed_inner()`'s output (the M05 lesson). Plant the key collision with a parameter id named like a metric. Write `@param type` text that names `collect_inner_metrics()` on both pages. -- [ ] T3: `id` on `autoplot.nested_results_set()` (`R/nested-results-plot.R:574`). A non-`NULL` id goes through `match_set_id()` and then `autoplot()` on that row's result, with the same `type`, `metric` and `eval_time`. Tests go in `test-nested-results-plot.R` beside the set snapshots. Write `@param id`. -- [ ] T4: Update the review page for AC4. Run `benchmarks/export-surface.R` at the head and give each new difference a row: `type` on the set's `collect_inner_metrics()` beside workflowsets' `collect_metrics()`, and the `id` default. Run the default comparison over the `pairs` table. Write each verdict, and name the procedure and its date in the page. Add a candidate row for any `proposed` default (search first). -- [ ] T5: One NEWS entry for the three arguments. Run `devtools::document()`, the prose sweeps from the verify slot, `air format --check .`, `devtools::test()` and `devtools::check()`. +- [x] T1: `verbose` in `R/nested-workflow-map.R:86`, before `...`. Check it before the per-workflow checks, with class `nestedtune_bad_verbose`. Print with `cli::cli_inform()` at each start and each clean finish, inside the loop at the `run_orchestrator()` call. Tests go in `test-nested-workflow-map-checks.R` and `test-nested-workflow-map-readers.R`, under `skip_if_no_wset_fixture()` (the M101 lesson). Mask the time in the snapshot with `transform`. Update `@param verbose` and the `@details` order sentence. +- [x] T2: `type` on `collect_inner_metrics.nested_results()` (`R/nested-results-collect.R:100`) and the set method (`R/nested-results-set.R:86`). Read it with `check_metrics_type()` and pivot with `pivot_metrics_wide()` (`R/nested-results.R:836`); the set pivots once after `stack_set()`, as `collect_metrics()` does. Build the fold 1 oracle on `analysis_framed_inner()`'s output (the M05 lesson). Plant the key collision with a parameter id named like a metric. Write `@param type` text that names `collect_inner_metrics()` on both pages. +- [x] T3: `id` on `autoplot.nested_results_set()` (`R/nested-results-plot.R:574`). A non-`NULL` id goes through `match_set_id()` and then `autoplot()` on that row's result, with the same `type`, `metric` and `eval_time`. Tests go in `test-nested-results-plot.R` beside the set snapshots. Write `@param id`. +- [x] T4: Update the review page for AC4. Run `benchmarks/export-surface.R` at the head and give each new difference a row: `type` on the set's `collect_inner_metrics()` beside workflowsets' `collect_metrics()`, and the `id` default. Run the default comparison over the `pairs` table. Write each verdict, and name the procedure and its date in the page. Add a candidate row for any `proposed` default (search first). +- [x] T5: One NEWS entry for the three arguments. Run `devtools::document()`, the prose sweeps from the verify slot, `air format --check .`, `devtools::test()` and `devtools::check()`. ## Work log @@ -54,6 +54,57 @@ The three argument gaps that M148's review proposed are closed in the tidymodels - 2026-10-18: plan gate chose no failure line under `verbose` over workflowsets' "failed with" line, because the error stops the run and already names the workflow; falsified by `nested_workflow_map()` coming to keep running after a workflow fails. - 2026-10-18: plan gate chose a one-time default comparison recorded in the page over a new section of `benchmarks/export-surface.R`, so the script's promise does not widen; falsified by a later review needing the default comparison again. +- 2026-10-18: T1 code and tests in place: `check_map_verbose()` (class `nestedtune_bad_verbose`), start and finish lines through `cli::cli_inform()` with the text passed as a value. A design passed third by position now lands in `verbose`, so its refusal gains a hint that every argument of `fn` must be named, and the old unnamed-dots test moved to a fourth positional. Full suite pending. +- 2026-10-18: T2 code and tests in place: both methods read `type` with `check_metrics_type()` and pivot with `pivot_metrics_wide()`, the set once after stacking. The fold 1 oracle tunes on `inner_resamples[[1]]` directly, since `det_grid()` names every candidate and no range depends on the data. A planted defect (dropping `rsq` before the pivot) failed 4 assertions, the oracle among them. Full suite pending. +- 2026-10-18: T3 code and tests in place: a non-`NULL` `id` goes through `match_set_id()` before the type check, and the single-run method then reads `type`, `metric` and `eval_time` and gives its own refusals. Tests compare `ggplot_build()` data under both views and a metric filter, and refuse four bad ids. The plot file passed with its three set snapshots unchanged. Full suite pending. +- 2026-10-18: T4 done: the review page marks the three rows `added` and defines the verdict, gives rows to the two new differences the script prints at `33d77fd6` (`type` on the set's `collect_inner_metrics()`, the order of `id`), and gives 7 default verdicts, all `kept`. No `proposed` default, so no candidate row. +- 2026-10-18: claim audit: 34 claims read, 2 corrected — tests/testthat/test-collect-inner-metrics-wide.R (a comment on the long table's rows), R/nested-results-collect.R and man/collect_selections.Rd (`type` now names the two-estimator refusal). The reader re-read both corrections and found them true. +- 2026-10-18: verify: `devtools::test()` at `33d77fd6` gave 15583 passes, 0 failures, 0 skips (later commits change docs, NEWS, a test comment and tracking only). `devtools::check()` at `832d8b17` in the main checkout gave 0 errors, 0 warnings, 0 notes; a first run in a git worktree gave one NOTE on the worktree's `.git` file, which a plain checkout lacks. `air format --check .` and both prose sweeps clean. +- 2026-10-18: minor amendment: T1's tests went to a new `test-nested-workflow-map-verbose.R` in place of the readers file, and T1's `@details` order sentence needed no change, since the details name no argument order. T1 to T3 and T5 ticked; status set to `review`. +- 2026-10-18: step-7 approval: m149-argument-gaps approved for merge + ## Decisions +- 2026-10-18 (review): `nested_workflow_map()` takes `verbose` by position, third, as `workflowsets::workflow_map()` does. This narrows M71's rule that every argument after `fn` is matched by name: now every argument after `fn` and `verbose` is. A third positional value that is not `TRUE` or `FALSE` is refused as `nestedtune_bad_verbose`, with a hint to name the rest. The plan gate chose this order over `verbose` after `...` (work log). It is milestone-local, since no D-entry made M71's rule. + ## Review + +Pass 1, 2026-10-18, at `d7c747b5`. The default branch had not moved (0 commits behind `origin/main`). + +- AC1 evidence: `formals(nested_workflow_map)` reads `object, fn, verbose, ...` with `verbose = FALSE`, the place `workflow_map()` gives it. `test-nested-workflow-map-verbose.R` passed 3 of 3 with `NOT_CRAN=true`: the snapshot holds the four lines (`i 1 of 2 tuning: tuned`, its `v` finish line with the time masked, then `resampling: fixed` twice), the `verbose = FALSE` run signals no message, and the two results are identical. `test-nested-workflow-map-checks.R` passed 197 of 197, including the block that refuses `NA`, `c(TRUE, TRUE)` and `1` with `nestedtune_bad_verbose` and an untouched `.Random.seed`. +- AC2 evidence: both methods' formals read `x, ..., type` with `type = c("long", "wide")`. `test-collect-inner-metrics-wide.R` passed 19 of 19: `"long"` is identical to the call without `type` on a run and on a set; fold 1's wide rows, less `id`, are identical to `tune::collect_metrics(type = "wide")` on the reference loop's fold 1 `tune_grid()` run; `.iter` stays a key on `bayes_results()` with `.metric`, `.estimator`, `mean`, `n` and `std_err` absent; the set's wide table opens with `wflow_id`; a parameter id `rmse` gives `nestedtune_wide_collision` and `type = "tall"` gives `nestedtune_bad_type` on both methods. `.weight` is dropped by `pivot_metrics_wide()` (R/nested-results.R), which both methods call. Implement's planted defect failed 4 of these assertions. +- AC3 evidence: `formals(autoplot.nested_results_set)` reads `object, type, metric, eval_time, ..., id` with `id = NULL`. `test-nested-results-plot.R` passed 285 of 285 with vdiffr installed and no snapshot file changed (`git status` clean): the `ggplot_build()` data of `autoplot(set, id = "tuned")` equal the single run's under `"parameters"`, `"performance"` and `"performance"` with `metric = "rsq"`; the three set snapshots pass; `"nonesuch"`, `c("tuned", "fixed")`, `1` and `NA_character_` give `nestedtune_unknown_id`. +- AC4 evidence: `Rscript benchmarks/export-surface.R` at the head printed an argument half identical to the run at `33d77fd6` that the page cites. For the three changed items it prints `seed` (workflow map), `eval_time`, `rank_metric`, `select_best`, `std_errs` and the shared order with `id` (set plot), `summarize` (run's inner metrics), and `type` and `summarize` (set's inner metrics); each has a row. The page has 3 `added` rows, 0 `proposed`, and defines `added` in its Scope paragraph. The default comparison over the `pairs` table printed 8 differing defaults, which the page's 7 rows cover (one row holds both `objective` methods), all `kept`. + +Consistency gate: `cairn_validate.py` passed every check. `devtools::document()` gave no diff. `pkgdown::check_pkgdown()` found no problems. All six gating prose sweeps clean. NEWS has the entry. No new top-level file. README not touched. No principle changed, so `cairn_impact` was skipped. + +spawned: diff-bug, blame-history, prior-review + +- diff-bug #1: the `id` path gives no partial-run warning where the whole-set plot warns — fix now, fixed 581fc633: the single-run plot states the shortfall in its subtitle, so `@param id` now says so, and a test asserts no warning and the subtitle. +- diff-bug #2: errors on the `id` path do not name the workflow — fix now, fixed 581fc633: the call goes through `for_workflow()`, so they gain `Workflow "": ` and the user's call; a test asserts both. +- diff-bug #3: the naming hint shows when `verbose` was given by name — fix now, fixed 581fc633: the hint shows only when the call did not name `verbose`; a test asserts its absence. +- diff-bug #4: the dots refusal and its comment still say everything after `fn` is matched by name — fix now, fixed 581fc633: both now say after `fn` and `verbose`. +- diff-bug #5: `verbose` is evaluated outside the RNG guard `capture_dots()` gives `...` — reject, not a defect: it is a flag, `fn` has the same exposure, and the guard covers the orchestrators' arguments. +- diff-bug #6: on a 0-row inner table the wide shape has no metric columns — reject, not a defect: there is no metric to pivot, as `collect_metrics(type = "wide")` on the same table. +- diff-bug #7: the set page's `type` text does not fit `collect_inner_metrics()` — fix now, fixed 581fc633: it now says a missing column is `NA` and a workflow with no inner candidates has no rows. +- diff-bug #8: the run page's `type` text says one row per candidate and metric — fix now, fixed 581fc633: one row per fold, candidate and metric, and a metric a fold did not score is `NA`. `.weight` is left unnamed: the inner tables carry none. +- diff-bug #9: the review page says workflowsets puts `id` third — fix now, fixed 581fc633: fourth, third of the shared arguments. +- diff-bug #10: the AC3 test compares only built data and passes no `eval_time` — fix now, fixed 581fc633: it compares the labels too, and asserts that `eval_time` reaches the single view's refusal. +- diff-bug #11: the Bayes test hard-codes two metrics — fix now, fixed 581fc633: it counts the metrics. +- diff-bug #12: the collision test's run half sits under the workflow-set skip — fix now, fixed 581fc633: the run and the set are two blocks with their own skips. +- blame-history #1: a positional third argument changes refusal class with no NEWS word — fix now, fixed 581fc633: the NEWS entry says so. +- blame-history #2: no record of the positional `verbose` beside M71's name-matching rule — fix now, fixed 581fc633: a milestone-local decision records it; the plan gate line holds the alternative. +- blame-history #3: stale rule text — fix now, as diff-bug #4. +- blame-history #4: the `verbose` check runs before the dots check, so a positional design hides a dots typo — reject, not a defect: either order reports one mistake first, and this order names the one that caused the first failure. +- blame-history #5: the `id` path's errors name the single-run call — fix now, as diff-bug #2. +- prior-review #1: the new oracle file has no CRAN guard (M118) — fix now, fixed 581fc633: `skip_heavy_on_cran()` at the top; the verbose file already skips on CRAN through `skip_if_no_wset_fixture()`. +- prior-review #2: a 0-row table loses its metric columns in the wide shape (M049) — reject, as diff-bug #6. +- prior-review #3: the run page's `type` text does not say a missing cell is `NA` (M065 F4) — fix now, as diff-bug #8. +- prior-review #4: a `...` name that is a prefix of `verbose` is partly matched — reject, not a defect: no orchestrator argument starts with `v`, so such a name is refused either way. +- prior-review #5: the verbose test uses the grid tuner alone (M120) — reject, not a defect: the log does not read the tuner, and both route words are drawn. +- prior-review #6: the `id` test reads only built data (the M08 lesson) — fix now, as diff-bug #10. +- prior-review #7: a 90-column test comment — reject, style; it is rewrapped with diff-bug #11's edit. + +After the fixes, the six touched test files passed fresh (verbose 3, map checks 200, wide 19, plot 294, map readers 451, dots barrier 96; 0 failed, 0 skipped), `air format --check .` and all six sweeps were clean. + +- AC5 evidence: `man/nested_workflow_map.Rd` gives `verbose` text naming `nested_workflow_map()`; `man/collect_selections.Rd` and `man/collect_metrics.nested_results_set.Rd` give `type` text naming `collect_inner_metrics()`; `man/summary.nested_results_set.Rd` gives `id` text naming `autoplot()`. `NEWS.md` opens with one entry naming `verbose`, `type = "wide"` and `id`. `devtools::check()` at `581fc633` in the main checkout gave 0 errors, 0 warnings, 0 notes, with the full test suite run inside it. diff --git a/cairn/references/export-surface-review.md b/cairn/references/export-surface-review.md index 3137293c..414aa65c 100644 --- a/cairn/references/export-surface-review.md +++ b/cairn/references/export-surface-review.md @@ -1,10 +1,10 @@ # The export surface beside its tidymodels counterparts (M148) -**Provenance.** Ingested 2026-10-18 by M148 from the output of `benchmarks/export-surface.R` and from `DECISIONS.md` read by its headings. The script first ran on the source tree at `93c10377`. The pairing table and the verdicts read its output at `890190d2`, after the argument reorder. Both runs used the installed tune 2.1.0, finetune 1.3.0, workflowsets 1.1.1 and hardhat 1.4.3. +**Provenance.** Ingested 2026-10-18 by M148 from the output of `benchmarks/export-surface.R` and from `DECISIONS.md` read by its headings. The script first ran on the source tree at `93c10377`. The pairing table and the verdicts read its output at `890190d2`, after the argument reorder. Both runs used the installed tune 2.1.0, finetune 1.3.0, workflowsets 1.1.1 and hardhat 1.4.3. M149 added the three arguments that the proposed verdicts asked for. It re-ran the script at `33d77fd6` under the same four versions and added the default verdicts. Pagination: —. Extraction: derived, a 2026-10-18 snapshot of this package's `NAMESPACE` and the four installed packages, which a new run of the script re-reads — observed 2026-10-18. -**Scope.** This page pairs each package-owned export, each method of an exported generic, and each result column with its tidymodels counterpart. It gives one verdict for each difference. A kept verdict cites the decision or reason that chose the difference. A changed verdict moves an argument after `...`, which breaks no call. A proposed verdict is any other change, left for a later plan. This page is not a style review of the help pages, which `tune-docs-comparison.md` owns. It is a reference, not an authority: status lives in `ROADMAP.md`, decisions in `DECISIONS.md`, and architecture in `DESIGN.md`. +**Scope.** This page pairs each package-owned export, each method of an exported generic, and each result column with its tidymodels counterpart. It gives one verdict for each difference. A kept verdict cites the decision or reason that chose the difference. A changed verdict moves an argument after `...`, which breaks no call. A proposed verdict is any other change, left for a later plan. An added verdict marks a proposed argument that a later milestone added, and its row names that milestone. This page is not a style review of the help pages, which `tune-docs-comparison.md` owns. It is a reference, not an authority: status lives in `ROADMAP.md`, decisions in `DECISIONS.md`, and architecture in `DESIGN.md`. **Evidence snapshot.** @@ -154,7 +154,7 @@ The grid, race and `nested_fit_resamples()` methods already had tune's order amo | Argument | Side | Verdict | Reason | |---|---|---|---| | `seed` | theirs only | kept | D-011 and D-058: the caller calls `set.seed()` first. The generator state is put back before each workflow, so every workflow runs under the same per-fold seeds. `workflow_map()` gets the same pairing by seeding each workflow with `seed`. | -| `verbose` | theirs only | proposed | Add `verbose` to log each workflow as it starts and ends. No decision chose its absence. | +| `verbose` | both, since M149 | added | M149 added `verbose = FALSE` between `fn` and `...`, where `workflow_map()` has it. It logs each workflow as it starts and as it finishes without error. | ### The readers on `nested_results` @@ -163,7 +163,7 @@ The grid, race and `nested_fit_resamples()` methods already had tune's order amo | `augment()` | `parameters` | theirs only | kept | D-092: `augment()` joins each row's saved prediction, or the D-090 average, and each fold predicted with the candidate it selected. So no single candidate exists to choose, as `?augment.nested_results` states. | | `collect_predictions()` | `parameters` | theirs only | kept | D-090, as above. | | `collect_inner_metrics()` against tune's `collect_metrics()` | `summarize` | theirs only | kept | D-043: `.inner_metrics` holds each fold's inner metrics in summarized form. There are no per-resample rows to return. | -| `collect_inner_metrics()` against tune's `collect_metrics()` | `type` | theirs only | proposed | Add `type = c("long", "wide")`, as the method in tune has. No decision chose its absence. | +| `collect_inner_metrics()` against tune's `collect_metrics()` | `type` | both, since M149 | added | M149 added `type = c("long", "wide")` after `...`, as the method in tune has it. | | `autoplot()` | `width` | theirs only | kept | In tune, `width` sets the width of the confidence interval bars. The `"performance"` view here plots each outer fold's score as a point and draws no interval bars. | | `autoplot()` | `call` | theirs only | kept | tune passes its error-reporting frame through `call`, with `rlang::current_env()` as the default. It is plumbing for the helpers in tune, not a setting for the plot. | @@ -173,11 +173,13 @@ The grid, race and `nested_fit_resamples()` methods already had tune's order amo |---|---|---|---|---| | `collect_metrics()` | `type` | ours only | kept | The method follows the `tune_results` method in tune and the `nested_results` method here, which both take `type`. | | `collect_inner_metrics()` against workflowsets' `collect_metrics()` | `summarize` | theirs only | kept | D-043, as on `nested_results`. | +| `collect_inner_metrics()` against workflowsets' `collect_metrics()` | `type` | ours only, since M149 | kept | The method follows the `nested_results` method and `collect_metrics()` on the set here, which both take `type`, as `collect_metrics()` in tune does. | | `collect_predictions()` | `parameters` | theirs only | kept | D-090, as on `nested_results`. | | `collect_predictions()` | `select_best`, `metric` | theirs only | kept | D-054: the outer predictions come from the candidate each fold selected. There are no other candidates to filter out. | | `autoplot()` | `rank_metric`, `select_best` | theirs only | kept | D-058: the set has no ranking or best-workflow view, because a ranking of nested estimates is the reading that IP3 forbids. | | `autoplot()` | `std_errs` | theirs only | kept | In workflowsets, `std_errs` sets how many standard errors the interval bars span. The set's plot here draws each fold's score as a point and the estimate as a flat rule, with no interval bars. | -| `autoplot()` | `id` | theirs only | proposed | Add `id` to plot one workflow's `nested_results`, as `autoplot(x$result[[i]])` does today. No decision chose its absence. | +| `autoplot()` | `id` | both, since M149 | added | M149 added `id = NULL` after `...`. One `wflow_id` draws the plot that `autoplot()` draws for that workflow's `nested_results`. | +| `autoplot()` | order of `id` | ours after `...`, theirs fourth (third of the shared arguments) | kept | The arguments before `...` keep the order of `autoplot.tune_results()`, so one call works on both classes, as the next row says. After `...`, `id` must be named, so adding it broke no call. | | `autoplot()` | `eval_time` | ours only | kept | The method follows `autoplot.nested_results()` and `autoplot.tune_results()` in tune, which both take it. | | `autoplot()` | order of `type` and `metric` | both before `...` | kept | The order here is `object, type, metric`, as in `autoplot.tune_results()`, so one call works on both classes. The `type` in workflowsets is another setting with the same name: it picks how points are colored. | @@ -198,8 +200,20 @@ Each orchestrator's result and its counterpart's result share `splits`, `id`, `. The `result` of each set row has the same columns as the result of a single run. -## What the proposed verdicts ask for +## What the proposed verdicts asked for -Three changes are left for a later plan, and the ROADMAP candidate row "Argument gaps beside tidymodels" holds them. They are a `verbose` argument on `nested_workflow_map()`, a `type` argument on `collect_inner_metrics()`, and an `id` argument on `autoplot()` for a `nested_results_set`. Each change can add its argument after `...`, and an argument there breaks no call. +M148 left three changes for a later plan, and M149 added them. They are `verbose` on `nested_workflow_map()`, `type` on `collect_inner_metrics()`, and `id` on `autoplot()` for a `nested_results_set`. `type` and `id` come after `...`. `verbose` comes before `...`, in the place `workflow_map()` gives it. Every argument in `...` must be named, so this broke no call. A third argument passed by position now lands in `verbose`, and its refusal says that every argument of `fn` must be named. -The script compares argument names and order, not defaults. The same row holds one default that differs: `autoplot()` on a `nested_results` defaults `type` to `"parameters"`, and `autoplot.tune_results()` defaults it to `"marginals"`. +## Default verdicts + +The script compares argument names and order, not defaults. M149 compared the defaults once, on 2026-10-18 at `33d77fd6`. For every pair in the script's `pairs` table, it compared the deparsed default of each argument that both sides share, other than `...`. These are the defaults that differ. + +| Item | Argument | Ours | Theirs | Verdict | Reason | +|---|---|---|---|---|---| +| `nested_workflow_map()` | `fn` | `"nested_tune_grid"` | `"tune_grid"` | kept | Each default names the grid search of its own package. | +| `nested_tune_bayes()`, `workflow` and `model_spec` methods | `objective` | `tune::exp_improve()` | `exp_improve()` | kept | The same call. Only the namespace prefix differs. | +| `autoplot.nested_results()` | `type` | `"parameters"` first | `"marginals"` first | kept | D-019 made the view of what each outer fold selected the default. There is no `"marginals"` view, because the outer folds score one candidate each. | +| `autoplot.nested_results_set()` | `type` | `"parameters"` first | `"class"` | kept | The `type` in workflowsets is another setting, as the order row above says. | +| `autoplot.nested_results_set()` | `id` | `NULL` | `"workflow_set"` | kept | M149's plan gate chose `NULL`, because a workflow can have the id `"workflow_set"`, and `nested_final_fit()` reads an id with no such word. | +| `collect_predictions.nested_results_set()` | `summarize` | `FALSE` | `TRUE` | kept | The default follows the `nested_results` method here and the `tune_results` method in tune, which both default to `FALSE`. So one call reads the same rows from a run and from a set. D-090 makes the average an explicit request. | +| `compute_metrics.nested_results()` | `event_level` | `NULL` | `"first"` | kept | `NULL` takes the level the run recorded, and `"first"` for a run that recorded none, as `?compute_metrics.nested_results` says. | diff --git a/man/collect_metrics.nested_results_set.Rd b/man/collect_metrics.nested_results_set.Rd index 6ac2a8de..eae66838 100644 --- a/man/collect_metrics.nested_results_set.Rd +++ b/man/collect_metrics.nested_results_set.Rd @@ -15,7 +15,7 @@ \method{collect_selections}{nested_results_set}(x, ...) -\method{collect_inner_metrics}{nested_results_set}(x, ...) +\method{collect_inner_metrics}{nested_results_set}(x, ..., type = c("long", "wide")) \method{collect_notes}{nested_results_set}(x, ...) @@ -35,9 +35,12 @@ \item{summarize}{As on \code{\link[=collect_metrics.nested_results]{collect_metrics.nested_results()}}, and for \code{collect_predictions()} as on \code{\link[=collect_predictions.nested_results]{collect_predictions.nested_results()}}.} -\item{type}{For \code{collect_metrics()}, \code{"long"} (the default) or \code{"wide"}. -The wide shape is keyed on \code{wflow_id} too, and a metric that a workflow -did not score is \code{NA} in its rows.} +\item{type}{For \code{collect_metrics()} and \code{collect_inner_metrics()}, +\code{"long"} (the default) or \code{"wide"}. The wide shape is keyed on +\code{wflow_id} too. A column that a workflow lacks is \code{NA} in its rows, +such as a metric it did not score or a parameter it does not tune. +A workflow with no inner candidates, such as a fixed one, has no rows +in \code{collect_inner_metrics()}.} \item{metrics, event_level}{As on \code{\link[=compute_metrics.nested_results]{compute_metrics.nested_results()}}. The default \code{event_level} takes each workflow's recorded level.} diff --git a/man/collect_selections.Rd b/man/collect_selections.Rd index ef5783c6..b147ec42 100644 --- a/man/collect_selections.Rd +++ b/man/collect_selections.Rd @@ -14,7 +14,7 @@ collect_selections(x, ...) collect_inner_metrics(x, ...) -\method{collect_inner_metrics}{nested_results}(x, ...) +\method{collect_inner_metrics}{nested_results}(x, ..., type = c("long", "wide")) \method{collect_notes}{nested_results}(x, ...) } @@ -23,6 +23,13 @@ collect_inner_metrics(x, ...) function.} \item{...}{Not used, and must be empty.} + +\item{type}{For \code{collect_inner_metrics()}, \code{"long"} (the default), one row +per fold, candidate and metric, or \code{"wide"}, one column per metric that +holds its \code{mean}. The wide shape drops \code{.estimator}, \code{n} and \code{std_err}, +and a metric that a fold did not score is \code{NA} in its rows. It +gives an error for a metric named like another column, such as a tuning +parameter, and for a metric scored with two estimators.} } \value{ A tibble with the fold labels first (\code{id}, and \code{id2} on a diff --git a/man/nested_workflow_map.Rd b/man/nested_workflow_map.Rd index f21f113f..01b017f4 100644 --- a/man/nested_workflow_map.Rd +++ b/man/nested_workflow_map.Rd @@ -4,7 +4,7 @@ \alias{nested_workflow_map} \title{Run every workflow of a workflow set through one nested design} \usage{ -nested_workflow_map(object, fn = "nested_tune_grid", ...) +nested_workflow_map(object, fn = "nested_tune_grid", verbose = FALSE, ...) } \arguments{ \item{object}{A workflow set.} @@ -15,6 +15,11 @@ default is \code{"nested_tune_grid"}. The others are \code{"nested_tune_bayes"}, \code{"nested_tune_sim_anneal"}. With \code{"nested_fit_resamples"}, no workflow can have a parameter to tune.} +\item{verbose}{Whether \code{nested_workflow_map()} writes a message as each +workflow starts and as it finishes. The default is \code{FALSE}. The finish +message gives the time the workflow took. A workflow that gives an +error has no finish message.} + \item{...}{The arguments of \code{fn}, each one named. \code{resamples}, the nested design, is required, and a \code{control} goes here as well. A name that \code{fn} does not take is an error. A \code{metrics} set reaches every workflow, and diff --git a/man/summary.nested_results_set.Rd b/man/summary.nested_results_set.Rd index 4232cd4a..44881c7d 100644 --- a/man/summary.nested_results_set.Rd +++ b/man/summary.nested_results_set.Rd @@ -15,7 +15,8 @@ type = c("parameters", "performance"), metric = NULL, eval_time = NULL, - ... + ..., + id = NULL ) \method{summary}{nested_results_set}(object, ...) @@ -34,6 +35,11 @@ returns.} \item{metric, eval_time}{For \code{type = "performance"}, the metrics and the evaluation times to draw, as on \code{\link[=autoplot.nested_results]{autoplot.nested_results()}}.} + +\item{id}{For \code{autoplot()}, \code{NULL} (the default) to draw every workflow, +or one \code{wflow_id} to draw that workflow alone. One \code{wflow_id} gives the +plot that \code{\link[=autoplot.nested_results]{autoplot.nested_results()}} draws for that workflow's result. +That plot states a failed fold in its subtitle and gives no warning.} } \value{ \code{summary()} returns a \code{summary.nested_results_set}, a list of diff --git a/tests/testthat/_snaps/nested-workflow-map-verbose.md b/tests/testthat/_snaps/nested-workflow-map-verbose.md new file mode 100644 index 00000000..40659f1a --- /dev/null +++ b/tests/testthat/_snaps/nested-workflow-map-verbose.md @@ -0,0 +1,10 @@ +# AC1: verbose = TRUE logs each workflow's start and finish, and the result does not change + + Code + loud <- nested_workflow_map(wset, verbose = TRUE, resamples = folds, grid = det_grid()) + Message + i 1 of 2 tuning: tuned + v 1 of 2 tuning: tuned (