From 23beebc6c3e0eae87c07e56239fa50ebf3177579 Mon Sep 17 00:00:00 2001 From: Matthijs Berends <31037261+msberends@users.noreply.github.com> Date: Thu, 30 Apr 2026 08:57:19 +0100 Subject: [PATCH] Migrate parallel computing in as.sir() from parallel:: to future/future.apply (#280) * Migrate parallel computing in as.sir() from parallel:: to future/future.apply Replace parallel::mclapply() and parallel::parLapply() with future.apply::future_lapply(), enabling transparent support for any future backend (multisession, multicore, mirai_multisession, cluster) on all platforms including Windows. When parallel = TRUE the function now: (1) respects an active future::plan() set by the user without overriding it on exit, or (2) sets a temporary multisession plan with parallelly::availableCores() and tears it down on exit. The max_cores argument controls worker count only when no user plan is active. future and future.apply are added to Suggests in DESCRIPTION. https://claude.ai/code/session_01M1Jvf2Miu6JL4TQrEh1wS8 * Require user plan() for parallel=TRUE; fix as_wt_nwt false-positive warnings - parallel = TRUE now errors with a cli-styled message if no non-sequential future::plan() is active; users must call e.g. future::plan(future::multisession) before using parallel = TRUE (breaking change) - Removed auto-setup/teardown of multisession plan inside as.sir(), which was slow and caused version-mismatch issues with load_all() workflows - Added as_wt_nwt to the exclusion list in as_sir_method() to suppress false-positive "no longer used" warnings during parallel runs - Fixed pieces_per_col row-batch calculation to use n_workers (total available workers from the active plan) instead of n_cores (workers clipped to n_cols), so row-batch mode activates correctly when n_cols < n_workers - Updated @param parallel and @param max_cores roxygen docs; regenerated man/as.sir.Rd - Updated sequential-mode hint to instruct users to set plan() first https://claude.ai/code/session_01M1Jvf2Miu6JL4TQrEh1wS8 * fix parallel * fix parallel * unit tests * unit tedts --------- Co-authored-by: Claude --- DESCRIPTION | 9 +- NEWS.md | 14 ++- R/aa_helper_functions.R | 22 ---- R/antibiogram.R | 5 +- R/bug_drug_combinations.R | 23 ++-- R/sir.R | 149 ++++++++++--------------- index.md | 118 ++++++++++---------- man/as.sir.Rd | 13 +-- tests/testthat/test-sir.R | 211 +++++++++++++++++++---------------- tests/testthat/test-zzz.R | 7 +- tools/benchmark_parallel.png | Bin 0 -> 80769 bytes 11 files changed, 277 insertions(+), 294 deletions(-) create mode 100644 tools/benchmark_parallel.png diff --git a/DESCRIPTION b/DESCRIPTION index d737e261c..b817b7a57 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: AMR -Version: 3.0.1.9052 -Date: 2026-04-25 +Version: 3.0.1.9053 +Date: 2026-04-27 Title: Antimicrobial Resistance Data Analysis Description: Functions to simplify and standardise antimicrobial resistance (AMR) data analysis and to work with microbial and antimicrobial properties by @@ -37,17 +37,18 @@ Authors@R: c( person(given = c("Casper", "J."), family = "Albers", role = "ths", comment = c(ORCID = "0000-0002-9213-6743")), person(given = c("Corinna"), family = "Glasner", role = "ths", comment = c(ORCID = "0000-0003-1241-1328"))) Depends: R (>= 3.0.0) -Suggests: +Suggests: cleaner, cli, crayon, curl, data.table, dplyr, + future, + future.apply, ggplot2, knitr, openxlsx, - parallelly, pillar, progress, readxl, diff --git a/NEWS.md b/NEWS.md index 42ca28b1e..9ae3064af 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,8 +1,13 @@ -# AMR 3.0.1.9052 +# AMR 3.0.1.9053 + +This will become release v3.1.0, intended for launch end of May. ### New * Support for clinical breakpoints of 2026 of both CLSI and EUCAST, by adding all of their over 5,700 new clinical breakpoints to the `clinical_breakpoints` data set for usage in `as.sir()`. EUCAST 2026 is now the new default guideline for all MIC and disk diffusion interpretations. -* Integration with the **tidymodels** framework to allow seamless use of SIR, MIC and disk data in modelling pipelines via `recipes` +* Support for the [`future`](https://future.futureverse.org) package and its framework, as the previous implementation of parallel computing was slow + - **Breaking change**: `as.sir()` with `parallel = TRUE` now requires a non-sequential `future::plan()` to be active before the call — e.g., `future::plan(future::multisession)` — and throws an informative error if none is set. + - New all-core usage setup: when the number of AB columns is smaller than the number of available cores, rows are now split into batches so all cores stay active (row-batch mode). Previously, a 6-column dataset on a 16-core machine would only use 6 cores; now all 16 are used, with each worker processing a smaller row slice (lower per-worker memory pressure and processing time) +* Integration with the *tidymodels* framework to allow seamless use of SIR, MIC and disk data in modelling pipelines via `recipes` - `step_mic_log2()` to transform `` columns with log2, and `step_sir_numeric()` to convert `` columns to numeric - New `tidyselect` helpers: - `all_sir()`, `all_sir_predictors()` @@ -21,7 +26,6 @@ * Two new `NA` objects, `NA_ab_` and `NA_mo_`, analogous to base R's `NA_character_` and `NA_integer_`, for use in pipelines that require typed missing values ### Fixes -* Fixed multiple bugs in the `parallel = TRUE` mode of `as.sir()` for data frames * Fixed a bug in `as.sir()` where values that were purely numeric (e.g., `"1"`) and matched the broad SIR-matching regex would be incorrectly stripped of all content by the Unicode letter filter * Fixed a bug in `as.mic()` where MIC values in scientific notation (e.g., `"1e-3"`) were incorrectly handled because the letter `e` was removed along with other Unicode letters; scientific notation `e` is now preserved * Fixed a bug in `as.ab()` where certain AB codes containing "PH" or "TH" (such as `ETH`, `MTH`, `PHE`, `PHN`, `STH`, `THA`, `THI1`) would incorrectly return `NA` when combined in a vector with any untranslatable value (#245) @@ -37,8 +41,7 @@ * Fixed BRMO classification by including bacterial complexes (#275) * Fixed `as.sir()` for data frames silently deleting columns whose AB class was already `` when called a second time (re-running on already-converted data) (#278) * Fixed `as.sir()` for data frames incorrectly treating metadata columns (e.g. `patient`, `ward`) as antibiotic columns when their names coincidentally matched an antibiotic code; column content is now validated against AMR data patterns before inclusion -* Improved parallel computing in `as.sir()`: when the number of AB columns is smaller than the number of available cores, rows are now split into batches so all cores stay active (row-batch mode). Previously, a 6-column dataset on a 16-core machine would only use 6 cores; now all 16 are used, with each worker processing a smaller row slice (lower per-worker memory pressure) -* Fixed `as.sir()` ignoring `info = FALSE` for columns with no breakpoints (e.g. cefoxitin against *E. coli*): an operator-precedence bug (`&&`/`||`) caused the "Interpreting MIC values" intro message to fire unconditionally when `nrow(breakpoints) == 0`, regardless of `info`; the progress bar title was also not gated by `info` +* Fixed `as.sir()` ignoring `info = FALSE` for columns with no breakpoints (e.g. cefoxitin against *E. coli*) ### Updates * `as.sir()` with `reference_data`: custom guideline names now correctly classify values as R using EUCAST convention (`> breakpoint_R` for MIC, `< breakpoint_R` for disk); custom breakpoints with `host = NA` now serve as a host-agnostic fallback when no host-specific row matches (#239) @@ -56,7 +59,6 @@ * This results in more reliable behaviour compared to previous versions for capped MIC values * Removed the `"inverse"` option, which has now become redundant * `ab_group()` now returns values consist with the AMR selectors (#246) -* Added two new `NA` objects, `NA_ab_` and `NA_mo_`, analogous to base R's `NA_character_` and `NA_integer_`, for use in pipelines that require typed missing values # AMR 3.0.1 diff --git a/R/aa_helper_functions.R b/R/aa_helper_functions.R index bae37579f..d321816aa 100644 --- a/R/aa_helper_functions.R +++ b/R/aa_helper_functions.R @@ -1681,28 +1681,6 @@ readRDS_AMR <- function(file, refhook = NULL) { readRDS(con, refhook = refhook) } -get_n_cores <- function(max_cores = Inf) { - if (pkg_is_available("parallelly", min_version = "0.8.0", also_load = FALSE)) { - available_cores <- import_fn("availableCores", "parallelly") - n_cores <- min(available_cores(), na.rm = TRUE) - } else { - # `parallel` is part of base R since 2.14.0, but detectCores() is not very precise on exotic systems like Docker and quota-set Linux environments - n_cores <- parallel::detectCores()[1] - if (is.na(n_cores)) { - n_cores <- 1 - } - } - max_cores <- floor(max_cores) - if (max_cores == 0) { - n_cores <- 1 - } else if (max_cores < 0) { - n_cores <- max(1, n_cores - abs(max_cores)) - } else if (max_cores > 0) { - n_cores <- min(n_cores, max_cores) - } - n_cores -} - # Support `where()` if tidyselect not installed ---- if (!is.null(import_fn("where", "tidyselect", error_on_fail = FALSE))) { # tidyselect::where() exists, retrieve from their namespace to make `where()`s work across the package in default arguments diff --git a/R/antibiogram.R b/R/antibiogram.R index a614ef7ac..6a8d086e5 100755 --- a/R/antibiogram.R +++ b/R/antibiogram.R @@ -1206,7 +1206,7 @@ retrieve_wisca_parameters <- function(wisca_model, ...) { #' @rawNamespace if(getRversion() >= "3.0.0") S3method(pillar::tbl_sum, antibiogram) tbl_sum.antibiogram <- function(x, ...) { dims <- paste(format(NROW(x), big.mark = ","), AMR_env$cross_icon, format(NCOL(x), big.mark = ",")) - names(dims) <- "An Antibiogram" + names(dims) <- "An antibiogram" if (isTRUE(attributes(x)$wisca)) { dims <- c(dims, Type = paste0("WISCA with ", attributes(x)$conf_interval * 100, "% CI")) } else if (isTRUE(attributes(x)$formatting_type >= 13)) { @@ -1226,8 +1226,7 @@ tbl_format_footer.antibiogram <- function(x, ...) { } c(footer, font_subtle(paste0( "# Use `ggplot2::autoplot()` or base R `plot()` to create a plot of this antibiogram,\n", - "# or use it directly in R Markdown or ", - font_url("https://quarto.org", "Quarto"), ", see ", word_wrap("?antibiogram") + "# or use it directly in R Markdown or Quarto, see ", word_wrap("?antibiogram") ))) } diff --git a/R/bug_drug_combinations.R b/R/bug_drug_combinations.R index 6ab0f1f73..a2d78971a 100755 --- a/R/bug_drug_combinations.R +++ b/R/bug_drug_combinations.R @@ -129,16 +129,21 @@ bug_drug_combinations <- function(x, # turn and merge everything pivot <- lapply(x_mo_filter, function(x) { m <- as.matrix(table(as.sir(x), useNA = "always")) + na_idx <- which(is.na(rownames(m))) + get_row <- function(lbl) { + idx <- which(rownames(m) == lbl) + if (length(idx) == 1L) unname(m[idx, ]) else rep(0L, ncol(m)) + } data.frame( - S = m["S", ], - SDD = m["SDD", ], - I = m["I", ], - R = m["R", ], - NI = m["NI", ], - WT = m["WT", ], - NWT = m["NWT", ], - NS = m["NS", ], - na = m[which(is.na(rownames(m))), ], + S = get_row("S"), + SDD = get_row("SDD"), + I = get_row("I"), + R = get_row("R"), + NI = get_row("NI"), + WT = get_row("WT"), + NWT = get_row("NWT"), + NS = get_row("NS"), + na = if (length(na_idx) == 1L) unname(m[na_idx, ]) else rep(0L, ncol(m)), stringsAsFactors = FALSE ) }) diff --git a/R/sir.R b/R/sir.R index 2d7f38379..f316d179b 100755 --- a/R/sir.R +++ b/R/sir.R @@ -95,7 +95,7 @@ VALID_SIR_LEVELS <- c("S", "SDD", "I", "R", "NI", "WT", "NWT", "NS") #' # for veterinary breakpoints, also set `host`: #' your_data %>% mutate_if(is.mic, as.sir, host = "column_with_animal_species", guideline = "CLSI") #' -#' # fast processing with parallel computing: +#' # fast processing with parallel computing (requires future.apply): #' as.sir(your_data, ..., parallel = TRUE) #' ``` #' * Operators like "<=" will be considered according to the `capped_mic_handling` setting. At default, an MIC value of e.g. ">2" will return "NI" (non-interpretable) if the breakpoint is 4-8; the *true* MIC could be at either side of the breakpoint. This is to prevent that capped values from raw laboratory data would not be treated conservatively. @@ -112,7 +112,7 @@ VALID_SIR_LEVELS <- c("S", "SDD", "I", "R", "NI", "WT", "NWT", "NS") #' # for veterinary breakpoints, also set `host`: #' your_data %>% mutate_if(is.disk, as.sir, host = "column_with_animal_species", guideline = "CLSI") #' -#' # fast processing with parallel computing: +#' # fast processing with parallel computing (requires future.apply): #' as.sir(your_data, ..., parallel = TRUE) #' ``` #' @@ -220,9 +220,6 @@ VALID_SIR_LEVELS <- c("S", "SDD", "I", "R", "NI", "WT", "NWT", "NS") #' sir_interpretation_history() #' #' \donttest{ -#' # using parallel computing, which is available in base R: -#' as.sir(df_wide, parallel = TRUE, info = TRUE) -#' #' #' ## Using dplyr ------------------------------------------------- #' if (require("dplyr")) { @@ -716,8 +713,7 @@ as.sir.disk <- function(x, } #' @rdname as.sir -#' @param parallel A [logical] to indicate if parallel computing must be used, defaults to `FALSE`. The `parallel` package is part of base \R and no additional packages are required. On Unix/macOS with \R >= 4.0.0, [parallel::mclapply()] (fork-based) is used; on Windows and \R < 4.0.0, [parallel::parLapply()] with a PSOCK cluster is used (requires the AMR package to be installed, not just loaded via `devtools::load_all()`). Parallelism distributes columns across cores; it is most beneficial when there are many antibiotic columns and a large number of rows. -#' @param max_cores Maximum number of cores to use if `parallel = TRUE`. Use a negative value to subtract that number from the available number of cores, e.g. a value of `-2` on an 8-core machine means that at most 6 cores will be used. Defaults to `-1`. There will never be used more cores than variables to analyse. The available number of cores are detected using [parallelly::availableCores()] if that package is installed, and base \R's [parallel::detectCores()] otherwise. +#' @param parallel A [logical] to indicate if parallel computing must be used, defaults to `FALSE`. Requires the [`future.apply`][future.apply::future_lapply()] package. **A non-sequential [future::plan()] must already be active before setting `parallel = TRUE`** — for example, `future::plan(future::multisession)`. An error is thrown if `parallel = TRUE` is used without a plan set by the user. Parallelism distributes columns (and optionally row batches) across workers; it is most beneficial when there are many antibiotic columns and a large number of rows. #' @export as.sir.data.frame <- function(x, ..., @@ -737,7 +733,6 @@ as.sir.data.frame <- function(x, verbose = FALSE, info = interactive(), parallel = FALSE, - max_cores = -1, conserve_capped_values = NULL) { meet_criteria(x, allow_class = "data.frame") # will also check for dimensions > 0 meet_criteria(col_mo, allow_class = "character", is_in = colnames(x), allow_NULL = TRUE) @@ -756,7 +751,6 @@ as.sir.data.frame <- function(x, meet_criteria(verbose, allow_class = "logical", has_length = 1) meet_criteria(info, allow_class = "logical", has_length = 1) meet_criteria(parallel, allow_class = "logical", has_length = 1) - meet_criteria(max_cores, allow_class = c("numeric", "integer"), has_length = 1) x.bak <- x if (isTRUE(info) && message_not_thrown_before("as.sir", "sir_interpretation_history")) { @@ -911,40 +905,34 @@ as.sir.data.frame <- function(x, } # set up parallel computing - n_cores <- get_n_cores(max_cores = max_cores) - n_cores <- min(n_cores, length(ab_cols)) # never more cores than variables required - if (isTRUE(parallel) && (.Platform$OS.type == "windows" || getRversion() < "4.0.0")) { - cl <- tryCatch(parallel::makeCluster(n_cores, type = "PSOCK"), - error = function(e) { - if (isTRUE(info)) { - message_("Could not create parallel cluster, using single-core computation. Error message: ", conditionMessage(e)) - } - return(NULL) - } - ) - if (!is.null(cl)) { - # Each PSOCK worker is a fresh R session — the AMR package must be loaded there - # so all exported functions (as.sir, as.mic, as.disk, ...) are available. - amr_loaded_on_workers <- tryCatch({ - parallel::clusterEvalQ(cl, library(AMR, quietly = TRUE)) - TRUE - }, error = function(e) FALSE) - if (!amr_loaded_on_workers) { - if (isTRUE(info)) { - message_("Could not load AMR on parallel workers (package may not be installed); falling back to single-core computation.") - } - parallel::stopCluster(cl) - cl <- NULL - } - } - if (is.null(cl)) { - n_cores <- 1 + if (requireNamespace("future.apply", quietly = TRUE) && !inherits(future::plan(), "sequential")) { + if (isFALSE(parallel)) { + message_("Assuming {.code parallel = TRUE} since parallel computing has been set up using the {.pkg future} package before. Set {.help [{.fun plan}](future::plan)} to sequential to prevent this.") } + parallel <- TRUE } + if (isTRUE(parallel)) { + stop_ifnot( + requireNamespace("future.apply", quietly = TRUE), + "Setting {.code parallel = TRUE} requires the {.pkg future.apply} package.\n", + "Install it with {.code install.packages(\"future.apply\")}." + ) + stop_if(inherits(future::plan(), "sequential"), + "Setting {.code parallel = TRUE} requires a non-sequential {.help [{.fun future::plan}](future::plan)} to be active.\n", + "For your system, you could first run: {.code library(future); ", + ifelse(.Platform$OS.type == "windows" || in_rstudio(), + "plan(multisession)", + "plan(multicore)" + ), + "}", + call = FALSE + ) - if (isTRUE(info)) { - message_(as_note = FALSE) # empty line - message_("Processing columns:", as_note = FALSE) + n_workers <- future::nbrOfWorkers() + n_cores <- min(n_workers, length(ab_cols)) + } else { + n_workers <- 1L + n_cores <- 1L } # In parallel mode suppress per-column messages: workers print simultaneously and @@ -952,31 +940,23 @@ as.sir.data.frame <- function(x, is_parallel_run <- isTRUE(parallel) && n_cores > 1 && length(ab_cols) > 1 effective_info <- if (is_parallel_run) FALSE else info - # Row-batch mode: when n_cols < n_cores we would leave cores idle under plain - # column-parallel dispatch. Instead we split rows into pieces so every core - # gets work. pieces_per_col = ceil(n_cores / n_cols) gives ~n_cores jobs + # Row-batch mode: when n_cols < n_workers we would leave workers idle under plain + # column-parallel dispatch. Instead we split rows into pieces so every worker + # gets work. pieces_per_col = ceil(n_workers / n_cols) gives ~n_workers jobs # total; each job processes one column on one row slice, which also reduces # per-worker memory pressure (smaller breakpoints search space). - # Only used for the fork path (R >= 4.0, non-Windows); PSOCK clusters already - # incur high per-job serialisation overhead so we keep column-mode there. - use_fork <- is_parallel_run && - !(.Platform$OS.type == "windows" || getRversion() < "4.0.0") - pieces_per_col <- if (use_fork && length(ab_cols) < n_cores) { - ceiling(n_cores / length(ab_cols)) + if (is_parallel_run && length(ab_cols) < n_workers) { + pieces_per_col <- ceiling(n_workers / length(ab_cols)) } else { - 1L + pieces_per_col <- 1L } run_as_sir_column <- function(i, rows = NULL) { - # Always resolve AMR_env from the package namespace. This is essential for - # PSOCK workers (where the closure-captured AMR_env is a stale serialised copy - # while as.sir() writes to the live AMR:::AMR_env) and also avoids capturing - # pre-existing log entries from earlier in the session when forking. + # Always resolve AMR_env from the package namespace so workers get the live + # environment rather than a stale serialised copy from the closure. .amr_env <- get("AMR_env", envir = asNamespace("AMR"), inherits = FALSE) - # In parallel mode each worker (fork or PSOCK) has its own copy of the - # history; record the current length so we capture only the new rows added - # by the as.sir() call below, not any pre-existing entries inherited at fork - # time or carried over from earlier as.sir() calls. + # In parallel mode each worker has its own copy of the history; record the + # current length so we capture only the rows added by this as.sir() call. if (is_parallel_run) pre_log_n <- NROW(.amr_env$sir_interpretation_history) ab_col <- ab_cols[i] @@ -1057,7 +1037,7 @@ as.sir.data.frame <- function(x, ab <- ab_col ab_coerced <- suppressWarnings(as.ab(ab, info = FALSE)) show_message <- FALSE - if (!all(x[row_idx, ab, drop = TRUE] %in% c("S", "SDD", "I", "R", "NI", NA), na.rm = TRUE)) { + if (!all(x[row_idx, ab, drop = TRUE] %in% c(VALID_SIR_LEVELS, NA), na.rm = TRUE)) { show_message <- TRUE if (isTRUE(effective_info)) { message_("\u00a0\u00a0", .amr_env$bullet_icon, " Cleaning values in column ", paste0("{.field ", font_bold(ab), "}"), " (", @@ -1090,31 +1070,17 @@ as.sir.data.frame <- function(x, return(out) } - if (isTRUE(parallel) && n_cores > 1 && length(ab_cols) > 1) { + if (is_parallel_run) { if (isTRUE(info)) { message_(as_note = FALSE) if (pieces_per_col > 1L) { - message_("Running in parallel mode using ", n_cores, " out of ", get_n_cores(Inf), " cores, on columns ", vector_and(paste0("{.field ", font_bold(ab_cols, collapse = NULL), "}"), quotes = FALSE, sort = FALSE), " (", pieces_per_col, " row slices per column)...", as_note = FALSE, appendLF = FALSE) + message_("Running in parallel mode using ", n_cores, " workers, on columns ", vector_and(paste0("{.field ", font_bold(ab_cols, collapse = NULL), "}"), quotes = FALSE, sort = FALSE), " (", pieces_per_col, " row slices per column)...", as_note = FALSE, appendLF = FALSE) } else { - message_("Running in parallel mode using ", n_cores, " out of ", get_n_cores(Inf), " cores, on columns ", vector_and(paste0("{.field ", font_bold(ab_cols, collapse = NULL), "}"), quotes = FALSE, sort = FALSE), "...", as_note = FALSE, appendLF = FALSE) + message_("Running in parallel mode using ", n_cores, " workers, on columns ", vector_and(paste0("{.field ", font_bold(ab_cols, collapse = NULL), "}"), quotes = FALSE, sort = FALSE), "...", as_note = FALSE, appendLF = FALSE) } } - if (.Platform$OS.type == "windows" || getRversion() < "4.0.0") { - # PSOCK cluster: column-mode only (row-batch serialisation overhead not worth it) - on.exit(parallel::stopCluster(cl), add = TRUE) - parallel::clusterExport(cl, varlist = c( - "x", "x.bak", "x_mo", "ab_cols", "types", - "capped_mic_handling", "as_wt_nwt", "add_intrinsic_resistance", - "reference_data", "substitute_missing_r_breakpoint", "include_screening", "include_PKPD", - "breakpoint_type", "guideline", "host", "uti", "verbose", - "col_mo", "conserve_capped_values", - "effective_info", "is_parallel_run", - "run_as_sir_column" - ), envir = environment()) - result_list <- parallel::parLapply(cl, seq_along(ab_cols), run_as_sir_column) - } else if (pieces_per_col > 1L) { - # Row-batch mode (R >= 4.0, non-Windows, n_cols < n_cores): - # build (col, row_slice) job pairs so all cores stay active + if (pieces_per_col > 1L) { + # Row-batch mode: build (col, row_slice) job pairs so all workers stay active row_cuts <- unique(round(seq(0, nrow(x), length.out = pieces_per_col + 1L))) row_ranges <- lapply(seq_len(length(row_cuts) - 1L), function(p) { seq.int(row_cuts[p] + 1L, row_cuts[p + 1L]) @@ -1122,23 +1088,23 @@ as.sir.data.frame <- function(x, jobs <- do.call(c, lapply(seq_along(ab_cols), function(ci) { lapply(seq_along(row_ranges), function(p) list(col = ci, rows = row_ranges[[p]])) })) - flat <- parallel::mclapply(jobs, function(job) { + flat <- future.apply::future_lapply(jobs, function(job) { run_as_sir_column(job$col, job$rows) - }, mc.cores = n_cores) + }, future.seed = TRUE) # Reassemble: for each column concatenate row pieces in order result_list <- lapply(seq_along(ab_cols), function(ci) { pieces <- flat[vapply(jobs, function(j) j$col == ci, logical(1L))] list( result = as.sir(do.call(c, lapply(pieces, function(p) as.character(p$result)))), - log = { + log = { logs <- Filter(Negate(is.null), lapply(pieces, function(p) p$log)) if (length(logs) > 0L) do.call(rbind_AMR, logs) else NULL } ) }) } else { - # Column-parallel mode (R >= 4.0, non-Windows, n_cols >= n_cores) - result_list <- parallel::mclapply(seq_along(ab_cols), run_as_sir_column, mc.cores = n_cores) + # Column-parallel mode: one job per antibiotic column + result_list <- future.apply::future_lapply(seq_along(ab_cols), run_as_sir_column, future.seed = TRUE) } if (isTRUE(info)) { message_(font_green_bg("\u00a0DONE\u00a0"), as_note = FALSE) @@ -1148,9 +1114,16 @@ as.sir.data.frame <- function(x, } else { # sequential mode (non-parallel) if (isTRUE(info) && n_cores > 1 && NROW(x) * NCOL(x) > 10000) { - # give a note that parallel mode might be better + suggest <- ifelse(.Platform$OS.type == "windows" || in_rstudio(), + "plan(multisession)", + "plan(multicore)" + ) message_(as_note = FALSE) - message_("Running in sequential mode. Consider setting {.arg parallel} to {.code TRUE} to speed up processing on multiple cores.\n") + if (requireNamespace("future.apply", quietly = TRUE)) { + message_("Running in sequential mode. To speed up processing, set a parallel {.help [{.fun future::plan}](future::plan)} such as {.code ", suggest, "}.") + } else { + message_("Running in sequential mode. To speed up processing, install the {.pkg future.apply} package and then set {.code parallel = TRUE}.\n") + } } # this will contain a progress bar already result_list <- lapply(seq_along(ab_cols), run_as_sir_column) @@ -1280,7 +1253,7 @@ as_sir_method <- function(method_short, # backward compatibilty dots <- list(...) - dots <- dots[which(!names(dots) %in% c("warn", "mo.bak", "is_data.frame"))] + dots <- dots[which(!names(dots) %in% c("warn", "mo.bak", "is_data.frame", "as_wt_nwt"))] if (length(dots) != 0) { warning_("These arguments in {.help [{.fun as.sir}](AMR::as.sir)} are no longer used: ", vector_and(names(dots), quotes = "`"), ".", call = FALSE) } @@ -2121,7 +2094,7 @@ sir_interpretation_history <- function(clean = FALSE) { #' @noRd print.sir_log <- function(x, ...) { if (NROW(x) == 0) { - message_("No results to print. First run {.help [{.fun as.sir}](AMR::as.sir)} on MIC values or disk diffusion zones (or on a {.cls data.frame} containing any of these) to print a {.val logbook} data set here.") + message_("No results to print. First run {.help [{.fun as.sir}](AMR::as.sir)} on MIC values or disk diffusion zones (or on a {.cls data.frame} containing any of these) to print a 'logbook' data set here.") return(invisible(NULL)) } class(x) <- class(x)[class(x) != "sir_log"] @@ -2363,7 +2336,7 @@ coerce_reference_data_columns <- function(x) { ref <- AMR::clinical_breakpoints for (col in names(ref)) { col_ref <- ref[[col]] - col_x <- x[[col]] + col_x <- x[[col]] if (identical(class(col_ref), class(col_x))) next if (col == "mo") { x[[col]] <- suppressMessages(as.mo(col_x)) diff --git a/index.md b/index.md index c512a8175..78a02c0a6 100644 --- a/index.md +++ b/index.md @@ -26,12 +26,9 @@

- amr-for-r.org

-

- doi.org/10.18637/jss.v104.i03

@@ -174,24 +171,26 @@ example_isolates %>% #> ℹ Using column mo as input for `mo_fullname()` #> ℹ Using column mo as input for `mo_is_gram_negative()` #> ℹ Using column mo as input for `mo_is_intrinsic_resistant()` -#> ℹ Determining intrinsic resistance based on 'EUCAST Expected Resistant -#> Phenotypes' v1.2 (2023). This note will be shown once per session. -#> ℹ For `aminoglycosides()` using columns GEN (gentamicin), TOB (tobramycin), AMK -#> (amikacin), and KAN (kanamycin) -#> ℹ For `carbapenems()` using columns IPM (imipenem) and MEM (meropenem) +#> ℹ Determining intrinsic resistance based on 'EUCAST Expected +#> Resistant Phenotypes' v1.2 (2023). This note will be shown +#> once per session. +#> ℹ For `aminoglycosides()` using columns GEN (gentamicin), TOB +#> (tobramycin), AMK (amikacin), and KAN (kanamycin) +#> ℹ For `carbapenems()` using columns IPM (imipenem) and MEM +#> (meropenem) #> # A tibble: 35 × 7 -#> bacteria GEN TOB AMK KAN IPM MEM -#> -#> 1 Pseudomonas aeruginosa I S NA R S NA -#> 2 Pseudomonas aeruginosa I S NA R S NA -#> 3 Pseudomonas aeruginosa I S NA R S NA -#> 4 Pseudomonas aeruginosa S S S R NA S -#> 5 Pseudomonas aeruginosa S S S R S S -#> 6 Pseudomonas aeruginosa S S S R S S -#> 7 Stenotrophomonas maltophilia R R R R R R -#> 8 Pseudomonas aeruginosa S S S R NA S -#> 9 Pseudomonas aeruginosa S S S R NA S -#> 10 Pseudomonas aeruginosa S S S R S S +#> bacteria GEN TOB AMK KAN IPM MEM +#> +#> 1 Pseudomonas aer… I S NA R S NA +#> 2 Pseudomonas aer… I S NA R S NA +#> 3 Pseudomonas aer… I S NA R S NA +#> 4 Pseudomonas aer… S S S R NA S +#> 5 Pseudomonas aer… S S S R S S +#> 6 Pseudomonas aer… S S S R S S +#> 7 Stenotrophomona… R R R R R R +#> 8 Pseudomonas aer… S S S R NA S +#> 9 Pseudomonas aer… S S S R NA S +#> 10 Pseudomonas aer… S S S R S S #> # ℹ 25 more rows ``` @@ -215,23 +214,24 @@ output format automatically (such as markdown, LaTeX, HTML, etc.). ``` r antibiogram(example_isolates, antimicrobials = c(aminoglycosides(), carbapenems())) -#> ℹ For `aminoglycosides()` using columns GEN (gentamicin), TOB (tobramycin), AMK -#> (amikacin), and KAN (kanamycin) -#> ℹ For `carbapenems()` using columns IPM (imipenem) and MEM (meropenem) +#> ℹ For `aminoglycosides()` using columns GEN (gentamicin), TOB +#> (tobramycin), AMK (amikacin), and KAN (kanamycin) +#> ℹ For `carbapenems()` using columns IPM (imipenem) and MEM +#> (meropenem) ``` -| Pathogen | Amikacin | Gentamicin | Imipenem | Kanamycin | Meropenem | Tobramycin | -|:---|:---|:---|:---|:---|:---|:---| -| CoNS | 0% (0-8%,N=43) | 86% (82-90%,N=309) | 52% (37-67%,N=48) | 0% (0-8%,N=43) | 52% (37-67%,N=48) | 22% (12-35%,N=55) | -| *E. coli* | 100% (98-100%,N=171) | 98% (96-99%,N=460) | 100% (99-100%,N=422) | NA | 100% (99-100%,N=418) | 97% (96-99%,N=462) | -| *E. faecalis* | 0% (0-9%,N=39) | 0% (0-9%,N=39) | 100% (91-100%,N=38) | 0% (0-9%,N=39) | NA | 0% (0-9%,N=39) | -| *K. pneumoniae* | NA | 90% (79-96%,N=58) | 100% (93-100%,N=51) | NA | 100% (93-100%,N=53) | 90% (79-96%,N=58) | -| *P. aeruginosa* | NA | 100% (88-100%,N=30) | NA | 0% (0-12%,N=30) | NA | 100% (88-100%,N=30) | -| *P. mirabilis* | NA | 94% (80-99%,N=34) | 94% (79-99%,N=32) | NA | NA | 94% (80-99%,N=34) | -| *S. aureus* | NA | 99% (97-100%,N=233) | NA | NA | NA | 98% (92-100%,N=86) | -| *S. epidermidis* | 0% (0-8%,N=44) | 79% (71-85%,N=163) | NA | 0% (0-8%,N=44) | NA | 51% (40-61%,N=89) | -| *S. hominis* | NA | 92% (84-97%,N=80) | NA | NA | NA | 85% (74-93%,N=62) | -| *S. pneumoniae* | 0% (0-3%,N=117) | 0% (0-3%,N=117) | NA | 0% (0-3%,N=117) | NA | 0% (0-3%,N=117) | +| Pathogen | Amikacin | Gentamicin | Imipenem | Kanamycin | Meropenem | Tobramycin | +|:-----------------|:---------------------|:--------------------|:---------------------|:----------------|:---------------------|:--------------------| +| CoNS | 0% (0-8%,N=43) | 86% (82-90%,N=309) | 52% (37-67%,N=48) | 0% (0-8%,N=43) | 52% (37-67%,N=48) | 22% (12-35%,N=55) | +| *E. coli* | 100% (98-100%,N=171) | 98% (96-99%,N=460) | 100% (99-100%,N=422) | NA | 100% (99-100%,N=418) | 97% (96-99%,N=462) | +| *E. faecalis* | 0% (0-9%,N=39) | 0% (0-9%,N=39) | 100% (91-100%,N=38) | 0% (0-9%,N=39) | NA | 0% (0-9%,N=39) | +| *K. pneumoniae* | NA | 90% (79-96%,N=58) | 100% (93-100%,N=51) | NA | 100% (93-100%,N=53) | 90% (79-96%,N=58) | +| *P. aeruginosa* | NA | 100% (88-100%,N=30) | NA | 0% (0-12%,N=30) | NA | 100% (88-100%,N=30) | +| *P. mirabilis* | NA | 94% (80-99%,N=34) | 94% (79-99%,N=32) | NA | NA | 94% (80-99%,N=34) | +| *S. aureus* | NA | 99% (97-100%,N=233) | NA | NA | NA | 98% (92-100%,N=86) | +| *S. epidermidis* | 0% (0-8%,N=44) | 79% (71-85%,N=163) | NA | 0% (0-8%,N=44) | NA | 51% (40-61%,N=89) | +| *S. hominis* | NA | 92% (84-97%,N=80) | NA | NA | NA | 85% (74-93%,N=62) | +| *S. pneumoniae* | 0% (0-3%,N=117) | 0% (0-3%,N=117) | NA | 0% (0-3%,N=117) | NA | 0% (0-3%,N=117) | In combination antibiograms, it is clear that combined antimicrobials yield higher empiric coverage: @@ -242,10 +242,10 @@ antibiogram(example_isolates, mo_transform = "gramstain") ``` -| Pathogen | Piperacillin/tazobactam | Piperacillin/tazobactam + Gentamicin | Piperacillin/tazobactam + Tobramycin | -|:---|:---|:---|:---| -| Gram-negative | 88% (85-91%,N=641) | 99% (97-99%,N=691) | 98% (97-99%,N=693) | -| Gram-positive | 86% (82-89%,N=345) | 98% (96-98%,N=1044) | 95% (93-97%,N=550) | +| Pathogen | Piperacillin/tazobactam | Piperacillin/tazobactam + Gentamicin | Piperacillin/tazobactam + Tobramycin | +|:--------------|:------------------------|:-------------------------------------|:-------------------------------------| +| Gram-negative | 88% (85-91%,N=641) | 99% (97-99%,N=691) | 98% (97-99%,N=693) | +| Gram-positive | 86% (82-89%,N=345) | 98% (96-98%,N=1044) | 95% (93-97%,N=550) | Like many other functions in this package, `antibiogram()` comes with support for 28 languages that are often detected automatically based on @@ -318,16 +318,18 @@ example_isolates %>% summarise(across(c(GEN, TOB), list(total_R = resistance, conf_int = function(x) sir_confidence_interval(x, collapse = "-")))) -#> ℹ `resistance()` assumes the EUCAST guideline and thus considers the 'I' -#> category susceptible. Set the `guideline` argument or the `AMR_guideline` -#> option to either "CLSI" or "EUCAST", see `?AMR-options`. +#> ℹ `resistance()` assumes the EUCAST guideline and thus +#> considers the 'I' category susceptible. Set the `guideline` +#> argument or the `AMR_guideline` option to either "CLSI" or +#> "EUCAST", see `?AMR-options`. #> ℹ This message will be shown once per session. #> # A tibble: 3 × 5 -#> ward GEN_total_R GEN_conf_int TOB_total_R TOB_conf_int -#> -#> 1 Clinical 0.229 0.205-0.254 0.315 0.284-0.347 -#> 2 ICU 0.290 0.253-0.33 0.400 0.353-0.449 -#> 3 Outpatient 0.2 0.131-0.285 0.368 0.254-0.493 +#> ward GEN_total_R GEN_conf_int TOB_total_R +#> +#> 1 Clinical 0.229 0.205-0.254 0.315 +#> 2 ICU 0.290 0.253-0.33 0.400 +#> 3 Outpatient 0.2 0.131-0.285 0.368 +#> # ℹ 1 more variable: TOB_conf_int ``` Or use [antimicrobial @@ -344,15 +346,16 @@ out <- example_isolates %>% # calculate AMR using resistance(), over all aminoglycosides and polymyxins: summarise(across(c(aminoglycosides(), polymyxins()), resistance)) -#> ℹ For `aminoglycosides()` using columns GEN (gentamicin), TOB (tobramycin), AMK -#> (amikacin), and KAN (kanamycin) +#> ℹ For `aminoglycosides()` using columns GEN (gentamicin), TOB +#> (tobramycin), AMK (amikacin), and KAN (kanamycin) #> ℹ For `polymyxins()` using column COL (colistin) #> Warning: There was 1 warning in `summarise()`. -#> ℹ In argument: `across(c(aminoglycosides(), polymyxins()), resistance)`. +#> ℹ In argument: `across(c(aminoglycosides(), polymyxins()), +#> resistance)`. #> ℹ In group 3: `ward = "Outpatient"`. #> Caused by warning: -#> ! Introducing NA: only 23 results available for KAN in group: ward = "Outpatient" -#> (whilst `minimum = 30`). +#> ! Introducing NA: only 23 results available for KAN in group: +#> ward = "Outpatient" (whilst `minimum = 30`). out #> # A tibble: 3 × 6 #> ward GEN TOB AMK KAN COL @@ -366,11 +369,12 @@ out # transform the antibiotic columns to names: out %>% set_ab_names() #> # A tibble: 3 × 6 -#> ward gentamicin tobramycin amikacin kanamycin colistin -#> -#> 1 Clinical 0.229 0.315 0.626 1 0.780 -#> 2 ICU 0.290 0.400 0.662 1 0.857 -#> 3 Outpatient 0.2 0.368 0.605 NA 0.889 +#> ward gentamicin tobramycin amikacin kanamycin +#> +#> 1 Clinical 0.229 0.315 0.626 1 +#> 2 ICU 0.290 0.400 0.662 1 +#> 3 Outpatient 0.2 0.368 0.605 NA +#> # ℹ 1 more variable: colistin ``` ``` r diff --git a/man/as.sir.Rd b/man/as.sir.Rd index 1121f8ff9..c08986e2e 100644 --- a/man/as.sir.Rd +++ b/man/as.sir.Rd @@ -73,7 +73,7 @@ is_sir_eligible(x, threshold = 0.05) include_PKPD = getOption("AMR_include_PKPD", TRUE), breakpoint_type = getOption("AMR_breakpoint_type", "human"), host = NULL, language = get_AMR_locale(), verbose = FALSE, info = interactive(), - parallel = FALSE, max_cores = -1, conserve_capped_values = NULL) + parallel = FALSE, conserve_capped_values = NULL) sir_interpretation_history(clean = FALSE) } @@ -150,9 +150,7 @@ The default \code{"conservative"} setting ensures cautious handling of uncertain \item{col_mo}{Column name of the names or codes of the microorganisms (see \code{\link[=as.mo]{as.mo()}}) - the default is the first column of class \code{\link{mo}}. Values will be coerced using \code{\link[=as.mo]{as.mo()}}.} -\item{parallel}{A \link{logical} to indicate if parallel computing must be used, defaults to \code{FALSE}. The \code{parallel} package is part of base \R and no additional packages are required. On Unix/macOS with \R >= 4.0.0, \code{\link[parallel:mclapply]{parallel::mclapply()}} (fork-based) is used; on Windows and \R < 4.0.0, \code{\link[parallel:clusterApply]{parallel::parLapply()}} with a PSOCK cluster is used (requires the AMR package to be installed, not just loaded via \code{devtools::load_all()}). Parallelism distributes columns across cores; it is most beneficial when there are many antibiotic columns and a large number of rows.} - -\item{max_cores}{Maximum number of cores to use if \code{parallel = TRUE}. Use a negative value to subtract that number from the available number of cores, e.g. a value of \code{-2} on an 8-core machine means that at most 6 cores will be used. Defaults to \code{-1}. There will never be used more cores than variables to analyse. The available number of cores are detected using \code{\link[parallelly:availableCores]{parallelly::availableCores()}} if that package is installed, and base \R's \code{\link[parallel:detectCores]{parallel::detectCores()}} otherwise.} +\item{parallel}{A \link{logical} to indicate if parallel computing must be used, defaults to \code{FALSE}. Requires the \code{\link[future.apply:future_lapply]{future.apply}} package. \strong{A non-sequential \code{\link[future:plan]{future::plan()}} must already be active before setting \code{parallel = TRUE}} — for example, \code{future::plan(future::multisession)}. An error is thrown if \code{parallel = TRUE} is used without a plan set by the user. Parallelism distributes columns (and optionally row batches) across workers; it is most beneficial when there are many antibiotic columns and a large number of rows.} \item{clean}{A \link{logical} to indicate whether previously stored results should be forgotten after returning the 'logbook' with results.} } @@ -183,7 +181,7 @@ your_data \%>\% mutate_if(is.mic, as.sir, ab = c("cipro", "ampicillin", ...), mo # for veterinary breakpoints, also set `host`: your_data \%>\% mutate_if(is.mic, as.sir, host = "column_with_animal_species", guideline = "CLSI") -# fast processing with parallel computing: +# fast processing with parallel computing (requires future.apply): as.sir(your_data, ..., parallel = TRUE) }\if{html}{\out{
}} \item Operators like "<=" will be considered according to the \code{capped_mic_handling} setting. At default, an MIC value of e.g. ">2" will return "NI" (non-interpretable) if the breakpoint is 4-8; the \emph{true} MIC could be at either side of the breakpoint. This is to prevent that capped values from raw laboratory data would not be treated conservatively. @@ -201,7 +199,7 @@ your_data \%>\% mutate_if(is.disk, as.sir, ab = c("cipro", "ampicillin", ...), m # for veterinary breakpoints, also set `host`: your_data \%>\% mutate_if(is.disk, as.sir, host = "column_with_animal_species", guideline = "CLSI") -# fast processing with parallel computing: +# fast processing with parallel computing (requires future.apply): as.sir(your_data, ..., parallel = TRUE) }\if{html}{\out{}} } @@ -313,9 +311,6 @@ as.sir(df_wide) sir_interpretation_history() \donttest{ -# using parallel computing, which is available in base R: -as.sir(df_wide, parallel = TRUE, info = TRUE) - ## Using dplyr ------------------------------------------------- if (require("dplyr")) { diff --git a/tests/testthat/test-sir.R b/tests/testthat/test-sir.R index 2e877c8d2..a648a0e83 100644 --- a/tests/testthat/test-sir.R +++ b/tests/testthat/test-sir.R @@ -408,13 +408,13 @@ test_that("test-sir.R", { # Issue #278: re-running as.sir() on already- data must preserve columns df_already_sir <- data.frame( - mo = "B_ESCHR_COLI", + mo = "B_ESCHR_COLI", AMC = as.mic(c("1", "2", "4")), GEN = sample(c("S", "I", "R"), 3, replace = TRUE), stringsAsFactors = FALSE ) - first_pass <- suppressMessages(as.sir(df_already_sir, col_mo = "mo", info = FALSE)) - second_pass <- suppressMessages(as.sir(first_pass, col_mo = "mo", info = FALSE)) + first_pass <- suppressMessages(as.sir(df_already_sir, col_mo = "mo", info = FALSE)) + second_pass <- suppressMessages(as.sir(first_pass, col_mo = "mo", info = FALSE)) expect_equal(ncol(first_pass), ncol(second_pass)) expect_true(is.sir(second_pass[["AMC"]])) expect_true(is.sir(second_pass[["GEN"]])) @@ -424,15 +424,15 @@ test_that("test-sir.R", { # Issue #278: metadata columns whose names coincidentally match antibiotic # codes (e.g. 'patient' -> OXY, 'ward' -> PRU) must not be processed df_meta <- data.frame( - mo = "B_ESCHR_COLI", + mo = "B_ESCHR_COLI", patient = paste0("Pt_", 1:20), - ward = rep(c("ICU", "Surgery", "Outpatient", "ED"), 5), - AMC = as.mic(rep(c("1", "2", "4", "8"), 5)), + ward = rep(c("ICU", "Surgery", "Outpatient", "ED"), 5), + AMC = as.mic(rep(c("1", "2", "4", "8"), 5)), stringsAsFactors = FALSE ) df_meta_sir <- suppressMessages(as.sir(df_meta, col_mo = "mo", info = FALSE)) expect_true("patient" %in% colnames(df_meta_sir)) - expect_true("ward" %in% colnames(df_meta_sir)) + expect_true("ward" %in% colnames(df_meta_sir)) expect_false(is.sir(df_meta_sir[["patient"]])) expect_false(is.sir(df_meta_sir[["ward"]])) expect_true(is.sir(df_meta_sir[["AMC"]])) @@ -441,92 +441,111 @@ test_that("test-sir.R", { # Tests must pass even when only 1 core is available; parallel = TRUE then # silently falls back to sequential, but results must still be identical. - set.seed(42) - n_par <- 200 - df_par <- data.frame( - mo = "B_ESCHR_COLI", - AMC = as.mic(sample(c("0.25", "0.5", "1", "2", "4", "8", "16", "32"), n_par, TRUE)), - GEN = as.mic(sample(c("0.5", "1", "2", "4", "8", "16", "32", "64"), n_par, TRUE)), - CIP = as.mic(sample(c("0.001", "0.002", "0.004", "0.008", "0.016", "0.032"), n_par, TRUE)), - PEN = sample(c("S", "I", "R", NA_character_), n_par, TRUE), - stringsAsFactors = FALSE - ) + if (AMR:::pkg_is_available("future.apply")) { + set.seed(42) + n_par <- 200 + df_par <- data.frame( + mo = "B_ESCHR_COLI", + AMC = as.mic(sample(c("0.25", "0.5", "1", "2", "4", "8", "16", "32"), n_par, TRUE)), + GEN = as.mic(sample(c("0.5", "1", "2", "4", "8", "16", "32", "64"), n_par, TRUE)), + CIP = as.mic(sample(c("0.001", "0.002", "0.004", "0.008", "0.016", "0.032"), n_par, TRUE)), + PEN = sample(c("S", "I", "R", NA_character_), n_par, TRUE), + stringsAsFactors = FALSE + ) - # clear any existing history before comparing - sir_interpretation_history(clean = TRUE) - sir_seq <- suppressMessages(as.sir(df_par, col_mo = "mo", info = FALSE)) - log_seq <- sir_interpretation_history(clean = TRUE) + # clear any existing history before comparing + sir_interpretation_history(clean = TRUE) + sir_seq <- suppressMessages(as.sir(df_par, col_mo = "mo", info = FALSE)) + log_seq <- sir_interpretation_history(clean = TRUE) - sir_par <- suppressMessages(as.sir(df_par, col_mo = "mo", info = FALSE, parallel = TRUE)) - log_par <- sir_interpretation_history(clean = TRUE) + future::plan(future::multicore) + n_max_workers <- future::nbrOfWorkers() - # 1. parallel = TRUE gives identical SIR results to sequential - expect_identical(sir_seq[["AMC"]], sir_par[["AMC"]]) - expect_identical(sir_seq[["GEN"]], sir_par[["GEN"]]) - expect_identical(sir_seq[["CIP"]], sir_par[["CIP"]]) - expect_identical(sir_seq[["PEN"]], sir_par[["PEN"]]) + sir_par <- suppressMessages(as.sir(df_par, col_mo = "mo", info = FALSE, parallel = TRUE)) + log_par <- sir_interpretation_history(clean = TRUE) - # 2. same number of log rows as sequential - expect_equal(nrow(log_seq), nrow(log_par)) + # 1. parallel = TRUE gives identical SIR results to sequential + expect_identical(sir_seq[["AMC"]], sir_par[["AMC"]]) + expect_identical(sir_seq[["GEN"]], sir_par[["GEN"]]) + expect_identical(sir_seq[["CIP"]], sir_par[["CIP"]]) + expect_identical(sir_seq[["PEN"]], sir_par[["PEN"]]) - # 3. pre-existing log entries must not be duplicated - # run sequential once to populate the history, then run parallel and - # verify the new parallel run adds exactly as many rows as sequential - sir_interpretation_history(clean = TRUE) - suppressMessages(as.sir(df_par, col_mo = "mo", info = FALSE)) # populate history - pre_n <- nrow(sir_interpretation_history()) - suppressMessages(as.sir(df_par, col_mo = "mo", info = FALSE, parallel = TRUE)) - post_n <- nrow(sir_interpretation_history()) - expect_equal(post_n - pre_n, nrow(log_seq)) # exactly one run's worth of new rows - sir_interpretation_history(clean = TRUE) + # 2. same number of log rows as sequential + expect_equal(nrow(log_seq), nrow(log_par)) - # 4. two sequential runs and two parallel runs yield identical results - sir_par2 <- suppressMessages(as.sir(df_par, col_mo = "mo", info = FALSE, parallel = TRUE)) - expect_identical(sir_par[["AMC"]], sir_par2[["AMC"]]) - expect_identical(sir_par[["GEN"]], sir_par2[["GEN"]]) + # 3. pre-existing log entries must not be duplicated + # run sequential once to populate the history, then run parallel and + # verify the new parallel run adds exactly as many rows as sequential + sir_interpretation_history(clean = TRUE) + future::plan(future::sequential) + suppressMessages(as.sir(df_par, col_mo = "mo", info = FALSE)) # populate history + pre_n <- nrow(sir_interpretation_history()) + future::plan(future::multicore) + suppressMessages(as.sir(df_par, col_mo = "mo", info = FALSE, parallel = TRUE)) + post_n <- nrow(sir_interpretation_history()) + expect_equal(post_n - pre_n, nrow(log_seq)) # exactly one run's worth of new rows + sir_interpretation_history(clean = TRUE) - # 5. max_cores = 1 gives same results as default sequential - sir_mc1 <- suppressMessages(as.sir(df_par, col_mo = "mo", info = FALSE, parallel = TRUE, max_cores = 1L)) - expect_identical(sir_seq[["AMC"]], sir_mc1[["AMC"]]) - expect_identical(sir_seq[["GEN"]], sir_mc1[["GEN"]]) + # 4. two sequential runs and two parallel runs yield identical results + sir_par2 <- suppressMessages(as.sir(df_par, col_mo = "mo", info = FALSE, parallel = TRUE)) + expect_identical(sir_par[["AMC"]], sir_par2[["AMC"]]) + expect_identical(sir_par[["GEN"]], sir_par2[["GEN"]]) - # 6. max_cores = 2 and max_cores = 3 give same results as sequential - sir_mc2 <- suppressMessages(as.sir(df_par, col_mo = "mo", info = FALSE, parallel = TRUE, max_cores = 2L)) - sir_mc3 <- suppressMessages(as.sir(df_par, col_mo = "mo", info = FALSE, parallel = TRUE, max_cores = 3L)) - expect_identical(sir_seq[["AMC"]], sir_mc2[["AMC"]]) - expect_identical(sir_seq[["GEN"]], sir_mc3[["GEN"]]) + # 5. used cores = 1 gives same results as default sequential + future::plan(future::multicore, workers = 1) + sir_mc1 <- suppressMessages(as.sir(df_par, col_mo = "mo", info = FALSE, parallel = TRUE)) + expect_identical(sir_seq[["AMC"]], sir_mc1[["AMC"]]) + expect_identical(sir_seq[["GEN"]], sir_mc1[["GEN"]]) - # 7. single-column data frame falls back silently to sequential - df_single <- df_par[, c("mo", "AMC")] - sir_single_seq <- suppressMessages(as.sir(df_single, col_mo = "mo", info = FALSE)) - sir_single_par <- suppressMessages(as.sir(df_single, col_mo = "mo", info = FALSE, parallel = TRUE)) - expect_identical(sir_single_seq[["AMC"]], sir_single_par[["AMC"]]) + # 6. used cores = 2 and used cores = 3 give same results as sequential + if (n_max_workers >= 3) { + future::plan(future::multicore, workers = 2) + sir_mc2 <- suppressMessages(as.sir(df_par, col_mo = "mo", info = FALSE, parallel = TRUE)) + future::plan(future::multicore, workers = 3) + sir_mc3 <- suppressMessages(as.sir(df_par, col_mo = "mo", info = FALSE, parallel = TRUE)) + expect_identical(sir_seq[["AMC"]], sir_mc2[["AMC"]]) + expect_identical(sir_seq[["GEN"]], sir_mc3[["GEN"]]) + } - # 9. row-batch mode (n_cols < n_cores): force row splitting via max_cores and - # verify identical output to sequential for a dataset with 2 AB columns so - # pieces_per_col = ceiling(max_cores / 2) >= 2 and row batching activates - df_wide <- data.frame( - mo = "B_ESCHR_COLI", - AMC = as.mic(sample(c("1", "2", "4", "8"), n_par, TRUE)), - GEN = as.mic(sample(c("1", "2", "4", "8"), n_par, TRUE)), - stringsAsFactors = FALSE - ) - sir_wide_seq <- suppressMessages(as.sir(df_wide, col_mo = "mo", info = FALSE)) - sir_wide_par <- suppressMessages(as.sir(df_wide, col_mo = "mo", info = FALSE, - parallel = TRUE, max_cores = 8L)) - expect_identical(sir_wide_seq[["AMC"]], sir_wide_par[["AMC"]]) - expect_identical(sir_wide_seq[["GEN"]], sir_wide_par[["GEN"]]) + # 7. single-column data frame falls back silently to sequential + df_single <- df_par[, c("mo", "AMC")] + future::plan(future::sequential) + sir_single_seq <- suppressMessages(as.sir(df_single, col_mo = "mo", info = FALSE)) + future::plan(future::multicore) + sir_single_par <- suppressMessages(as.sir(df_single, col_mo = "mo", info = FALSE, parallel = TRUE)) + expect_identical(sir_single_seq[["AMC"]], sir_single_par[["AMC"]]) - # 8. info = TRUE with parallel does not produce per-column worker messages - # (messages should only appear in the main process, not duplicated from workers) - msgs <- capture.output( - suppressWarnings(as.sir(df_par, col_mo = "mo", info = TRUE, parallel = TRUE)), - type = "message" - ) - # each AB column name should appear at most once in all messages combined - for (ab_nm in c("AMC", "GEN", "CIP", "PEN")) { - n_mentions <- sum(grepl(ab_nm, msgs, fixed = TRUE)) - expect_lte(n_mentions, 1L) + # 8. row-batch mode (n_cols < n_cores): force row splitting via used cores and + # verify identical output to sequential for a dataset with 2 AB columns so + # pieces_per_col = ceiling(used cores / 2) >= 2 and row batching activates + df_wide <- data.frame( + mo = "B_ESCHR_COLI", + AMC = as.mic(sample(c("1", "2", "4", "8"), n_par, TRUE)), + GEN = as.mic(sample(c("1", "2", "4", "8"), n_par, TRUE)), + stringsAsFactors = FALSE + ) + future::plan(future::sequential) + sir_wide_seq <- suppressMessages(as.sir(df_wide, col_mo = "mo", info = FALSE)) + future::plan(future::multicore) + sir_wide_par <- suppressMessages(as.sir(df_wide, + col_mo = "mo", info = FALSE, + parallel = TRUE + )) + expect_identical(sir_wide_seq[["AMC"]], sir_wide_par[["AMC"]]) + expect_identical(sir_wide_seq[["GEN"]], sir_wide_par[["GEN"]]) + + # 8. info = TRUE with parallel does not produce per-column worker messages + # (messages should only appear in the main process, not duplicated from workers) + msgs <- capture.output( + suppressWarnings(as.sir(df_par, col_mo = "mo", info = TRUE, parallel = TRUE)), + type = "message" + ) + # each AB column name should appear at most once in all messages combined + for (ab_nm in c("AMC", "GEN", "CIP", "PEN")) { + n_mentions <- sum(grepl(ab_nm, msgs, fixed = TRUE)) + expect_lte(n_mentions, 1L) + } + future::plan(future::sequential) } }) @@ -536,9 +555,9 @@ test_that("custom reference_data: non-EUCAST/CLSI guideline produces R", { # coerce_reference_data_columns() will coerce mo/ab to the right class. my_bp <- clinical_breakpoints[clinical_breakpoints$method == "MIC" & clinical_breakpoints$type == "human", ][1, ] - my_bp$guideline <- "MyLab 2025" - my_bp$mo <- "B_ACHRMB_XYLS" # plain character — coerced to - my_bp$ab <- "MEM" # plain character — coerced to + my_bp$guideline <- "MyLab 2025" + my_bp$mo <- "B_ACHRMB_XYLS" # plain character — coerced to + my_bp$ab <- "MEM" # plain character — coerced to my_bp$breakpoint_S <- 8 my_bp$breakpoint_R <- 32 @@ -556,26 +575,30 @@ test_that("custom reference_data: non-EUCAST/CLSI guideline produces R", { # guideline explicitly set: same result when it matches the data expect_equal(as.character(suppressMessages( - as.sir(as.mic(64), mo = "B_ACHRMB_XYLS", ab = "MEM", - guideline = "MyLab 2025", reference_data = my_bp) + as.sir(as.mic(64), + mo = "B_ACHRMB_XYLS", ab = "MEM", + guideline = "MyLab 2025", reference_data = my_bp + ) )), "R") }) test_that("custom reference_data: host = NA acts as host-agnostic fallback", { my_bp <- clinical_breakpoints[clinical_breakpoints$method == "MIC" & clinical_breakpoints$type == "human", ][1, ] - my_bp$guideline <- "MyLab 2025" - my_bp$mo <- "B_ACHRMB_XYLS" - my_bp$ab <- "MEM" - my_bp$type <- "animal" - my_bp$host <- NA # logical NA — coerced to character by coerce_reference_data_columns() + my_bp$guideline <- "MyLab 2025" + my_bp$mo <- "B_ACHRMB_XYLS" + my_bp$ab <- "MEM" + my_bp$type <- "animal" + my_bp$host <- NA # logical NA — coerced to character by coerce_reference_data_columns() my_bp$breakpoint_S <- 8 my_bp$breakpoint_R <- 32 # NA host should match when no species-specific row exists result <- suppressMessages( - as.sir(as.mic(64), mo = "B_ACHRMB_XYLS", ab = "MEM", - host = "dogs", breakpoint_type = "animal", reference_data = my_bp) + as.sir(as.mic(64), + mo = "B_ACHRMB_XYLS", ab = "MEM", + host = "dogs", breakpoint_type = "animal", reference_data = my_bp + ) ) expect_equal(as.character(result), "R") }) diff --git a/tests/testthat/test-zzz.R b/tests/testthat/test-zzz.R index 153fc97fe..c2429c02c 100644 --- a/tests/testthat/test-zzz.R +++ b/tests/testthat/test-zzz.R @@ -89,6 +89,11 @@ test_that("test-zzz.R", { "symbol" = "cli", # curl "has_internet" = "curl", + # future + "plan" = "future", + "nbrOfWorkers" = "future", + # future.apply + "future_lapply" = "future.apply", # ggplot2 "aes" = "ggplot2", "arrow" = "ggplot2", @@ -127,8 +132,6 @@ test_that("test-zzz.R", { "kable" = "knitr", "knit_print" = "knitr", "opts_chunk" = "knitr", - # parallelly - "availableCores" = "parallelly", # pillar "pillar_shaft" = "pillar", "style_na" = "pillar", diff --git a/tools/benchmark_parallel.png b/tools/benchmark_parallel.png new file mode 100644 index 0000000000000000000000000000000000000000..a73c821d0837bbffb2db9a49fe71adf23566b261 GIT binary patch literal 80769 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%0A9# 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 literal 0 HcmV?d00001