From bdbf5198a25f6972e8aca8e6a9ac1f0809973157 Mon Sep 17 00:00:00 2001 From: Matthijs Berends Date: Sat, 15 Jun 2024 15:33:49 +0200 Subject: [PATCH] (v2.1.1.9050) vctrs fix for `sir`, small documentation fixes --- DESCRIPTION | 4 +- NAMESPACE | 1 + NEWS.md | 2 +- R/aa_helper_functions.R | 3 + R/ab_selectors.R | 109 ++++++++++++++++++------------ R/eucast_rules.R | 2 +- R/key_antimicrobials.R | 2 +- R/pca.R | 2 +- R/resistance_predict.R | 2 +- R/sir.R | 88 ++++++++++++++---------- R/vctrs.R | 15 ++-- R/zzz.R | 11 ++- data/example_isolates.rda | Bin 24316 -> 23388 bytes man/antibiotic_class_selectors.Rd | 97 ++++++++++++++------------ man/as.sir.Rd | 75 ++++++++++++-------- 15 files changed, 248 insertions(+), 165 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 65dca906..8fa764c7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: AMR -Version: 2.1.1.9049 -Date: 2024-06-14 +Version: 2.1.1.9050 +Date: 2024-06-15 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 diff --git a/NAMESPACE b/NAMESPACE index b168cc8e..e81a07bd 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -84,6 +84,7 @@ S3method(plot,mic) S3method(plot,resistance_predict) S3method(plot,sir) S3method(print,ab) +S3method(print,ab_selector) S3method(print,av) S3method(print,bug_drug_combinations) S3method(print,custom_eucast_rules) diff --git a/NEWS.md b/NEWS.md index aaab189e..d8d8df8e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# AMR 2.1.1.9049 +# AMR 2.1.1.9050 *(this beta version will eventually become v3.0. We're happy to reach a new major milestone soon, which will be all about the new One Health support!)* diff --git a/R/aa_helper_functions.R b/R/aa_helper_functions.R index 91c5f59c..25938aa1 100644 --- a/R/aa_helper_functions.R +++ b/R/aa_helper_functions.R @@ -524,6 +524,9 @@ word_wrap <- function(..., # otherwise, give a 'click to run' popup parts[cmds & parts %unlike% "[.]"] <- font_url(url = paste0("ide:run:AMR::", parts[cmds & parts %unlike% "[.]"]), txt = parts[cmds & parts %unlike% "[.]"]) + # text starting with `?` must also lead to the help page + parts[parts %like% "^[?]"] <- font_url(url = paste0("ide:help:AMR::", gsub("()", "", gsub("^[?]", "", parts[parts %like% "^[?]"]), fixed = TRUE)), + txt = parts[parts %like% "^[?]"]) msg <- paste0(parts, collapse = "`") } msg <- gsub("`(.+?)`", font_grey_bg("\\1"), msg) diff --git a/R/ab_selectors.R b/R/ab_selectors.R index 4a20fee6..56c6a7f8 100755 --- a/R/ab_selectors.R +++ b/R/ab_selectors.R @@ -57,59 +57,31 @@ #' example_isolates #' #' -#' # Examples sections below are split into 'base R', 'dplyr', and 'data.table': -#' -#' -#' # base R ------------------------------------------------------------------ -#' -#' # select columns 'IPM' (imipenem) and 'MEM' (meropenem) -#' example_isolates[, carbapenems()] -#' -#' # select columns 'mo', 'AMK', 'GEN', 'KAN' and 'TOB' -#' example_isolates[, c("mo", aminoglycosides())] -#' -#' # select only antibiotic columns with DDDs for oral treatment -#' example_isolates[, administrable_per_os()] -#' -#' # filter using any() or all() -#' example_isolates[any(carbapenems() == "R"), ] -#' subset(example_isolates, any(carbapenems() == "R")) -#' -#' # filter on any or all results in the carbapenem columns (i.e., IPM, MEM): -#' example_isolates[any(carbapenems()), ] -#' example_isolates[all(carbapenems()), ] -#' -#' # filter with multiple antibiotic selectors using c() -#' example_isolates[all(c(carbapenems(), aminoglycosides()) == "R"), ] -#' -#' # filter + select in one go: get penicillins in carbapenem-resistant strains -#' example_isolates[any(carbapenems() == "R"), penicillins()] -#' -#' # You can combine selectors with '&' to be more specific. For example, -#' # penicillins() would select benzylpenicillin ('peni G') and -#' # administrable_per_os() would select erythromycin. Yet, when combined these -#' # drugs are both omitted since benzylpenicillin is not administrable per os -#' # and erythromycin is not a penicillin: -#' example_isolates[, penicillins() & administrable_per_os()] -#' -#' # ab_selector() applies a filter in the `antibiotics` data set and is thus -#' # very flexible. For instance, to select antibiotic columns with an oral DDD -#' # of at least 1 gram: -#' example_isolates[, ab_selector(oral_ddd > 1 & oral_units == "g")] -#' +#' # Examples sections below are split into 'dplyr', 'base R', and 'data.table': +#' #' \donttest{ #' # dplyr ------------------------------------------------------------------- +#' +#' if (require("dplyr")) { +#'. example_isolates %>% select(carbapenems()) +#' } #' #' if (require("dplyr")) { -#' tibble(kefzol = random_sir(5)) %>% -#' select(cephalosporins()) +#' # select columns 'mo', 'AMK', 'GEN', 'KAN' and 'TOB' +#' example_isolates %>% select(mo, aminoglycosides()) +#' } +#' +#' if (require("dplyr")) { +#' # select only antibiotic columns with DDDs for oral treatment +#'. example_isolates %>% select(administrable_per_os()) #' } #' #' if (require("dplyr")) { #' # get AMR for all aminoglycosides e.g., per ward: #' example_isolates %>% #' group_by(ward) %>% -#' summarise(across(aminoglycosides(), resistance)) +#' summarise(across(aminoglycosides(), +#' resistance)) #' } #' if (require("dplyr")) { #' # You can combine selectors with '&' to be more specific: @@ -121,7 +93,8 @@ #' example_isolates %>% #' filter(mo_genus() %in% c("Escherichia", "Klebsiella")) %>% #' group_by(ward) %>% -#' summarise(across(not_intrinsic_resistant(), resistance)) +#' summarise_at(not_intrinsic_resistant(), +#' resistance) #' } #' if (require("dplyr")) { #' # get susceptibility for antibiotics whose name contains "trim": @@ -187,6 +160,44 @@ #' } #' #' +#' # base R ------------------------------------------------------------------ +#' +#' # select columns 'IPM' (imipenem) and 'MEM' (meropenem) +#' example_isolates[, carbapenems()] +#' +#' # select columns 'mo', 'AMK', 'GEN', 'KAN' and 'TOB' +#' example_isolates[, c("mo", aminoglycosides())] +#' +#' # select only antibiotic columns with DDDs for oral treatment +#' example_isolates[, administrable_per_os()] +#' +#' # filter using any() or all() +#' example_isolates[any(carbapenems() == "R"), ] +#' subset(example_isolates, any(carbapenems() == "R")) +#' +#' # filter on any or all results in the carbapenem columns (i.e., IPM, MEM): +#' example_isolates[any(carbapenems()), ] +#' example_isolates[all(carbapenems()), ] +#' +#' # filter with multiple antibiotic selectors using c() +#' example_isolates[all(c(carbapenems(), aminoglycosides()) == "R"), ] +#' +#' # filter + select in one go: get penicillins in carbapenem-resistant strains +#' example_isolates[any(carbapenems() == "R"), penicillins()] +#' +#' # You can combine selectors with '&' to be more specific. For example, +#' # penicillins() would select benzylpenicillin ('peni G') and +#' # administrable_per_os() would select erythromycin. Yet, when combined these +#' # drugs are both omitted since benzylpenicillin is not administrable per os +#' # and erythromycin is not a penicillin: +#' example_isolates[, penicillins() & administrable_per_os()] +#' +#' # ab_selector() applies a filter in the `antibiotics` data set and is thus +#' # very flexible. For instance, to select antibiotic columns with an oral DDD +#' # of at least 1 gram: +#' example_isolates[, ab_selector(oral_ddd > 1 & oral_units == "g")] +#' +#' #' # data.table -------------------------------------------------------------- #' #' # data.table is supported as well, just use it in the same way as with @@ -679,6 +690,16 @@ ab_select_exec <- function(function_name, ) } +#' @method print ab_selector +#' @export +#' @noRd +print.ab_selector <- function(x, ...) { + warning_("It should never be needed to print an antibiotic selector class. Are you using data.table? Then add the argument `with = FALSE`, see our examples at `?ab_selector`.", + immediate = TRUE) + cat("Class 'ab_selector'\n") + print(as.character(x), quote = FALSE) +} + #' @method c ab_selector #' @export #' @noRd diff --git a/R/eucast_rules.R b/R/eucast_rules.R index c74c05d8..ce330387 100755 --- a/R/eucast_rules.R +++ b/R/eucast_rules.R @@ -462,7 +462,7 @@ eucast_rules <- function(x, font_red(paste0( "v", utils::packageDescription("AMR")$Version, ", ", format(as.Date(utils::packageDescription("AMR")$Date), format = "%Y") - )), "), see ?eucast_rules\n" + )), "), see `?eucast_rules`\n" )) )) } diff --git a/R/key_antimicrobials.R b/R/key_antimicrobials.R index fbae1a3f..2916c13c 100755 --- a/R/key_antimicrobials.R +++ b/R/key_antimicrobials.R @@ -188,7 +188,7 @@ key_antimicrobials <- function(x = NULL, "No columns available ", paste0("Only using ", values_new_length, " out of ", values_old_length, " defined columns ") ), - "as key antimicrobials for ", name, "s. See ?key_antimicrobials." + "as key antimicrobials for ", name, "s. See `?key_antimicrobials`." ) } diff --git a/R/pca.R b/R/pca.R index 7c13a348..802a058c 100755 --- a/R/pca.R +++ b/R/pca.R @@ -113,7 +113,7 @@ pca <- function(x, x <- as.data.frame(new_list, stringsAsFactors = FALSE) if (any(vapply(FUN.VALUE = logical(1), x, function(y) !is.numeric(y)))) { - warning_("in `pca()`: be sure to first calculate the resistance (or susceptibility) of variables with antimicrobial test results, since PCA works with numeric variables only. See Examples in ?pca.", call = FALSE) + warning_("in `pca()`: be sure to first calculate the resistance (or susceptibility) of variables with antimicrobial test results, since PCA works with numeric variables only. See Examples in `?pca`.", call = FALSE) } # set column names diff --git a/R/resistance_predict.R b/R/resistance_predict.R index 2c50a36c..c4221e68 100755 --- a/R/resistance_predict.R +++ b/R/resistance_predict.R @@ -231,7 +231,7 @@ resistance_predict <- function(x, prediction <- predictmodel$fit se <- predictmodel$se.fit } else { - stop("no valid model selected. See ?resistance_predict.") + stop("no valid model selected. See `?resistance_predict`.") } # prepare the output dataframe diff --git a/R/sir.R b/R/sir.R index c797635e..affb5ce0 100755 --- a/R/sir.R +++ b/R/sir.R @@ -158,6 +158,51 @@ #' #' # For INTERPRETING disk diffusion and MIC values ----------------------- #' +#' \donttest{ +#' ## Using dplyr ------------------------------------------------- +#' if (require("dplyr")) { +#' # approaches that all work without additional arguments: +#' df %>% mutate_if(is.mic, as.sir) +#' df %>% mutate_if(function(x) is.mic(x) | is.disk(x), as.sir) +#' df %>% mutate(across(where(is.mic), as.sir)) +#' df %>% mutate_at(vars(AMP:TOB), as.sir) +#' df %>% mutate(across(AMP:TOB, as.sir)) +#' +#' # approaches that all work with additional arguments: +#' df %>% mutate_if(is.mic, as.sir, mo = "column1", guideline = "CLSI") +#' df %>% mutate(across(where(is.mic), +#' function(x) as.sir(x, mo = "column1", guideline = "CLSI"))) +#' df %>% mutate_at(vars(AMP:TOB), as.sir, mo = "column1", guideline = "CLSI") +#' df %>% mutate(across(AMP:TOB, +#' function(x) as.sir(x, mo = "column1", guideline = "CLSI"))) +#' +#' # for veterinary breakpoints, add 'host': +#' df %>% mutate_if(is.mic, as.sir, guideline = "CLSI", host = "species_column") +#' df %>% mutate_if(is.mic, as.sir, guideline = "CLSI", host = "horse") +#' df %>% mutate(across(where(is.mic), +#' function(x) as.sir(x, guideline = "CLSI", host = "species_column"))) +#' df %>% mutate_at(vars(AMP:TOB), as.sir, guideline = "CLSI", host = "species_column") +#' df %>% mutate(across(AMP:TOB, +#' function(x) as.sir(x, mo = "column1", guideline = "CLSI"))) +#' +#' # to include information about urinary tract infections (UTI) +#' data.frame(mo = "E. coli", +#' nitrofuratoin = c("<= 2", 32), +#' from_the_bladder = c(TRUE, FALSE)) %>% +#' as.sir(uti = "from_the_bladder") +#' +#' data.frame(mo = "E. coli", +#' nitrofuratoin = c("<= 2", 32), +#' specimen = c("urine", "blood")) %>% +#' as.sir() # automatically determines urine isolates +#' +#' df %>% +#' mutate_at(vars(AMP:TOB), as.sir, mo = "E. coli", uti = TRUE) +#' } +#' +#' +#' ## Using base R ------------------------------------------------ +#' #' # a whole data set, even with combined MIC values and disk zones #' df <- data.frame( #' microorganism = "Escherichia coli", @@ -187,36 +232,6 @@ #' guideline = "EUCAST" #' ) #' -#' \donttest{ -#' # the dplyr way -#' if (require("dplyr")) { -#' df %>% mutate_if(is.mic, as.sir) -#' df %>% mutate_if(function(x) is.mic(x) | is.disk(x), as.sir) -#' df %>% mutate(across(where(is.mic), as.sir)) -#' df %>% mutate_at(vars(AMP:TOB), as.sir) -#' df %>% mutate(across(AMP:TOB, as.sir)) -#' -#' df %>% -#' mutate_at(vars(AMP:TOB), as.sir, mo = "microorganism") -#' -#' # to include information about urinary tract infections (UTI) -#' data.frame( -#' mo = "E. coli", -#' NIT = c("<= 2", 32), -#' from_the_bladder = c(TRUE, FALSE) -#' ) %>% -#' as.sir(uti = "from_the_bladder") -#' -#' data.frame( -#' mo = "E. coli", -#' NIT = c("<= 2", 32), -#' specimen = c("urine", "blood") -#' ) %>% -#' as.sir() # automatically determines urine isolates -#' -#' df %>% -#' mutate_at(vars(AMP:TOB), as.sir, mo = "E. coli", uti = TRUE) -#' } #' #' # For CLEANING existing SIR values ------------------------------------ #' @@ -1121,6 +1136,7 @@ as_sir_method <- function(method_short, suppressMessages(suppressWarnings(ab_name(ab_current, language = NULL, tolower = TRUE))), " (", ab_current, ")" ) + notes <- character(0) # gather all available breakpoints for current MO breakpoints_current <- breakpoints %pm>% @@ -1165,7 +1181,8 @@ as_sir_method <- function(method_short, subset(host_match == TRUE) } else { # no breakpoint found for this host, so sort on mostly available guidelines - msgs <- c(msgs, paste0("No breakpoints available for ", font_bold(host_current), " for ", ab_formatted, " in ", mo_formatted, " - using ", font_bold(breakpoints_current$host[1]), " instead.")) + notes <- c(notes, paste0("No breakpoints available for ", font_bold(host_current), " for ", ab_formatted, " in ", mo_formatted, " - using ", font_bold(breakpoints_current$host[1]), " instead.")) + # msgs <- c(msgs, paste0("No breakpoints available for ", font_bold(host_current), " for ", ab_formatted, " in ", mo_formatted, " - using ", font_bold(breakpoints_current$host[1]), " instead.")) } } @@ -1243,14 +1260,15 @@ as_sir_method <- function(method_short, mo_user = rep(mo.bak[match(mo_current, df$mo)][1], length(rows)), ab = rep(ab_current, length(rows)), mo = rep(breakpoints_current[, "mo", drop = TRUE], length(rows)), + method = rep(method_coerced, length(rows)), input = as.double(values), outcome = as.sir(new_sir), - method = rep(method_coerced, length(rows)), - breakpoint_S_R = rep(paste0(breakpoints_current[, "breakpoint_S", drop = TRUE], "-", breakpoints_current[, "breakpoint_R", drop = TRUE]), length(rows)), - guideline = rep(guideline_coerced, length(rows)), host = rep(breakpoints_current[, "host", drop = TRUE], length(rows)), + notes = rep(paste0(notes, collapse = " "), length(rows)), + guideline = rep(guideline_coerced, length(rows)), ref_table = rep(breakpoints_current[, "ref_tbl", drop = TRUE], length(rows)), uti = rep(breakpoints_current[, "uti", drop = TRUE], length(rows)), + breakpoint_S_R = rep(paste0(breakpoints_current[, "breakpoint_S", drop = TRUE], "-", breakpoints_current[, "breakpoint_R", drop = TRUE]), length(rows)), stringsAsFactors = FALSE ) ) @@ -1268,6 +1286,8 @@ as_sir_method <- function(method_short, } if (isTRUE(rise_warning)) { message(font_rose_bg(" WARNING ")) + } else if (length(notes) > 0) { + message(font_yellow_bg(" NOTES ")) } else if (length(msgs) == 0) { message(font_green_bg(" OK ")) } else { diff --git a/R/vctrs.R b/R/vctrs.R index db8855cd..36483276 100755 --- a/R/vctrs.R +++ b/R/vctrs.R @@ -109,10 +109,13 @@ vec_ptype_abbr.disk <- function(x, ...) { "dsk" } vec_ptype2.disk.default <- function (x, y, ..., x_arg = "", y_arg = "") { - x + NA_disk_[0] } vec_ptype2.disk.disk <- function(x, y, ...) { - x + NA_disk_[0] +} +vec_cast.disk.disk <- function(x, to, ...) { + as.disk(x) } vec_cast.integer.disk <- function(x, to, ...) { unclass(x) @@ -136,11 +139,11 @@ vec_cast.disk.character <- function(x, to, ...) { # S3: mic ---- vec_ptype2.mic.default <- function (x, y, ..., x_arg = "", y_arg = "") { # this will make sure that currently implemented MIC levels are returned - as.mic(x) + NA_mic_[0] } vec_ptype2.mic.mic <- function(x, y, ...) { # this will make sure that currently implemented MIC levels are returned - as.mic(x) + NA_mic_[0] } vec_cast.mic.mic <- function(x, to, ...) { # this will make sure that currently implemented MIC levels are returned @@ -187,6 +190,10 @@ vec_ptype2.sir.sir <- function(x, y, ...) { vec_ptype2.character.sir <- function(x, y, ...) { NA_sir_[0] } +vec_cast.sir.sir <- function(x, to, ...) { + # this makes sure that old SIR objects (with S/I/R) are converted to the current structure (S/SDD/I/R/NI) + as.sir(x) +} vec_cast.character.sir <- function(x, to, ...) { as.character(x) } diff --git a/R/zzz.R b/R/zzz.R index 8af3068b..39cfcf78 100755 --- a/R/zzz.R +++ b/R/zzz.R @@ -62,13 +62,15 @@ AMR_env$sir_interpretation_history <- data.frame( mo_user = character(0), ab = set_clean_class(character(0), c("ab", "character")), mo = set_clean_class(character(0), c("mo", "character")), + method = character(0), input = double(0), outcome = NA_sir_[0], - method = character(0), - breakpoint_S_R = character(0), - guideline = character(0), host = character(0), + notes = character(0), + guideline = character(0), ref_table = character(0), + uti = logical(0), + breakpoint_S_R = character(0), stringsAsFactors = FALSE ) @@ -95,6 +97,7 @@ AMR_env$sup_1_icon <- import_fn("symbol", "cli", error_on_fail = FALSE)$sup_1 %o s3_register("pillar::pillar_shaft", "sir") s3_register("pillar::pillar_shaft", "mic") s3_register("pillar::pillar_shaft", "disk") + # no type_sum of disk, that's now in vctrs::vec_ptype_full s3_register("pillar::type_sum", "ab") s3_register("pillar::type_sum", "av") s3_register("pillar::type_sum", "mo") @@ -153,6 +156,7 @@ AMR_env$sup_1_icon <- import_fn("symbol", "cli", error_on_fail = FALSE)$sup_1 %o s3_register("vctrs::vec_ptype_abbr", "disk") s3_register("vctrs::vec_ptype2", "disk.default") s3_register("vctrs::vec_ptype2", "disk.disk") + s3_register("vctrs::vec_cast", "disk.disk") s3_register("vctrs::vec_cast", "integer.disk") s3_register("vctrs::vec_cast", "disk.integer") s3_register("vctrs::vec_cast", "double.disk") @@ -179,6 +183,7 @@ AMR_env$sup_1_icon <- import_fn("symbol", "cli", error_on_fail = FALSE)$sup_1 %o s3_register("vctrs::vec_ptype2", "character.sir") s3_register("vctrs::vec_cast", "character.sir") s3_register("vctrs::vec_cast", "sir.character") + s3_register("vctrs::vec_cast", "sir.sir") # if mo source exists, fire it up (see mo_source()) if (tryCatch(file.exists(getOption("AMR_mo_source", "~/mo_source.rds")), error = function(e) FALSE)) { diff --git a/data/example_isolates.rda b/data/example_isolates.rda index 3a5d7af1e60bd9954cdb9bc7216dd227991dedf3..bb1308843df5b336f4c0e771783f05d41ea8c8cf 100644 GIT binary patch delta 11146 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 1 & oral_units == "g")] +# Examples sections below are split into 'dplyr', 'base R', and 'data.table': \donttest{ # dplyr ------------------------------------------------------------------- if (require("dplyr")) { - tibble(kefzol = random_sir(5)) \%>\% - select(cephalosporins()) +. example_isolates \%>\% select(carbapenems()) +} + +if (require("dplyr")) { + # select columns 'mo', 'AMK', 'GEN', 'KAN' and 'TOB' + example_isolates \%>\% select(mo, aminoglycosides()) +} + +if (require("dplyr")) { + # select only antibiotic columns with DDDs for oral treatment +. example_isolates \%>\% select(administrable_per_os()) } if (require("dplyr")) { # get AMR for all aminoglycosides e.g., per ward: example_isolates \%>\% group_by(ward) \%>\% - summarise(across(aminoglycosides(), resistance)) + summarise(across(aminoglycosides(), + resistance)) } if (require("dplyr")) { # You can combine selectors with '&' to be more specific: @@ -249,7 +221,8 @@ if (require("dplyr")) { example_isolates \%>\% filter(mo_genus() \%in\% c("Escherichia", "Klebsiella")) \%>\% group_by(ward) \%>\% - summarise(across(not_intrinsic_resistant(), resistance)) + summarise_at(not_intrinsic_resistant(), + resistance) } if (require("dplyr")) { # get susceptibility for antibiotics whose name contains "trim": @@ -315,6 +288,44 @@ if (require("dplyr")) { } +# base R ------------------------------------------------------------------ + +# select columns 'IPM' (imipenem) and 'MEM' (meropenem) +example_isolates[, carbapenems()] + +# select columns 'mo', 'AMK', 'GEN', 'KAN' and 'TOB' +example_isolates[, c("mo", aminoglycosides())] + +# select only antibiotic columns with DDDs for oral treatment +example_isolates[, administrable_per_os()] + +# filter using any() or all() +example_isolates[any(carbapenems() == "R"), ] +subset(example_isolates, any(carbapenems() == "R")) + +# filter on any or all results in the carbapenem columns (i.e., IPM, MEM): +example_isolates[any(carbapenems()), ] +example_isolates[all(carbapenems()), ] + +# filter with multiple antibiotic selectors using c() +example_isolates[all(c(carbapenems(), aminoglycosides()) == "R"), ] + +# filter + select in one go: get penicillins in carbapenem-resistant strains +example_isolates[any(carbapenems() == "R"), penicillins()] + +# You can combine selectors with '&' to be more specific. For example, +# penicillins() would select benzylpenicillin ('peni G') and +# administrable_per_os() would select erythromycin. Yet, when combined these +# drugs are both omitted since benzylpenicillin is not administrable per os +# and erythromycin is not a penicillin: +example_isolates[, penicillins() & administrable_per_os()] + +# ab_selector() applies a filter in the `antibiotics` data set and is thus +# very flexible. For instance, to select antibiotic columns with an oral DDD +# of at least 1 gram: +example_isolates[, ab_selector(oral_ddd > 1 & oral_units == "g")] + + # data.table -------------------------------------------------------------- # data.table is supported as well, just use it in the same way as with diff --git a/man/as.sir.Rd b/man/as.sir.Rd index 6384a13a..2ea89c64 100644 --- a/man/as.sir.Rd +++ b/man/as.sir.Rd @@ -251,6 +251,51 @@ summary(example_isolates) # see all SIR results at a glance # For INTERPRETING disk diffusion and MIC values ----------------------- +\donttest{ +## Using dplyr ------------------------------------------------- +if (require("dplyr")) { + # approaches that all work without additional arguments: + df \%>\% mutate_if(is.mic, as.sir) + df \%>\% mutate_if(function(x) is.mic(x) | is.disk(x), as.sir) + df \%>\% mutate(across(where(is.mic), as.sir)) + df \%>\% mutate_at(vars(AMP:TOB), as.sir) + df \%>\% mutate(across(AMP:TOB, as.sir)) + + # approaches that all work with additional arguments: + df \%>\% mutate_if(is.mic, as.sir, mo = "column1", guideline = "CLSI") + df \%>\% mutate(across(where(is.mic), + function(x) as.sir(x, mo = "column1", guideline = "CLSI"))) + df \%>\% mutate_at(vars(AMP:TOB), as.sir, mo = "column1", guideline = "CLSI") + df \%>\% mutate(across(AMP:TOB, + function(x) as.sir(x, mo = "column1", guideline = "CLSI"))) + + # for veterinary breakpoints, add 'host': + df \%>\% mutate_if(is.mic, as.sir, guideline = "CLSI", host = "species_column") + df \%>\% mutate_if(is.mic, as.sir, guideline = "CLSI", host = "horse") + df \%>\% mutate(across(where(is.mic), + function(x) as.sir(x, guideline = "CLSI", host = "species_column"))) + df \%>\% mutate_at(vars(AMP:TOB), as.sir, guideline = "CLSI", host = "species_column") + df \%>\% mutate(across(AMP:TOB, + function(x) as.sir(x, mo = "column1", guideline = "CLSI"))) + + # to include information about urinary tract infections (UTI) + data.frame(mo = "E. coli", + nitrofuratoin = c("<= 2", 32), + from_the_bladder = c(TRUE, FALSE)) \%>\% + as.sir(uti = "from_the_bladder") + + data.frame(mo = "E. coli", + nitrofuratoin = c("<= 2", 32), + specimen = c("urine", "blood")) \%>\% + as.sir() # automatically determines urine isolates + + df \%>\% + mutate_at(vars(AMP:TOB), as.sir, mo = "E. coli", uti = TRUE) +} + + +## Using base R ------------------------------------------------ + # a whole data set, even with combined MIC values and disk zones df <- data.frame( microorganism = "Escherichia coli", @@ -280,36 +325,6 @@ as.sir( guideline = "EUCAST" ) -\donttest{ -# the dplyr way -if (require("dplyr")) { - df \%>\% mutate_if(is.mic, as.sir) - df \%>\% mutate_if(function(x) is.mic(x) | is.disk(x), as.sir) - df \%>\% mutate(across(where(is.mic), as.sir)) - df \%>\% mutate_at(vars(AMP:TOB), as.sir) - df \%>\% mutate(across(AMP:TOB, as.sir)) - - df \%>\% - mutate_at(vars(AMP:TOB), as.sir, mo = "microorganism") - - # to include information about urinary tract infections (UTI) - data.frame( - mo = "E. coli", - NIT = c("<= 2", 32), - from_the_bladder = c(TRUE, FALSE) - ) \%>\% - as.sir(uti = "from_the_bladder") - - data.frame( - mo = "E. coli", - NIT = c("<= 2", 32), - specimen = c("urine", "blood") - ) \%>\% - as.sir() # automatically determines urine isolates - - df \%>\% - mutate_at(vars(AMP:TOB), as.sir, mo = "E. coli", uti = TRUE) -} # For CLEANING existing SIR values ------------------------------------