From 9ebc33a59df6ae77c7088cf3278c50348be2b550 Mon Sep 17 00:00:00 2001 From: Matthijs Berends Date: Tue, 13 Oct 2026 21:43:19 +0200 Subject: [PATCH] (v3.0.1.9129) Add guideline argument to random_mic() and random_disk() so that values are based on one guideline version, make guideline years in examples, vignettes and website always the latest, and reword NEWS --- DESCRIPTION | 2 +- NEWS.md | 5 +++-- R/plotting.R | 2 +- R/random.R | 29 +++++++++++++++++++---------- index.Rmd | 4 ++-- index.md | 4 ++-- man/plot.Rd | 2 +- man/random.Rd | 8 ++++++-- tests/testthat/test-random.R | 17 +++++++++++++++++ vignettes/AMR.Rmd | 4 ++-- vignettes/welcome_to_AMR.Rmd | 2 +- 11 files changed, 55 insertions(+), 24 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 1078f6995..79b379575 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: AMR -Version: 3.0.1.9128 +Version: 3.0.1.9129 Date: 2026-10-13 Title: Antimicrobial Resistance Data Analysis Description: Functions to simplify and standardise antimicrobial resistance (AMR) diff --git a/NEWS.md b/NEWS.md index cd09e4c2c..bfdb75dc4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# AMR 3.0.1.9128 +# AMR 3.0.1.9129 Planned as v3.1.0, mid October 2026. @@ -105,7 +105,7 @@ Planned as v3.1.0, mid October 2026. * Fixed a bug where both functions failed without the `antimicrobials` argument if `tidyselect` was not available, e.g., for binary installations without `dplyr` * Resistance and susceptibility calculation * New argument `guideline` (default: EUCAST) in `susceptibility()` and `resistance()` to interpret the 'I' category correctly per guideline - * `count_*()` and `proportion_*()` with multiple antimicrobials behave as in AMR 3.0.1 again, and now handle WT, NWT, NS, and NI results correctly (#321) + * `count_*()` and `proportion_*()` now handle WT, NWT, NS, and NI results correctly (#321) * MICs and disk diffusion * New argument `round_to_next_log2` in `as.mic()` and `rescale_mic()` to round values up to the nearest log2 dilution level (#255) * `as.mic()` now handles values in scientific notation (e.g., `1e-3`) correctly @@ -129,6 +129,7 @@ Planned as v3.1.0, mid October 2026. * The `rule` column of the output of `verbose = TRUE` no longer contains formatting markup (e.g., `{.field AMC}`) for the inhibitor-inheritance rules * `custom_eucast_rules()` has been renamed to `custom_interpretive_rules()`. The old name is now deprecated but still works (#268). * Logical comparison (`==`, `!=`) of the `sir` class with a `factor` is now possible on R >= 4.3 +* New argument `guideline` in `random_mic()` and `random_disk()`, defaulting to the `AMR_guideline` option (otherwise EUCAST), so that generated values are based on the latest breakpoints of one guideline instead of those of all guidelines and years combined * Fixed a bug in `g.test()` that returned the degrees of freedom as `argument` instead of `parameter`, so that they were not printed * Fixed a bug where R could crash after unloading the `data.table` package, and where suggested packages available on the machine that built the package were used even if they were not available to the user diff --git a/R/plotting.R b/R/plotting.R index 97f0bd800..527a113b7 100755 --- a/R/plotting.R +++ b/R/plotting.R @@ -105,7 +105,7 @@ #' # support for 27 languages, various guidelines, and many options #' autoplot(some_disk_values, #' mo = "Escherichia coli", ab = "cipro", -#' guideline = "CLSI 2024", language = "no", +#' guideline = "CLSI", language = "no", #' title = "Disk diffusion from the North" #' ) #' } diff --git a/R/random.R b/R/random.R index 7d2a7bffd..053281991 100755 --- a/R/random.R +++ b/R/random.R @@ -36,9 +36,10 @@ #' @param prob_SIR A vector of length 3: the probabilities for "S" (1st value), "I" (2nd value) and "R" (3rd value). #' @param skew Direction of skew for MIC or disk values, either `"right"` or `"left"`. A left-skewed distribution has the majority of the data on the right. #' @param severity Skew severity; higher values will increase the skewedness. Default is `2`; use `0` to prevent skewedness. +#' @param guideline A guideline name to base the generated values on, either `"EUCAST"` (default) or `"CLSI"` for the latest implemented version, or a guideline including a year (e.g., `"CLSI 2019"`). The default can be changed with the package option [`AMR_guideline`][AMR-options], see [as.sir()]. #' @param ... Ignored, only in place to allow future extensions. #' @details -#' Internally, MIC and disk zone values are sampled based on clinical breakpoints defined in the [clinical_breakpoints] data set. To create specific generated values per bug or drug, set the `mo` and/or `ab` argument. The MICs are sampled on a log2 scale and disks linearly, using weighted probabilities. The weights are based on the `skew` and `severity` arguments: +#' Internally, MIC and disk zone values are sampled based on the human clinical breakpoints of `guideline` in the [clinical_breakpoints] data set. To create specific generated values per bug or drug, set the `mo` and/or `ab` argument. The MICs are sampled on a log2 scale and disks linearly, using weighted probabilities. The weights are based on the `skew` and `severity` arguments: #' * `skew = "right"` places more emphasis on lower MIC or higher disk values. #' * `skew = "left"` places more emphasis on higher MIC or lower disk values. #' * `severity` controls the exponential bias applied. @@ -55,7 +56,7 @@ #' disks <- random_disk(100, severity = 2, mo = "Escherichia coli", ab = "CIP") #' plot(disks) #' # `plot()` and `ggplot2::autoplot()` allow for coloured bars if `mo` and `ab` are set -#' plot(disks, mo = "Escherichia coli", ab = "CIP", guideline = "CLSI 2025") +#' plot(disks, mo = "Escherichia coli", ab = "CIP", guideline = "CLSI") #' #' \donttest{ #' random_mic(25, "Klebsiella pneumoniae") # range 0.0625-64 @@ -66,12 +67,14 @@ #' random_disk(25, "Klebsiella pneumoniae", "ampicillin") # range 11-17 #' random_disk(25, "Streptococcus pneumoniae", "ampicillin") # range 12-27 #' } -random_mic <- function(size = NULL, mo = NULL, ab = NULL, skew = "right", severity = 1, ...) { +random_mic <- function(size = NULL, mo = NULL, ab = NULL, skew = "right", severity = 1, guideline = getOption("AMR_guideline", "EUCAST"), ...) { meet_criteria(size, allow_class = c("numeric", "integer"), has_length = 1, is_positive = TRUE, is_finite = TRUE, allow_NULL = TRUE) meet_criteria(mo, allow_class = "character", has_length = c(1, size), allow_NULL = TRUE) meet_criteria(ab, allow_class = "character", has_length = 1, allow_NULL = TRUE) meet_criteria(skew, allow_class = "character", is_in = c("right", "left"), has_length = 1) meet_criteria(severity, allow_class = c("numeric", "integer"), has_length = 1, is_positive_or_zero = TRUE, is_finite = TRUE) + meet_criteria(guideline, allow_class = "character", has_length = 1) + guideline <- get_guideline(guideline, AMR::clinical_breakpoints) if (is.null(size)) { size <- NROW(get_current_data(arg_name = "size", call = -3)) @@ -81,7 +84,7 @@ random_mic <- function(size = NULL, mo = NULL, ab = NULL, skew = "right", severi p <- progress_ticker(n = length(unique(mo)), n_min = 10, title = "Generating random MIC values") for (mo_ in unique(mo)) { p$tick() - out[which(mo == mo_)] <- random_exec("MIC", size = sum(mo == mo_), mo = mo_, ab = ab, skew = skew, severity = severity) + out[which(mo == mo_)] <- random_exec("MIC", size = sum(mo == mo_), mo = mo_, ab = ab, skew = skew, severity = severity, guideline = guideline) } out <- as.mic(out, keep_operators = "none") if (stats::runif(1) > 0.5 && length(unique(out)) > 1) { @@ -93,33 +96,35 @@ random_mic <- function(size = NULL, mo = NULL, ab = NULL, skew = "right", severi return(out) } else { - random_exec("MIC", size = size, mo = mo, ab = ab, skew = skew, severity = severity) + random_exec("MIC", size = size, mo = mo, ab = ab, skew = skew, severity = severity, guideline = guideline) } } #' @rdname random #' @export -random_disk <- function(size = NULL, mo = NULL, ab = NULL, skew = "left", severity = 1, ...) { +random_disk <- function(size = NULL, mo = NULL, ab = NULL, skew = "left", severity = 1, guideline = getOption("AMR_guideline", "EUCAST"), ...) { meet_criteria(size, allow_class = c("numeric", "integer"), has_length = 1, is_positive = TRUE, is_finite = TRUE, allow_NULL = TRUE) meet_criteria(mo, allow_class = "character", has_length = c(1, size), allow_NULL = TRUE) meet_criteria(ab, allow_class = "character", has_length = 1, allow_NULL = TRUE) meet_criteria(skew, allow_class = "character", is_in = c("right", "left"), has_length = 1) meet_criteria(severity, allow_class = c("numeric", "integer"), has_length = 1, is_positive_or_zero = TRUE, is_finite = TRUE) + meet_criteria(guideline, allow_class = "character", has_length = 1) + guideline <- get_guideline(guideline, AMR::clinical_breakpoints) if (is.null(size)) { size <- NROW(get_current_data(arg_name = "size", call = -3)) } if (length(mo) > 1) { out <- rep(NA_mic_, length(size)) - p <- progress_ticker(n = length(unique(mo)), n_min = 10, title = "Generating random MIC values") + p <- progress_ticker(n = length(unique(mo)), n_min = 10, title = "Generating random disk zones") for (mo_ in unique(mo)) { p$tick() - out[which(mo == mo_)] <- random_exec("DISK", size = sum(mo == mo_), mo = mo_, ab = ab, skew = skew, severity = severity) + out[which(mo == mo_)] <- random_exec("DISK", size = sum(mo == mo_), mo = mo_, ab = ab, skew = skew, severity = severity, guideline = guideline) } out <- as.disk(out) return(out) } else { - random_exec("DISK", size = size, mo = mo, ab = ab, skew = skew, severity = severity) + random_exec("DISK", size = size, mo = mo, ab = ab, skew = skew, severity = severity, guideline = guideline) } } @@ -135,8 +140,12 @@ random_sir <- function(size = NULL, prob_SIR = c(0.33, 0.33, 0.33), ...) { } -random_exec <- function(method_type, size, mo = NULL, ab = NULL, skew = "right", severity = 1) { +random_exec <- function(method_type, size, mo = NULL, ab = NULL, skew = "right", severity = 1, guideline = NULL) { df <- AMR::clinical_breakpoints %pm>% subset(method == method_type & type == "human") + # leave out blocking rows (`NA` breakpoints), so that min() and max() below always have values + df <- df[which(!is.na(df$breakpoint_S) & !is.na(df$breakpoint_R)), , drop = FALSE] + df_new <- df[which(df$guideline %in% guideline), , drop = FALSE] + if (nrow(df_new) > 0) df <- df_new if (!is.null(mo)) { mo_coerced <- as.mo(mo, info = FALSE) diff --git a/index.Rmd b/index.Rmd index 132c5f7b1..97f99d9d2 100644 --- a/index.Rmd +++ b/index.Rmd @@ -146,7 +146,7 @@ library(AMR) some_mic_values <- random_mic(size = 100) some_groups <- sample(LETTERS[1:5], 20, replace = TRUE) interpretation <- as.sir(some_mic_values, - guideline = "EUCAST 2024", + guideline = "EUCAST", mo = "E. coli", # or any code or name resembling a known species ab = "Cipro") # or any code or name resembling an antibiotic @@ -229,7 +229,7 @@ This package was intended as a comprehensive toolbox for integrated AMR data ana * Applying EUCAST expert rules ([manual](./reference/eucast_rules.html)) * Getting SNOMED codes of a microorganism, or getting properties of a microorganism based on a SNOMED code ([manual](./reference/mo_property.html)) * Getting LOINC codes of an antibiotic, or getting properties of an antibiotic based on a LOINC code ([manual](./reference/ab_property.html)) - * Machine reading the EUCAST and CLSI guidelines from 2011-2021 to translate MIC values and disk diffusion diameters to SIR ([link](./articles/datasets.html)) + * Machine reading the EUCAST and CLSI guidelines from `r min(as.integer(gsub("[^0-9]", "", AMR::clinical_breakpoints$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", AMR::clinical_breakpoints$guideline)))` to translate MIC values and disk diffusion diameters to SIR ([link](./articles/datasets.html)) * Principal component analysis for AMR ([tutorial](./articles/PCA.html)) ## Get this package diff --git a/index.md b/index.md index 49be060f7..c66f7a0ed 100644 --- a/index.md +++ b/index.md @@ -305,7 +305,7 @@ library(AMR) some_mic_values <- random_mic(size = 100) some_groups <- sample(LETTERS[1:5], 20, replace = TRUE) interpretation <- as.sir(some_mic_values, - guideline = "EUCAST 2024", + guideline = "EUCAST", mo = "E. coli", # or any code or name resembling a known species ab = "Cipro") # or any code or name resembling an antibiotic @@ -444,7 +444,7 @@ data analysis. This package can be used for: - Getting LOINC codes of an antibiotic, or getting properties of an antibiotic based on a LOINC code ([manual](./reference/ab_property.html)) -- Machine reading the EUCAST and CLSI guidelines from 2011-2021 to +- Machine reading the EUCAST and CLSI guidelines from 2011-2026 to translate MIC values and disk diffusion diameters to SIR ([link](./articles/datasets.html)) - Principal component analysis for AMR ([tutorial](./articles/PCA.html)) diff --git a/man/plot.Rd b/man/plot.Rd index 5a5a721e0..1af0585e6 100644 --- a/man/plot.Rd +++ b/man/plot.Rd @@ -281,7 +281,7 @@ if (require("ggplot2")) { # support for 27 languages, various guidelines, and many options autoplot(some_disk_values, mo = "Escherichia coli", ab = "cipro", - guideline = "CLSI 2024", language = "no", + guideline = "CLSI", language = "no", title = "Disk diffusion from the North" ) } diff --git a/man/random.Rd b/man/random.Rd index 76268eec0..a3d3f06e0 100644 --- a/man/random.Rd +++ b/man/random.Rd @@ -13,6 +13,7 @@ random_mic( ab = NULL, skew = "right", severity = 1, + guideline = getOption("AMR_guideline", "EUCAST"), ... ) @@ -22,6 +23,7 @@ random_disk( ab = NULL, skew = "left", severity = 1, + guideline = getOption("AMR_guideline", "EUCAST"), ... ) @@ -38,6 +40,8 @@ random_sir(size = NULL, prob_SIR = c(0.33, 0.33, 0.33), ...) \item{severity}{Skew severity; higher values will increase the skewedness. Default is \code{2}; use \code{0} to prevent skewedness.} +\item{guideline}{A guideline name to base the generated values on, either \code{"EUCAST"} (default) or \code{"CLSI"} for the latest implemented version, or a guideline including a year (e.g., \code{"CLSI 2019"}). The default can be changed with the package option \code{\link[=AMR-options]{AMR_guideline}}, see \code{\link[=as.sir]{as.sir()}}.} + \item{...}{Ignored, only in place to allow future extensions.} \item{prob_SIR}{A vector of length 3: the probabilities for "S" (1st value), "I" (2nd value) and "R" (3rd value).} @@ -49,7 +53,7 @@ class \code{mic} for \code{\link[=random_mic]{random_mic()}} (see \code{\link[=a These functions can be used for generating random MIC values and disk diffusion diameters, for AMR data analysis practice. By providing a microorganism and antimicrobial drug, the generated results will reflect reality as much as possible. } \details{ -Internally, MIC and disk zone values are sampled based on clinical breakpoints defined in the \link{clinical_breakpoints} data set. To create specific generated values per bug or drug, set the \code{mo} and/or \code{ab} argument. The MICs are sampled on a log2 scale and disks linearly, using weighted probabilities. The weights are based on the \code{skew} and \code{severity} arguments: +Internally, MIC and disk zone values are sampled based on the human clinical breakpoints of \code{guideline} in the \link{clinical_breakpoints} data set. To create specific generated values per bug or drug, set the \code{mo} and/or \code{ab} argument. The MICs are sampled on a log2 scale and disks linearly, using weighted probabilities. The weights are based on the \code{skew} and \code{severity} arguments: \itemize{ \item \code{skew = "right"} places more emphasis on lower MIC or higher disk values. \item \code{skew = "left"} places more emphasis on higher MIC or lower disk values. @@ -65,7 +69,7 @@ random_sir(25) disks <- random_disk(100, severity = 2, mo = "Escherichia coli", ab = "CIP") plot(disks) # `plot()` and `ggplot2::autoplot()` allow for coloured bars if `mo` and `ab` are set -plot(disks, mo = "Escherichia coli", ab = "CIP", guideline = "CLSI 2025") +plot(disks, mo = "Escherichia coli", ab = "CIP", guideline = "CLSI") \donttest{ random_mic(25, "Klebsiella pneumoniae") # range 0.0625-64 diff --git a/tests/testthat/test-random.R b/tests/testthat/test-random.R index 77df92d9a..7eda12650 100644 --- a/tests/testthat/test-random.R +++ b/tests/testthat/test-random.R @@ -41,4 +41,21 @@ test_that("test-random.R", { expect_inherits(random_disk(100, mo = "Klebsiella pneumoniae", ab = "meropenem"), "disk") expect_inherits(random_disk(100, ab = "meropenem"), "disk") expect_inherits(random_sir(100), "sir") + + # guideline: without a year the latest version is used, and the AMR_guideline option is the default + latest_clsi <- max(AMR::clinical_breakpoints$guideline[AMR::clinical_breakpoints$guideline %like% "CLSI"]) + set.seed(1) + x1 <- random_disk(100, "Klebsiella pneumoniae", "ampicillin", guideline = "CLSI") + set.seed(1) + x2 <- random_disk(100, "Klebsiella pneumoniae", "ampicillin", guideline = latest_clsi) + old_opt <- options(AMR_guideline = "CLSI") + set.seed(1) + x3 <- random_disk(100, "Klebsiella pneumoniae", "ampicillin") + options(old_opt) + set.seed(1) + x4 <- random_disk(100, "Klebsiella pneumoniae", "ampicillin", guideline = "EUCAST") + expect_identical(x1, x2) + expect_identical(x1, x3) + expect_false(identical(x1, x4)) + expect_error(random_mic(10, guideline = "EUCAST 1999")) }) diff --git a/vignettes/AMR.Rmd b/vignettes/AMR.Rmd index 53e57f6a0..b489c80fc 100755 --- a/vignettes/AMR.Rmd +++ b/vignettes/AMR.Rmd @@ -390,7 +390,7 @@ Minimal inhibitory concentration (MIC) values and disk diffusion diameters can b ```{r mic_interpretation} set.seed(123) mic_values <- random_mic(100) -sir_values <- as.sir(mic_values, mo = "K. pneumoniae", ab = "cipro", guideline = "EUCAST 2024") +sir_values <- as.sir(mic_values, mo = "K. pneumoniae", ab = "cipro", guideline = "EUCAST") my_data <- tibble(MIC = mic_values, SIR = sir_values) my_data @@ -429,7 +429,7 @@ For a more straightforward and less manual approach, `ggplot2`'s function `autop autoplot(mic_values) # by providing `mo` and `ab`, colours will indicate the SIR interpretation: -autoplot(mic_values, mo = "K. pneumoniae", ab = "cipro", guideline = "EUCAST 2024") +autoplot(mic_values, mo = "K. pneumoniae", ab = "cipro", guideline = "EUCAST") ``` ---- diff --git a/vignettes/welcome_to_AMR.Rmd b/vignettes/welcome_to_AMR.Rmd index 294f836d5..c31bb5458 100644 --- a/vignettes/welcome_to_AMR.Rmd +++ b/vignettes/welcome_to_AMR.Rmd @@ -50,7 +50,7 @@ This package was intended as a comprehensive toolbox for integrated AMR data ana * Applying EUCAST expert rules ([manual](https://amr-for-r.org/reference/eucast_rules.html)) * Getting SNOMED codes of a microorganism, or getting properties of a microorganism based on a SNOMED code ([manual](https://amr-for-r.org/reference/mo_property.html)) * Getting LOINC codes of an antibiotic, or getting properties of an antibiotic based on a LOINC code ([manual](https://amr-for-r.org/reference/ab_property.html)) - * Machine reading the EUCAST and CLSI guidelines from 2011-2021 to translate MIC values and disk diffusion diameters to SIR ([link](https://amr-for-r.org/articles/datasets.html)) + * Machine reading the EUCAST and CLSI guidelines from `r min(as.integer(gsub("[^0-9]", "", AMR::clinical_breakpoints$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", AMR::clinical_breakpoints$guideline)))` to translate MIC values and disk diffusion diameters to SIR ([link](https://amr-for-r.org/articles/datasets.html)) * Principal component analysis for AMR ([tutorial](https://amr-for-r.org/articles/PCA.html)) All reference data sets in the AMR package - including information on microorganisms, antimicrobials, and clinical breakpoints - are freely available for download in multiple formats: R, MS Excel, Apache Feather, Apache Parquet, SPSS, and Stata.