From 8e01366e14728c5c18e92ade0194e7c130c1929f Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 13 Jul 2023 10:39:23 +0200 Subject: [PATCH 01/32] optimize_design and assign_ return a copy of bc, bc has a tibble with the trace --- R/assignment.R | 18 +- R/batch_container.R | 140 +++++++++--- R/optimize.R | 92 +++++--- R/score_autoscaling.R | 22 +- R/score_plates.R | 17 +- R/trace.R | 254 ++------------------- man/examples/assignment.R | 4 +- tests/testthat/test-compute-score-vector.R | 21 ++ 8 files changed, 243 insertions(+), 325 deletions(-) create mode 100644 tests/testthat/test-compute-score-vector.R diff --git a/R/assignment.R b/R/assignment.R index f52f0572..f2fcd044 100644 --- a/R/assignment.R +++ b/R/assignment.R @@ -4,16 +4,16 @@ #' @param samples data.frame with samples. #' @param batch_container Instance of BatchContainer class #' -#' @return Returns `BatchContainer`, invisibly. +#' @return Returns a new `BatchContainer`. #' @example man/examples/assignment.R assign_random <- function(batch_container, samples = NULL) { - assign_in_order(batch_container, samples) + batch_container <- assign_in_order(batch_container, samples) batch_container$move_samples( location_assignment = sample(batch_container$assignment) ) - invisible(batch_container) + batch_container } #' Distributes samples in order. @@ -25,9 +25,10 @@ assign_random <- function(batch_container, samples = NULL) { #' @param samples data.frame with samples. #' @param batch_container Instance of BatchContainer class #' -#' @return Returns `BatchContainer`, invisibly. +#' @return Returns a new `BatchContainer`. #' @example man/examples/assignment.R assign_in_order <- function(batch_container, samples = NULL) { + batch_container <- batch_container$copy() if (is.null(samples)) { assertthat::assert_that(batch_container$has_samples, msg = "batch-container is empty and no samples provided" @@ -46,7 +47,7 @@ assign_in_order <- function(batch_container, samples = NULL) { rep(NA_integer_, n_locations - n_samples) )) - invisible(batch_container) + batch_container } #' Shuffling proposal function with constraints. @@ -113,7 +114,7 @@ shuffle_with_constraints <- function(src = TRUE, dst = TRUE) { #' the function will check if samples in `batch_container` are identical to the ones in the #' `samples` argument. #' -#' @return Returns `BatchContainer`, invisibly. +#' @return Returns a new `BatchContainer`. #' #' @examples #' bc <- BatchContainer$new( @@ -133,11 +134,12 @@ shuffle_with_constraints <- function(src = TRUE, dst = TRUE) { #' 2, "a", 3, 5, "TRT", #' ) #' # assign samples from the sample sheet -#' assign_from_table(bc, sample_sheet) +#' bc <- assign_from_table(bc, sample_sheet) #' #' bc$get_samples(remove_empty_locations = TRUE) #' assign_from_table <- function(batch_container, samples) { + batch_container <- batch_container$copy() # sample sheet has all the batch variable assertthat::assert_that(is.data.frame(samples) && nrow(samples) > 0, msg = "samples should be non-empty data.frame" @@ -177,5 +179,5 @@ assign_from_table <- function(batch_container, samples) { batch_container$move_samples(location_assignment = samples_with_id$.sample_id) - invisible(batch_container) + batch_container } diff --git a/R/batch_container.R b/R/batch_container.R index 19c0b763..18efc1b4 100644 --- a/R/batch_container.R +++ b/R/batch_container.R @@ -328,23 +328,51 @@ BatchContainer <- R6::R6Class("BatchContainer", #' @description #' Score current sample assignment, - #' @return Returns a vector of all scoring functions values. - score = function() { - assertthat::assert_that(!is.null(private$scoring_funcs), - msg = "Scoring function needs to be assigned" + #' @param scoring names list of scoring functions. Each functin should + #' return a numeric vector. + #' @return Returns a named vector of all scoring functions values. + score = function(scoring) { + assertthat::assert_that( + !missing(scoring), + !is.null(scoring), + msg = "Scoring function needs to be provided" ) - assertthat::assert_that(is.list(private$scoring_funcs), - length(private$scoring_funcs) >= 1, + assertthat::assert_that(is.list(scoring), + length(scoring) >= 1, msg = "Scroring function should be a non-empty list" ) - assertthat::assert_that(!is.null(private$samples_table), + assertthat::assert_that(!is.null(names(scoring)), + msg = "scoring should be a named list" + ) + assertthat::assert_that(self$has_samples, msg = "No samples in the batch container, cannot compute score" ) - - res <- purrr::map_dbl(private$scoring_funcs, ~ .x(self)) - assertthat::assert_that(length(res) == length(private$scoring_funcs)) - - assertthat::assert_that(is.numeric(res), msg = "Scoring function should return a number") + res <- purrr::imap( + scoring, + \(f, i) { + v <- f(self) + assertthat::assert_that( + is.numeric(v), + length(v) >= 1, + msg = "scoring function should return a numeric vector of positive length" + ) + if (length(v) > 1) { + if (is.null(names(v))) { + names(v) <- seq_along(v) + } + names(v) <- stringr::str_c(i, names(v)) + } else { + names(v) <- i + } + v + } + ) |> + purrr::flatten_dbl() + assertthat::assert_that(length(res) >= length(scoring)) + assertthat::assert_that( + !any(names(res) == "step"), + msg = "score name cannot be 'step'" + ) return(res) }, @@ -368,7 +396,7 @@ BatchContainer <- R6::R6Class("BatchContainer", bc$samples_attr <- private$samples_attributes } - bc$scoring_f <- self$scoring_f + bc$trace <- self$trace bc }, @@ -398,6 +426,75 @@ BatchContainer <- R6::R6Class("BatchContainer", cat() cat("\n") invisible(self) + }, + + #' @description + #' Optimization trace, a [tibble::tibble()] + trace = tibble::tibble( + optimization_index = numeric(), + call = list(), + start_assignment_vec = list(), + end_assignment_vec = list(), + scores = list(), + aggregated_scores = list(), + seed = list(), + elapsed = as.difftime(character(0)) + ), + + #' @description + #' Return a tibble with scores from the last optimization. + #' + #' @param include_aggregated shall aggregated scores be included + #' @return a [tibble::tibble()] with scores + last_optimization_tibble = function(include_aggregated = FALSE) { + assertthat::assert_that( + tibble::is_tibble(self$trace), + nrow(self$trace) >= 1, + msg = "trace should be available" + ) + assertthat::assert_that(assertthat::is.flag(include_aggregated)) + d <- tail(self$trace, 1)$scores[[1]] %>% + tidyr::pivot_longer(-.data$step, + names_to = "score", + values_to = "value") + d$aggregated <- FALSE + if (include_aggregated) { + d_agg <- tail(self$trace, 1)$aggregated_scores[[1]] + if (!is.null(d_agg)) { + d_agg <- tidyr::pivot_longer( + d_agg, + -.data$step, + names_to = "score", + values_to = "value" + ) + d_agg$score <- paste0("agg.", d_agg$score) + d_agg$aggregated <- TRUE + d <- dplyr::bind_rows( + d, + d_agg + ) + } + } else { + d_agg <- NULL + } + d + }, + + #' @description + #' Plot trace + plot_trace = function(include_aggregated = FALSE, ...) { + d <- self$last_optimization_tibble(include_aggregated) + p <- ggplot2::ggplot(d) + + ggplot2::aes(.data$step, .data$value, group = .data$score, color = .data$score) + + ggplot2::geom_line() + + ggplot2::geom_point() + if (include_aggregated && any(d$aggregated)) { + p <- p + + ggplot2::facet_wrap(~ score, scales = "free_y", ncol = 1) + } else { + p <- p + + ggplot2::facet_wrap(~ aggregated, scales = "free_y", ncol = 1) + } } ), private = list( @@ -445,22 +542,7 @@ BatchContainer <- R6::R6Class("BatchContainer", #' Upon assignment a single function will be automatically converted to a list #' In the later case each function is called. scoring_f = function(value) { - if (missing(value)) { - private$scoring_funcs - } else { - if (is.null(value)) { - private$scoring_funcs <- NULL - } else if (is.function(value)) { - private$scoring_funcs <- list(value) - } else { - assertthat::assert_that(is.list(value), length(value) >= 1) - assertthat::assert_that( - all(purrr::map_lgl(self$scoring_f, is.function)), - msg = "All elements of scoring_f should be functions" - ) - private$scoring_funcs <- value - } - } + stop("scoring_f is deprecated, pass it to optimize_design() directly instead") }, #' @field has_samples diff --git a/R/optimize.R b/R/optimize.R index 2db24aa5..c8951a41 100644 --- a/R/optimize.R +++ b/R/optimize.R @@ -123,6 +123,7 @@ update_batchcontainer <- function(bc, shuffle_params) { #' @param batch_container An instance of `BatchContainer`. #' @param samples A `data.frame` with sample information. #' Should be `NULL` if the `BatchContainer` already has samples in it. +#' @param scoring Scoring function or a named [list()] of scoring functions. #' @param n_shuffle Vector of length 1 or larger, defining how many random sample #' swaps should be performed in each iteration. If length(n_shuffle)==1, #' this sets no limit to the number of iterations. Otherwise, the optimization @@ -170,10 +171,14 @@ update_batchcontainer <- function(bc, shuffle_params) { #' bc <- BatchContainer$new( #' dimensions = c("plate" = 2, "column" = 5, "row" = 6) #' ) -#' bc$scoring_f <- osat_score_generator("plate", "Sex") -#' optimize_design(bc, invivo_study_samples, max_iter = 100) +#' bc <- optimize_design(bc, invivo_study_samples, +#' scoring = osat_score_generator("plate", "Sex"), +#' max_iter = 100 +#' ) #' plot_plate(bc$get_samples(), .col = Sex) -optimize_design <- function(batch_container, samples = NULL, n_shuffle = NULL, +optimize_design <- function(batch_container, samples = NULL, + scoring = NULL, + n_shuffle = NULL, shuffle_proposal_func = NULL, acceptance_func = accept_strict_improvement, aggregate_scores_func = identity, @@ -182,10 +187,19 @@ optimize_design <- function(batch_container, samples = NULL, n_shuffle = NULL, sample_attributes_fixed = FALSE, max_iter = 1e4, min_delta = NA, quiet = FALSE) { start_time <- Sys.time() + cl <- match.call() + + # create a copy, so that we do not modify the BatchContainer + batch_container <- batch_container$copy() + trace <- tibble::tibble( + optimization_index = max(batch_container$trace$optimization_index, 0) + 1, + call = list(cl), + start_assignment_vec = list(batch_container$assignment) + ) # based on https://stat.ethz.ch/pipermail/r-help/2007-September/141717.html if (!exists(".Random.seed")) stats::runif(1) - save_random_seed <- .Random.seed + trace$seed <- list(.Random.seed) if (is.null(samples)) { assertthat::assert_that(batch_container$has_samples, @@ -197,11 +211,22 @@ optimize_design <- function(batch_container, samples = NULL, n_shuffle = NULL, } - # Check presence of scoring function and that it's a list of functions - assertthat::assert_that(!is.null(batch_container$scoring_f), msg = "no scoring function set for BatchContainer") - assertthat::assert_that(is.list(batch_container$scoring_f), msg = "scoring function is expected to be a list") - assertthat::assert_that(all(purrr::map_lgl(batch_container$scoring_f, is.function)), msg = "All scoring functions have to be function definitions") - + assertthat::assert_that( + !is.null(scoring), + msg = "Scoring should be provided when calling optimize_design()" + ) + if (is.function(scoring)) { + scoring <- list(scoring) + } else { + assertthat::assert_that(is.list(scoring), length(scoring) >= 1) + assertthat::assert_that( + all(purrr::map_lgl(scoring, is.function)), + msg = "All elements of scoring should be functions" + ) + } + if (is.null(names(scoring))) { + names(scoring) <- stringr::str_c("score_", seq_along(scoring)) + } # Get assigned samples and locations from the batch container samp <- batch_container$get_samples(include_id = TRUE, assignment = TRUE, remove_empty_locations = FALSE) @@ -257,13 +282,14 @@ optimize_design <- function(batch_container, samples = NULL, n_shuffle = NULL, best_shuffle <- list(src = NULL, dst = NULL, location_assignment = batch_container$assignment, samples_attr = NULL) } - initial_score <- batch_container$score() # Evaluate this just once in order not to break current tests + # Evaluate this just once in order not to break current tests + initial_score <- batch_container$score(scoring) score_dim <- length(initial_score) # Check score variances (should be all >0) if (check_score_variance) { bc_copy <- batch_container$copy() - score_vars <- random_score_variances(batch_container$copy(), random_perm = 100, sample_attributes_fixed) + score_vars <- random_score_variances(batch_container$copy(), scoring = scoring, random_perm = 100, sample_attributes_fixed) low_var_scores <- score_vars < 1e-10 if (!quiet) { message( @@ -298,6 +324,7 @@ optimize_design <- function(batch_container, samples = NULL, n_shuffle = NULL, ) } autoscale_func <- mk_autoscale_function(batch_container$copy(), + scoring = scoring, random_perm = autoscaling_permutations, use_boxcox = autoscale_useboxcox, sample_attributes_fixed @@ -312,20 +339,23 @@ optimize_design <- function(batch_container, samples = NULL, n_shuffle = NULL, prev_agg <- NULL - trace <- OptimizationTrace$new( - max_iter + 1, # + 1 to accommodate initial score - length(batch_container$scoring_f), - names(batch_container$scoring_f) + scores_mat <- matrix( + nrow = max_iter + 1, # + 1 to accommodate initial score + ncol = length(best_score), + dimnames = list(NULL, names(best_score)) ) + scores_mat[1,] <- best_score if (identical(aggregate_scores_func, identity)) { - # Do not store aggregated scores if unnecessary - trace$set_scores(1, best_score, NULL) + aggregated_scores_mat <- NULL } else { - trace$set_scores(1, best_score, best_agg) + aggregated_scores_mat <- matrix( + nrow = max_iter + 1, # + 1 to accommodate initial score + ncol = length(best_agg), + dimnames = list(NULL, names(best_agg)) + ) + aggregated_scores_mat[1,] <- best_agg } - # to do: make work with >1-dim agg, line should read as - # trace$set_scores(1, best_score, best_agg) if (!quiet) report_scores(best_score, best_agg, iteration = 0) @@ -338,7 +368,7 @@ optimize_design <- function(batch_container, samples = NULL, n_shuffle = NULL, using_attributes <- TRUE } - new_score <- autoscale_func(batch_container$score()) + new_score <- autoscale_func(batch_container$score(scoring)) assertthat::assert_that(!any(is.na(new_score)), msg = stringr::str_c("NA apprearing during scoring in iteration ", iteration)) new_agg <- aggregate_scores_func(new_score) @@ -358,11 +388,9 @@ optimize_design <- function(batch_container, samples = NULL, n_shuffle = NULL, } iteration <- iteration + 1 - if (identical(aggregate_scores_func, identity)) { - # Do not store aggregated scores if unnecessary - trace$set_scores(iteration, best_score, NULL) - } else { - trace$set_scores(iteration, best_score, best_agg) + scores_mat[iteration,] <- best_score + if (!is.null(aggregated_scores_mat)) { + aggregated_scores_mat[iteration,] <- best_agg } # Test stopping criteria @@ -384,8 +412,14 @@ optimize_design <- function(batch_container, samples = NULL, n_shuffle = NULL, # In the end, always make sure that final state of bc is the one with the best score update_batchcontainer(batch_container, best_shuffle) - trace$shrink(iteration) - trace$seed <- save_random_seed + # shrink + trace$scores <- shrink_mat(scores_mat, iteration) + trace$aggregated_scores <- shrink_mat(aggregated_scores_mat, iteration) trace$elapsed <- Sys.time() - start_time - trace + trace$end_assignment_vec = list(bc$assignment) + batch_container$trace <- dplyr::bind_rows( + batch_container$trace, + trace + ) + batch_container } diff --git a/R/score_autoscaling.R b/R/score_autoscaling.R index bdcf1dfc..8104d48c 100644 --- a/R/score_autoscaling.R +++ b/R/score_autoscaling.R @@ -1,20 +1,22 @@ #' Sample scores from a number of completely random sample permutations #' #' @param batch_container An instance of [BatchContainer]. +#' @param scoring A named [list()] of scoring function. Each function should +#' return a vector of non-zero length. #' @param random_perm Number of random sample permutations to be done. #' @param sample_attributes_fixed Logical; if `FALSE`, simulate a shuffle function that alters sample attributes at each iteration. #' #' @return A score matrix with n (# of permutations) rows and m (dimensionality of score) columns. #' #' @keywords internal -sample_random_scores <- function(batch_container, random_perm, sample_attributes_fixed) { - random_scores <- matrix(NA_real_, nrow = random_perm, ncol = length(batch_container$score())) +sample_random_scores <- function(batch_container, scoring, random_perm, sample_attributes_fixed) { + random_scores <- matrix(NA_real_, nrow = random_perm, ncol = length(batch_container$score(scoring))) for (i in seq_len(random_perm)) { batch_container$move_samples(location_assignment = complete_random_shuffling(batch_container)) if (!sample_attributes_fixed && batch_container$has_samples_attr) { batch_container$samples_attr <- batch_container$samples_attr[sample(nrow(batch_container$samples_attr)), ] } - random_scores[i, ] <- batch_container$score() + random_scores[i, ] <- batch_container$score(scoring) } random_scores @@ -24,6 +26,8 @@ sample_random_scores <- function(batch_container, random_perm, sample_attributes #' Create a function that transforms a current (multi-dimensional) score into a boxcox normalized one #' #' @param batch_container An instance of [BatchContainer]. +#' @param scoring A named [list()] of scoring function. Each function should +#' return a vector of non-zero length. #' @param random_perm Number of random sample permutations for the estimation of the scaling params. #' @param use_boxcox Logical; if TRUE and the `bestNormalize` package is available, boxcox transformations will be used to #' normalize individual scores. If not possible, scores will just be transformed to a zero mean and unit standard deviation. @@ -31,9 +35,9 @@ sample_random_scores <- function(batch_container, random_perm, sample_attributes #' #' @return The transformation function for a new score vector #' @keywords internal -mk_autoscale_function <- function(batch_container, random_perm, use_boxcox = TRUE, sample_attributes_fixed = FALSE) { - random_scores <- sample_random_scores(batch_container, random_perm, sample_attributes_fixed) - score_dim <- length(batch_container$score()) +mk_autoscale_function <- function(batch_container, scoring, random_perm, use_boxcox = TRUE, sample_attributes_fixed = FALSE) { + random_scores <- sample_random_scores(batch_container, scoring, random_perm, sample_attributes_fixed) + score_dim <- length(batch_container$score(scoring)) # Return function using boxcox transform if bestNormalize package is available if (use_boxcox && requireNamespace("bestNormalize", quietly = T)) { @@ -69,13 +73,15 @@ mk_autoscale_function <- function(batch_container, random_perm, use_boxcox = TRU #' Estimate the variance of individual scores by a series of completely random sample permutations #' #' @param batch_container An instance of `BatchContainer`. +#' @param scoring A named [list()] of scoring function. Each function should +#' return a vector of non-zero length. #' @param random_perm Number of random sample permutations to be done. #' @param sample_attributes_fixed Logical; if FALSE, simulate a shuffle function that alters sample attributes at each iteration. #' #' @return Vector of length m (=dimensionality of score) with estimated variances of each subscore #' #' @keywords internal -random_score_variances <- function(batch_container, random_perm, sample_attributes_fixed) { - random_scores <- sample_random_scores(batch_container, random_perm, sample_attributes_fixed) +random_score_variances <- function(batch_container, scoring, random_perm, sample_attributes_fixed) { + random_scores <- sample_random_scores(batch_container, scoring, random_perm, sample_attributes_fixed) purrr::map_dbl(asplit(random_scores, 2), stats::var, na.rm = T) } diff --git a/R/score_plates.R b/R/score_plates.R index 7cf1c50c..793bc24f 100644 --- a/R/score_plates.R +++ b/R/score_plates.R @@ -229,8 +229,6 @@ optimize_multi_plate_design <- function(batch_container, across_plates_variables msg = "All columns in 'within_plate_variable' argument have to be found in batch container samples." ) - traces <- list() - skip_osat <- is.null(across_plates_variables) || is.null(plate) || dplyr::n_distinct(bc$get_locations()[[plate]]) < 2 if (skip_osat && !quiet) message("\nNo balancing of variables across plates required...") @@ -239,25 +237,22 @@ optimize_multi_plate_design <- function(batch_container, across_plates_variables scoring_funcs <- purrr::map(across_plates_variables, ~ osat_score_generator(batch_vars = plate, feature_vars = .x)) %>% unlist() names(scoring_funcs) <- across_plates_variables - bc$scoring_f <- scoring_funcs if (!quiet) message("\nAssigning samples to plates...") - trace1 <- optimize_design(bc, + bc <- optimize_design(bc, + scoring = scoring_funcs, max_iter = max_iter, n_shuffle = n_shuffle, acceptance_func = accept_leftmost_improvement, quiet = TRUE ) - traces <- c(traces, osat_across_plates = trace1) } if (!is.null(within_plate_variables)) { - within_traces <- list() plate_levels <- unique(bc$get_locations()[[plate]]) scoring_funcs <- purrr::map(within_plate_variables, ~ mk_plate_scoring_functions(bc, plate = plate, row = row, column = column, group = .x)) %>% unlist() names(scoring_funcs) <- paste(rep(within_plate_variables, each = length(plate_levels)), names(scoring_funcs)) - bc$scoring_f <- scoring_funcs if (!quiet) { @@ -270,7 +265,8 @@ optimize_multi_plate_design <- function(batch_container, across_plates_variables for (curr_plate in plate_levels) { if (!quiet && length(plate_levels) > 1) cat(curr_plate, "... ") - trace2 <- optimize_design(bc, + bc <- optimize_design(bc, + scoring = scoring_funcs, max_iter = max_iter, quiet = TRUE, shuffle_proposal_func = mk_subgroup_shuffling_function( @@ -279,16 +275,13 @@ optimize_multi_plate_design <- function(batch_container, across_plates_variables ), acceptance_func = accept_leftmost_improvement ) - within_traces <- c(within_traces, trace2) } if (!quiet) cat("\n") - names(within_traces) <- paste0("within_plate_", plate_levels) - traces <- c(traces, within_traces) } if (skip_osat && is.null(within_plate_variables) && !quiet) { message("\nBoth across plates and within plate optimization skipped ('within_plate_variables' is empty).\nBatch container unchanged.\n") } - invisible(traces) + bc } diff --git a/R/trace.R b/R/trace.R index b4a88dad..7abc89ac 100644 --- a/R/trace.R +++ b/R/trace.R @@ -1,240 +1,3 @@ -#' @title -#' OptimizationTrace represents optimization trace. -#' -#' @description -#' Usually it is created by [optimize_design()]. -#' -#' @export -OptimizationTrace <- R6::R6Class("OptimizationTrace", - public = list( - #' @field scores - #' Contains a matrix of scores. The matrix size is usually - #' `c(iterations + 1, length(bc$scoring_f))` - scores = NULL, - - #' @field aggregated_scores - #' Contains a matrix of scores after aggregation. - #' The matrix size is usually `c(iterations + 1, length(aggregated))`, - #' where `length(aggregated)` is the length of aggregated scores vector. - #' Can be `NULL` if aggregated scores are not used. - aggregated_scores = NULL, - - #' @field seed - #' Saved value of [.Random.seed]. - seed = NULL, - - #' @field elapsed - #' Running time of the optimization. - elapsed = NULL, - - #' @field last_step - #' Last iteration step for which the score was set. - last_step = 0, - - #' @description - #' Create a new `OptimizationTrace` object. - #' - #' @param n_steps - #' Number of values to save. Usually `n_steps == iterations + 1`. - #' @param n_scores - #' Number of scoring functions. - #' @param score_names - #' Names of scoring functions. - #' - #' @examples - #' tr <- OptimizationTrace$new(10, 2, c("score1", "score2")) - initialize = function(n_steps, n_scores, score_names) { - self$scores <- matrix(NA_real_, nrow = n_steps, ncol = n_scores) - if (!is.null(score_names)) { - dimnames(self$scores) <- list(NULL, score_names) - } - }, - - #' @description - #' Set scores for i-th step. - #' - #' @param i - #' Step number. - #' @param scores - #' Scores, a vector or a value if no auxiliary functions are used. - #' @param aggregated_scores - #' Vector of aggregated scores. Can be NULL. - #' - #' @return `OptimizationTrace` invisibly. - #' - #' @examples - #' tr$set_scores(1, c(0.5, 0.5), NULL) - #' tr$set_scores(2, c(0.5, 0.5), NULL) - set_scores = function(i, scores, aggregated_scores) { - assertthat::assert_that( - assertthat::is.count(i), - is.vector(scores), - is.null(aggregated_scores) || is.vector(aggregated_scores) - ) - # initialize aggregated scores, in case they're empty - self$scores[i, ] <- scores - if (!is.null(aggregated_scores)) { - if (is.null(self$aggregated_scores)) { - self$aggregated_scores <- matrix( - NA_real_, - nrow = nrow(self$scores), - ncol = length(aggregated_scores) - ) - } - assertthat::assert_that( - length(aggregated_scores) == ncol(self$aggregated_scores) - ) - self$aggregated_scores[i, ] <- aggregated_scores - } - self$last_step <- i - invisible(self) - }, - - #' @description - #' Shrink scores by keeping only first `last_step` scores. - #' - #' @param last_step - #' Last step to keep. - #' - #' @return `OptimizationTrace` invisibly. - #' @examples - #' tr$shrink(2) - shrink = function(last_step = self$last_step) { - self$scores <- head(self$scores, last_step) - if (!is.null(self$aggregated_scores)) { - self$aggregated_scores <- head(self$aggregated_scores, last_step) - } - invisible(self) - }, - - #' @description - #' Return individual (not aggregated!) scores by keeping only first `last_step` scores. - #' - #' @param last_step - #' Last step to keep. - #' - #' @return `OptimizationTrace` invisibly. - #' @examples - #' tr$get_scores() - get_scores = function(last_step = self$last_step) { - head(self$scores, last_step) - }, - - #' @description - #' Print `OptimizationTrace`. - #' - #' @param ... - #' Unused. - #' - #' @return `OptimizationTrace` invisibly. - #' @examples - #' print(tr) - print = function(...) { - start_score <- self$scores[1, ] %>% - round(3) %>% - stringr::str_c(collapse = ",") - final_score <- tail(self$scores, 1) %>% - round(3) %>% - stringr::str_c(collapse = ",") - cat(stringr::str_glue("Optimization trace ({self$n_steps} score values, elapsed {format(self$elapsed)}).\n\n")) - cat(" Starting score: ", start_score, "\n", sep = "") - cat(" Final score : ", final_score, "\n", sep = "") - invisible(self) - }, - - #' @description - #' Convert to a [data.frame]. - #' - #' @param include_aggregated Include aggregated scores. Otherwise only - #' raw scores are exported. - #' - #' @return [data.frame] - #' @examples - #' tr$as_tibble() - as_tibble = function(include_aggregated = TRUE) { - scores <- make_colnames(self$scores, "score") %>% - tibble::as_tibble() %>% - dplyr::mutate(step = dplyr::row_number()) %>% - tidyr::pivot_longer( - c(-step), - names_to = "score", - values_to = "value" - ) %>% - dplyr::mutate(score = factor(score)) - if (include_aggregated) { - agg_scores <- self$aggregated_scores - } else { - agg_scores <- NULL - } - if (!is.null(agg_scores) && include_aggregated) { - colnames(agg_scores) <- stringr::str_c( - "agg.", seq_len(ncol(agg_scores)) - ) - agg_scores <- agg_scores %>% - tibble::as_tibble() %>% - dplyr::mutate(step = dplyr::row_number()) %>% - tidyr::pivot_longer( - c(-step), - names_to = "score", - values_to = "value" - ) %>% - dplyr::mutate(score = factor(score)) - } - dplyr::bind_rows( - score = scores, - aggregated = agg_scores, - .id = "type" - ) %>% - dplyr::mutate(type = factor(type, levels = c("score", "aggregated"))) - }, - - #' @description - #' Plot `OptimizationTrace`. Only the main score at the moment. - #' - #' @param include_aggregated Include aggregated scores. Otherwise only - #' raw scores are plotted. - #' @param ... - #' Not used. - #' - #' @examples - #' tr <- OptimizationTrace$new(10, 3, letters[1:3]) - #' for (i in seq_len(10)) { - #' tr$set_scores(i, rnorm(3)*(1:3), rnorm(3)*(1:3)) - #' } - #' - #' # plot only the main scores - #' plot(tr) - #' # plot main and aggregated scores - #' plot(tr, include_aggregated=TRUE) - plot = function(include_aggregated = FALSE, ...) { - p <- self$as_tibble(include_aggregated = include_aggregated) %>% - ggplot2::ggplot() + - ggplot2::aes(x = step, y = value, group = score, color = score) + - ggplot2::geom_point() + - ggplot2::geom_line() - - if (include_aggregated) { - p + - ggplot2::facet_wrap(~type, scales = "free_y", ncol = 1) - } else { - p + - ggplot2::facet_wrap(~score, scales = "free_y", ncol = 1) - } - } - ), - active = list( - #' @field n_steps - #' Returns number of steps in the `OptimizationTrace`. - n_steps = function(value) { - if (missing(value)) { - nrow(self$scores) - } else { - stop("Cannot set n_steps (read-only).") - } - } - ) -) - #' Make [matrix] column names unique. #' #' @param prefix Prefix to add if column names are empty. @@ -251,3 +14,20 @@ make_colnames <- function(m, prefix = "X") { colnames(m) <- make.names(colnames(m), unique = TRUE) m } + +#' Shrinks a matrix with scores and adds an iteration column. +#' +#' @param m input matrix +#' @param last_iteration last iteration +#' +#' @return a [tibble::tibble()] wrapped in a list +#' @keywords internal +shrink_mat <- function(m, last_iteration) { + if (is.null(m)) + return(m) + dplyr::bind_cols( + tibble::tibble(step=seq_len(last_iteration)), + as.data.frame(head(m, last_iteration)) + ) %>% + list() +} diff --git a/man/examples/assignment.R b/man/examples/assignment.R index e6604410..bc1b2a9f 100644 --- a/man/examples/assignment.R +++ b/man/examples/assignment.R @@ -6,9 +6,9 @@ bc set.seed(42) # assigns samples randomly -assign_random(bc, samples) +bc <- assign_random(bc, samples) bc$get_samples() # assigns samples in order -assign_in_order(bc) +bc <- assign_in_order(bc) bc$get_samples() diff --git a/tests/testthat/test-compute-score-vector.R b/tests/testthat/test-compute-score-vector.R new file mode 100644 index 00000000..205cc8ac --- /dev/null +++ b/tests/testthat/test-compute-score-vector.R @@ -0,0 +1,21 @@ +test_that("bc$score() produces correct vector names", { + bc <- BatchContainer$new( + dimensions = c(row = 3, column = 3) + ) + samp <- data.frame(sid = 1) + bc <- assign_in_order(bc, samp) + expect_equal( + bc$score( + list( + a = function(...) c(1, 2), + b = function(...) c(1), + c = function(...) c(x=1, y=2), + d = function(...) c(1) + ) + ), + setNames( + c(1, 2, 1, 1, 2, 1), + c("a1", "a2", "b", "cx", "cy", "d") + ) + ) +}) From dde80ea683a77f3e8cb5c74cc66c59e8cf5b0e58 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 13 Jul 2023 11:28:10 +0200 Subject: [PATCH 02/32] plot_plate and scores_tibble can support multiple optimization --- R/batch_container.R | 78 +++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 28 deletions(-) diff --git a/R/batch_container.R b/R/batch_container.R index 18efc1b4..946acef9 100644 --- a/R/batch_container.R +++ b/R/batch_container.R @@ -442,59 +442,81 @@ BatchContainer <- R6::R6Class("BatchContainer", ), #' @description - #' Return a tibble with scores from the last optimization. + #' Return a tibble with scores from an optimization. #' - #' @param include_aggregated shall aggregated scores be included + #' @param index optimization index, all by default + #' @param include_aggregated include aggregated scores #' @return a [tibble::tibble()] with scores - last_optimization_tibble = function(include_aggregated = FALSE) { + scores_tibble = function(index = NULL, include_aggregated = FALSE) { assertthat::assert_that( tibble::is_tibble(self$trace), nrow(self$trace) >= 1, msg = "trace should be available" ) assertthat::assert_that(assertthat::is.flag(include_aggregated)) - d <- tail(self$trace, 1)$scores[[1]] %>% - tidyr::pivot_longer(-.data$step, + if (is.null(index)) { + index <- self$trace$optimization_index + } + assertthat::assert_that( + rlang::is_integerish(index), + msg = "index should be an integer" + ) + d <- self$trace %>% + dplyr::filter(.data$optimization_index %in% index) %>% + dplyr::select(.data$optimization_index, .data$scores) %>% + tidyr::unnest(.data$scores) %>% + tidyr::pivot_longer(c(-.data$optimization_index, -.data$step), names_to = "score", - values_to = "value") - d$aggregated <- FALSE + values_to = "value") %>% + dplyr::mutate(aggregated = FALSE) if (include_aggregated) { - d_agg <- tail(self$trace, 1)$aggregated_scores[[1]] - if (!is.null(d_agg)) { - d_agg <- tidyr::pivot_longer( - d_agg, - -.data$step, - names_to = "score", - values_to = "value" - ) - d_agg$score <- paste0("agg.", d_agg$score) - d_agg$aggregated <- TRUE - d <- dplyr::bind_rows( - d, - d_agg - ) + d_agg <- self$trace %>% + dplyr::filter(.data$optimization_index %in% index) %>% + dplyr::select(.data$optimization_index, .data$aggregated_scores) %>% + tidyr::unnest(.data$aggregated_scores) + + if ("step" %in% colnames(d_agg)) { + # if no aggregated scores are provided (aggregated_scores=NULL), + # there will be no step column after unnesting + d_agg <- d_agg %>% + tidyr::pivot_longer(c(-.data$optimization_index, -.data$step), + names_to = "score", + values_to = "value") %>% + dplyr::mutate( + aggregated = TRUE, + score = paste0("agg.", .data$score) + ) + d <- dplyr::bind_rows( + d, + d_agg + ) } - } else { - d_agg <- NULL } d }, #' @description #' Plot trace - plot_trace = function(include_aggregated = FALSE, ...) { - d <- self$last_optimization_tibble(include_aggregated) + #' @param index optimization index, all by default + #' @param include_aggregated include aggregated scores + #' @return a [ggplot2::ggplot()] object + plot_trace = function(index = NULL, include_aggregated = FALSE, ...) { + d <- self$scores(index, include_aggregated) p <- ggplot2::ggplot(d) + ggplot2::aes(.data$step, .data$value, group = .data$score, color = .data$score) + ggplot2::geom_line() + ggplot2::geom_point() - if (include_aggregated && any(d$aggregated)) { + if (length(unique(d$optimization_index)) > 1) { p <- p + - ggplot2::facet_wrap(~ score, scales = "free_y", ncol = 1) - } else { + ggplot2::facet_wrap(~ optimization_index, scales = "free") + } else if (include_aggregated && any(d$aggregated)) { p <- p + ggplot2::facet_wrap(~ aggregated, scales = "free_y", ncol = 1) + } else { + p <- p + + ggplot2::facet_wrap(~ score, scales = "free_y", ncol = 1) } + p } ), private = list( From 996339fd9e5415ba68d638258cd947afebe3fd19 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 13 Jul 2023 11:28:40 +0200 Subject: [PATCH 03/32] update NCS22 and basic examples vignettes to use read-only bc --- vignettes/NCS22_talk.Rmd | 50 +++++++++++++++++------------------- vignettes/basic_examples.Rmd | 37 +++++++++++++------------- 2 files changed, 41 insertions(+), 46 deletions(-) diff --git a/vignettes/NCS22_talk.Rmd b/vignettes/NCS22_talk.Rmd index 07746f40..7580c73f 100644 --- a/vignettes/NCS22_talk.Rmd +++ b/vignettes/NCS22_talk.Rmd @@ -57,16 +57,8 @@ set.seed(17) # gives `bad` random assignment bc <- BatchContainer$new( dimensions = list("batch" = 3, "location" = 11), -) - -bc$scoring_f <- list( - group = osat_score_generator(batch_vars = "batch", - feature_vars = "Group"), - sex = osat_score_generator(batch_vars = "batch", - feature_vars = "Sex") -) - -assign_random(bc, subject_data) +) %>% + assign_random(subject_data) ``` Gone wrong: Random distribution of 31 grouped subjects into 3 batches @@ -119,14 +111,8 @@ set.seed(17) # gives `bad` random assignment ```{r} bc <- BatchContainer$new( dimensions = list("batch" = 3, "location" = 11), -) -bc$scoring_f <- list( - group = osat_score_generator(batch_vars = "batch", - feature_vars = "Group"), - sex = osat_score_generator(batch_vars = "batch", - feature_vars = "Sex") -) -assign_random(bc, subject_data) +) %>% + assign_random(subject_data) ``` @@ -172,10 +158,20 @@ bind_rows(head(bc$get_samples(), 3) %>% * sex (lower priority) ```{r, warning=FALSE} -trace <- optimize_design( +bc <- optimize_design( bc, + scoring = list( + group = osat_score_generator( + batch_vars = "batch", + feature_vars = "Group" + ), + sex = osat_score_generator( + batch_vars = "batch", + feature_vars = "Sex" + ) + ), n_shuffle = 1, - acceptance_func = + acceptance_func = ~ accept_leftmost_improvement(..., tolerance = 0.01), max_iter = 150, quiet = TRUE @@ -193,7 +189,7 @@ cowplot::plot_grid( bc$get_samples() %>% ggplot(aes(x = batch, fill = Sex)) + geom_bar() + labs(y = "subject count"), - trace$plot(include_aggregated = TRUE) + bc$plot_trace(include_aggregated = TRUE) ), ncol = 3 ) @@ -243,9 +239,9 @@ set.seed(1) #1 #2 bc <- BatchContainer$new( dimensions = list("plate" = 3, "row" = 4, "col" = 6), -) -assign_random(bc, dat) -#assign_in_order(bc, dat) +) %>% + assign_random(dat) +# assign_in_order(dat) ``` ```{r, fig.width= 5, fig.height=4.5, eval=FALSE} @@ -276,7 +272,7 @@ cowplot::plot_grid( ## Spatial arrangement ```{r, warning=FALSE, message=FALSE} -traces <- optimize_multi_plate_design( +bc <- optimize_multi_plate_design( bc, across_plates_variables = c("Group", "Sex"), within_plate_variables = c("Group"), @@ -299,8 +295,8 @@ cowplot::plot_grid( ) ``` -```{r fig.width=3, fig.height=3, echo=FALSE} -purrr::imap(traces, ~ .x$plot(include_aggregated=TRUE) + labs(title = .y))[1:2] +```{r fig.width=5, fig.height=4, echo=FALSE} +bc$plot_trace() ``` diff --git a/vignettes/basic_examples.Rmd b/vignettes/basic_examples.Rmd index 0f53c09f..66144d0f 100644 --- a/vignettes/basic_examples.Rmd +++ b/vignettes/basic_examples.Rmd @@ -102,7 +102,7 @@ bc$get_locations() %>% head() Use random assignment function to place samples to plate locations ```{r} -assign_random(bc, samples) +bc <- assign_random(bc, samples) bc$get_samples() bc$get_samples(remove_empty_locations = TRUE) @@ -128,7 +128,9 @@ plot_plate(bc$get_samples(remove_empty_locations = TRUE), To move individual samples or manually assigning all locations we can use the `batchContainer$move_samples()` method -To swap two or more samples use +To swap two or more samples use: + +**Warning**: This will change your BatchContainer in-place. ```{r, fig.width=6, fig.height=3.5} bc$move_samples(src = c(1L, 2L), dst = c(2L, 1L)) @@ -140,6 +142,9 @@ plot_plate(bc$get_samples(remove_empty_locations = TRUE), ``` To assign all samples in one go, use the option `location_assignment`. + +**Warning**: This will change your BatchContainer in-place. + The example below orders samples by ID and adds the empty locations afterwards ```{r, fig.width=6, fig.height=3.5} bc$move_samples( @@ -155,29 +160,23 @@ plot_plate(bc$get_samples(remove_empty_locations = TRUE, include_id = TRUE), ) ``` -## Scoring a layout - -To evaluate how good a layout is, we need a scoring function. -This we also assign to the batch container. - -This function will assess how well treatment -and dose are balanced across the two plates. - -```{r} -bc$scoring_f <- osat_score_generator( - batch_vars = "plate", - feature_vars = c("treatment", "dose") -) -``` - ## Run an optimization The optimization procedure is invoked with e.g. `optimize_design`. Here we use a simple shuffling schedule: swap 10 samples for 100 times, then swap 2 samples for 400 times. +To evaluate how good a layout is, we need a scoring function. + +This function will assess how well treatment +and dose are balanced across the two plates. + ```{r} -trace <- optimize_design(bc, +bc <- optimize_design(bc, + scoring = osat_score_generator( + batch_vars = "plate", + feature_vars = c("treatment", "dose") + ), # shuffling schedule n_shuffle = c(rep(10, 200), rep(2, 400)) ) @@ -186,7 +185,7 @@ trace <- optimize_design(bc, Development of the score can be viewed with ```{r, fig.width=3.5, fig.height=3} -trace$plot() +bc$plot_trace() ``` The layout after plate batching looks the following From c710b02388735e7122883bb9f6c091b665feeedc Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:45:59 +0200 Subject: [PATCH 04/32] move scoring preprocessing/validation to $score() for consistency the behaviour of optimize_design and bc$score() should be identical now --- R/batch_container.R | 16 ++++++++++++++-- R/optimize.R | 12 ------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/R/batch_container.R b/R/batch_container.R index 946acef9..4ebac8c0 100644 --- a/R/batch_container.R +++ b/R/batch_container.R @@ -328,8 +328,8 @@ BatchContainer <- R6::R6Class("BatchContainer", #' @description #' Score current sample assignment, - #' @param scoring names list of scoring functions. Each functin should - #' return a numeric vector. + #' @param scoring a function or a names list of scoring functions. + #' Each function should return a numeric vector. #' @return Returns a named vector of all scoring functions values. score = function(scoring) { assertthat::assert_that( @@ -337,6 +337,18 @@ BatchContainer <- R6::R6Class("BatchContainer", !is.null(scoring), msg = "Scoring function needs to be provided" ) + if (is.function(scoring)) { + scoring <- list(scoring) + } else { + assertthat::assert_that(is.list(scoring), length(scoring) >= 1) + assertthat::assert_that( + all(purrr::map_lgl(scoring, is.function)), + msg = "All elements of scoring should be functions" + ) + } + if (is.null(names(scoring))) { + names(scoring) <- stringr::str_c("score_", seq_along(scoring)) + } assertthat::assert_that(is.list(scoring), length(scoring) >= 1, msg = "Scroring function should be a non-empty list" diff --git a/R/optimize.R b/R/optimize.R index c8951a41..be82f3eb 100644 --- a/R/optimize.R +++ b/R/optimize.R @@ -215,18 +215,6 @@ optimize_design <- function(batch_container, samples = NULL, !is.null(scoring), msg = "Scoring should be provided when calling optimize_design()" ) - if (is.function(scoring)) { - scoring <- list(scoring) - } else { - assertthat::assert_that(is.list(scoring), length(scoring) >= 1) - assertthat::assert_that( - all(purrr::map_lgl(scoring, is.function)), - msg = "All elements of scoring should be functions" - ) - } - if (is.null(names(scoring))) { - names(scoring) <- stringr::str_c("score_", seq_along(scoring)) - } # Get assigned samples and locations from the batch container samp <- batch_container$get_samples(include_id = TRUE, assignment = TRUE, remove_empty_locations = FALSE) From 678fba3e95f98485ae9386d4df1b03cc22ccab76 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:46:29 +0200 Subject: [PATCH 05/32] rename scores_tibble to scores_table --- R/batch_container.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/batch_container.R b/R/batch_container.R index 4ebac8c0..7824ab24 100644 --- a/R/batch_container.R +++ b/R/batch_container.R @@ -454,12 +454,12 @@ BatchContainer <- R6::R6Class("BatchContainer", ), #' @description - #' Return a tibble with scores from an optimization. + #' Return a table with scores from an optimization. #' #' @param index optimization index, all by default #' @param include_aggregated include aggregated scores #' @return a [tibble::tibble()] with scores - scores_tibble = function(index = NULL, include_aggregated = FALSE) { + scores_table = function(index = NULL, include_aggregated = FALSE) { assertthat::assert_that( tibble::is_tibble(self$trace), nrow(self$trace) >= 1, From b2437567474ec96922b73cef85653f1402f87712 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:47:20 +0200 Subject: [PATCH 06/32] when facet-plotting aggregated scores show nicer labes (T/F -> agg/score) --- R/batch_container.R | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/R/batch_container.R b/R/batch_container.R index 7824ab24..3f3cc915 100644 --- a/R/batch_container.R +++ b/R/batch_container.R @@ -513,7 +513,10 @@ BatchContainer <- R6::R6Class("BatchContainer", #' @param include_aggregated include aggregated scores #' @return a [ggplot2::ggplot()] object plot_trace = function(index = NULL, include_aggregated = FALSE, ...) { - d <- self$scores(index, include_aggregated) + d <- self$scores_table(index, include_aggregated) %>% + dplyr::mutate( + agg_title = dplyr::if_else(.data$aggregated, "aggregated", "score") + ) p <- ggplot2::ggplot(d) + ggplot2::aes(.data$step, .data$value, group = .data$score, color = .data$score) + ggplot2::geom_line() + @@ -523,7 +526,7 @@ BatchContainer <- R6::R6Class("BatchContainer", ggplot2::facet_wrap(~ optimization_index, scales = "free") } else if (include_aggregated && any(d$aggregated)) { p <- p + - ggplot2::facet_wrap(~ aggregated, scales = "free_y", ncol = 1) + ggplot2::facet_wrap(~ agg_title, scales = "free_y", ncol = 1) } else { p <- p + ggplot2::facet_wrap(~ score, scales = "free_y", ncol = 1) From 1dd126d3c37d8d39657f6a620b0d4c8938d8fdcb Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:48:12 +0200 Subject: [PATCH 07/32] adapt custom_shuffle and optimizer vignettes to the new bc behaviour --- vignettes/custom_shuffle.Rmd | 26 +++--- vignettes/optimizer_examples.Rmd | 154 ++++++++++++++----------------- 2 files changed, 80 insertions(+), 100 deletions(-) diff --git a/vignettes/custom_shuffle.Rmd b/vignettes/custom_shuffle.Rmd index 6d835657..1dec234f 100644 --- a/vignettes/custom_shuffle.Rmd +++ b/vignettes/custom_shuffle.Rmd @@ -55,8 +55,8 @@ We start by assigning samples randomly. set.seed(42) bc <- BatchContainer$new( dimensions = c("cage" = 11, "position" = 5) -) -assign_random(bc, samples) +) %>% + assign_random(samples) bc ``` @@ -96,15 +96,14 @@ and `sex` interactions are considered in the scoring function. We only use 10 it shuffling is limited to locations with males and enforces change of cage on every iteration. ```{r} -bc$scoring_f <- osat_score_generator( - "cage", - "sex" -) - set.seed(10) -res <- optimize_design( +bc <- optimize_design( bc, + scoring = osat_score_generator( + "cage", + "sex" + ), shuffle_proposal_func = shuffle_with_constraints( sex == "M", cage != .src$cage @@ -112,7 +111,7 @@ res <- optimize_design( max_iter = 10 ) -plot(res) +bc$plot_trace() ``` We expect the distribution of males become even, while other variables are not significantly affected. @@ -129,7 +128,7 @@ locations. We also ensure that on every iteration the cage number is changed; we `position` dimension does affect actual animal allocation. ```{r} -bc$scoring_f <- function(bc) { +scoring_f <- function(bc) { samples <- bc$get_samples(include_id = TRUE, as_tibble = FALSE) avg_w <- samples[, mean(weight, na.rm = TRUE)] avg_w_per_cage <- samples[!is.na(weight), mean(weight), by = cage]$V1 @@ -141,7 +140,8 @@ bc$scoring_f <- function(bc) { } set.seed(12) -res <- optimize_design(bc, +bc <- optimize_design(bc, + scoring = scoring_f, shuffle_proposal = shuffle_with_constraints( sex == "F", cage != .src$cage & (is.na(sex) | sex != "M") @@ -149,8 +149,8 @@ res <- optimize_design(bc, n_shuffle = c(rep(10, 20), rep(5, 20), rep(3, 20), rep(1, 140)), max_iter = 200 ) -plot(res) -bc$score() +bc$plot_trace() +scoring_f(bc) ``` Now we have a much more even distribution of weights and treatment/control balance. diff --git a/vignettes/optimizer_examples.Rmd b/vignettes/optimizer_examples.Rmd index c707c416..be3bf7de 100644 --- a/vignettes/optimizer_examples.Rmd +++ b/vignettes/optimizer_examples.Rmd @@ -17,6 +17,7 @@ knitr::opts_chunk$set( ```{r setup} library(designit) +library(magrittr) ``` @@ -60,12 +61,9 @@ bc <- BatchContainer$new( run = 2, position = 5 ), exclude = tibble::tibble(batch = 4, run = c(1, 2), position = c(5, 5)) -) - -# Add samples to container -assign_in_order(bc, samples = multi_trt_day_samples) -# Set scoring function -bc$scoring_f <- osat_score_generator(c("batch"), c("Treatment", "Time")) +) %>% + # Add samples to container + assign_in_order(samples = multi_trt_day_samples) bc ``` @@ -85,15 +83,15 @@ Optimization finishes after the list of permutations is exhausted. ```{r} n_shuffle <- rep(c(32, 10, 5, 2, 1), c(20, 40, 40, 50, 50)) +scoring_f <- osat_score_generator(c("batch"), c("Treatment", "Time")) -bc1 <- bc$copy() - -trace1 <- optimize_design( - bc1, +bc1 <- optimize_design( + bc, + scoring = scoring_f, n_shuffle = n_shuffle # will implicitly generate a shuffling function according to the provided schedule ) -trace1$elapsed +bc1$trace$elapsed ``` ## Optimization trace @@ -101,14 +99,27 @@ trace1$elapsed Custom plot with some colours: ```{r, fig.width=5, fig.height= 4} -ggplot2::qplot(x = seq_along(trace1$scores), y = trace1$scores, color = factor(n_shuffle)[1:length(trace1$scores)], geom = "point") + - ggplot2::labs(title = "Score 1 tracing", subtitle = stringr::str_glue("Final score = {bc1$score()}"), x = "Iteration", y = "Score", color = "n_shuffle") +bc1$scores_table() %>% + dplyr::mutate( + n_shuffle = c(NA, n_shuffle) + ) %>% + ggplot2::ggplot( + ggplot2::aes(step, value, color = factor(n_shuffle)) + ) + + ggplot2::geom_point() + + ggplot2::labs( + title = "Score 1 tracing", + subtitle = stringr::str_glue("Final score = {bc1$score(scoring_f)}"), + x = "Iteration", + y = "Score", + color = "n_shuffle" + ) ``` Using the internal method... ```{r, fig.width=5, fig.height= 4} -trace1$plot() +bc1$plot_trace() ``` We may safely apply the batch container methods get_samples() and score() also @@ -116,7 +127,7 @@ after using the new optimization code. ## Final batch layout ```{r, fig.width=6, fig.height=5} -bc1$score() +bc1$score(scoring_f) bc1$get_samples(assignment = TRUE) %>% dplyr::filter(!is.na(Treatment)) %>% @@ -136,8 +147,9 @@ immediately on the same batch container. ```{r} n_shuffle <- rep(c(5, 2, 1), c(30, 30, 30)) -optimize_design( +bc1 <- optimize_design( bc1, + scoring = scoring_f, n_shuffle = n_shuffle ) ``` @@ -154,10 +166,9 @@ reaching a specific minimum delta threshold (score improvement from one selected solution to the next). ```{r} -bc2 <- bc$copy() - -optimize_design( - bc2, +bc2 <- optimize_design( + bc, + scoring = scoring_f, n_shuffle = 3, # will implicitly generate a shuffling function that will do 3 swaps at each iteration max_iter = 2000, min_delta = 0.1 @@ -168,7 +179,7 @@ optimize_design( # Optimization with multi-variate scoring function Instead of passing a single scoring function, a list of multiple scoring -functions can be assigned to a batch container, each of which to return a scalar +functions can be passed to the optimizer, each of which to return a scalar value on evaluation. By default, a strict improvement rule is applied for classifying a potential @@ -182,16 +193,15 @@ The second scoring function used here is by the way rather redundant and just serves for illustration. ```{r} -bc3 <- bc$copy() - -bc3$scoring_f <- list( +multi_scoring_f <- list( osat_score_generator(c("batch"), c("Treatment", "Time")), osat_score_generator(c("batch"), c("Treatment")) ) -trace <- optimize_design( - bc3, +bc3 <- optimize_design( + bc, + scoring = multi_scoring_f, n_shuffle = 3, max_iter = 200, min_delta = 0.1 @@ -216,16 +226,9 @@ We may also want to decrease the delta_min parameter to match the new numerical range. ```{r} -bc3_as <- bc$copy() - -bc3_as$scoring_f <- list( - osat_score_generator(c("batch"), c("Treatment", "Time")), - osat_score_generator(c("batch"), c("Treatment")) -) - - -trace <- optimize_design( - bc3_as, +bc3_as <- optimize_design( + bc, + scoring = multi_scoring_f, n_shuffle = 3, max_iter = 200, min_delta = 0.01, @@ -245,16 +248,9 @@ simply set the aggregated score to whichever of the individual scores is larger ```{r} -bc4 <- bc$copy() - -bc4$scoring_f <- list( - osat_score_generator(c("batch"), c("Treatment", "Time")), - osat_score_generator(c("batch"), c("Treatment")) -) - - -optimize_design( - bc4, +bc4 <- optimize_design( + bc, + scoring = multi_scoring_f, n_shuffle = 3, aggregate_scores_func = worst_score, max_iter = 200, @@ -271,15 +267,9 @@ For illustration, we omit the `n_shuffle` parameter here, which will lead by default to pairwise sample swaps being done on each iteration. ```{r, eval = FALSE} -bc5 <- bc$copy() - -bc5$scoring_f <- list( - osat_score_generator(c("batch"), c("Treatment", "Time")), - osat_score_generator(c("batch"), c("Treatment")) -) - -optimize_design( - bc5, +bc5 <- optimize_design( + bc, + scoring = multi_scoring_f, aggregate_scores_func = sum_scores, max_iter = 200, autoscale_scores = TRUE, @@ -295,15 +285,9 @@ Note that we don't use the auto-scaling in this case as the L2-norm based optimi not the minimal (negative) value that would be desired in that case. ```{r, eval = FALSE} -bc5_2 <- bc$copy() - -bc5_2$scoring_f <- list( - osat_score_generator(c("batch"), c("Treatment", "Time")), - osat_score_generator(c("batch"), c("Treatment")) -) - -optimize_design( - bc5_2, +bc5_2 <- optimize_design( + bc, + scoring = multi_scoring_f, aggregate_scores_func = L2s_norm, max_iter = 200, ) @@ -322,10 +306,9 @@ across all available positions in the batch container. Note that this is usually not a good strategy for converging to a solution. ```{r} -bc6 <- bc$copy() - -optimize_design( - bc6, +bc6 <- optimize_design( + bc, + scoring = scoring_f, shuffle_proposal_func = complete_random_shuffling, max_iter = 200 ) @@ -349,10 +332,9 @@ to be optimized. Choose an appropriate aggregation function if you happen to have multiple scores initially. ```{r} -bc7 <- bc$copy() - -trace7 <- optimize_design( - bc7, +bc7 <- optimize_design( + bc, + scoring = scoring_f, n_shuffle = 1, acceptance_func = mk_simanneal_acceptance_func(), max_iter = 200 @@ -362,23 +344,22 @@ The trace may show a non strictly monotonic behavior now, reflecting the SA protocol at work. ```{r, fig.width=5, fig.height= 4} -trace7$plot() +bc7$plot_trace() ``` Better results and quicker convergence may be achieved by playing with the starting temperature (T0) and cooling speed (alpha) in a specific case. ```{r} -bc8 <- bc$copy() - -trace8 <- optimize_design( - bc8, +bc8 <- optimize_design( + bc, + scoring = scoring_f, n_shuffle = 1, acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 100, alpha = 2)), max_iter = 150 ) -trace8$plot() +bc8$plot_trace() ``` # Full blown example @@ -387,16 +368,15 @@ The following example puts together all possible options to illustrate the flexibility of the optimization. ```{r} -bc$scoring_f <- list( - osat_score_generator(c("batch"), c("Treatment", "Time")), - osat_score_generator(c("batch"), c("Treatment")), - osat_score_generator(c("batch"), c("Time")) -) - n_shuffle <- rep(c(3, 2, 1), c(20, 20, 200)) -trace <- optimize_design( +bc9 <- optimize_design( bc, + scoring = list( + osat_score_generator(c("batch"), c("Treatment", "Time")), + osat_score_generator(c("batch"), c("Treatment")), + osat_score_generator(c("batch"), c("Time")) + ), n_shuffle = n_shuffle, aggregate_scores_func = sum_scores, acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 500, alpha = 1)), @@ -405,9 +385,9 @@ trace <- optimize_design( autoscale_scores = T ) -trace$plot() +bc9$plot_trace() -bc$get_samples(assignment = TRUE) %>% +bc9$get_samples(assignment = TRUE) %>% dplyr::mutate(batch = factor(batch)) %>% ggplot2::ggplot(ggplot2::aes(x = batch, fill = Treatment, alpha = factor(Time))) + ggplot2::geom_bar() From dcfce24f1d3979dc02d9a57f3e4b5034b04d6b59 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:53:44 +0200 Subject: [PATCH 08/32] improve BatchContainer roxygen docs --- R/batch_container.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/batch_container.R b/R/batch_container.R index 3f3cc915..3f5e3775 100644 --- a/R/batch_container.R +++ b/R/batch_container.R @@ -440,8 +440,7 @@ BatchContainer <- R6::R6Class("BatchContainer", invisible(self) }, - #' @description - #' Optimization trace, a [tibble::tibble()] + #' @field trace Optimization trace, a [tibble::tibble()] trace = tibble::tibble( optimization_index = numeric(), call = list(), @@ -511,6 +510,7 @@ BatchContainer <- R6::R6Class("BatchContainer", #' Plot trace #' @param index optimization index, all by default #' @param include_aggregated include aggregated scores + #' @param ... not used. #' @return a [ggplot2::ggplot()] object plot_trace = function(index = NULL, include_aggregated = FALSE, ...) { d <- self$scores_table(index, include_aggregated) %>% From 69c29f8d3b98fb8e9d79a50fe1cc8c47baee1806 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 13 Jul 2023 14:16:16 +0200 Subject: [PATCH 09/32] remove warning from a vignette --- vignettes/NCS22_talk.Rmd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vignettes/NCS22_talk.Rmd b/vignettes/NCS22_talk.Rmd index 7580c73f..19c59010 100644 --- a/vignettes/NCS22_talk.Rmd +++ b/vignettes/NCS22_talk.Rmd @@ -138,13 +138,13 @@ bc$get_samples() ```{r, echo=FALSE} bind_rows(head(bc$get_samples(), 3) %>% - mutate(across(, as.character)), + mutate(across(everything(), as.character)), tibble(batch = "...", location = " ...", SubjectID = "...", Group = "...", Sex = "..."), tail(bc$get_samples(), 3) %>% - mutate(across(, as.character))) %>% + mutate(across(everything(), as.character))) %>% gt::gt() %>% gt::tab_options(table.font.size = 11, data_row.padding = 0.1) ``` @@ -198,13 +198,13 @@ cowplot::plot_grid( ```{r, echo=FALSE} bind_rows(head(bc$get_samples(), 3) %>% - mutate(across(, as.character)), + mutate(across(everything(), as.character)), tibble(batch = "...", location = " ...", SubjectID = "...", Group = "...", Sex = "..."), tail(bc$get_samples(), 3) %>% - mutate(across(, as.character))) %>% + mutate(across(everything(), as.character))) %>% gt::gt() %>% gt::tab_options(table.font.size = 11, data_row.padding = 0.1) ``` From fa7a88faa038321908f7e3d6ba1419f3ae4f90a7 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 13 Jul 2023 14:17:01 +0200 Subject: [PATCH 10/32] _doc --- DESCRIPTION | 2 +- NAMESPACE | 1 - man/BatchContainer.Rd | 75 +++++++- man/OptimizationTrace.Rd | 329 ---------------------------------- man/assign_from_table.Rd | 4 +- man/assign_in_order.Rd | 6 +- man/assign_random.Rd | 6 +- man/mk_autoscale_function.Rd | 4 + man/optimize_design.Rd | 9 +- man/random_score_variances.Rd | 10 +- man/sample_random_scores.Rd | 10 +- man/shrink_mat.Rd | 20 +++ 12 files changed, 127 insertions(+), 349 deletions(-) delete mode 100644 man/OptimizationTrace.Rd create mode 100644 man/shrink_mat.Rd diff --git a/DESCRIPTION b/DESCRIPTION index b38c59f7..36c7f444 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -74,7 +74,7 @@ Suggests: Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.1 +RoxygenNote: 7.2.3 VignetteBuilder: knitr biocViews: Remotes: diff --git a/NAMESPACE b/NAMESPACE index 2cc2cf16..e8a9d03c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -6,7 +6,6 @@ export(BatchContainer) export(BatchContainerDimension) export(L1_norm) export(L2s_norm) -export(OptimizationTrace) export(accept_leftmost_improvement) export(as_label) export(as_name) diff --git a/man/BatchContainer.Rd b/man/BatchContainer.Rd index db4a1e6b..e5c32ac8 100644 --- a/man/BatchContainer.Rd +++ b/man/BatchContainer.Rd @@ -27,6 +27,13 @@ bc <- BatchContainer$new( bc } +\section{Public fields}{ +\if{html}{\out{
}} +\describe{ +\item{\code{trace}}{Optimization trace, a \code{\link[tibble:tibble]{tibble::tibble()}}} +} +\if{html}{\out{
}} +} \section{Active bindings}{ \if{html}{\out{
}} \describe{ @@ -71,6 +78,8 @@ Assigning this field is deprecated, please use \verb{$move_samples()} instead.} \item \href{#method-BatchContainer-score}{\code{BatchContainer$score()}} \item \href{#method-BatchContainer-copy}{\code{BatchContainer$copy()}} \item \href{#method-BatchContainer-print}{\code{BatchContainer$print()}} +\item \href{#method-BatchContainer-scores_table}{\code{BatchContainer$scores_table()}} +\item \href{#method-BatchContainer-plot_trace}{\code{BatchContainer$plot_trace()}} } } \if{html}{\out{
}} @@ -200,11 +209,19 @@ The length of the vector should match the number of locations, \subsection{Method \code{score()}}{ Score current sample assignment, \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{BatchContainer$score()}\if{html}{\out{
}} +\if{html}{\out{
}}\preformatted{BatchContainer$score(scoring)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{scoring}}{a function or a names list of scoring functions. +Each function should return a numeric vector.} +} +\if{html}{\out{
}} +} \subsection{Returns}{ -Returns a vector of all scoring functions values. +Returns a named vector of all scoring functions values. } } \if{html}{\out{
}} @@ -232,16 +249,62 @@ Prints information about \code{BatchContainer}. \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{...}}{not used. +\item{\code{...}}{not used.} +} +\if{html}{\out{
}} +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-BatchContainer-scores_table}{}}} +\subsection{Method \code{scores_table()}}{ +Return a table with scores from an optimization. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{BatchContainer$scores_table(index = NULL, include_aggregated = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{index}}{optimization index, all by default} + +\item{\code{include_aggregated}}{include aggregated scores} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +a \code{\link[tibble:tibble]{tibble::tibble()}} with scores +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-BatchContainer-plot_trace}{}}} +\subsection{Method \code{plot_trace()}}{ +Plot trace +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{BatchContainer$plot_trace(index = NULL, include_aggregated = FALSE, ...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{index}}{optimization index, all by default} + +\item{\code{include_aggregated}}{include aggregated scores} + +\item{\code{...}}{not used.} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +a \code{\link[ggplot2:ggplot]{ggplot2::ggplot()}} object List of scoring functions. Tibble with batch container locations. Tibble with sample information and sample ids. Sample attributes, a data.table. Vector with assignment of sample ids to locations. Cached data.table with samples assignment. -Validate sample assignment.} -} -\if{html}{\out{
}} +Validate sample assignment. } } } diff --git a/man/OptimizationTrace.Rd b/man/OptimizationTrace.Rd deleted file mode 100644 index 0ee9731a..00000000 --- a/man/OptimizationTrace.Rd +++ /dev/null @@ -1,329 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/trace.R -\name{OptimizationTrace} -\alias{OptimizationTrace} -\title{OptimizationTrace represents optimization trace.} -\description{ -Usually it is created by \code{\link[=optimize_design]{optimize_design()}}. -} -\examples{ - -## ------------------------------------------------ -## Method `OptimizationTrace$new` -## ------------------------------------------------ - -tr <- OptimizationTrace$new(10, 2, c("score1", "score2")) - -## ------------------------------------------------ -## Method `OptimizationTrace$set_scores` -## ------------------------------------------------ - -tr$set_scores(1, c(0.5, 0.5), NULL) -tr$set_scores(2, c(0.5, 0.5), NULL) - -## ------------------------------------------------ -## Method `OptimizationTrace$shrink` -## ------------------------------------------------ - -tr$shrink(2) - -## ------------------------------------------------ -## Method `OptimizationTrace$get_scores` -## ------------------------------------------------ - -tr$get_scores() - -## ------------------------------------------------ -## Method `OptimizationTrace$print` -## ------------------------------------------------ - -print(tr) - -## ------------------------------------------------ -## Method `OptimizationTrace$as_tibble` -## ------------------------------------------------ - -tr$as_tibble() - -## ------------------------------------------------ -## Method `OptimizationTrace$plot` -## ------------------------------------------------ - -tr <- OptimizationTrace$new(10, 3, letters[1:3]) -for (i in seq_len(10)) { - tr$set_scores(i, rnorm(3)*(1:3), rnorm(3)*(1:3)) -} - -# plot only the main scores -plot(tr) -# plot main and aggregated scores -plot(tr, include_aggregated=TRUE) -} -\section{Public fields}{ -\if{html}{\out{
}} -\describe{ -\item{\code{scores}}{Contains a matrix of scores. The matrix size is usually -\code{c(iterations + 1, length(bc$scoring_f))}} - -\item{\code{aggregated_scores}}{Contains a matrix of scores after aggregation. -The matrix size is usually \code{c(iterations + 1, length(aggregated))}, -where \code{length(aggregated)} is the length of aggregated scores vector. -Can be \code{NULL} if aggregated scores are not used.} - -\item{\code{seed}}{Saved value of \link{.Random.seed}.} - -\item{\code{elapsed}}{Running time of the optimization.} - -\item{\code{last_step}}{Last iteration step for which the score was set.} -} -\if{html}{\out{
}} -} -\section{Active bindings}{ -\if{html}{\out{
}} -\describe{ -\item{\code{n_steps}}{Returns number of steps in the \code{OptimizationTrace}.} -} -\if{html}{\out{
}} -} -\section{Methods}{ -\subsection{Public methods}{ -\itemize{ -\item \href{#method-OptimizationTrace-new}{\code{OptimizationTrace$new()}} -\item \href{#method-OptimizationTrace-set_scores}{\code{OptimizationTrace$set_scores()}} -\item \href{#method-OptimizationTrace-shrink}{\code{OptimizationTrace$shrink()}} -\item \href{#method-OptimizationTrace-get_scores}{\code{OptimizationTrace$get_scores()}} -\item \href{#method-OptimizationTrace-print}{\code{OptimizationTrace$print()}} -\item \href{#method-OptimizationTrace-as_tibble}{\code{OptimizationTrace$as_tibble()}} -\item \href{#method-OptimizationTrace-plot}{\code{OptimizationTrace$plot()}} -\item \href{#method-OptimizationTrace-clone}{\code{OptimizationTrace$clone()}} -} -} -\if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-OptimizationTrace-new}{}}} -\subsection{Method \code{new()}}{ -Create a new \code{OptimizationTrace} object. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{OptimizationTrace$new(n_steps, n_scores, score_names)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{n_steps}}{Number of values to save. Usually \code{n_steps == iterations + 1}.} - -\item{\code{n_scores}}{Number of scoring functions.} - -\item{\code{score_names}}{Names of scoring functions.} -} -\if{html}{\out{
}} -} -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{tr <- OptimizationTrace$new(10, 2, c("score1", "score2")) -} -\if{html}{\out{
}} - -} - -} -\if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-OptimizationTrace-set_scores}{}}} -\subsection{Method \code{set_scores()}}{ -Set scores for i-th step. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{OptimizationTrace$set_scores(i, scores, aggregated_scores)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{i}}{Step number.} - -\item{\code{scores}}{Scores, a vector or a value if no auxiliary functions are used.} - -\item{\code{aggregated_scores}}{Vector of aggregated scores. Can be NULL.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -\code{OptimizationTrace} invisibly. -} -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{tr$set_scores(1, c(0.5, 0.5), NULL) -tr$set_scores(2, c(0.5, 0.5), NULL) -} -\if{html}{\out{
}} - -} - -} -\if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-OptimizationTrace-shrink}{}}} -\subsection{Method \code{shrink()}}{ -Shrink scores by keeping only first \code{last_step} scores. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{OptimizationTrace$shrink(last_step = self$last_step)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{last_step}}{Last step to keep.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -\code{OptimizationTrace} invisibly. -} -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{tr$shrink(2) -} -\if{html}{\out{
}} - -} - -} -\if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-OptimizationTrace-get_scores}{}}} -\subsection{Method \code{get_scores()}}{ -Return individual (not aggregated!) scores by keeping only first \code{last_step} scores. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{OptimizationTrace$get_scores(last_step = self$last_step)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{last_step}}{Last step to keep.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -\code{OptimizationTrace} invisibly. -} -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{tr$get_scores() -} -\if{html}{\out{
}} - -} - -} -\if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-OptimizationTrace-print}{}}} -\subsection{Method \code{print()}}{ -Print \code{OptimizationTrace}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{OptimizationTrace$print(...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{Unused.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -\code{OptimizationTrace} invisibly. -} -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{print(tr) -} -\if{html}{\out{
}} - -} - -} -\if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-OptimizationTrace-as_tibble}{}}} -\subsection{Method \code{as_tibble()}}{ -Convert to a \link{data.frame}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{OptimizationTrace$as_tibble(include_aggregated = TRUE)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{include_aggregated}}{Include aggregated scores. Otherwise only -raw scores are exported.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -\link{data.frame} -} -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{tr$as_tibble() -} -\if{html}{\out{
}} - -} - -} -\if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-OptimizationTrace-plot}{}}} -\subsection{Method \code{plot()}}{ -Plot \code{OptimizationTrace}. Only the main score at the moment. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{OptimizationTrace$plot(include_aggregated = FALSE, ...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{include_aggregated}}{Include aggregated scores. Otherwise only -raw scores are plotted.} - -\item{\code{...}}{Not used.} -} -\if{html}{\out{
}} -} -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{tr <- OptimizationTrace$new(10, 3, letters[1:3]) -for (i in seq_len(10)) { - tr$set_scores(i, rnorm(3)*(1:3), rnorm(3)*(1:3)) -} - -# plot only the main scores -plot(tr) -# plot main and aggregated scores -plot(tr, include_aggregated=TRUE) -} -\if{html}{\out{
}} - -} - -} -\if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-OptimizationTrace-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{OptimizationTrace$clone(deep = FALSE)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} -} diff --git a/man/assign_from_table.Rd b/man/assign_from_table.Rd index 8f802078..770d3cd8 100644 --- a/man/assign_from_table.Rd +++ b/man/assign_from_table.Rd @@ -16,7 +16,7 @@ the function will check if samples in \code{batch_container} are identical to th \code{samples} argument.} } \value{ -Returns \code{BatchContainer}, invisibly. +Returns a new \code{BatchContainer}. } \description{ Distributes samples based on a sample sheet. @@ -39,7 +39,7 @@ sample_sheet <- tibble::tribble( 2, "a", 3, 5, "TRT", ) # assign samples from the sample sheet -assign_from_table(bc, sample_sheet) +bc <- assign_from_table(bc, sample_sheet) bc$get_samples(remove_empty_locations = TRUE) diff --git a/man/assign_in_order.Rd b/man/assign_in_order.Rd index 2263f586..4fa43a7c 100644 --- a/man/assign_in_order.Rd +++ b/man/assign_in_order.Rd @@ -12,7 +12,7 @@ assign_in_order(batch_container, samples = NULL) \item{samples}{data.frame with samples.} } \value{ -Returns \code{BatchContainer}, invisibly. +Returns a new \code{BatchContainer}. } \description{ First sample is assigned to the first location, second @@ -27,10 +27,10 @@ bc set.seed(42) # assigns samples randomly -assign_random(bc, samples) +bc <- assign_random(bc, samples) bc$get_samples() # assigns samples in order -assign_in_order(bc) +bc <- assign_in_order(bc) bc$get_samples() } diff --git a/man/assign_random.Rd b/man/assign_random.Rd index 50afcbb3..5742e59a 100644 --- a/man/assign_random.Rd +++ b/man/assign_random.Rd @@ -12,7 +12,7 @@ assign_random(batch_container, samples = NULL) \item{samples}{data.frame with samples.} } \value{ -Returns \code{BatchContainer}, invisibly. +Returns a new \code{BatchContainer}. } \description{ Assignment function which distributes samples randomly. @@ -26,10 +26,10 @@ bc set.seed(42) # assigns samples randomly -assign_random(bc, samples) +bc <- assign_random(bc, samples) bc$get_samples() # assigns samples in order -assign_in_order(bc) +bc <- assign_in_order(bc) bc$get_samples() } diff --git a/man/mk_autoscale_function.Rd b/man/mk_autoscale_function.Rd index 3da74fab..c885e84d 100644 --- a/man/mk_autoscale_function.Rd +++ b/man/mk_autoscale_function.Rd @@ -6,6 +6,7 @@ \usage{ mk_autoscale_function( batch_container, + scoring, random_perm, use_boxcox = TRUE, sample_attributes_fixed = FALSE @@ -14,6 +15,9 @@ mk_autoscale_function( \arguments{ \item{batch_container}{An instance of \link{BatchContainer}.} +\item{scoring}{A named \code{\link[=list]{list()}} of scoring function. Each function should +return a vector of non-zero length.} + \item{random_perm}{Number of random sample permutations for the estimation of the scaling params.} \item{use_boxcox}{Logical; if TRUE and the \code{bestNormalize} package is available, boxcox transformations will be used to diff --git a/man/optimize_design.Rd b/man/optimize_design.Rd index f0f8ef6e..b9bf91f2 100644 --- a/man/optimize_design.Rd +++ b/man/optimize_design.Rd @@ -7,6 +7,7 @@ optimize_design( batch_container, samples = NULL, + scoring = NULL, n_shuffle = NULL, shuffle_proposal_func = NULL, acceptance_func = accept_strict_improvement, @@ -27,6 +28,8 @@ optimize_design( \item{samples}{A \code{data.frame} with sample information. Should be \code{NULL} if the \code{BatchContainer} already has samples in it.} +\item{scoring}{Scoring function or a named \code{\link[=list]{list()}} of scoring functions.} + \item{n_shuffle}{Vector of length 1 or larger, defining how many random sample swaps should be performed in each iteration. If length(n_shuffle)==1, this sets no limit to the number of iterations. Otherwise, the optimization @@ -87,7 +90,9 @@ data("invivo_study_samples") bc <- BatchContainer$new( dimensions = c("plate" = 2, "column" = 5, "row" = 6) ) -bc$scoring_f <- osat_score_generator("plate", "Sex") -optimize_design(bc, invivo_study_samples, max_iter = 100) +bc <- optimize_design(bc, invivo_study_samples, + scoring = osat_score_generator("plate", "Sex"), + max_iter = 100 +) plot_plate(bc$get_samples(), .col = Sex) } diff --git a/man/random_score_variances.Rd b/man/random_score_variances.Rd index a8451066..e539b28c 100644 --- a/man/random_score_variances.Rd +++ b/man/random_score_variances.Rd @@ -4,11 +4,19 @@ \alias{random_score_variances} \title{Estimate the variance of individual scores by a series of completely random sample permutations} \usage{ -random_score_variances(batch_container, random_perm, sample_attributes_fixed) +random_score_variances( + batch_container, + scoring, + random_perm, + sample_attributes_fixed +) } \arguments{ \item{batch_container}{An instance of \code{BatchContainer}.} +\item{scoring}{A named \code{\link[=list]{list()}} of scoring function. Each function should +return a vector of non-zero length.} + \item{random_perm}{Number of random sample permutations to be done.} \item{sample_attributes_fixed}{Logical; if FALSE, simulate a shuffle function that alters sample attributes at each iteration.} diff --git a/man/sample_random_scores.Rd b/man/sample_random_scores.Rd index d50f275e..f8546c6f 100644 --- a/man/sample_random_scores.Rd +++ b/man/sample_random_scores.Rd @@ -4,11 +4,19 @@ \alias{sample_random_scores} \title{Sample scores from a number of completely random sample permutations} \usage{ -sample_random_scores(batch_container, random_perm, sample_attributes_fixed) +sample_random_scores( + batch_container, + scoring, + random_perm, + sample_attributes_fixed +) } \arguments{ \item{batch_container}{An instance of \link{BatchContainer}.} +\item{scoring}{A named \code{\link[=list]{list()}} of scoring function. Each function should +return a vector of non-zero length.} + \item{random_perm}{Number of random sample permutations to be done.} \item{sample_attributes_fixed}{Logical; if \code{FALSE}, simulate a shuffle function that alters sample attributes at each iteration.} diff --git a/man/shrink_mat.Rd b/man/shrink_mat.Rd new file mode 100644 index 00000000..d3ffc2ec --- /dev/null +++ b/man/shrink_mat.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/trace.R +\name{shrink_mat} +\alias{shrink_mat} +\title{Shrinks a matrix with scores and adds an iteration column.} +\usage{ +shrink_mat(m, last_iteration) +} +\arguments{ +\item{m}{input matrix} + +\item{last_iteration}{last iteration} +} +\value{ +a \code{\link[tibble:tibble]{tibble::tibble()}} wrapped in a list +} +\description{ +Shrinks a matrix with scores and adds an iteration column. +} +\keyword{internal} From eb67a8586e88c024d375ba1ce2a61031969e6eb8 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 13 Jul 2023 14:39:00 +0200 Subject: [PATCH 11/32] end_assignment_vec was acquired via bc in gloabl env (error) --- R/optimize.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/optimize.R b/R/optimize.R index be82f3eb..bc2e7615 100644 --- a/R/optimize.R +++ b/R/optimize.R @@ -404,7 +404,7 @@ optimize_design <- function(batch_container, samples = NULL, trace$scores <- shrink_mat(scores_mat, iteration) trace$aggregated_scores <- shrink_mat(aggregated_scores_mat, iteration) trace$elapsed <- Sys.time() - start_time - trace$end_assignment_vec = list(bc$assignment) + trace$end_assignment_vec = list(batch_container$assignment) batch_container$trace <- dplyr::bind_rows( batch_container$trace, trace From fb8cce0c2ce90139d326448cb97367a78794ebc5 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Wed, 19 Jul 2023 10:15:19 +0200 Subject: [PATCH 12/32] fix msg typo Co-authored-by: Balazs Banfai <5557093+banfai@users.noreply.github.com> --- R/batch_container.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/batch_container.R b/R/batch_container.R index 3f5e3775..d35eb762 100644 --- a/R/batch_container.R +++ b/R/batch_container.R @@ -351,7 +351,7 @@ BatchContainer <- R6::R6Class("BatchContainer", } assertthat::assert_that(is.list(scoring), length(scoring) >= 1, - msg = "Scroring function should be a non-empty list" + msg = "Scoring function should be a non-empty list" ) assertthat::assert_that(!is.null(names(scoring)), msg = "scoring should be a named list" From a5d4d8b9907ffcc15d685696f464ab8a64cf5eef Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:25:59 +0200 Subject: [PATCH 13/32] all_equal_df function to replace depricated dplyr::all_equal --- R/all_equal_df.R | 45 ++++++++++++++++++++++ tests/testthat/test-all-equal.R | 68 +++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 R/all_equal_df.R create mode 100644 tests/testthat/test-all-equal.R diff --git a/R/all_equal_df.R b/R/all_equal_df.R new file mode 100644 index 00000000..c52e2044 --- /dev/null +++ b/R/all_equal_df.R @@ -0,0 +1,45 @@ +#' Compare two data.frames. +#' +#' This will convert factors to characters and disregard +#' row and column order +#' +#' @param df1 first [data.frame()] to compare +#' @param df2 second `data.frame()` to compare +#' @return `TRUE` or `FALSE` in case differences are present +#' @keywords internal +all_equal_df <- function(df1, df2) { + if (!is.data.frame(df1) || !is.data.frame(df2)) { + return(FALSE) + } + + if (nrow(df1) != nrow(df2) || ncol(df1) != ncol(df2)) { + return(FALSE) + } + + assertthat::assert_that( + !any(duplicated(colnames(df1))), + !any(duplicated(colnames(df2))), + msg = "duplicated colnames" + ) + + df2 <- df2[colnames(df1)] + + # convert factors to characters + df1 <- df1 |> + dplyr::mutate(dplyr::across(dplyr::where(is.factor), as.character)) + df2 <- df2 |> + dplyr::mutate(dplyr::across(dplyr::where(is.factor), as.character)) + + # order by all columns + df1 <- df1[do.call(order, df1),] + df2 <- df2[do.call(order, df2),] + + # remove row names + rownames(df1) <- NULL + rownames(df2) <- NULL + + assertthat::are_equal( + all.equal(df1, df2, check.attributes = FALSE), + TRUE + ) +} diff --git a/tests/testthat/test-all-equal.R b/tests/testthat/test-all-equal.R new file mode 100644 index 00000000..24673d8c --- /dev/null +++ b/tests/testthat/test-all-equal.R @@ -0,0 +1,68 @@ +test_that("basic all_equal_df behavior", { + expect_true(all_equal_df(iris, iris)) + + x <- iris + expect_true(all_equal_df(x, iris)) + # add some NAs + x[1, 2] <- 20 + expect_false(all_equal_df(x, iris)) + + # add some NAs + x <- iris + x[1, 1] <- NA + expect_false(all_equal_df(x, iris)) +}) + +test_that("all_equal_df on empty dfs", { + expect_true(all_equal_df(data.frame(), data.frame())) + expect_true(all_equal_df( + data.frame(a=character(0), b=integer(0)), + data.frame(a=character(0), b=integer(0)) + )) +}) + +test_that("all_equal_df on NA dfs", { + expect_true(all_equal_df( + data.frame(a=rep(NA_character_,10), b=rep(NA_integer_, 10)), + data.frame(a=rep(NA_character_,10), b=rep(NA_integer_, 10)) + )) + x <- data.frame(a=rep(NA_character_,10), b=rep(NA_integer_, 10)) + y <- x + x[3, "b"] = 2 + expect_false(all_equal_df(x, y)) +}) + + +test_that("all_equal_df on reorder", { + for (i in 1:10) { + row_reorder <- iris[sample(seq_len(nrow(iris))),] + expect_true(all_equal_df(iris, row_reorder)) + } + + for (i in 1:10) { + col_reorder <- iris[sample(colnames(iris))] + expect_true(all_equal_df(iris, col_reorder)) + } + + for (i in 1:10) { + row_col_reorder <- iris[sample(seq_len(nrow(iris))),] + row_col_reorder <- row_col_reorder[sample(seq_len(nrow(row_col_reorder))),] + expect_true(all_equal_df(iris, row_col_reorder)) + } +}) + +test_that("all_equal_df on reorder with NAs", { + x <- data.frame(a=rep(NA_character_,10), b=rep(NA_integer_, 10)) + x[3, "b"] = 2 + + for (i in 1:10) { + row_col_reorder <- x[sample(seq_len(nrow(x))),] + row_col_reorder <- row_col_reorder[sample(seq_len(nrow(row_col_reorder))),] + expect_true(all_equal_df(x, row_col_reorder)) + } +}) + +test_that("compare tibble and a data.frame with all_equal_df", { + x <- tibble::tibble(iris) + expect_true(all_equal_df(x, iris)) +}) From b0517e4aa55f29724498a5a5b0f539ca227d4a22 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:26:55 +0200 Subject: [PATCH 14/32] use new all_equal_df instead of the depricated dplyr::all_equal --- R/assignment.R | 8 +++----- tests/testthat/test_batch_container_from_table.R | 6 +++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/R/assignment.R b/R/assignment.R index f2fcd044..c9a44f54 100644 --- a/R/assignment.R +++ b/R/assignment.R @@ -158,11 +158,9 @@ assign_from_table <- function(batch_container, samples) { if (is.null(batch_container$samples)) { batch_container$samples <- only_samples } else { - assertthat::assert_that(dplyr::all_equal(only_samples, - batch_container$get_samples(assignment = FALSE), - ignore_col_order = TRUE, - ignore_row_order = TRUE, - convert = TRUE + assertthat::assert_that(all_equal_df( + only_samples, + batch_container$get_samples(assignment = FALSE) ), msg = "sample sheet should be compatible with samples inside the batch container" ) diff --git a/tests/testthat/test_batch_container_from_table.R b/tests/testthat/test_batch_container_from_table.R index 7b0aa918..68c7723d 100644 --- a/tests/testthat/test_batch_container_from_table.R +++ b/tests/testthat/test_batch_container_from_table.R @@ -31,7 +31,7 @@ test_that( sample_id = 1:9 ) bc <- batch_container_from_table(tab, c("row", "column")) - expect_true(dplyr::all_equal(bc$get_samples(), tab)) + expect_true(all_equal_df(bc$get_samples(), tab)) } ) @@ -44,7 +44,7 @@ test_that( sample_id = c(1, 2, 3, NA, 5, 6, 7, NA, 9) ) bc <- batch_container_from_table(tab, c("row", "column")) - expect_true(dplyr::all_equal(bc$get_samples(), tab)) + expect_true(all_equal_df(bc$get_samples(), tab)) } ) @@ -61,7 +61,7 @@ test_that( expect_warning({ bc <- batch_container_from_table(tab, c("row", "column")) }) - expect_true(dplyr::all_equal(bc$get_samples(), tab)) + expect_true(all_equal_df(bc$get_samples(), tab)) } ) From 43ad3df293e56d86b31a3e90ec55a4c89afb9665 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:27:18 +0200 Subject: [PATCH 15/32] fix sample assignment in optimize_design --- R/optimize.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/optimize.R b/R/optimize.R index bc2e7615..48f2b650 100644 --- a/R/optimize.R +++ b/R/optimize.R @@ -207,7 +207,7 @@ optimize_design <- function(batch_container, samples = NULL, ) } else { assertthat::assert_that(nrow(samples) > 0) - assign_in_order(batch_container, samples) + batch_container <- assign_in_order(batch_container, samples) } From aedb347a68d5af2624d6335ae49bcd501987d6c1 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:27:56 +0200 Subject: [PATCH 16/32] fix all tests --- tests/testthat/test-sample_attr.R | 2 +- tests/testthat/test_assign_from_table.R | 16 ++--- tests/testthat/test_assign_random.R | 6 +- tests/testthat/test_batch_container.R | 6 +- tests/testthat/test_copy_samples_cache.R | 8 +-- tests/testthat/test_get_samples.R | 4 +- tests/testthat/test_mk_shuffle_function.R | 2 +- tests/testthat/test_move_samples.R | 8 +-- tests/testthat/test_no_shuff_proposed.R | 4 +- tests/testthat/test_optimization_trace.R | 61 ------------------- tests/testthat/test_optimize_design.R | 46 +++++++------- ..._optimize_design_complete_random_shuffle.R | 7 ++- .../test_optimize_design_simple_shuffle.R | 9 ++- .../test_optimize_design_uses_all_locations.R | 10 ++- tests/testthat/test_samples_attr.R | 2 +- tests/testthat/test_scores.R | 30 ++++----- 16 files changed, 80 insertions(+), 141 deletions(-) delete mode 100644 tests/testthat/test_optimization_trace.R diff --git a/tests/testthat/test-sample_attr.R b/tests/testthat/test-sample_attr.R index f96fb2e7..d5f5572f 100644 --- a/tests/testthat/test-sample_attr.R +++ b/tests/testthat/test-sample_attr.R @@ -2,7 +2,7 @@ bc <- BatchContainer$new( dimensions = c(row = 3, column = 3) ) samp <- data.frame(i = 1:8) -assign_in_order(bc, samp) +bc <- assign_in_order(bc, samp) test_that("cannot assign has_samples_attr", { bc1 <- bc$copy() diff --git a/tests/testthat/test_assign_from_table.R b/tests/testthat/test_assign_from_table.R index 57b0d7ad..4206681a 100644 --- a/tests/testthat/test_assign_from_table.R +++ b/tests/testthat/test_assign_from_table.R @@ -40,17 +40,17 @@ sample_sheet4 <- rbind( test_that("assign samples from sample sheet", { bc <- bc1$copy() expect_null(bc$samples) - assign_from_table(bc, sample_sheet1) + bc <- assign_from_table(bc, sample_sheet1) expect_equal(nrow(sample_sheet1), nrow(bc$samples)) bc <- bc1$copy() expect_null(bc$samples) - assign_from_table(bc, sample_sheet2) + bc <- assign_from_table(bc, sample_sheet2) expect_equal(nrow(sample_sheet2), nrow(bc$samples)) bc <- bc2$copy() expect_null(bc$samples) - assign_from_table(bc, sample_sheet2) + bc <- assign_from_table(bc, sample_sheet2) expect_equal(nrow(sample_sheet2), nrow(bc$samples)) }) @@ -58,20 +58,20 @@ test_that("assign samples from sample sheet works when samples are added/assigne bc <- bc1$copy() expect_null(bc$samples) bc$samples <- subset(sample_sheet1, select = sampleID) - assign_from_table(bc, sample_sheet1) + bc <- assign_from_table(bc, sample_sheet1) bc <- bc2$copy() expect_null(bc$samples) - assign_from_table(bc, sample_sheet2) - assign_random(bc) - assign_from_table(bc, sample_sheet2) + bc <- assign_from_table(bc, sample_sheet2) + bc <- assign_random(bc) + bc <- assign_from_table(bc, sample_sheet2) }) test_that("assign samples from sample sheet works only when samples match what's in the container", { bc <- bc1$copy() expect_null(bc$samples) - assign_from_table(bc, sample_sheet1) + bc <- assign_from_table(bc, sample_sheet1) expect_error(assign_from_table(bc, sample_sheet2)) }) diff --git a/tests/testthat/test_assign_random.R b/tests/testthat/test_assign_random.R index 4d6378db..fc4c0bca 100644 --- a/tests/testthat/test_assign_random.R +++ b/tests/testthat/test_assign_random.R @@ -6,12 +6,12 @@ bc <- BatchContainer$new( samples <- data.frame(i = 1:384, x = rnorm(384)) test_that("assign_random shuffles elements", { - assign_random(bc, samples) + bc <- assign_random(bc, samples) # this can fail by chance, but the probability extremely low # we set the random seed to make sure this is reproducible expect_true(any(bc$get_samples()$i != 1:384)) - assign_in_order(bc) + bc <- assign_in_order(bc) expect_equal(bc$get_samples()$i, 1:384) - assign_random(bc) + bc <- assign_random(bc) expect_true(any(bc$get_samples()$i != 1:384)) }) diff --git a/tests/testthat/test_batch_container.R b/tests/testthat/test_batch_container.R index 0931293b..5f2e7276 100644 --- a/tests/testthat/test_batch_container.R +++ b/tests/testthat/test_batch_container.R @@ -47,14 +47,14 @@ test_that("Test adding samples and then assigning them", { bc1_copy <- bc1$copy() bc1_copy$samples <- samples expect_null(bc1_copy$assignment) - assign_in_order(bc1_copy) + bc1_copy <- assign_in_order(bc1_copy) expect_equal( bc1_copy$assignment, c(seq_len(nrow(samples)), rep(NA_integer_, bc1_copy$n_locations - nrow(samples))) ) bc1_copy <- bc1$copy() - assign_in_order(bc1_copy, samples) + bc1_copy <- assign_in_order(bc1_copy, samples) expect_equal( bc1_copy$assignment, c(seq_len(nrow(samples)), rep(NA_integer_, bc1_copy$n_locations - nrow(samples))) @@ -66,7 +66,7 @@ test_that("Test assigning samples randomly", { bc3_copy <- bc3_excl$copy() expect_null(bc3_copy$assignment) expect_false(any(!is.na(bc3_copy$assignment))) - assign_random(bc3_copy, samples) + bc3_copy <- assign_random(bc3_copy, samples) expect_true(any(!is.na(bc3_copy$assignment))) }) diff --git a/tests/testthat/test_copy_samples_cache.R b/tests/testthat/test_copy_samples_cache.R index 807767a8..826d3822 100644 --- a/tests/testthat/test_copy_samples_cache.R +++ b/tests/testthat/test_copy_samples_cache.R @@ -3,7 +3,7 @@ test_that("$copy() does not preserve samples table cache", { dimensions = c(row = 3, column = 3) ) samp <- data.frame(i = 1:9) - assign_in_order(bc, samp) + bc <- assign_in_order(bc, samp) # creates cache bc$get_samples() bc_clone <- bc$copy() @@ -18,14 +18,14 @@ test_that("Cloning preservs dimensions, samples, scores & assignment", { exclude = data.frame(row = 3, column = 1:3) ) samp <- data.frame(i = 1:6) - assign_in_order(bc, samp) - bc$scoring_f <- function(...) 42L + bc <- assign_in_order(bc, samp) + scoring_f <- function(...) 42L bc_clone <- bc$copy() expect_equal(bc$samples, bc_clone$samples) expect_equal(bc$get_locations(), bc_clone$get_locations()) expect_equal(bc$n_locations, bc_clone$n_locations) - expect_equal(bc$score(), bc_clone$score()) + expect_equal(bc$score(scoring_f), bc_clone$score(scoring_f)) expect_equal(bc$n_dimensions, bc_clone$n_dimensions) expect_equal(bc$dimension_names, bc_clone$dimension_names) expect_equal(bc$get_samples(include_id = TRUE), bc_clone$get_samples(include_id = TRUE)) diff --git a/tests/testthat/test_get_samples.R b/tests/testthat/test_get_samples.R index 908acfa7..0fdc8588 100644 --- a/tests/testthat/test_get_samples.R +++ b/tests/testthat/test_get_samples.R @@ -5,7 +5,7 @@ test_that("$get_samles(as_tibble=TRUE) returns correct columns & expected number samp <- data.frame(i = 1:8) expect_error(bc$get_samples()) bc$samples <- samp - assign_in_order(bc) + bc <- assign_in_order(bc) stab <- bc$get_samples() expect_true(tibble::is_tibble(stab)) expect_named(stab, c("row", "column", "i")) @@ -33,7 +33,7 @@ test_that("$get_samles(as_tibble=FALSE) returns correct columns & expected numbe samp <- data.frame(i = 1:8) expect_error(bc$get_samples()) bc$samples <- samp - assign_in_order(bc) + bc <- assign_in_order(bc) stab <- bc$get_samples(as_tibble = FALSE) expect_true(data.table::is.data.table(stab)) expect_named(stab, c("row", "column", "i")) diff --git a/tests/testthat/test_mk_shuffle_function.R b/tests/testthat/test_mk_shuffle_function.R index 2db08caa..bc6c4ec9 100644 --- a/tests/testthat/test_mk_shuffle_function.R +++ b/tests/testthat/test_mk_shuffle_function.R @@ -6,7 +6,7 @@ samples <- data.frame( sampleId = seq_len(bc$n_locations) ) -assign_in_order(bc, samples) +bc <- assign_in_order(bc, samples) test_that("mk_swapping_function returns an error if iteration number is too large", { f <- mk_swapping_function(c(1, 2, 3)) diff --git a/tests/testthat/test_move_samples.R b/tests/testthat/test_move_samples.R index 690cf341..b6b4a85a 100644 --- a/tests/testthat/test_move_samples.R +++ b/tests/testthat/test_move_samples.R @@ -2,7 +2,7 @@ bc <- BatchContainer$new( dimensions = c(row = 3, column = 3) ) samp <- data.frame(i = 1:9) -assign_in_order(bc, samp) +bc <- assign_in_order(bc, samp) test_that("assigning $assignment generates a warning", { expect_silent(bc$assignment) @@ -23,7 +23,7 @@ bc <- BatchContainer$new( dimensions = c(row = 3, column = 3) ) samp <- data.frame(i = 1:8) -assign_in_order(bc, samp) +bc <- assign_in_order(bc, samp) test_that("$move_samples() works as expected with src & dst (without $get_samples())", { bc$move_samples(src = c(1L, 2L), dst = c(2L, 1L)) @@ -52,7 +52,7 @@ test_that("$move_samples() does not accept non-integers", { }) -assign_in_order(bc) +bc <- assign_in_order(bc) test_that("$move_samples() works as expected with src & dst (after $get_samples())", { bc$get_samples() @@ -82,7 +82,7 @@ bc <- BatchContainer$new( dimensions = c(row = 3, column = 3) ) samp <- data.frame(i = 1:8) -assign_in_order(bc, samp) +bc <- assign_in_order(bc, samp) test_that("$move_samples() works as expected with location_assignment (without $get_samples())", { a <- as.integer(c(2, 3, NA, 4, 5, 6, 7, 8, 1)) diff --git a/tests/testthat/test_no_shuff_proposed.R b/tests/testthat/test_no_shuff_proposed.R index 62b0452c..27d23121 100644 --- a/tests/testthat/test_no_shuff_proposed.R +++ b/tests/testthat/test_no_shuff_proposed.R @@ -7,12 +7,12 @@ samples <- data.frame( sampleId = seq_len(bc$n_locations) ) -bc$scoring_f <- function(...) rnorm(1) +scoring_f <- function(...) rnorm(1) test_that("No shuffling proposed error is generated", { set.seed(6) expect_error( - optimize_design(bc, samples = samples, max_iter = 30, n_shuffle = 2), + optimize_design(bc, scoring = scoring_f, samples = samples, max_iter = 30, n_shuffle = 2), NA ) }) diff --git a/tests/testthat/test_optimization_trace.R b/tests/testthat/test_optimization_trace.R deleted file mode 100644 index 21696f69..00000000 --- a/tests/testthat/test_optimization_trace.R +++ /dev/null @@ -1,61 +0,0 @@ -test_that("n_steps cannot be negative", { - expect_error(OptimizationTrace$new(-10, 10)) -}) - -test_that("n_scores cannot be negative", { - expect_error(OptimizationTrace$new(10, -10)) -}) - -test_that("OptimiztionTrace returns a single-column score matrix", { - ot <- OptimizationTrace$new(10, 1, "") - ot$set_scores(1, 2, NULL) - ot$set_scores(2, 3, NULL) - ot$shrink() - expect_true(all(ot$scores == matrix(c(2, 3), ncol = 1))) - expect_null(ot$aggregated_scores) -}) - -test_that("OptimiztionTrace fails on incorrect number of aggregated scores", { - ot <- OptimizationTrace$new(10, 1, "") - ot$set_scores(1, 2, c(2, 4)) - expect_error(ot$set_scores(2, 3, 3)) -}) - -test_that("OptimiztionTrace returns a correct matrix of aggregated scores", { - ot <- OptimizationTrace$new(10, 1, "a") - ot$set_scores(1, 2, c(2, 4)) - ot$set_scores(2, 2, c(4, 2)) - ot$shrink() - expect_true(all(ot$aggregated_scores == matrix(c(2, 4, 4, 2), ncol = 2))) -}) - -test_that("OptimiztionTrace returns a tibble", { - ot <- OptimizationTrace$new(10, 1, "a") - ot$set_scores(1, 2, c(2, 4)) - ot$set_scores(2, 2, c(4, 2)) - ot$shrink() - tb <- ot$as_tibble() - expect_true(tibble::is_tibble(tb)) - expect_named(tb, c("type", "step", "score", "value")) - expect_true(all(tb$step %in% c(1, 2))) - expect_true(is.factor(tb$type)) - expect_equal(levels(tb$type), c("score", "aggregated")) - expect_true(all(tb$score %in% c("a", "agg.1", "agg.2"))) - expect_equal(nrow(tb), 6) -}) - -test_that("OptimiztionTrace returns a tibble (include_aggregated = FALSE)", { - ot <- OptimizationTrace$new(10, 1, "a") - ot$set_scores(1, 2, c(2, 4)) - ot$set_scores(2, 2, c(4, 2)) - ot$shrink() - tb <- ot$as_tibble(FALSE) - expect_true(tibble::is_tibble(tb)) - expect_named(tb, c("type", "step", "score", "value")) - expect_true(all(tb$step %in% c(1, 2))) - expect_true(is.factor(tb$type)) - expect_equal(levels(tb$type), c("score", "aggregated")) - expect_true(all(tb$type == "score")) - expect_true(all(tb$score == "a")) - expect_equal(nrow(tb), 2) -}) diff --git a/tests/testthat/test_optimize_design.R b/tests/testthat/test_optimize_design.R index a43d7a11..c710f856 100644 --- a/tests/testthat/test_optimize_design.R +++ b/tests/testthat/test_optimize_design.R @@ -6,9 +6,9 @@ samples <- data.frame( sampleId = seq_len(bc$n_locations) ) -assign_in_order(bc, samples) +bc <- assign_in_order(bc, samples) -bc$scoring_f <- function(...) rnorm(1) +scoring_f <- function(...) rnorm(1) my_shuffle_proposal <- function(...) { v <- sample(20, 2) @@ -16,72 +16,72 @@ my_shuffle_proposal <- function(...) { } test_that("n_shuffle could be >=2 for simple shuffling", { - expect_error(optimize_design(bc, max_iter = 10, n_shuffle = 2), NA) - expect_error(optimize_design(bc, max_iter = 10, n_shuffle = rep(c(3, 2), c(5, 5))), NA) - expect_error(optimize_design(bc, max_iter = 10, n_shuffle = 10), NA) + expect_error(optimize_design(bc, scoring = scoring_f, max_iter = 10, n_shuffle = 2), NA) + expect_error(optimize_design(bc, scoring = scoring_f, max_iter = 10, n_shuffle = rep(c(3, 2), c(5, 5))), NA) + expect_error(optimize_design(bc, scoring = scoring_f, max_iter = 10, n_shuffle = 10), NA) # n_shuffle should be >= 1 - expect_error(optimize_design(bc, max_iter = 10, n_shuffle = 0)) - expect_error(optimize_design(bc, max_iter = 10, n_shuffle = c(rep(9, 9), 0))) + expect_error(optimize_design(bc, scoring = scoring_f, max_iter = 10, n_shuffle = 0)) + expect_error(optimize_design(bc, scoring = scoring_f, max_iter = 10, n_shuffle = c(rep(9, 9), 0))) }) test_that("error when shuffle proposal function returns not a list", { expect_error( - optimize_design(bc, max_iter = 10, n_shuffle = 2, shuffle_proposal = function(...) 0:1), + optimize_design(bc, scoring = scoring_f, max_iter = 10, n_shuffle = 2, shuffle_proposal = function(...) 0:1), "sample assignment length doesn't match the number of available locations" ) }) test_that("n_shuffle could be >=1 for shuffling with proposal function", { expect_error( - optimize_design(bc, max_iter = 10, n_shuffle = 1, shuffle_proposal = my_shuffle_proposal), + optimize_design(bc, scoring = scoring_f, max_iter = 10, n_shuffle = 1, shuffle_proposal = my_shuffle_proposal), NA ) expect_error( - optimize_design(bc, max_iter = 10, n_shuffle = rep(c(3, 1), c(5, 5)), shuffle_proposal = my_shuffle_proposal), + optimize_design(bc, scoring = scoring_f, max_iter = 10, n_shuffle = rep(c(3, 1), c(5, 5)), shuffle_proposal = my_shuffle_proposal), NA ) expect_error( - optimize_design(bc, max_iter = 10, n_shuffle = 10, shuffle_proposal = my_shuffle_proposal), + optimize_design(bc, scoring = scoring_f, max_iter = 10, n_shuffle = 10, shuffle_proposal = my_shuffle_proposal), NA ) # n_shuffle should be >= 0 - expect_error(optimize_design(bc, max_iter = 10, n_shuffle = 0, shuffle_proposal = my_shuffle_proposal)) - expect_error(optimize_design(bc, max_iter = 10, n_shuffle = c(rep(9, 9), 0), shuffle_proposal = my_shuffle_proposal)) + expect_error(optimize_design(bc, scoring = scoring_f, max_iter = 10, n_shuffle = 0, shuffle_proposal = my_shuffle_proposal)) + expect_error(optimize_design(bc, scoring = scoring_f, max_iter = 10, n_shuffle = c(rep(9, 9), 0), shuffle_proposal = my_shuffle_proposal)) }) test_that("mismatch between number of iterations and n_shuffle", { expect_error( - trace <- optimize_design(bc, max_iter = 5, n_shuffle = 2), + bc <- optimize_design(bc, scoring = scoring_f, max_iter = 5, n_shuffle = 2), NA ) - expect_equal(trace$n_steps, 5 + 1) + expect_equal(nrow(bc$trace$scores[[nrow(bc$trace)]]), 5 + 1) expect_error( - trace <- optimize_design(bc, max_iter = 10, n_shuffle = c(2, 2)), + bc <- optimize_design(bc, scoring = scoring_f, max_iter = 10, n_shuffle = c(2, 2)), NA ) - expect_equal(trace$n_steps, 2 + 1) + expect_equal(nrow(bc$trace$scores[[nrow(bc$trace)]]), 2 + 1) expect_error( - trace <- optimize_design(bc, max_iter = 10, n_shuffle = rep(2, 200)), + bc <- optimize_design(bc, scoring = scoring_f, max_iter = 10, n_shuffle = rep(2, 200)), NA ) - expect_equal(trace$n_steps, 10 + 1) + expect_equal(nrow(bc$trace$scores[[nrow(bc$trace)]]), 10 + 1) }) test_that("inferring iterations from n_shuffle", { expect_error( - trace <- optimize_design(bc, n_shuffle = rep(2, 5)), + bc <- optimize_design(bc, scoring = scoring_f, n_shuffle = rep(2, 5)), NA ) - expect_equal(trace$n_steps, 5 + 1) + expect_equal(nrow(bc$trace$scores[[nrow(bc$trace)]]), 5 + 1) }) test_that("default n_shuffle", { expect_error( - optimize_design(bc, max_iter = 10), + optimize_design(bc, scoring = scoring_f, max_iter = 10), NA ) expect_error( - optimize_design(bc, max_iter = 10, shuffle_proposal = my_shuffle_proposal), + optimize_design(bc, scoring = scoring_f, max_iter = 10, shuffle_proposal = my_shuffle_proposal), NA ) }) diff --git a/tests/testthat/test_optimize_design_complete_random_shuffle.R b/tests/testthat/test_optimize_design_complete_random_shuffle.R index ee3c0f2d..d680e7a2 100644 --- a/tests/testthat/test_optimize_design_complete_random_shuffle.R +++ b/tests/testthat/test_optimize_design_complete_random_shuffle.R @@ -6,10 +6,10 @@ samples <- data.frame( sampleId = seq_len(98) ) -assign_in_order(bc, samples) +bc <- assign_in_order(bc, samples) # decreasing score -bc$scoring_f <- (function() { +scoring_f <- (function() { score <- 1 function(...) { score <<- score - 1 @@ -21,8 +21,9 @@ set.seed(42) start_state <- ifelse(is.na(bc$assignment), -1, bc$assignment) test_that("complete_random_shuffling shuffles most of the elements", { - optimize_design( + bc <- optimize_design( bc, + scoring = scoring_f, max_iter = 1, shuffle_proposal_func = complete_random_shuffling ) diff --git a/tests/testthat/test_optimize_design_simple_shuffle.R b/tests/testthat/test_optimize_design_simple_shuffle.R index e6e073bc..e037ca12 100644 --- a/tests/testthat/test_optimize_design_simple_shuffle.R +++ b/tests/testthat/test_optimize_design_simple_shuffle.R @@ -15,9 +15,9 @@ n_elements_changed <- function(bc) { Inf } -assign_in_order(bc, samples) +bc <- assign_in_order(bc, samples) -bc$scoring_f <- n_elements_changed +scoring_f <- n_elements_changed set.seed(42) @@ -27,6 +27,7 @@ n_changed <- numeric(0) test_that("correct number of shuffles = 1", { optimize_design( bc, + scoring = scoring_f, max_iter = 10, check_score_variance = F, autoscale_scores = F @@ -38,6 +39,7 @@ n_changed <- numeric(0) test_that("correct number of shuffles = 2", { optimize_design( bc, + scoring = scoring_f, max_iter = 10, n_shuffle = 2, check_score_variance = F, @@ -50,6 +52,7 @@ n_changed <- numeric(0) test_that("correct number of shuffles = 5", { optimize_design( bc, + scoring = scoring_f, max_iter = 10, n_shuffle = 5, check_score_variance = FALSE, @@ -62,6 +65,7 @@ n_changed <- numeric(0) test_that("specify too many shuffles", { optimize_design( bc, + scoring = scoring_f, max_iter = 10, n_shuffle = 40, check_score_variance = FALSE, @@ -74,6 +78,7 @@ n_changed <- numeric(0) test_that("complex shuffling schedule", { optimize_design( bc, + scoring = scoring_f, max_iter = 10, n_shuffle = c(2, 2, 5, 2, 2, 10, 20, 40, 40), check_score_variance = F, diff --git a/tests/testthat/test_optimize_design_uses_all_locations.R b/tests/testthat/test_optimize_design_uses_all_locations.R index 9dd777c1..4ac764b5 100644 --- a/tests/testthat/test_optimize_design_uses_all_locations.R +++ b/tests/testthat/test_optimize_design_uses_all_locations.R @@ -6,9 +6,7 @@ samples <- data.frame( sampleId = seq_len(bc$n_locations - 5) ) -assign_in_order(bc, samples) - -bc$scoring_f <- function(...) rnorm(1) +bc <- assign_in_order(bc, samples) set.seed(42) @@ -17,9 +15,9 @@ test_that("empty locations are used by optimize_design", { assignments <- purrr::map_dfr( seq_len(20), function(...) { - bc1 <- bc$copy() - optimize_design( - bc1, + bc1 <- optimize_design( + bc, + scoring = function(...) rnorm(1), max_iter = 5 ) bc1$get_samples() diff --git a/tests/testthat/test_samples_attr.R b/tests/testthat/test_samples_attr.R index 57503ee0..74fbb43c 100644 --- a/tests/testthat/test_samples_attr.R +++ b/tests/testthat/test_samples_attr.R @@ -10,7 +10,7 @@ test_that("add attributes before assigning samples", { expect_equal(bc$get_samples(assignment = FALSE)$attr1, rev(1:8)) }) -assign_in_order(bc) +bc <- assign_in_order(bc) test_that("add attributes after assigning samples", { bc$samples_attr <- NULL diff --git a/tests/testthat/test_scores.R b/tests/testthat/test_scores.R index b97f6e5a..b3202f16 100644 --- a/tests/testthat/test_scores.R +++ b/tests/testthat/test_scores.R @@ -6,53 +6,49 @@ samples <- data.frame( sampleId = seq_len(bc$n_locations) ) -assign_random(bc, samples) +bc <- assign_random(bc, samples) test_that("Score lenght is correct", { - bc$scoring_f <- list( + scoring_f <- list( function(...) rnorm(1) ) - expect_length(bc$scoring_f, 1) - expect_length(bc$score(), 1) - # single value is automatically converted to a list - expect_type(bc$scoring_f, "list") + expect_length(bc$score(scoring_f), 1) - bc$scoring_f <- function(...) rnorm(1) - expect_length(bc$score(), 1) - - bc$scoring_f <- list( + scoring_f <- list( function(...) rnorm(1), function(...) rnorm(1) ) - expect_length(bc$score(), 2) + expect_length(bc$score(scoring_f), 2) }) test_that("Score names are correct", { - bc$scoring_f <- list( + scoring_f <- list( a = function(...) rnorm(1), b = function(...) rnorm(1) ) - expect_named(bc$score(), c("a", "b")) + expect_named(bc$score(scoring_f), c("a", "b")) }) test_that("Can optimize a single score", { set.seed(6) - bc$scoring_f <- function(...) rnorm(1) + scoring_f <- function(...) rnorm(1) + bc <- optimize_design(bc, scoring = scoring_f, max_iter = 30, n_shuffle = 2) expect_equal( - optimize_design(bc, max_iter = 30, n_shuffle = 2)$n_steps, + nrow(bc$trace$scores[[1]]), 31 ) }) test_that("Can optimize multiple scores", { set.seed(6) - bc$scoring_f <- list( + scoring_f <- list( a = function(...) rnorm(1), b = function(...) rnorm(1) ) + bc <- optimize_design(bc, scoring = scoring_f, max_iter = 30, n_shuffle = 2, aggregate_scores_func = first_score_only) expect_equal( - optimize_design(bc, max_iter = 30, n_shuffle = 2, aggregate_scores_func = first_score_only)$n_steps, + nrow(bc$trace$scores[[1]]), 31 ) }) From 26b70c1780d093f793989e190defba88745ccc17 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:28:05 +0200 Subject: [PATCH 17/32] _document --- man/all_equal_df.Rd | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 man/all_equal_df.Rd diff --git a/man/all_equal_df.Rd b/man/all_equal_df.Rd new file mode 100644 index 00000000..d44ce8fe --- /dev/null +++ b/man/all_equal_df.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/all_equal_df.R +\name{all_equal_df} +\alias{all_equal_df} +\title{Compare two data.frames.} +\usage{ +all_equal_df(df1, df2) +} +\arguments{ +\item{df1}{first \code{\link[=data.frame]{data.frame()}} to compare} + +\item{df2}{second \code{data.frame()} to compare} +} +\value{ +\code{TRUE} or \code{FALSE} in case differences are present +} +\description{ +This will convert factors to characters and disregard +row and column order +} +\keyword{internal} From cc7186aaf60ff39ee15e74217fcdff263bf4a282 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Wed, 26 Jul 2023 18:02:56 +0200 Subject: [PATCH 18/32] adapt vignettes to the new API --- vignettes/NCS22_talk.Rmd | 2 +- vignettes/nested_dimensions_examples.Rmd | 48 ++++++++++---------- vignettes/osat.Rmd | 44 +++++++++---------- vignettes/plate_layouts.Rmd | 56 +++++++++++++++--------- vignettes/shuffling_with_constraints.Rmd | 20 ++++----- 5 files changed, 88 insertions(+), 82 deletions(-) diff --git a/vignettes/NCS22_talk.Rmd b/vignettes/NCS22_talk.Rmd index 19c59010..adf608d9 100644 --- a/vignettes/NCS22_talk.Rmd +++ b/vignettes/NCS22_talk.Rmd @@ -4,7 +4,7 @@ author: "Juliane Siebourg-Polster, Iakov Davydov, Guido Steiner, Balazs Banfai" output: rmarkdown::html_vignette: vignette: > - %\VignetteIndexEntry{NCS Talk} + %\VignetteIndexEntry{designit: a flexible engine to generate experiment layouts} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} editor_options: diff --git a/vignettes/nested_dimensions_examples.Rmd b/vignettes/nested_dimensions_examples.Rmd index ffa9226f..575c3965 100644 --- a/vignettes/nested_dimensions_examples.Rmd +++ b/vignettes/nested_dimensions_examples.Rmd @@ -54,13 +54,8 @@ bc <- BatchContainer$new( ) ) -# Add samples to container -bc$samples <- multi_trt_day_samples # Initial random assignment -assign_in_order(bc) -# Set scoring function -bc$scoring_f <- osat_score_generator(c("batch"), c("Treatment", "Time")) - +bc <- assign_in_order(bc, multi_trt_day_samples) bc ``` @@ -73,10 +68,10 @@ n_iterations <- length(n_shuffle) set.seed(42) # should we have conventions for this? -# initial score -# bc$score() -trace <- optimize_design( +scoring_f <- osat_score_generator(c("batch"), c("Treatment", "Time")) +bc <- optimize_design( bc, + scoring = scoring_f, n_shuffle = n_shuffle, max_iter = n_iterations ) # default is 10000 @@ -89,8 +84,10 @@ I practice you will have to run for a much higher number of iterations. ```{r, fig.width=5, fig.height= 4} qplot( - x = 1:trace$n_steps, y = trace$scores, color = factor(c(32, n_shuffle)), - main = str_glue("Final score={bc$score()}"), geom = "point" + x = bc$trace$scores[[1]]$step, + y = bc$trace$scores[[1]]$score_1, + color = factor(c(32, n_shuffle)), + main = str_glue("Final score={bc$score(scoring_f)}"), geom = "point" ) ``` @@ -105,18 +102,16 @@ bc$get_samples(assignment = TRUE) %>% # Repeat but use shuffle with contraints ```{r, fig.width=6, fig.height= 4} - # copy batch container for second optimization -bc2 <- bc$copy() -# Initial random assignment -assign_in_order(bc2) +bc2 <- assign_in_order(bc) n_iterations <- 200 set.seed(42) # should we have conventions for this? -trace2 <- optimize_design( +bc2 <- optimize_design( bc2, + scoring = scoring_f, shuffle_proposal = shuffle_with_constraints( src = TRUE, # batch needs to change for shuffle to be accepted @@ -126,8 +121,9 @@ trace2 <- optimize_design( ) qplot( - x = 1:trace$n_steps, y = trace2$scores, # color = factor(n_shuffle), - main = str_glue("Final score={bc2$score()}"), geom = "point" + x = bc2$trace$scores[[1]]$step, + y = bc2$trace$scores[[1]]$score_1, + main = str_glue("Final score={bc2$score(scoring_f)}"), geom = "point" ) bc2$get_samples(assignment = TRUE) %>% @@ -147,16 +143,17 @@ For this we keep the optimized `batch` and now only optimize `run` with constrai ```{r} n_iterations <- 100 -# assign new optimization function -bc$scoring_f <- osat_score_generator(c("run"), c("Treatment", "Time")) +# new optimization function +scoring_f <- osat_score_generator(c("run"), c("Treatment", "Time")) # like this the optimization score is wrong because it tries to optimize across Batches. # Possible ways to go: # - we'd need something like c("batch", batch/run") for optimize by batch and run within batch. # - or we add "batch/run" to the constraints somehow. -bc$score() +bc$score(scoring_f) -trace_run <- optimize_design( +bc <- optimize_design( bc, + scoring = scoring_f, shuffle_proposal = shuffle_with_constraints( src = TRUE, # batch remains the same and run needs to change @@ -168,8 +165,10 @@ trace_run <- optimize_design( ```{r, fig.width=6, fig.height= 4} qplot( - x = 1:trace_run$n_steps, y = trace_run$scores, color = factor(n_iterations), - main = str_glue("Final score={bc$score()}"), geom = "point" + x = bc$trace$scores[[1]]$step, + y = bc$trace$scores[[1]]$score_1, + color = factor(n_iterations), + main = str_glue("Final score={bc$score(scoring_f)}"), geom = "point" ) ``` @@ -178,7 +177,6 @@ qplot( This is not giving the expected mix of treatments across runs. ```{r, fig.width=6, fig.height= 4} - bc$get_samples() %>% mutate(run = factor(run)) %>% ggplot(aes(x = run, fill = Treatment, alpha = factor(Time))) + diff --git a/vignettes/osat.Rmd b/vignettes/osat.Rmd index 1b9c38c7..17549095 100644 --- a/vignettes/osat.Rmd +++ b/vignettes/osat.Rmd @@ -124,7 +124,7 @@ bc$n_locations Assign samples and get initial setup. ```{r} -bc$samples <- samples +bc <- assign_in_order(bc, samples) starting_assignment <- bc$get_locations() %>% left_join(g_setup_start) %>% @@ -139,22 +139,23 @@ bc$get_samples(remove_empty_locations = TRUE) %>% ## Using designit OSAT score implementation ```{r} -bc$scoring_f <- osat_score_generator("plates", c("SampleType", "Race", "AgeGrp")) +scoring_f <- osat_score_generator("plates", c("SampleType", "Race", "AgeGrp")) -bc$score() +bc$score(scoring_f) g_setup@metadata$optValue %>% head(1) # should be identical bench::system_time({ set.seed(123) - trace_reference <- optimize_design(bc, max_iter = params$iterations) + bc_reference <- optimize_design(bc, scoring = scoring_f, max_iter = params$iterations) }) ``` ```{r} # final score -bc$score() -plot(trace_reference, main = str_glue("Final score={bc$score()}")) +bc_reference$score(scoring_f) +bc_reference$plot_trace() + + ggtitle(str_glue("Final score={bc$score(scoring_f)}")) bc$get_samples(remove_empty_locations = TRUE) %>% plot_batch() ``` @@ -163,9 +164,8 @@ bc$get_samples(remove_empty_locations = TRUE) %>% Instead of relying on `BatchContainer`, here we have a manual optimization process using `data.table`. ```{r} -bc$move_samples(location_assignment = starting_assignment) - fast_osat_optimize <- function(bc, batch_vars, feature_vars, iterations) { + bc <- bc$copy() ldf <- data.table::data.table(bc$get_locations())[, c("plates")][, ".sample_id" := bc$assignment] fcols <- c(".sample_id", feature_vars) smp <- data.table::data.table(bc$samples)[, ..fcols] @@ -202,27 +202,27 @@ fast_osat_optimize <- function(bc, batch_vars, feature_vars, iterations) { bc$assignment <- df$.sample_id - scores + list(bc=bc, scores=scores) } bench::system_time({ set.seed(123) - trace <- fast_osat_optimize(bc, "plates", c("SampleType", "Race", "AgeGrp"), iterations = params$iterations) + opt_res <- fast_osat_optimize(bc, "plates", c("SampleType", "Race", "AgeGrp"), iterations = params$iterations) }) ``` # Shuffle optimization with burn-in ```{r} -bc$move_samples(location_assignment = starting_assignment) - -bc$scoring_f <- osat_score_generator("plates", c("SampleType", "Race", "AgeGrp")) +scoring_f <- osat_score_generator("plates", c("SampleType", "Race", "AgeGrp")) burn_in_it <- floor(params$iterations * 0.1) burn_in_it bench::system_time({ set.seed(123) - trace_burn_in <- optimize_design(bc, + bc_burn_in <- optimize_design( + bc, + scoring = scoring_f, n_shuffle = c( rep(20, burn_in_it), rep( @@ -237,9 +237,9 @@ bench::system_time({ ```{r} tibble( - i = seq_len(trace_burn_in$n_steps), - normal = trace_reference$scores, - burnin = trace_burn_in$scores + i = bc_burn_in$trace$scores[[1]]$step, + normal = bc_reference$trace$scores[[1]]$score_1, + burnin = bc_burn_in$trace$scores[[1]]$score_1 ) %>% pivot_longer(-i, names_to = "method", values_to = "score") %>% ggplot(aes(i, score, col = method)) + @@ -249,9 +249,7 @@ tibble( # Score demonstration ```{r} -bc$score() -bc$scoring_f <- function(...) rnorm(1) -bc$score() +bc$score(scoring_f) ``` ```{r} @@ -260,13 +258,11 @@ assign_random(bc) bc$get_samples() bc$get_samples(remove_empty_locations = TRUE) -bc$score() - -bc$scoring_f <- list( +scoring_f <- list( fc0 = function(samples) rnorm(1) + 2 * rexp(1), fc1 = function(samples) rnorm(1, 100), fc2 = function(samples) -7 ) -bc$score() +bc$score(scoring_f) ``` diff --git a/vignettes/plate_layouts.Rmd b/vignettes/plate_layouts.Rmd index fe1af67b..b2c81095 100644 --- a/vignettes/plate_layouts.Rmd +++ b/vignettes/plate_layouts.Rmd @@ -68,7 +68,7 @@ bc <- BatchContainer$new( dimensions = list("plate" = 3, "row" = 4, "col" = 6), ) -assign_in_order(bc, dat) +bc <- assign_in_order(bc, dat) head(bc$get_samples()) %>% gt::gt() ``` @@ -99,7 +99,7 @@ The order of the factors indicate their relative importance. In this case we prioritize Group over Sex. ```{r} -traces <- optimize_multi_plate_design(bc, +bc <- optimize_multi_plate_design(bc, across_plates_variables = c("Group", "Sex"), within_plate_variables = c("Group"), plate = "plate", @@ -130,8 +130,12 @@ cowplot::plot_grid( We can look at the trace objects for each internal `optimize_design` run, returned from the wrapper function. -```{r fig.width=3, fig.height=3} -purrr::imap(traces, ~ .x$plot(include_aggregated = TRUE) + labs(title = .y)) +```{r fig.width=6, fig.height=3} +bc$scores_table() |> + ggplot(aes(step, value, color = score)) + + geom_line() + + geom_point() + + facet_wrap(~ optimization_index, scales = "free_y") ``` ## Plate scoring @@ -140,7 +144,7 @@ Note that internally the wrapper function sets up plate specific scoring functio that could manually be set up in the following way. ```{r, eval = FALSE} -bc$scoring_f <- c( +scoring_f <- c( Group = mk_plate_scoring_functions(bc, plate = "plate", row = "row", column = "col", group = "Group", penalize_lines = "hard" @@ -184,12 +188,12 @@ bc <- BatchContainer$new( dimensions = list("batch" = 3, "location" = 11) ) -bc$scoring_f <- list( +scoring_f <- list( group = osat_score_generator(batch_vars = "batch", feature_vars = "Group"), sex = osat_score_generator(batch_vars = "batch", feature_vars = "Sex") ) -assign_random( +bc <- assign_random( bc, dat %>% select(SubjectID, Group, Sex) %>% distinct() ) @@ -217,8 +221,9 @@ cowplot::plot_grid( Optimizing the layout with `optimize_design()` ```{r} -trace <- optimize_design( +bc <- optimize_design( bc, + scoring = scoring_f, n_shuffle = 1, acceptance_func = ~ accept_leftmost_improvement(..., tolerance = 0.01), max_iter = 150, @@ -238,7 +243,7 @@ cowplot::plot_grid( bc$get_samples() %>% ggplot(aes(x = batch, fill = Sex)) + geom_bar() + labs(y = "subject count"), - trace$plot(include_aggregated = TRUE) + bc$plot_trace(include_aggregated = TRUE) ), ncol = 3 ) @@ -268,7 +273,7 @@ bc <- BatchContainer$new( ) # initial assignment such that the original plate assigned stays the same -assign_in_order( +bc <- assign_in_order( bc, dat %>% arrange(batch) ) @@ -295,7 +300,7 @@ For distributing samples within each plate, we use variables Group and Sex again The order of the factors indicate their relative importance. ```{r} -traces <- optimize_multi_plate_design(bc, +bc <- optimize_multi_plate_design(bc, within_plate_variables = c("Group", "Sex"), plate = "plate", row = "row", @@ -322,8 +327,12 @@ cowplot::plot_grid( ) ``` -```{r fig.width=3, fig.height=3} -purrr::imap(traces, ~ .x$plot(include_aggregated = TRUE) + labs(title = .y)) +```{r fig.width=9, fig.height=3} +bc$scores_table() |> + ggplot(aes(step, value, color = score)) + + geom_line() + + geom_point() + + facet_wrap(~ optimization_index) ``` # Full dataset @@ -381,12 +390,12 @@ bc <- BatchContainer$new( dimensions = list("plate" = 3, "locations" = 11) ) -bc$scoring_f <- list( +scoring_f <- list( group = osat_score_generator(batch_vars = "plate", feature_vars = c("Group")), sex = osat_score_generator(batch_vars = "plate", feature_vars = "Sex") ) -assign_random(bc, subjects) +bc <- assign_random(bc, subjects) ``` ```{r, fig.width= 8, fig.height=3} @@ -409,8 +418,9 @@ cowplot::plot_grid( Optimizing the layout with `optimize_design()` ```{r} -trace <- optimize_design( +bc <- optimize_design( bc, + scoring = scoring_f, n_shuffle = 1, acceptance_func = ~ accept_leftmost_improvement(..., tolerance = 0.1), max_iter = 150, @@ -433,7 +443,7 @@ cowplot::plot_grid( bc$get_samples() %>% ggplot(aes(x = factor(plate), y = Age)) + geom_boxplot() + geom_point(), - trace$plot(include_aggregated = TRUE) + bc$plot_trace(include_aggregated = TRUE) ), nrow = 2 ) @@ -472,7 +482,7 @@ bc <- BatchContainer$new( ) # assign samples in order of plate -assign_in_order( +bc <- assign_in_order( bc, samples_with_plate %>% arrange(plate) %>% @@ -512,7 +522,7 @@ For distributing samples within each plate, we use variables Group and Sex again The order of the factors indicate their relative importance. ```{r} -traces <- optimize_multi_plate_design(bc, +bc <- optimize_multi_plate_design(bc, within_plate_variables = c("Group", "SubjectID", "Sex"), plate = "plate", row = "row", @@ -545,6 +555,10 @@ cowplot::plot_grid( ``` -```{r fig.width=5, fig.height=5} -purrr::imap(traces, ~ .x$plot(include_aggregated = TRUE) + labs(title = .y)) +```{r fig.width=6, fig.height=5} +bc$scores_table() |> + ggplot(aes(step, value, color = score)) + + geom_line() + + geom_point() + + facet_wrap(~ optimization_index) ``` diff --git a/vignettes/shuffling_with_constraints.Rmd b/vignettes/shuffling_with_constraints.Rmd index 9fe748af..4ebf87de 100644 --- a/vignettes/shuffling_with_constraints.Rmd +++ b/vignettes/shuffling_with_constraints.Rmd @@ -68,9 +68,9 @@ table(treatments) bc <- BatchContainer$new(locations_table = data.frame(Treatment = treatments, Position = seq_along(treatments))) -assign_in_order(bc, invivo_study_samples) +bc <- assign_in_order(bc, invivo_study_samples) -bc$scoring_f <- osat_score_generator(batch_vars = "Treatment", feature_vars = c("Strain", "Sex")) +scoring_f <- osat_score_generator(batch_vars = "Treatment", feature_vars = c("Strain", "Sex")) bc ``` @@ -100,11 +100,10 @@ defined above at the same time. It can be passed to the optimizer together with such as the scoring or acceptance functions. ```{r} -bc2 <- bc$copy() - -optimize_design( - bc2, - shuffle_proposal_func = shuffle_grouped_data(bc2, +bc2 <- optimize_design( + bc, + scoring = scoring_f, + shuffle_proposal_func = shuffle_grouped_data(bc, allocate_var = "Treatment", keep_together_vars = c("Strain", "Sex"), keep_separate_vars = c("Earmark"), @@ -238,10 +237,9 @@ the beginning!) We can finally use the customized shuffling function in the optimization. ```{r echo=TRUE} -bc3 <- bc$copy() - -trace <- optimize_design( - bc3, +bc3 <- optimize_design( + bc, + scoring = scoring_f, shuffle_proposal_func = shuffle_proposal, max_iter = 300 ) From 4a2df6b41cdab8c5690e3f86dd924c1fd2ca5512 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Wed, 26 Jul 2023 18:22:16 +0200 Subject: [PATCH 19/32] replace bc$scoring_f assignments with correct calls --- R/score_plates.R | 6 +++--- R/shuffle_samples.R | 6 +++--- README.Rmd | 10 ++++++---- README.md | 10 ++++++---- man/examples/shuffle_with_constraints.R | 3 ++- man/examples/two_step_optimization.R | 17 +++++++++++------ man/figures/README-optimized_assignment-1.png | Bin 2785 -> 4250 bytes man/figures/README-optimized_assignment-2.png | Bin 3834 -> 14595 bytes man/figures/README-random_assignment-1.png | Bin 2689 -> 4146 bytes 9 files changed, 31 insertions(+), 21 deletions(-) diff --git a/R/score_plates.R b/R/score_plates.R index 793bc24f..addea7e4 100644 --- a/R/score_plates.R +++ b/R/score_plates.R @@ -69,12 +69,12 @@ mk_dist_matrix <- function(plate_x = 12, plate_y = 8, dist = "minkowski", p = 2, #' bc <- BatchContainer$new( #' dimensions = c("column" = 6, "row" = 10) #' ) -#' assign_random(bc, invivo_study_samples) -#' bc$scoring_f <- mk_plate_scoring_functions( +#' bc <- assign_random(bc, invivo_study_samples) +#' scoring_f <- mk_plate_scoring_functions( #' bc, #' row = "row", column = "column", group = "Sex" #' ) -#' optimize_design(bc, max_iter = 100) +#' bc <- optimize_design(bc, scoring = scoring_f, max_iter = 100) #' plot_plate(bc$get_samples(), .col = Sex) #' mk_plate_scoring_functions <- function(batch_container, plate = NULL, row, column, group, p = 2, penalize_lines = "soft") { diff --git a/R/shuffle_samples.R b/R/shuffle_samples.R index ef56deb4..d54dab88 100644 --- a/R/shuffle_samples.R +++ b/R/shuffle_samples.R @@ -86,9 +86,9 @@ mk_constant_swapping_function <- function(n_swaps, quiet = FALSE) { #' bc <- BatchContainer$new( #' dimensions = c("plate" = 2, "column" = 5, "row" = 6) #' ) -#' bc$scoring_f <- osat_score_generator("plate", "Sex") -#' optimize_design( -#' bc, invivo_study_samples, +#' scoring_f <- osat_score_generator("plate", "Sex") +#' bc <- optimize_design( +#' bc, scoring = scoring_f, invivo_study_samples, #' max_iter = 100, #' shuffle_proposal_func = complete_random_shuffling #' ) diff --git a/README.Rmd b/README.Rmd index e5a3507c..5df79833 100644 --- a/README.Rmd +++ b/README.Rmd @@ -69,7 +69,7 @@ bc <- BatchContainer$new( # assign samples randomly set.seed(17) -assign_random(bc, subject_data) +bc <- assign_random(bc, subject_data) bc$get_samples() %>% ggplot() + @@ -82,7 +82,7 @@ Random assignmet of samples to batches produced an uneven distribution. ### Optimizing the assignemnt ```{r optimized_assignment, warning=FALSE} # set scoring functions -bc$scoring_f <- list( +scoring_f <- list( # first priority, groups are evenly distributed group = osat_score_generator(batch_vars = "batch", feature_vars = "Group"), @@ -91,7 +91,9 @@ bc$scoring_f <- list( feature_vars = "Sex") ) -trace <- optimize_design(bc, max_iter = 150, quiet = TRUE) +bc <- optimize_design( + bc, scoring = scoring_f, max_iter = 150, quiet = TRUE +) bc$get_samples() %>% ggplot() + @@ -99,7 +101,7 @@ bc$get_samples() %>% geom_bar() # show optimization trace -plot(trace) +bc$plot_trace() ``` ## Examples diff --git a/README.md b/README.md index bcfa081b..8be1fe49 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ bc <- BatchContainer$new( # assign samples randomly set.seed(17) -assign_random(bc, subject_data) +bc <- assign_random(bc, subject_data) bc$get_samples() %>% ggplot() + @@ -85,7 +85,7 @@ Random assignmet of samples to batches produced an uneven distribution. ``` r # set scoring functions -bc$scoring_f <- list( +scoring_f <- list( # first priority, groups are evenly distributed group = osat_score_generator(batch_vars = "batch", feature_vars = "Group"), @@ -94,7 +94,9 @@ bc$scoring_f <- list( feature_vars = "Sex") ) -trace <- optimize_design(bc, max_iter = 150, quiet = TRUE) +bc <- optimize_design( + bc, scoring = scoring_f, max_iter = 150, quiet = TRUE +) bc$get_samples() %>% ggplot() + @@ -107,7 +109,7 @@ bc$get_samples() %>% ``` r # show optimization trace -plot(trace) +bc$plot_trace() ``` ![](man/figures/README-optimized_assignment-2.png) diff --git a/man/examples/shuffle_with_constraints.R b/man/examples/shuffle_with_constraints.R index a8168bbe..6f9096d3 100644 --- a/man/examples/shuffle_with_constraints.R +++ b/man/examples/shuffle_with_constraints.R @@ -10,7 +10,7 @@ bc <- BatchContainer$new( dimensions = c("plate" = 5, "position" = 25) ) -bc$scoring_f <- function(samples) { +scoring_f <- function(samples) { osat_score( samples, "plate", @@ -23,6 +23,7 @@ bc$scoring_f <- function(samples) { # and destination location has a different plate number optimize_design( bc, + scoring = scoring_f, samples, shuffle_proposal = shuffle_with_constraints( # source is non-empty location diff --git a/man/examples/two_step_optimization.R b/man/examples/two_step_optimization.R index 7ca0f39c..7c8cbff2 100644 --- a/man/examples/two_step_optimization.R +++ b/man/examples/two_step_optimization.R @@ -7,7 +7,7 @@ bc <- BatchContainer$new( ) ) -assign_in_order(bc, samples = tibble::tibble( +bc <- assign_in_order(bc, samples = tibble::tibble( Group = c(rep(c("Grp 1", "Grp 2", "Grp 3", "Grp 4"), each = 8)), ID = 1:32 )) @@ -22,24 +22,29 @@ plot_plate(bc, ) # Step 1, assign samples to plates -bc$scoring_f <- osat_score_generator( +scoring_f <- osat_score_generator( batch_vars = c("plate"), feature_vars = c("Group") ) -optimize_design(bc, +optimize_design( + bc, + scoring = scoring_f, max_iter = 10, # the real number of iterations should be bigger n_shuffle = 2, quiet = TRUE ) -plot_plate(bc, +plot_plate( + bc, plate = plate, row = row, column = col, .color = Group ) # Step 2, distribute samples within plates -bc$scoring_f <- mk_plate_scoring_functions( +scoring_f <- mk_plate_scoring_functions( bc, plate = "plate", row = "row", column = "col", group = "Group" ) -optimize_design(bc, +bc <- optimize_design( + bc, + scoring = scoring_f, max_iter = 50, shuffle_proposal_func = mk_subgroup_shuffling_function(subgroup_vars = c("plate")), aggregate_scores_func = L2s_norm, diff --git a/man/figures/README-optimized_assignment-1.png b/man/figures/README-optimized_assignment-1.png index d2f931ee075bc7f91cae837a00d41a27549d17c8..cc5cd7802edb75426876ab510028ed7d978819d6 100644 GIT binary patch literal 4250 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 literal 2785 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 literal 3834 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 HmUQsHT_XC! diff --git a/man/figures/README-random_assignment-1.png b/man/figures/README-random_assignment-1.png index af7c64798a289056ef1ce1b43526cfd224840495..9ad8551c3e41c494eb63bbe48cf688d872f3b6d1 100644 GIT binary patch literal 4146 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 literal 2689 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 J)NJPF{s;a@1oHp@ From 22029e1f8a61c8ceb933aa497f1cb422c47b1243 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Wed, 26 Jul 2023 18:24:13 +0200 Subject: [PATCH 20/32] correct bc$scoring_f call --- R/shuffle_samples.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/shuffle_samples.R b/R/shuffle_samples.R index d54dab88..ad94b7ef 100644 --- a/R/shuffle_samples.R +++ b/R/shuffle_samples.R @@ -114,9 +114,9 @@ complete_random_shuffling <- function(batch_container, ...) { #' bc <- BatchContainer$new( #' dimensions = c("plate" = 2, "column" = 5, "row" = 6) #' ) -#' bc$scoring_f <- osat_score_generator("plate", "Sex") +#' scoring_f <- osat_score_generator("plate", "Sex") #' optimize_design( -#' bc, invivo_study_samples, +#' bc, scoring = scoring_f, invivo_study_samples, #' max_iter = 100, #' shuffle_proposal_func = mk_swapping_function(1) #' ) From 61e510829193e3c4b9ee818f2df82ebb7289e62a Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 27 Jul 2023 09:10:21 +0200 Subject: [PATCH 21/32] use full utils::head call --- R/trace.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/trace.R b/R/trace.R index 7abc89ac..72fa6358 100644 --- a/R/trace.R +++ b/R/trace.R @@ -27,7 +27,7 @@ shrink_mat <- function(m, last_iteration) { return(m) dplyr::bind_cols( tibble::tibble(step=seq_len(last_iteration)), - as.data.frame(head(m, last_iteration)) + as.data.frame(utils::head(m, last_iteration)) ) %>% list() } From eef96d434293e59b6f646874ba09c59d417e6bdf Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 27 Jul 2023 10:58:03 +0200 Subject: [PATCH 22/32] use here::here for rendering cached vignettes (more robust) --- DESCRIPTION | 3 ++- vignettes/cached/README.md | 2 +- vignettes/cached/_invivo_computed.Rmd | 2 +- vignettes/cached/_plate_scoring_ex1.Rmd | 2 +- vignettes/cached/_plate_scoring_ex2.Rmd | 2 +- vignettes/cached/_plate_scoring_ex3.Rmd | 2 +- vignettes/cached/_plate_scoring_ex4.Rmd | 2 +- vignettes/cached/_plate_scoring_ex5.Rmd | 2 +- 8 files changed, 9 insertions(+), 8 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 36c7f444..150abaa9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -70,7 +70,8 @@ Suggests: gridpattern, ggpattern, cowplot, - bestNormalize + bestNormalize, + here Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) diff --git a/vignettes/cached/README.md b/vignettes/cached/README.md index 3758311b..63f2916a 100644 --- a/vignettes/cached/README.md +++ b/vignettes/cached/README.md @@ -10,7 +10,7 @@ --- title: "Title of vignette child" output: html_fragment - knit: (\(input, ...) rmarkdown::render(input, output_dir = "vignettes/cached")) + knit: (\(input, ...) rmarkdown::render(input, output_dir = here::here("vignettes/cached"))) --- ```` * knit child to HTML fragment (either in RStudio or with `rmarkdown::render()`) diff --git a/vignettes/cached/_invivo_computed.Rmd b/vignettes/cached/_invivo_computed.Rmd index 596bb980..719042bd 100644 --- a/vignettes/cached/_invivo_computed.Rmd +++ b/vignettes/cached/_invivo_computed.Rmd @@ -1,7 +1,7 @@ --- title: "In vivo examples computed" output: html_fragment -knit: (\(input, ...) rmarkdown::render(input, output_dir = "vignettes/cached")) +knit: (\(input, ...) rmarkdown::render(input, output_dir = here::here("vignettes/cached"))) --- ```{r, include = FALSE} diff --git a/vignettes/cached/_plate_scoring_ex1.Rmd b/vignettes/cached/_plate_scoring_ex1.Rmd index 6a534e32..6314af63 100644 --- a/vignettes/cached/_plate_scoring_ex1.Rmd +++ b/vignettes/cached/_plate_scoring_ex1.Rmd @@ -1,7 +1,7 @@ --- title: "Plate scoring example 1" output: html_fragment -knit: (\(input, ...) rmarkdown::render(input, output_dir = "vignettes/cached")) +knit: (\(input, ...) rmarkdown::render(input, output_dir = here::here("vignettes/cached"))) --- ```{r, include = FALSE} diff --git a/vignettes/cached/_plate_scoring_ex2.Rmd b/vignettes/cached/_plate_scoring_ex2.Rmd index cc47b1e0..43ce067c 100644 --- a/vignettes/cached/_plate_scoring_ex2.Rmd +++ b/vignettes/cached/_plate_scoring_ex2.Rmd @@ -1,7 +1,7 @@ --- title: "Plate scoring example 2" output: html_fragment -knit: (\(input, ...) rmarkdown::render(input, output_dir = "vignettes/cached")) +knit: (\(input, ...) rmarkdown::render(input, output_dir = here::here("vignettes/cached"))) --- ```{r, include = FALSE} diff --git a/vignettes/cached/_plate_scoring_ex3.Rmd b/vignettes/cached/_plate_scoring_ex3.Rmd index 8f5fdca6..898c5c4e 100644 --- a/vignettes/cached/_plate_scoring_ex3.Rmd +++ b/vignettes/cached/_plate_scoring_ex3.Rmd @@ -1,7 +1,7 @@ --- title: "Plate scoring example 3" output: html_fragment -knit: (\(input, ...) rmarkdown::render(input, output_dir = "vignettes/cached")) +knit: (\(input, ...) rmarkdown::render(input, output_dir = here::here("vignettes/cached"))) --- ```{r, include = FALSE} diff --git a/vignettes/cached/_plate_scoring_ex4.Rmd b/vignettes/cached/_plate_scoring_ex4.Rmd index c12c29bc..603fe20f 100644 --- a/vignettes/cached/_plate_scoring_ex4.Rmd +++ b/vignettes/cached/_plate_scoring_ex4.Rmd @@ -1,7 +1,7 @@ --- title: "Plate scoring example 4" output: html_fragment -knit: (\(input, ...) rmarkdown::render(input, output_dir = "vignettes/cached")) +knit: (\(input, ...) rmarkdown::render(input, output_dir = here::here("vignettes/cached"))) --- ```{r, include = FALSE} diff --git a/vignettes/cached/_plate_scoring_ex5.Rmd b/vignettes/cached/_plate_scoring_ex5.Rmd index be4af9be..9eb6840e 100644 --- a/vignettes/cached/_plate_scoring_ex5.Rmd +++ b/vignettes/cached/_plate_scoring_ex5.Rmd @@ -1,7 +1,7 @@ --- title: "Plate scoring example 5" output: html_fragment -knit: (\(input, ...) rmarkdown::render(input, output_dir = "vignettes/cached")) +knit: (\(input, ...) rmarkdown::render(input, output_dir = here::here("vignettes/cached"))) --- ```{r, include = FALSE} From 26839c2ecdcd2b7325605faed920d852e17dfa53 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 27 Jul 2023 10:58:52 +0200 Subject: [PATCH 23/32] ggpattern is on CRAN, remove remotes statement --- DESCRIPTION | 4 ---- 1 file changed, 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 150abaa9..c0cca1e0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -67,7 +67,6 @@ Suggests: tidyverse, printr, devtools (>= 2.0.0), - gridpattern, ggpattern, cowplot, bestNormalize, @@ -78,6 +77,3 @@ Roxygen: list(markdown = TRUE) RoxygenNote: 7.2.3 VignetteBuilder: knitr biocViews: -Remotes: - github::trevorld/gridpattern, - github::coolbutuseless/ggpattern From 37d158d5e448b31aa3354522e97071b7bee16a12 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 27 Jul 2023 10:59:48 +0200 Subject: [PATCH 24/32] examples and test, adapt to new read-only bc --- R/plot.R | 2 +- man/examples/shuffle_with_constraints.R | 2 +- man/examples/two_step_optimization.R | 2 +- tests/testthat/test_get_samples.R | 4 ++-- tests/testthat/test_samples_attr.R | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/R/plot.R b/R/plot.R index 6f880a24..d1a9c4bc 100644 --- a/R/plot.R +++ b/R/plot.R @@ -78,7 +78,7 @@ plot_design <- function(.tbl, ..., .color, .alpha = NULL) { #' ) #' #' # assign samples from the sample sheet -#' assign_random(bc, samples = sample_sheet) +#' bc <- assign_random(bc, samples = sample_sheet) #' #' plot_plate(bc$get_samples(), #' plate = plate, column = column, row = row, diff --git a/man/examples/shuffle_with_constraints.R b/man/examples/shuffle_with_constraints.R index 6f9096d3..fe704c5e 100644 --- a/man/examples/shuffle_with_constraints.R +++ b/man/examples/shuffle_with_constraints.R @@ -21,7 +21,7 @@ scoring_f <- function(samples) { # in this example we treat all the positions in the plate as equal. # when shuffling we enforce that source location is non-empty, # and destination location has a different plate number -optimize_design( +bc <- optimize_design( bc, scoring = scoring_f, samples, diff --git a/man/examples/two_step_optimization.R b/man/examples/two_step_optimization.R index 7c8cbff2..94635413 100644 --- a/man/examples/two_step_optimization.R +++ b/man/examples/two_step_optimization.R @@ -25,7 +25,7 @@ plot_plate(bc, scoring_f <- osat_score_generator( batch_vars = c("plate"), feature_vars = c("Group") ) -optimize_design( +bc <- optimize_design( bc, scoring = scoring_f, max_iter = 10, # the real number of iterations should be bigger diff --git a/tests/testthat/test_get_samples.R b/tests/testthat/test_get_samples.R index 0fdc8588..df3f3a82 100644 --- a/tests/testthat/test_get_samples.R +++ b/tests/testthat/test_get_samples.R @@ -18,7 +18,7 @@ test_that("$get_samles(as_tibble=TRUE) returns correct columns & expected number expect_true(tibble::is_tibble(stab)) expect_named(stab, c("row", "column", ".sample_id", "i")) expect_equal(nrow(stab), 8) - assign_random(bc) + bc <- assign_random(bc) stab <- bc$get_samples(include_id = TRUE, assignment = FALSE) expect_true(tibble::is_tibble(stab)) expect_named(stab, c(".sample_id", "i")) @@ -46,7 +46,7 @@ test_that("$get_samles(as_tibble=FALSE) returns correct columns & expected numbe expect_true(data.table::is.data.table(stab)) expect_named(stab, c("row", "column", ".sample_id", "i")) expect_equal(nrow(stab), 8) - assign_random(bc) + bc <- assign_random(bc) stab <- bc$get_samples(include_id = TRUE, assignment = FALSE, as_tibble = FALSE) expect_true(data.table::is.data.table(stab)) expect_named(stab, c(".sample_id", "i")) diff --git a/tests/testthat/test_samples_attr.R b/tests/testthat/test_samples_attr.R index 74fbb43c..26c58565 100644 --- a/tests/testthat/test_samples_attr.R +++ b/tests/testthat/test_samples_attr.R @@ -23,7 +23,7 @@ test_that("add attributes after assigning samples", { test_that("shuffling samples keeps attributes order", { set.seed(42) - assign_random(bc) + bc <- assign_random(bc) bc$get_samples(include_id = TRUE, as_tibble = FALSE) bc$samples_attr <- data.frame(attr1 = rev(1:8)) expect_equal(bc$get_samples(assignment = FALSE)$attr1, rev(1:8)) From e263aadcac5ed6ab07856c2cb91ed9aa317870b8 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 27 Jul 2023 11:01:49 +0200 Subject: [PATCH 25/32] adapt invivo and plate scoring vignettes --- vignettes/cached/_plate_scoring_ex1.Rmd | 25 ++++---- vignettes/cached/_plate_scoring_ex2.Rmd | 73 ++++++++++++----------- vignettes/cached/_plate_scoring_ex3.Rmd | 24 ++++---- vignettes/cached/_plate_scoring_ex4.Rmd | 79 ++++++++++++------------- vignettes/cached/_plate_scoring_ex5.Rmd | 18 +++--- vignettes/invivo_study_design.Rmd | 42 ++++++------- 6 files changed, 132 insertions(+), 129 deletions(-) diff --git a/vignettes/cached/_plate_scoring_ex1.Rmd b/vignettes/cached/_plate_scoring_ex1.Rmd index 6314af63..7a0ada3f 100644 --- a/vignettes/cached/_plate_scoring_ex1.Rmd +++ b/vignettes/cached/_plate_scoring_ex1.Rmd @@ -35,7 +35,7 @@ example1 <- BatchContainer$new( # Add samples to container # Need unique Sample ID. Can we drop this constraint? -assign_in_order(example1, +example1 <- assign_in_order(example1, samples = tibble::tibble( Group = rep(c("Grp 1", "Grp 2", "Grp 3", "Grp 4"), each = 4), ID = 1:16 @@ -43,26 +43,25 @@ assign_in_order(example1, ) # The following does not work (an gives a constant score of 144!) -# example1$scoring_f <- osat_score_generator(batch_vars = c("row","col"), feature_vars = c("Group")) +# scoring_f <- osat_score_generator(batch_vars = c("row","col"), feature_vars = c("Group")) # First analysis of problem indicates that osat_score generates a full row*col vector of 'ideal scores' # which are in fact the same value, implying an identical overall result as each position can be either # allocated by 1 sample or 0 samples, the sum of 1's being the sample count. # --> don't use osat_score if there's a lack of samples as compared to possible positioning -bc <- example1$copy() - # # Set scoring function -bc$scoring_f <- list( +scoring_f <- list( Row.Score = osat_score_generator(batch_vars = c("row"), feature_vars = c("Group")), Column.Score = osat_score_generator(batch_vars = c("col"), feature_vars = c("Group")) ) ``` ```{r} - set.seed(41) -trace <- optimize_design(bc, +bc <- optimize_design( + example1, + scoring = scoring_f, max_iter = 300, # this is set to shorten vignette run-time based on known random seed, normally we don't know. n_shuffle = 2, acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 1000, alpha = 0.5)), @@ -71,7 +70,7 @@ trace <- optimize_design(bc, ``` ```{r} -trace +bc$trace$elapsed plot_plate(bc, plate = plate, row = row, column = col, .color = Group, @@ -85,14 +84,14 @@ This should reliably lead to a nice symmetry-bearing latin square design with only a one-dimensional score to look at. ```{r} -bc <- example1$copy() - -bc$scoring_f <- mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Group") +scoring_f <- mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Group") ``` ```{r} set.seed(42) -trace <- optimize_design(bc, +bc <- optimize_design( + example1, + scoring = scoring_f, max_iter = 1000, # this is set to shorten vignette run-time based on random seed, normally we don't know. n_shuffle = 2, acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 1000, alpha = 0.5)), @@ -101,7 +100,7 @@ trace <- optimize_design(bc, ``` ```{r} -trace$elapsed +bc$trace$elapsed plot_plate(bc, plate = plate, row = row, column = col, .color = Group, diff --git a/vignettes/cached/_plate_scoring_ex2.Rmd b/vignettes/cached/_plate_scoring_ex2.Rmd index 43ce067c..b3cba4bc 100644 --- a/vignettes/cached/_plate_scoring_ex2.Rmd +++ b/vignettes/cached/_plate_scoring_ex2.Rmd @@ -40,28 +40,28 @@ example2 <- BatchContainer$new( ) # Add samples to container -assign_in_order(example2, samples = tibble::tibble( +example2 <- assign_in_order(example2, samples = tibble::tibble( Group = c(rep(c("Grp 1", "Grp 2", "Grp 3", "Grp 4"), each = 8)), ID = 1:32 )) -bc <- example2$copy() - -bc$scoring_f <- c(mk_plate_scoring_functions(bc, plate = "plate", row = "row", column = "col", group = "Group"), +scoring_f <- c(mk_plate_scoring_functions(example2, plate = "plate", row = "row", column = "col", group = "Group"), osat_plate = osat_score_generator(batch_vars = c("plate"), feature_vars = c("Group")) ) -plot_plate(bc, +plot_plate(example2, plate = plate, row = row, column = col, .color = Group, title = "Ex2: Initial sample arrangement" ) -bc$score() +example2$score(scoring_f) ``` ```{r} set.seed(41) -trace <- optimize_design(bc, +bc <- optimize_design( + example2, + scoring = scoring_f, n_shuffle = c(rep(10, 10), rep(3, 90), rep(2, 100), rep(1, 1400)), acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 10000, alpha = 0.5)), aggregate_scores_func = worst_score, @@ -70,9 +70,9 @@ trace <- optimize_design(bc, ``` ```{r} -trace$elapsed +bc$trace$elapsed -bc$score() +bc$score(scoring_f) plot_plate(bc, plate = plate, row = row, column = col, .color = Group, @@ -92,33 +92,34 @@ permutation function which takes the plate structure into account and only shuffles samples around within one plate. ```{r} -# Setting up the batch container - -bc <- example2$copy() - -bc$scoring_f <- osat_score_generator(batch_vars = c("plate"), feature_vars = c("Group")) +scoring_f <- osat_score_generator(batch_vars = c("plate"), feature_vars = c("Group")) set.seed(42) -optimize_design(bc, +bc <- optimize_design( + example2, + scoring = scoring_f, quiet = TRUE, max_iter = 200, # this is set to shorten vignette run-time, normally we don't know. n_shuffle = 2, acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 10000, alpha = 0.5)), ) +bc$trace$elapsed plot_plate(bc, plate = plate, row = row, column = col, .color = Group, title = "Ex2: 'Plate wise' design\nStep 1: after allocating samples to plates" ) -bc$scoring_f <- mk_plate_scoring_functions(bc, plate = "plate", row = "row", column = "col", group = "Group") +scoring_f <- mk_plate_scoring_functions(bc, plate = "plate", row = "row", column = "col", group = "Group") -bc$score() +bc$score(scoring_f) ``` ```{r} set.seed(42) -trace <- optimize_design(bc, +bc <- optimize_design( + bc, + scoring = scoring_f, max_iter = 400, shuffle_proposal_func = mk_subgroup_shuffling_function(subgroup_vars = c("plate")), acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 1000, alpha = 0.5)), @@ -128,9 +129,9 @@ trace <- optimize_design(bc, ``` ```{r} -trace +bc$trace$elapsed -bc$score() +bc$score(scoring_f) plot_plate(bc, plate = plate, row = row, column = col, .color = Group, @@ -153,16 +154,14 @@ happen first within plate 1, then within plate 2, so that the two scores can be optimized in succeeding runs. ```{r} -# Setting up the batch container - -bc <- example2$copy() - -bc$scoring_f <- osat_score_generator(batch_vars = c("plate"), feature_vars = c("Group")) +scoring_f <- osat_score_generator(batch_vars = c("plate"), feature_vars = c("Group")) ``` ```{r} set.seed(42) -trace <- optimize_design(bc, +bc <- optimize_design( + example2, + scoring = scoring_f, quiet = TRUE, max_iter = 150, # this is set to shorten vignette run-time, normally we don't know. n_shuffle = 2, @@ -171,21 +170,23 @@ trace <- optimize_design(bc, ``` ```{r} -trace +bc$trace$elapsed plot_plate(bc, plate = plate, row = row, column = col, .color = Group, title = "Ex2: 'Serial plate' design\nStep 1: after allocating samples to plates" ) -bc$scoring_f <- mk_plate_scoring_functions(bc, plate = "plate", row = "row", column = "col", group = "Group") +scoring_f <- mk_plate_scoring_functions(bc, plate = "plate", row = "row", column = "col", group = "Group") -bc$score() +bc$score(scoring_f) ``` ```{r} set.seed(42) -trace <- optimize_design(bc, +bc <- optimize_design( + bc, + scoring = scoring_f, max_iter = 150, quiet = TRUE, shuffle_proposal_func = mk_subgroup_shuffling_function(subgroup_vars = c("plate"), restrain_on_subgroup_levels = c(1)), @@ -195,14 +196,16 @@ trace <- optimize_design(bc, ``` ```{r} -trace +bc$trace$elapsed -bc$score() +bc$score(scoring_f) ``` ```{r} set.seed(42) -trace <- optimize_design(bc, +bc <- optimize_design( + bc, + scoring = scoring_f, max_iter = 550, quiet = TRUE, shuffle_proposal_func = mk_subgroup_shuffling_function(subgroup_vars = c("plate"), restrain_on_subgroup_levels = c(2)), @@ -212,9 +215,9 @@ trace <- optimize_design(bc, ``` ```{r} -trace +bc$trace$elapsed -bc$score() +bc$score(scoring_f) plot_plate(bc, plate = plate, row = row, column = col, .color = Group, diff --git a/vignettes/cached/_plate_scoring_ex3.Rmd b/vignettes/cached/_plate_scoring_ex3.Rmd index 898c5c4e..872e9d9d 100644 --- a/vignettes/cached/_plate_scoring_ex3.Rmd +++ b/vignettes/cached/_plate_scoring_ex3.Rmd @@ -51,7 +51,7 @@ example3 <- BatchContainer$new( # Assign samples randomly to start from a better initial state -assign_random(example3, +example3 <- assign_random(example3, samples = tibble::tibble( Group = rep.int(c("Grp 1", "Grp 2", "Grp3"), times = c(69, 30, 69) @@ -60,14 +60,14 @@ assign_random(example3, ) ) -bc <- example3$copy() - -bc$scoring_f <- osat_score_generator(batch_vars = c("plate"), feature_vars = c("Group")) +scoring_f <- osat_score_generator(batch_vars = c("plate"), feature_vars = c("Group")) ``` ```{r} set.seed(42) -trace <- optimize_design(bc, +bc <- optimize_design( + example3, + scoring = scoring_f, quiet = TRUE, max_iter = 150, n_shuffle = 2, @@ -76,7 +76,7 @@ trace <- optimize_design(bc, ``` ```{r} -trace +bc$trace$elapsed ``` ```{r, fig.width=7, fig.height=3.5} @@ -87,17 +87,19 @@ plot_plate(bc, ``` ```{r} -bc$scoring_f <- mk_plate_scoring_functions(bc, +scoring_f <- mk_plate_scoring_functions(bc, plate = "plate", row = "row", column = "col", group = "Group" ) -bc$score() +bc$score(scoring_f) ``` ```{r} set.seed(42) -trace <- optimize_design(bc, +bc <- optimize_design( + bc, + scoring = scoring_f, max_iter = 300, shuffle_proposal_func = mk_subgroup_shuffling_function( subgroup_vars = c("plate"), @@ -110,9 +112,9 @@ trace <- optimize_design(bc, ``` ```{r} -trace$elapsed +bc$trace$elapsed -bc$score() +bc$score(scoring_f) ``` ```{r, fig.width=7, fig.height=3.5} diff --git a/vignettes/cached/_plate_scoring_ex4.Rmd b/vignettes/cached/_plate_scoring_ex4.Rmd index 603fe20f..6ce2e1c5 100644 --- a/vignettes/cached/_plate_scoring_ex4.Rmd +++ b/vignettes/cached/_plate_scoring_ex4.Rmd @@ -38,31 +38,31 @@ example4 <- BatchContainer$new( # Assign samples randomly to start from lower score (avoid Inf values even since plate 3 will miss 2 groups initially :) -assign_in_order(example4, samples = tibble::tibble( +example4 <- assign_in_order(example4, samples = tibble::tibble( Group = rep.int(c("Treatment 1", "Treatment 2"), times = c(10, 10)), Sex = c(rep(c("M", "F", "F", "M"), times = 4), "M", NA, NA, "F"), ID = 1:20 )) -bc <- example4$copy() - cowplot::plot_grid( - plot_plate(bc, plate = plate, row = row, column = col, .color = Group, title = "Initial layout by Group"), - plot_plate(bc, plate = plate, row = row, column = col, .color = Sex, title = "Initial layout by Sex"), + plot_plate(example4, plate = plate, row = row, column = col, .color = Group, title = "Initial layout by Group"), + plot_plate(example4, plate = plate, row = row, column = col, .color = Sex, title = "Initial layout by Sex"), ncol = 2 ) -bc$scoring_f <- c( - Group = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Group"), - Sex = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Sex") +scoring_f <- c( + Group = mk_plate_scoring_functions(example4, row = "row", column = "col", group = "Group"), + Sex = mk_plate_scoring_functions(example4, row = "row", column = "col", group = "Sex") ) -bc$score() +example4$score(scoring_f) ``` ```{r} set.seed(42) -trace <- optimize_design(bc, +bc <- optimize_design( + example4, + scoring = scoring_f, max_iter = 750, n_shuffle = 1, acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 10000, alpha = 1)), @@ -74,9 +74,9 @@ trace <- optimize_design(bc, ``` ```{r, fig.width=7, fig.height=3.5} -trace$elapsed +bc$trace$elapsed -bc$score() +bc$score(scoring_f) cowplot::plot_grid( plot_plate(bc, plate = plate, row = row, column = col, .color = Group, title = "Final layout by Group"), @@ -90,7 +90,9 @@ reference! ```{r} set.seed(42) -trace <- optimize_design(bc, +bc <- optimize_design( + bc, + scoring = scoring_f, max_iter = 500, n_shuffle = 1, acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 10000, alpha = 1)), @@ -103,7 +105,7 @@ trace <- optimize_design(bc, ``` ```{r, fig.width=7, fig.height=3.5} -bc$score() +bc$score(scoring_f) cowplot::plot_grid( plot_plate(bc, plate = plate, row = row, column = col, .color = Group, title = "Final layout by Group"), @@ -118,17 +120,17 @@ default acceptance function. We are strictly prioritizing the leftmost score in addition to reflect relevance for the design. ```{r} -bc <- example4$copy() - -bc$scoring_f <- c( - Group = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Group"), - Sex = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Sex") +scoring_f <- c( + Group = mk_plate_scoring_functions(example4, row = "row", column = "col", group = "Group"), + Sex = mk_plate_scoring_functions(example4, row = "row", column = "col", group = "Sex") ) -bc$score() +example4$score(scoring_f) set.seed(42) -trace <- optimize_design(bc, +bc <- optimize_design( + example4, + scoring = scoring_f, max_iter = 5000, n_shuffle = 1, acceptance_func = accept_leftmost_improvement, @@ -138,7 +140,7 @@ trace <- optimize_design(bc, ``` ```{r, fig.width=7, fig.height=3.5} -bc$score() +bc$score(scoring_f) cowplot::plot_grid( plot_plate(bc, plate = plate, row = row, column = col, .color = Group, title = "Final layout by Group"), @@ -151,17 +153,16 @@ Using a tolerance value to accept slightly worse solutions in the leftmost relevant score if overcompensated by other scores: ```{r} -bc <- example4$copy() - - -bc$scoring_f <- c( - Group = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Group"), - Sex = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Sex") +scoring_f <- c( + Group = mk_plate_scoring_functions(example4, row = "row", column = "col", group = "Group"), + Sex = mk_plate_scoring_functions(example4, row = "row", column = "col", group = "Sex") ) set.seed(42) -trace <- optimize_design(bc, +bc <- optimize_design( + example4, + scoring = scoring_f, max_iter = 5000, n_shuffle = 1, acceptance_func = ~ accept_leftmost_improvement(..., tolerance = 0.1), @@ -169,12 +170,10 @@ trace <- optimize_design(bc, quiet = TRUE ) -bc$score() +bc$score(scoring_f) ``` ```{r, fig.width=7, fig.height=3.5} -bc$score() - cowplot::plot_grid( plot_plate(bc, plate = plate, row = row, column = col, .color = Group, title = "Final layout by Group"), plot_plate(bc, plate = plate, row = row, column = col, .color = Sex, title = "Final layout by Sex"), @@ -188,17 +187,17 @@ $\kappa^p$, $0 < \kappa < 1$ We choose a $\kappa$ of 0.5, i.e. the second score's improvement counts half of that of the first one. ```{r} -bc <- example4$copy() - -bc$scoring_f <- c( - Group = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Group"), - Sex = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Sex") +scoring_f <- c( + Group = mk_plate_scoring_functions(example4, row = "row", column = "col", group = "Group"), + Sex = mk_plate_scoring_functions(example4, row = "row", column = "col", group = "Sex") ) -bc$score() +bc$score(scoring_f) set.seed(42) -trace <- optimize_design(bc, +bc <- optimize_design( + example4, + scoring = scoring_f, max_iter = 1000, n_shuffle = 1, acceptance_func = mk_exponentially_weighted_acceptance_func(kappa = 0.5, simulated_annealing = T), @@ -208,7 +207,7 @@ trace <- optimize_design(bc, ``` ```{r, fig.width=7, fig.height=3.5} -bc$score() +bc$score(scoring_f) cowplot::plot_grid( plot_plate(bc, plate = plate, row = row, column = col, .color = Group, title = "Final layout by Group"), diff --git a/vignettes/cached/_plate_scoring_ex5.Rmd b/vignettes/cached/_plate_scoring_ex5.Rmd index 9eb6840e..064d0571 100644 --- a/vignettes/cached/_plate_scoring_ex5.Rmd +++ b/vignettes/cached/_plate_scoring_ex5.Rmd @@ -33,25 +33,25 @@ example5 <- BatchContainer$new( ) # Assign samples randomly to start from lower score (avoid `Inf` values when doing the 'hard' penalization) -assign_random(example5, samples = tibble::tibble( +example5 <- assign_random(example5, samples = tibble::tibble( Group = rep.int(paste("Group", 1:5), times = c(8, 8, 8, 8, 64)), ID = 1:96 )) penalize_lines <- "hard" -bc <- example5$copy() - -bc$scoring_f <- c( - Group = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Group", p = 2, penalize_lines = penalize_lines) +scoring_f <- c( + Group = mk_plate_scoring_functions(example5, row = "row", column = "col", group = "Group", p = 2, penalize_lines = penalize_lines) ) -bc$score() +example5$score(scoring_f) ``` ```{r} set.seed(42) -trace <- optimize_design(bc, +bc <- optimize_design( + example5, + scoring = scoring_f, max_iter = 5000, n_shuffle = 1, acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 500, alpha = 0.1)), @@ -60,9 +60,9 @@ trace <- optimize_design(bc, ``` ```{r, fig.width=7, fig.height=3.5} -trace$elapsed +bc$trace$elapsed -bc$score() +bc$score(scoring_f) plot_plate(bc, plate = plate, row = row, column = col, .color = Group, title = stringr::str_c("Line penalization: ", penalize_lines)) ``` diff --git a/vignettes/invivo_study_design.Rmd b/vignettes/invivo_study_design.Rmd index d09375ef..fec8ac1f 100644 --- a/vignettes/invivo_study_design.Rmd +++ b/vignettes/invivo_study_design.Rmd @@ -215,7 +215,7 @@ InVivo_assignTreatments <- function(animal_list, treatments, exclude = exclude_table ) - assign_in_order(bc_treatment, ani_bclevels) + bc_treatment <- assign_in_order(bc_treatment, ani_bclevels) if (!quiet_process) message("Constructing scoring functions:") @@ -275,20 +275,20 @@ InVivo_assignTreatments <- function(animal_list, treatments, } assertthat::assert_that(length(scoring_functions) > 0, msg = "No variables for scoring found or all have only one level. Nothing to do.") - bc_treatment$scoring_f <- scoring_functions - bc_treatment$score() + bc_treatment$score(scoring_functions) - trace <- optimize_design( + bc_treatment <- optimize_design( bc_treatment, + scoring = scoring_functions, n_shuffle = n_shuffle, acceptance_func = ~ accept_leftmost_improvement(..., tolerance = 0.1), quiet = quiet_optimize ) # Check if user given constraints (if provided) could be satisfied - if ("trt_constraints" %in% names(bc_treatment$score())) { - if (bc_treatment$score()[["trt_constraints"]] > 0) { - message("CAUTION: User defined constraints could not be fully met (remaining score ", bc_treatment$score()[["trt_constraints"]], ")") + if ("trt_constraints" %in% names(bc_treatment$score(scoring_functions))) { + if (bc_treatment$score(scoring_functions)[["trt_constraints"]] > 0) { + message("CAUTION: User defined constraints could not be fully met (remaining score ", bc_treatment$score(scoring_functions)[["trt_constraints"]], ")") } else { if (!quiet_process) message("Success. User provided constraints could be fully met.") } @@ -325,7 +325,7 @@ Invivo_assignCages <- function(design_trt, bc_cage <- BatchContainer$new( dimensions = c("Dummy" = 1, ID = nrow(design_trt)) ) - assign_in_order(bc_cage, design_trt) + bc_cage <- assign_in_order(bc_cage, design_trt) shuffle_proposal <- shuffle_grouped_data(bc_cage, allocate_var = "Dummy", @@ -372,15 +372,14 @@ Invivo_assignCages <- function(design_trt, scoring_functions <- c(scoring_functions, sf) } - if (length(scoring_functions) > 0) { - bc_cage$scoring_f <- scoring_functions - } else { + if (length(scoring_functions) == 0) { if (!quiet_process) message(" ... just a dummy score as there are no user provided balancing variables") - bc_cage$scoring_f <- osat_score_generator(batch_vars = "Dummy", feature_vars = c("Treatment")) + scoring_functions <- osat_score_generator(batch_vars = "Dummy", feature_vars = c("Treatment")) } - trace <- optimize_design( + bc_cage <- optimize_design( bc_cage, + scoring = scoring_functions, shuffle_proposal_func = shuffle_proposal, acceptance_func = accept_leftmost_improvement, max_iter = maxiter, @@ -436,7 +435,7 @@ Invivo_arrangeCages <- function(design_cage, dimensions = c(Rack = nr_racks, CageRow = n_cage_x, CageCol = n_cage_y) ) - assign_random(bc_rack, design_rack) + bc_rack <- assign_random(bc_rack, design_rack) # Firstly, distribute variables across racks if necessary if (nr_racks > 1) { @@ -447,16 +446,18 @@ Invivo_arrangeCages <- function(design_cage, ) } - bc_rack$scoring_f <- list(across_rack = osat_score_generator(batch_vars = c("Rack"), feature_vars = distribute_cagerack_vars)) + scoring_functions <- list(across_rack = osat_score_generator(batch_vars = c("Rack"), feature_vars = distribute_cagerack_vars)) - optimize_design(bc_rack, + bc_rack <- optimize_design( + bc_rack, + scoring = scoring_functions, quiet = quiet_optimize, min_score = 0, max_iter = 1e3, n_shuffle = 2, acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 10000, alpha = 0.5)) ) - if (!quiet_process) message(" ... final score: ", bc_rack$score()) + if (!quiet_process) message(" ... final score: ", bc_rack$score(scoring_f)) } if (!quiet_process) { @@ -475,13 +476,12 @@ Invivo_arrangeCages <- function(design_cage, } names(scoring_functions) <- stringr::str_c(names(scoring_functions), rep(distribute_cagerack_vars, each = nr_racks), sep = "_") - bc_rack$scoring_f <- scoring_functions - for (i in 1:nr_racks) { if (!quiet_process) message(" ... Rack ", i) - trace <- optimize_design( + bc_rack <- optimize_design( bc_rack, + scoring = scoring_functions, shuffle_proposal_func = mk_subgroup_shuffling_function( subgroup_vars = "Rack", restrain_on_subgroup_levels = c(i), @@ -495,7 +495,7 @@ Invivo_arrangeCages <- function(design_cage, ) } - if (!quiet_process) message(" ... final scores: ", paste(names(bc_rack$score()), round(bc_rack$score(), 2), sep = ": ", collapse = ", ")) + if (!quiet_process) message(" ... final scores: ", paste(names(bc_rack$score(scoring_functions)), round(bc_rack$score(scoring_functions), 2), sep = ": ", collapse = ", ")) # Translate Rack numbers to some text output and assign CageNr design_rack <- bc_rack$get_samples() %>% From c792a8cefeaec20eabab32c08edf9cb5b8e3e0d5 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 27 Jul 2023 11:02:28 +0200 Subject: [PATCH 26/32] re-render cache --- vignettes/cached/_invivo_computed.html | 18 ++-- vignettes/cached/_plate_scoring_ex1.html | 37 ++++--- vignettes/cached/_plate_scoring_ex2.html | 118 ++++++++++++----------- vignettes/cached/_plate_scoring_ex3.html | 47 +++++---- vignettes/cached/_plate_scoring_ex4.html | 107 ++++++++++---------- vignettes/cached/_plate_scoring_ex5.html | 34 ++++--- 6 files changed, 191 insertions(+), 170 deletions(-) diff --git a/vignettes/cached/_invivo_computed.html b/vignettes/cached/_invivo_computed.html index cdf00f45..548ed2ba 100644 --- a/vignettes/cached/_invivo_computed.html +++ b/vignettes/cached/_invivo_computed.html @@ -25,7 +25,7 @@

Calculating the design

#> ... user specified treatment allocation constraint (Treatment-Strain-Sex) #> ... facilitating homogeneity of treatment in cages (CageGroup) #> ... ANOVA -logP for numerical variables balanced across treatment (ArrivalWeight, AgeGroup) -#> CAUTION: User defined constraints could not be fully met (remaining score 4) +#> Success. User provided constraints could be fully met. # Form cages with reasonable animal numbers and compliant with all constraints design_cage <- Invivo_assignCages(design_trt, @@ -39,17 +39,15 @@

Calculating the design

) #> Setting up batch container. #> -#> Formed 19 homogeneous groups using 59 samples. +#> Formed 22 homogeneous groups using 59 samples. #> 27 subgroups needed to satisfy size constraints. #> #> Finding possible ways to allocate variable of interest with 1 levels ... #> #> Finished with 27 recursive calls. #> 1 allocations found. -#> No permutations fulfilling the 'keep_separate' constraints in 1000 iters! -#> Increasing number of tolerated violations to 1 #> -#> Expecting 27 cages to be created and 3 single-housed animals. +#> Expecting 27 cages to be created and 4 single-housed animals. #> Constructing scoring functions: #> ... ANOVA -logP for numerical variables balanced across cages (ArrivalWeight, AgeGroup) #> Adding 4 attributes to samples. @@ -70,7 +68,7 @@

Calculating the design

#> Distributing target variables (Treatment, Strain, Sex) within rack #> ... Rack 1 #> ... Performing simple mean/stddev adjustment. -#> ... final scores: Plate_Treatment: 5.2, Plate_Strain: 5.46, Plate_Sex: 5.67 +#> ... final scores: Plate_Treatment: 5.12, Plate_Strain: 5.48, Plate_Sex: 5.72

Visualization of the study design

@@ -82,7 +80,7 @@

Cage composition

treatment, strain and sex.

Females are exclusively used for treatment 2, as was specified in the treatment list.

-

+

Body weights

@@ -95,19 +93,19 @@

Body weights

compensate, achieving better cross-treatment balance of this factor.

Red diamonds mark the mean values for a specific sex within each treatment group.

-

+

Cage arrangement in rack

The following plots show the organization of the cage rack, individual cages colored by different variables each time.

-

+

Individual animals in cages

Finally, an overview plot illustrates the placement of animals in the cages. Notice the distinct earmarks within each cage, a ‘soft’ design constraint that could be achieved with the given solution.

-

+

diff --git a/vignettes/cached/_plate_scoring_ex1.html b/vignettes/cached/_plate_scoring_ex1.html index 6f97caa9..332c6764 100644 --- a/vignettes/cached/_plate_scoring_ex1.html +++ b/vignettes/cached/_plate_scoring_ex1.html @@ -31,7 +31,7 @@

Example 1: An expensive way to construct a 4x4 latin square (one # Add samples to container # Need unique Sample ID. Can we drop this constraint? -assign_in_order(example1, +example1 <- assign_in_order(example1, samples = tibble::tibble( Group = rep(c("Grp 1", "Grp 2", "Grp 3", "Grp 4"), each = 4), ID = 1:16 @@ -39,23 +39,22 @@

Example 1: An expensive way to construct a 4x4 latin square (one ) # The following does not work (an gives a constant score of 144!) -# example1$scoring_f <- osat_score_generator(batch_vars = c("row","col"), feature_vars = c("Group")) +# scoring_f <- osat_score_generator(batch_vars = c("row","col"), feature_vars = c("Group")) # First analysis of problem indicates that osat_score generates a full row*col vector of 'ideal scores' # which are in fact the same value, implying an identical overall result as each position can be either # allocated by 1 sample or 0 samples, the sum of 1's being the sample count. # --> don't use osat_score if there's a lack of samples as compared to possible positioning -bc <- example1$copy() - # # Set scoring function -bc$scoring_f <- list( +scoring_f <- list( Row.Score = osat_score_generator(batch_vars = c("row"), feature_vars = c("Group")), Column.Score = osat_score_generator(batch_vars = c("col"), feature_vars = c("Group")) ) -

-set.seed(41)
+
set.seed(41)
 
-trace <- optimize_design(bc,
+bc <- optimize_design(
+  example1,
+  scoring = scoring_f,
   max_iter = 300, # this is set to shorten vignette run-time based on known random seed, normally we don't know.
   n_shuffle = 2,
   acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 1000, alpha = 0.5)),
@@ -121,35 +120,33 @@ 

Example 1: An expensive way to construct a 4x4 latin square (one #> Aggregated: 16 #> Achieved score: c(0, 0) at iteration 284 #> Aggregated: 0

-
trace
-#> Optimization trace (301 score values, elapsed 21.10947 secs).
-#>   Starting score: 48,0
-#>   Final score   : 0,0
+
bc$trace$elapsed
+#> Time difference of 5.483629 secs
 
 plot_plate(bc,
   plate = plate, row = row, column = col, .color = Group,
   title = "Ex1: Using OSAT scores for plate design\n(not the recommended way!)"
 )
-

+

Now using a dedicated scoring for the group distances on a plate.

This should reliably lead to a nice symmetry-bearing latin square design with only a one-dimensional score to look at.

-
bc <- example1$copy()
-
-bc$scoring_f <- mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Group")
+
scoring_f <- mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Group")
set.seed(42)
-trace <- optimize_design(bc,
+bc <- optimize_design(
+  example1,
+  scoring = scoring_f,
   max_iter = 1000, # this is set to shorten vignette run-time based on random seed, normally we don't know.
   n_shuffle = 2,
   acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 1000, alpha = 0.5)),
   quiet = TRUE
 )
-
trace$elapsed
-#> Time difference of 11.43023 secs
+
bc$trace$elapsed
+#> Time difference of 6.182886 secs
 
 plot_plate(bc,
   plate = plate, row = row, column = col, .color = Group,
   title = "Ex1: Using a dedicated plate scoring function:\nThis should show a latin square!"
 )
-

+

diff --git a/vignettes/cached/_plate_scoring_ex2.html b/vignettes/cached/_plate_scoring_ex2.html index a5299f2d..efcb9fbf 100644 --- a/vignettes/cached/_plate_scoring_ex2.html +++ b/vignettes/cached/_plate_scoring_ex2.html @@ -4,6 +4,14 @@
library(designit)
 library(ggplot2)
 library(dplyr)
+#> 
+#> Attaching package: 'dplyr'
+#> The following objects are masked from 'package:stats':
+#> 
+#>     filter, lag
+#> The following objects are masked from 'package:base':
+#> 
+#>     intersect, setdiff, setequal, union
 library(tidyr)

Example 2: Scoring two plates at once

@@ -26,37 +34,37 @@

Example 2: Scoring two plates at once

) # Add samples to container -assign_in_order(example2, samples = tibble::tibble( +example2 <- assign_in_order(example2, samples = tibble::tibble( Group = c(rep(c("Grp 1", "Grp 2", "Grp 3", "Grp 4"), each = 8)), ID = 1:32 )) -bc <- example2$copy() - -bc$scoring_f <- c(mk_plate_scoring_functions(bc, plate = "plate", row = "row", column = "col", group = "Group"), +scoring_f <- c(mk_plate_scoring_functions(example2, plate = "plate", row = "row", column = "col", group = "Group"), osat_plate = osat_score_generator(batch_vars = c("plate"), feature_vars = c("Group")) ) -plot_plate(bc, +plot_plate(example2, plate = plate, row = row, column = col, .color = Group, title = "Ex2: Initial sample arrangement" )
-

+


-bc$score()
+example2$score(scoring_f)
 #>    Plate 1    Plate 2 osat_plate 
 #>   23.89265   23.89265  128.00000
set.seed(41)
-trace <- optimize_design(bc,
+bc <- optimize_design(
+  example2,
+  scoring = scoring_f,
   n_shuffle = c(rep(10, 10), rep(3, 90), rep(2, 100), rep(1, 1400)),
   acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 10000, alpha = 0.5)),
   aggregate_scores_func = worst_score,
   quiet = TRUE
 )
-
trace$elapsed
-#> Time difference of 25.73155 secs
+
bc$trace$elapsed
+#> Time difference of 16.73602 secs
 
-bc$score()
+bc$score(scoring_f)
 #>    Plate 1    Plate 2 osat_plate 
 #>   6.127258   6.094080   0.000000
 
@@ -64,7 +72,7 @@ 

Example 2: Scoring two plates at once

plate = plate, row = row, column = col, .color = Group, title = "Ex2: Design created by swapping samples 'globally' across the plates" )
-

+

While this ‘global’ optimization is possible, it does probably not converge to an (almost) ideal solution in an acceptable time if there are more samples involved. This is due to a lot of unproductive sample @@ -75,48 +83,46 @@

Example 2: Scoring two plates at once

the use of a dedicated sample permutation function which takes the plate structure into account and only shuffles samples around within one plate.

-
# Setting up the batch container
-
-bc <- example2$copy()
-
-bc$scoring_f <- osat_score_generator(batch_vars = c("plate"), feature_vars = c("Group"))
+
scoring_f <- osat_score_generator(batch_vars = c("plate"), feature_vars = c("Group"))
 
 set.seed(42)
-optimize_design(bc,
+bc <- optimize_design(
+  example2,
+  scoring = scoring_f,
   quiet = TRUE,
   max_iter = 200, # this is set to shorten vignette run-time, normally we don't know.
   n_shuffle = 2,
   acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 10000, alpha = 0.5)),
 )
-#> Optimization trace (201 score values, elapsed 4.254329 secs).
-#>   Starting score: 128
-#>   Final score   : 0
+bc$trace$elapsed
+#> Time difference of 2.140794 secs
 
 plot_plate(bc,
   plate = plate, row = row, column = col, .color = Group,
   title = "Ex2: 'Plate wise' design\nStep 1: after allocating samples to plates"
 )
-

+


-bc$scoring_f <- mk_plate_scoring_functions(bc, plate = "plate", row = "row", column = "col", group = "Group")
+scoring_f <- mk_plate_scoring_functions(bc, plate = "plate", row = "row", column = "col", group = "Group")
 
-bc$score()
+bc$score(scoring_f)
 #>  Plate 1  Plate 2 
 #> 12.77527 13.63704
set.seed(42)
-trace <- optimize_design(bc,
+bc <- optimize_design(
+  bc,
+  scoring = scoring_f,
   max_iter = 400,
   shuffle_proposal_func = mk_subgroup_shuffling_function(subgroup_vars = c("plate")),
   acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 1000, alpha = 0.5)),
   aggregate_scores_func = L2s_norm,
   quiet = TRUE
 )
-
trace
-#> Optimization trace (401 score values, elapsed 3.579697 secs).
-#>   Starting score: 12.775,13.637
-#>   Final score   : 6.855,6.309
+
bc$trace$elapsed
+#> Time differences in secs
+#> [1] 2.140794 3.270934
 
-bc$score()
+bc$score(scoring_f)
 #>  Plate 1  Plate 2 
 #> 6.854748 6.309297
 
@@ -124,7 +130,7 @@ 

Example 2: Scoring two plates at once

plate = plate, row = row, column = col, .color = Group, title = "Ex2: 'Plate wise' design\nStep 2: after arranging samples within plates" )
-

+

In this case, the shuffling function exchanges 1 pair of sample assignments every time (the default). However, any number of constant swaps or a swapping protocol (formally a vector of integers) can be @@ -137,64 +143,62 @@

Example 2: Scoring two plates at once

generates the permutations. It enforces permutation only to happen first within plate 1, then within plate 2, so that the two scores can be optimized in succeeding runs.

-
# Setting up the batch container
-
-bc <- example2$copy()
-
-bc$scoring_f <- osat_score_generator(batch_vars = c("plate"), feature_vars = c("Group"))
+
scoring_f <- osat_score_generator(batch_vars = c("plate"), feature_vars = c("Group"))
set.seed(42)
-trace <- optimize_design(bc,
+bc <- optimize_design(
+  example2,
+  scoring = scoring_f,
   quiet = TRUE,
   max_iter = 150, # this is set to shorten vignette run-time, normally we don't know.
   n_shuffle = 2,
   acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 10000, alpha = 0.5)),
 )
-
trace
-#> Optimization trace (151 score values, elapsed 4.056035 secs).
-#>   Starting score: 128
-#>   Final score   : 0
+
bc$trace$elapsed
+#> Time difference of 1.791624 secs
 
 plot_plate(bc,
   plate = plate, row = row, column = col, .color = Group,
   title = "Ex2: 'Serial plate' design\nStep 1: after allocating samples to plates"
 )
-

+


-bc$scoring_f <- mk_plate_scoring_functions(bc, plate = "plate", row = "row", column = "col", group = "Group")
+scoring_f <- mk_plate_scoring_functions(bc, plate = "plate", row = "row", column = "col", group = "Group")
 
-bc$score()
+bc$score(scoring_f)
 #>  Plate 1  Plate 2 
 #> 10.57482 26.16613
set.seed(42)
-trace <- optimize_design(bc,
+bc <- optimize_design(
+  bc,
+  scoring = scoring_f,
   max_iter = 150,
   quiet = TRUE,
   shuffle_proposal_func = mk_subgroup_shuffling_function(subgroup_vars = c("plate"), restrain_on_subgroup_levels = c(1)),
   acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 1000, alpha = 0.5)),
   aggregate_scores_func = L2s_norm
 )
-
trace
-#> Optimization trace (151 score values, elapsed 2.528134 secs).
-#>   Starting score: 10.575,26.166
-#>   Final score   : 6.416,26.166
+
bc$trace$elapsed
+#> Time differences in secs
+#> [1] 1.791624 1.591002
 
-bc$score()
+bc$score(scoring_f)
 #>   Plate 1   Plate 2 
 #>  6.416193 26.166134
set.seed(42)
-trace <- optimize_design(bc,
+bc <- optimize_design(
+  bc,
+  scoring = scoring_f,
   max_iter = 550,
   quiet = TRUE,
   shuffle_proposal_func = mk_subgroup_shuffling_function(subgroup_vars = c("plate"), restrain_on_subgroup_levels = c(2)),
   acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 1000, alpha = 0.5)),
   aggregate_scores_func = L2s_norm
 )
-
trace
-#> Optimization trace (551 score values, elapsed 2.93787 secs).
-#>   Starting score: 6.416,26.166
-#>   Final score   : 6.416,6.582
+
bc$trace$elapsed
+#> Time differences in secs
+#> [1] 1.791624 1.591002 4.226352
 
-bc$score()
+bc$score(scoring_f)
 #>  Plate 1  Plate 2 
 #> 6.416193 6.581966
 
@@ -202,5 +206,5 @@ 

Example 2: Scoring two plates at once

plate = plate, row = row, column = col, .color = Group, title = "Ex2: 'Serial plate' design\nStep 2: after optimizing each plate in turn" )
-

+

diff --git a/vignettes/cached/_plate_scoring_ex3.html b/vignettes/cached/_plate_scoring_ex3.html index 35a11141..40ed3f06 100644 --- a/vignettes/cached/_plate_scoring_ex3.html +++ b/vignettes/cached/_plate_scoring_ex3.html @@ -4,6 +4,14 @@
library(designit)
 library(ggplot2)
 library(dplyr)
+#> 
+#> Attaching package: 'dplyr'
+#> The following objects are masked from 'package:stats':
+#> 
+#>     filter, lag
+#> The following objects are masked from 'package:base':
+#> 
+#>     intersect, setdiff, setequal, union
 library(tidyr)

Example 3: 3 plates with different dimension and different sample @@ -39,7 +47,7 @@

Example 3: 3 plates with different dimension and different sample # Assign samples randomly to start from a better initial state -assign_random(example3, +example3 <- assign_random(example3, samples = tibble::tibble( Group = rep.int(c("Grp 1", "Grp 2", "Grp3"), times = c(69, 30, 69) @@ -48,35 +56,35 @@

Example 3: 3 plates with different dimension and different sample ) ) -bc <- example3$copy() - -bc$scoring_f <- osat_score_generator(batch_vars = c("plate"), feature_vars = c("Group"))

+scoring_f <- osat_score_generator(batch_vars = c("plate"), feature_vars = c("Group"))
set.seed(42)
-trace <- optimize_design(bc,
+bc <- optimize_design(
+  example3,
+  scoring = scoring_f,
   quiet = TRUE,
   max_iter = 150,
   n_shuffle = 2,
   acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 1000, alpha = 0.5)),
 )
-
trace
-#> Optimization trace (151 score values, elapsed 3.348524 secs).
-#>   Starting score: 17.714
-#>   Final score   : 1.429
+
bc$trace$elapsed
+#> Time difference of 2.03003 secs
plot_plate(bc,
   plate = plate, row = row, column = col, .color = Group,
   title = "Ex3: Dealing with plates of different size\nStep 1: after distributing groups across plates"
 )
-

-
bc$scoring_f <- mk_plate_scoring_functions(bc,
+

+
scoring_f <- mk_plate_scoring_functions(bc,
   plate = "plate", row = "row",
   column = "col", group = "Group"
 )
 
-bc$score()
+bc$score(scoring_f)
 #>   Plate 1   Plate 2   Plate 3 
-#>  9.706637  9.585770 10.419567
+#> 9.387071 10.302690 9.826243
set.seed(42)
-trace <- optimize_design(bc,
+bc <- optimize_design(
+  bc,
+  scoring = scoring_f,
   max_iter = 300,
   shuffle_proposal_func = mk_subgroup_shuffling_function(
     subgroup_vars = c("plate"),
@@ -86,15 +94,16 @@ 

Example 3: 3 plates with different dimension and different sample aggregate_scores_func = L2s_norm, quiet = TRUE )

-
trace$elapsed
-#> Time difference of 40.35472 secs
+
bc$trace$elapsed
+#> Time differences in secs
+#> [1] 2.030030 4.778121
 
-bc$score()
+bc$score(scoring_f)
 #>  Plate 1  Plate 2  Plate 3 
-#> 8.974408 8.253074 7.980756
+#> 8.809205 8.553802 8.185525
plot_plate(bc,
   plate = plate, row = row, column = col, .color = Group,
   title = "Ex3: Dealing with plates of different size\nStep 2: after swapping samples within plates"
 )
-

+

diff --git a/vignettes/cached/_plate_scoring_ex4.html b/vignettes/cached/_plate_scoring_ex4.html index 61806644..d3c1472d 100644 --- a/vignettes/cached/_plate_scoring_ex4.html +++ b/vignettes/cached/_plate_scoring_ex4.html @@ -4,6 +4,14 @@
library(designit)
 library(ggplot2)
 library(dplyr)
+#> 
+#> Attaching package: 'dplyr'
+#> The following objects are masked from 'package:stats':
+#> 
+#>     filter, lag
+#> The following objects are masked from 'package:base':
+#> 
+#>     intersect, setdiff, setequal, union
 library(tidyr)

Example 4: More than one group factor to balance and empty plate @@ -26,31 +34,31 @@

Example 4: More than one group factor to balance and empty plate # Assign samples randomly to start from lower score (avoid Inf values even since plate 3 will miss 2 groups initially :) -assign_in_order(example4, samples = tibble::tibble( +example4 <- assign_in_order(example4, samples = tibble::tibble( Group = rep.int(c("Treatment 1", "Treatment 2"), times = c(10, 10)), Sex = c(rep(c("M", "F", "F", "M"), times = 4), "M", NA, NA, "F"), ID = 1:20 )) -bc <- example4$copy() - cowplot::plot_grid( - plot_plate(bc, plate = plate, row = row, column = col, .color = Group, title = "Initial layout by Group"), - plot_plate(bc, plate = plate, row = row, column = col, .color = Sex, title = "Initial layout by Sex"), + plot_plate(example4, plate = plate, row = row, column = col, .color = Group, title = "Initial layout by Group"), + plot_plate(example4, plate = plate, row = row, column = col, .color = Sex, title = "Initial layout by Sex"), ncol = 2 )

-

+


 
-bc$scoring_f <- c(
-  Group = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Group"),
-  Sex = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Sex")
+scoring_f <- c(
+  Group = mk_plate_scoring_functions(example4, row = "row", column = "col", group = "Group"),
+  Sex = mk_plate_scoring_functions(example4, row = "row", column = "col", group = "Sex")
 )
 
-bc$score()
+example4$score(scoring_f)
 #> Group.Plate   Sex.Plate 
 #>    83.63858   239.20748
set.seed(42)
-trace <- optimize_design(bc,
+bc <- optimize_design(
+  example4,
+  scoring = scoring_f,
   max_iter = 750,
   n_shuffle = 1,
   acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 10000, alpha = 1)),
@@ -59,10 +67,10 @@ 

Example 4: More than one group factor to balance and empty plate }, quiet = TRUE )

-
trace$elapsed
-#> Time difference of 7.062086 secs
+
bc$trace$elapsed
+#> Time difference of 6.251454 secs
 
-bc$score()
+bc$score(scoring_f)
 #> Group.Plate   Sex.Plate 
 #>    8.019656    7.608810
 
@@ -71,11 +79,13 @@ 

Example 4: More than one group factor to balance and empty plate plot_plate(bc, plate = plate, row = row, column = col, .color = Sex, title = "Final layout by Sex"), ncol = 2 )

-

+

We do the same example with auto-scaling, weighted scoring and SA to have a reference!

set.seed(42)
-trace <- optimize_design(bc,
+bc <- optimize_design(
+  bc,
+  scoring = scoring_f,
   max_iter = 500,
   n_shuffle = 1,
   acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 10000, alpha = 1)),
@@ -86,7 +96,7 @@ 

Example 4: More than one group factor to balance and empty plate quiet = TRUE ) #> ... Performing simple mean/stddev adjustment.

-
bc$score()
+
bc$score(scoring_f)
 #> Group.Plate   Sex.Plate 
 #>    8.080860    7.458345
 
@@ -95,25 +105,25 @@ 

Example 4: More than one group factor to balance and empty plate plot_plate(bc, plate = plate, row = row, column = col, .color = Sex, title = "Final layout by Sex"), ncol = 2 )

-

+

We do the same example with auto-scaling and position-dependent scoring now, not aggregating the score vector! This is more effective even when using the default acceptance function. We are strictly prioritizing the leftmost score in addition to reflect relevance for the design.

-
bc <- example4$copy()
-
-bc$scoring_f <- c(
-  Group = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Group"),
-  Sex = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Sex")
+
scoring_f <- c(
+  Group = mk_plate_scoring_functions(example4, row = "row", column = "col", group = "Group"),
+  Sex = mk_plate_scoring_functions(example4, row = "row", column = "col", group = "Sex")
 )
 
-bc$score()
+example4$score(scoring_f)
 #> Group.Plate   Sex.Plate 
 #>    83.63858   239.20748
 
 set.seed(42)
-trace <- optimize_design(bc,
+bc <- optimize_design(
+  example4,
+  scoring = scoring_f,
   max_iter = 5000,
   n_shuffle = 1,
   acceptance_func = accept_leftmost_improvement,
@@ -121,7 +131,7 @@ 

Example 4: More than one group factor to balance and empty plate quiet = TRUE ) #> ... Performing simple mean/stddev adjustment.

-
bc$score()
+
bc$score(scoring_f)
 #> Group.Plate   Sex.Plate 
 #>    7.619846    7.473524
 
@@ -130,20 +140,19 @@ 

Example 4: More than one group factor to balance and empty plate plot_plate(bc, plate = plate, row = row, column = col, .color = Sex, title = "Final layout by Sex"), ncol = 2 )

-

+

Using a tolerance value to accept slightly worse solutions in the leftmost relevant score if overcompensated by other scores:

-
bc <- example4$copy()
-
-
-bc$scoring_f <- c(
-  Group = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Group"),
-  Sex = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Sex")
+
scoring_f <- c(
+  Group = mk_plate_scoring_functions(example4, row = "row", column = "col", group = "Group"),
+  Sex = mk_plate_scoring_functions(example4, row = "row", column = "col", group = "Sex")
 )
 
 
 set.seed(42)
-trace <- optimize_design(bc,
+bc <- optimize_design(
+  example4,
+  scoring = scoring_f,
   max_iter = 5000,
   n_shuffle = 1,
   acceptance_func = ~ accept_leftmost_improvement(..., tolerance = 0.1),
@@ -152,37 +161,33 @@ 

Example 4: More than one group factor to balance and empty plate ) #> ... Performing simple mean/stddev adjustment. -bc$score() +bc$score(scoring_f) #> Group.Plate Sex.Plate #> 7.366667 7.323324

-
bc$score()
-#> Group.Plate   Sex.Plate 
-#>    7.366667    7.323324
-
-cowplot::plot_grid(
+
cowplot::plot_grid(
   plot_plate(bc, plate = plate, row = row, column = col, .color = Group, title = "Final layout by Group"),
   plot_plate(bc, plate = plate, row = row, column = col, .color = Sex, title = "Final layout by Sex"),
   ncol = 2
 )
-

+

Testing an alternative left-to-right weighing of scores, based on exponential down-weighing of the respective score differences at position \(p\) with factor \(\kappa^p\), \(0 < \kappa < 1\) We choose a \(\kappa\) of 0.5, i.e. the second score’s improvement counts half of that of the first one.

-
bc <- example4$copy()
-
-bc$scoring_f <- c(
-  Group = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Group"),
-  Sex = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Sex")
+
scoring_f <- c(
+  Group = mk_plate_scoring_functions(example4, row = "row", column = "col", group = "Group"),
+  Sex = mk_plate_scoring_functions(example4, row = "row", column = "col", group = "Sex")
 )
 
-bc$score()
+bc$score(scoring_f)
 #> Group.Plate   Sex.Plate 
-#>    83.63858   239.20748
+#>    7.366667    7.323324
 
 set.seed(42)
-trace <- optimize_design(bc,
+bc <- optimize_design(
+  example4,
+  scoring = scoring_f,
   max_iter = 1000,
   n_shuffle = 1,
   acceptance_func = mk_exponentially_weighted_acceptance_func(kappa = 0.5, simulated_annealing = T),
@@ -190,7 +195,7 @@ 

Example 4: More than one group factor to balance and empty plate quiet = TRUE ) #> ... Performing simple mean/stddev adjustment.

-
bc$score()
+
bc$score(scoring_f)
 #> Group.Plate   Sex.Plate 
 #>    7.630367    7.616179
 
@@ -199,5 +204,5 @@ 

Example 4: More than one group factor to balance and empty plate plot_plate(bc, plate = plate, row = row, column = col, .color = Sex, title = "Final layout by Sex"), ncol = 2 )

-

+

diff --git a/vignettes/cached/_plate_scoring_ex5.html b/vignettes/cached/_plate_scoring_ex5.html index 35874c9f..368fc058 100644 --- a/vignettes/cached/_plate_scoring_ex5.html +++ b/vignettes/cached/_plate_scoring_ex5.html @@ -4,6 +4,14 @@
library(designit)
 library(ggplot2)
 library(dplyr)
+#> 
+#> Attaching package: 'dplyr'
+#> The following objects are masked from 'package:stats':
+#> 
+#>     filter, lag
+#> The following objects are masked from 'package:base':
+#> 
+#>     intersect, setdiff, setequal, union
 library(tidyr)

Example 5: Avoiding ‘regular patterns’ in plate layout

@@ -21,36 +29,36 @@

Example 5: Avoiding ‘regular patterns’ in plate layout

) # Assign samples randomly to start from lower score (avoid `Inf` values when doing the 'hard' penalization) -assign_random(example5, samples = tibble::tibble( +example5 <- assign_random(example5, samples = tibble::tibble( Group = rep.int(paste("Group", 1:5), times = c(8, 8, 8, 8, 64)), ID = 1:96 )) penalize_lines <- "hard" -bc <- example5$copy() - -bc$scoring_f <- c( - Group = mk_plate_scoring_functions(bc, row = "row", column = "col", group = "Group", p = 2, penalize_lines = penalize_lines) +scoring_f <- c( + Group = mk_plate_scoring_functions(example5, row = "row", column = "col", group = "Group", p = 2, penalize_lines = penalize_lines) ) -bc$score() +example5$score(scoring_f) #> Group.Plate -#> 9.960584
+#> 11.08608
set.seed(42)
-trace <- optimize_design(bc,
+bc <- optimize_design(
+  example5,
+  scoring = scoring_f,
   max_iter = 5000,
   n_shuffle = 1,
   acceptance_func = mk_simanneal_acceptance_func(mk_simanneal_temp_func(T0 = 500, alpha = 0.1)),
   quiet = T
 )
-
trace$elapsed
-#> Time difference of 28.7293 secs
+
bc$trace$elapsed
+#> Time difference of 29.47413 secs
 
-bc$score()
+bc$score(scoring_f)
 #> Group.Plate 
-#>    8.819968
+#>    8.785693
 
 plot_plate(bc, plate = plate, row = row, column = col, .color = Group, title = stringr::str_c("Line penalization: ", penalize_lines))
-

+

From 43a1a58be0ea5f70df183d55217fce039e85afb7 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 27 Jul 2023 11:17:07 +0200 Subject: [PATCH 27/32] silence "Namespace in Imports field not imported from" --- R/batch_container.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/batch_container.R b/R/batch_container.R index d35eb762..97b23678 100644 --- a/R/batch_container.R +++ b/R/batch_container.R @@ -123,6 +123,7 @@ locations_table_from_dimensions <- function(dimensions, exclude) { #' samples can be assigned to locations in that container. #' #' @export +#' @import R6 BatchContainer <- R6::R6Class("BatchContainer", public = list( #' @description From 18e190035cffd678232b88ddc6911180aea4ff8c Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 27 Jul 2023 11:17:29 +0200 Subject: [PATCH 28/32] _doc --- man/complete_random_shuffling.Rd | 6 +++--- man/mk_plate_scoring_functions.Rd | 6 +++--- man/mk_subgroup_shuffling_function.Rd | 17 +++++++++++------ man/mk_swapping_function.Rd | 4 ++-- man/plot_plate.Rd | 2 +- man/shuffle_with_constraints.Rd | 5 +++-- 6 files changed, 23 insertions(+), 17 deletions(-) diff --git a/man/complete_random_shuffling.Rd b/man/complete_random_shuffling.Rd index 75611c6c..e8b849f2 100644 --- a/man/complete_random_shuffling.Rd +++ b/man/complete_random_shuffling.Rd @@ -23,9 +23,9 @@ data("invivo_study_samples") bc <- BatchContainer$new( dimensions = c("plate" = 2, "column" = 5, "row" = 6) ) -bc$scoring_f <- osat_score_generator("plate", "Sex") -optimize_design( - bc, invivo_study_samples, +scoring_f <- osat_score_generator("plate", "Sex") +bc <- optimize_design( + bc, scoring = scoring_f, invivo_study_samples, max_iter = 100, shuffle_proposal_func = complete_random_shuffling ) diff --git a/man/mk_plate_scoring_functions.Rd b/man/mk_plate_scoring_functions.Rd index 560b9a41..722ac793 100644 --- a/man/mk_plate_scoring_functions.Rd +++ b/man/mk_plate_scoring_functions.Rd @@ -42,12 +42,12 @@ data("invivo_study_samples") bc <- BatchContainer$new( dimensions = c("column" = 6, "row" = 10) ) -assign_random(bc, invivo_study_samples) -bc$scoring_f <- mk_plate_scoring_functions( +bc <- assign_random(bc, invivo_study_samples) +scoring_f <- mk_plate_scoring_functions( bc, row = "row", column = "column", group = "Sex" ) -optimize_design(bc, max_iter = 100) +bc <- optimize_design(bc, scoring = scoring_f, max_iter = 100) plot_plate(bc$get_samples(), .col = Sex) } diff --git a/man/mk_subgroup_shuffling_function.Rd b/man/mk_subgroup_shuffling_function.Rd index dd0728e0..dd265dd4 100644 --- a/man/mk_subgroup_shuffling_function.Rd +++ b/man/mk_subgroup_shuffling_function.Rd @@ -34,7 +34,7 @@ bc <- BatchContainer$new( ) ) -assign_in_order(bc, samples = tibble::tibble( +bc <- assign_in_order(bc, samples = tibble::tibble( Group = c(rep(c("Grp 1", "Grp 2", "Grp 3", "Grp 4"), each = 8)), ID = 1:32 )) @@ -49,24 +49,29 @@ plot_plate(bc, ) # Step 1, assign samples to plates -bc$scoring_f <- osat_score_generator( +scoring_f <- osat_score_generator( batch_vars = c("plate"), feature_vars = c("Group") ) -optimize_design(bc, +bc <- optimize_design( + bc, + scoring = scoring_f, max_iter = 10, # the real number of iterations should be bigger n_shuffle = 2, quiet = TRUE ) -plot_plate(bc, +plot_plate( + bc, plate = plate, row = row, column = col, .color = Group ) # Step 2, distribute samples within plates -bc$scoring_f <- mk_plate_scoring_functions( +scoring_f <- mk_plate_scoring_functions( bc, plate = "plate", row = "row", column = "col", group = "Group" ) -optimize_design(bc, +bc <- optimize_design( + bc, + scoring = scoring_f, max_iter = 50, shuffle_proposal_func = mk_subgroup_shuffling_function(subgroup_vars = c("plate")), aggregate_scores_func = L2s_norm, diff --git a/man/mk_swapping_function.Rd b/man/mk_swapping_function.Rd index 1fdde7c5..65bffa4e 100644 --- a/man/mk_swapping_function.Rd +++ b/man/mk_swapping_function.Rd @@ -22,9 +22,9 @@ data("invivo_study_samples") bc <- BatchContainer$new( dimensions = c("plate" = 2, "column" = 5, "row" = 6) ) -bc$scoring_f <- osat_score_generator("plate", "Sex") +scoring_f <- osat_score_generator("plate", "Sex") optimize_design( - bc, invivo_study_samples, + bc, scoring = scoring_f, invivo_study_samples, max_iter = 100, shuffle_proposal_func = mk_swapping_function(1) ) diff --git a/man/plot_plate.Rd b/man/plot_plate.Rd index 276f48ed..24a53ce5 100644 --- a/man/plot_plate.Rd +++ b/man/plot_plate.Rd @@ -70,7 +70,7 @@ sample_sheet <- tibble::tibble( ) # assign samples from the sample sheet -assign_random(bc, samples = sample_sheet) +bc <- assign_random(bc, samples = sample_sheet) plot_plate(bc$get_samples(), plate = plate, column = column, row = row, diff --git a/man/shuffle_with_constraints.Rd b/man/shuffle_with_constraints.Rd index 23880a09..48134daf 100644 --- a/man/shuffle_with_constraints.Rd +++ b/man/shuffle_with_constraints.Rd @@ -40,7 +40,7 @@ bc <- BatchContainer$new( dimensions = c("plate" = 5, "position" = 25) ) -bc$scoring_f <- function(samples) { +scoring_f <- function(samples) { osat_score( samples, "plate", @@ -51,8 +51,9 @@ bc$scoring_f <- function(samples) { # in this example we treat all the positions in the plate as equal. # when shuffling we enforce that source location is non-empty, # and destination location has a different plate number -optimize_design( +bc <- optimize_design( bc, + scoring = scoring_f, samples, shuffle_proposal = shuffle_with_constraints( # source is non-empty location From 0fc707f96f1b3216a2a8580ae5dfba578bcc4825 Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Thu, 27 Jul 2023 11:41:03 +0200 Subject: [PATCH 29/32] remove import R6, this doesn't seem to fix the r check note --- R/batch_container.R | 1 - 1 file changed, 1 deletion(-) diff --git a/R/batch_container.R b/R/batch_container.R index 97b23678..d35eb762 100644 --- a/R/batch_container.R +++ b/R/batch_container.R @@ -123,7 +123,6 @@ locations_table_from_dimensions <- function(dimensions, exclude) { #' samples can be assigned to locations in that container. #' #' @export -#' @import R6 BatchContainer <- R6::R6Class("BatchContainer", public = list( #' @description From 40dd496ca397948aeab1b3e6ca2ef3224b2280ef Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Mon, 7 Aug 2023 17:21:59 +0200 Subject: [PATCH 30/32] error when passing a partially named list of scoring functions --- R/batch_container.R | 4 +++ tests/testthat/test-scoring-names.R | 46 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 tests/testthat/test-scoring-names.R diff --git a/R/batch_container.R b/R/batch_container.R index d35eb762..f2983c03 100644 --- a/R/batch_container.R +++ b/R/batch_container.R @@ -349,6 +349,10 @@ BatchContainer <- R6::R6Class("BatchContainer", if (is.null(names(scoring))) { names(scoring) <- stringr::str_c("score_", seq_along(scoring)) } + assertthat::assert_that( + !any(names(scoring) == ""), + msg = "scoring cannot be a partially named list" + ) assertthat::assert_that(is.list(scoring), length(scoring) >= 1, msg = "Scoring function should be a non-empty list" diff --git a/tests/testthat/test-scoring-names.R b/tests/testthat/test-scoring-names.R new file mode 100644 index 00000000..b4f4931e --- /dev/null +++ b/tests/testthat/test-scoring-names.R @@ -0,0 +1,46 @@ +samples <- data.frame(i = 1:384, x = rnorm(384)) + +bc <- BatchContainer$new( + dimensions = c(row = 16, column = 24) +) |> + assign_in_order(samples) + + +test_that("scoring functions can be an unnamed list", { + tmp <- optimize_design(bc, + scoring = list( + \(...) rnorm(1), + \(...) rnorm(1) + ), + max_iter = 10 + ) + expect_equal( + colnames(tmp$trace$scores[[1]]), + c("step", "score_1", "score_2") + ) +}) + +test_that("all scoring functions should be named (or unnamed)", { + expect_error( + optimize_design(bc, + scoring = list( + \(...) rnorm(1), + f = \(...) rnorm(1) + ), + max_iter = 10 + ), + "scoring cannot be a partially named list" + ) +}) + +test_that("scoring function cannot have a name 'step'", { + expect_error( + optimize_design(bc, + scoring = list( + step = \(...) rnorm(1) + ), + max_iter = 10 + ), + "score name cannot be 'step'" + ) +}) From 88810597cfd7e9f4c07fd82a6cd66f9be78e168c Mon Sep 17 00:00:00 2001 From: Iakov Davydov <671660+idavydov@users.noreply.github.com> Date: Wed, 23 Aug 2023 11:19:30 +0200 Subject: [PATCH 31/32] Update R/optimize.R Co-authored-by: Balazs Banfai <5557093+banfai@users.noreply.github.com> --- R/optimize.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/optimize.R b/R/optimize.R index 48f2b650..2ae7b8eb 100644 --- a/R/optimize.R +++ b/R/optimize.R @@ -125,7 +125,7 @@ update_batchcontainer <- function(bc, shuffle_params) { #' Should be `NULL` if the `BatchContainer` already has samples in it. #' @param scoring Scoring function or a named [list()] of scoring functions. #' @param n_shuffle Vector of length 1 or larger, defining how many random sample -#' swaps should be performed in each iteration. If length(n_shuffle)==1, +#' swaps should be performed in each iteration. If `length(n_shuffle)==1`, #' this sets no limit to the number of iterations. Otherwise, the optimization #' stops if the swapping protocol is exhausted. #' @param shuffle_proposal_func A user defined function to propose the next shuffling of samples. From d5cc0520dbdc19c52d67e020668dd30bebafacf4 Mon Sep 17 00:00:00 2001 From: Balazs Banfai <5557093+banfai@users.noreply.github.com> Date: Wed, 23 Aug 2023 11:58:05 +0200 Subject: [PATCH 32/32] Update man/optimize_design.Rd --- man/optimize_design.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/optimize_design.Rd b/man/optimize_design.Rd index b9bf91f2..5ce33e36 100644 --- a/man/optimize_design.Rd +++ b/man/optimize_design.Rd @@ -31,7 +31,7 @@ Should be \code{NULL} if the \code{BatchContainer} already has samples in it.} \item{scoring}{Scoring function or a named \code{\link[=list]{list()}} of scoring functions.} \item{n_shuffle}{Vector of length 1 or larger, defining how many random sample -swaps should be performed in each iteration. If length(n_shuffle)==1, +swaps should be performed in each iteration. If \code{length(n_shuffle)==1}, this sets no limit to the number of iterations. Otherwise, the optimization stops if the swapping protocol is exhausted.}