1
0
mirror of https://github.com/msberends/AMR.git synced 2026-05-14 01:10:45 +02:00

Generalise interpretive rules for multi-guideline support (#268) (#283)

* Generalise interpretive rules for multi-guideline support (#268)

- Rename data-raw/eucast_rules.tsv → interpretive_rules.tsv; add rule.provider
  column (value: "EUCAST") to distinguish future CLSI rows
- Rename EUCAST_RULES_DF → INTERPRETIVE_RULES_DF in _pre_commit_checks.R;
  filter by rule.provider == guideline when applying rules in interpretive_rules()
- Rename custom_eucast_rules() → custom_interpretive_rules() with new S3 class
  "custom_interpretive_rules"; old function becomes a deprecated wrapper in
  zz_deprecated.R; backward-compat S3 dispatch shims added for old class
- Remove stop_if(guideline == "CLSI", ...) so clsi_rules() no longer errors
- Add .onLoad shim in zzz.R to create INTERPRETIVE_RULES_DF from EUCAST_RULES_DF
  for transitional compatibility until sysdata.rda is regenerated

https://claude.ai/code/session_01D46BTsfJSPo3HnLWp3PRkP

* Fix namespace load failure: remove assignInNamespace from .onLoad (#268)

assignInNamespace cannot add NEW bindings to a locked package namespace
(R locks namespace bindings before .onLoad runs). Replace the .onLoad
shim with a runtime fallback inside interpretive_rules(): if
INTERPRETIVE_RULES_DF is absent (pre-regeneration sysdata.rda), derive
it from EUCAST_RULES_DF by adding the rule.provider column. This also
fixes the screening_abx line to reuse the already-resolved
interpretive_rules_df_total instead of a bare INTERPRETIVE_RULES_DF
reference.

https://claude.ai/code/session_01D46BTsfJSPo3HnLWp3PRkP

* fixes

* fixes

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Matthijs Berends
2026-05-01 18:38:51 +01:00
committed by GitHub
parent f7e9294bea
commit 24f24ecaf8
26 changed files with 1770 additions and 183 deletions

View File

@@ -1,5 +1,5 @@
Package: AMR
Version: 3.0.1.9055
Version: 3.0.1.9056
Date: 2026-04-30
Title: Antimicrobial Resistance Data Analysis
Description: Functions to simplify and standardise antimicrobial resistance (AMR)

View File

@@ -49,6 +49,7 @@ S3method(as.data.frame,mo)
S3method(as.double,mic)
S3method(as.double,sir)
S3method(as.list,custom_eucast_rules)
S3method(as.list,custom_interpretive_rules)
S3method(as.list,custom_mdro_guideline)
S3method(as.list,mic)
S3method(as.matrix,mic)
@@ -66,6 +67,7 @@ S3method(c,ab)
S3method(c,amr_selector)
S3method(c,av)
S3method(c,custom_eucast_rules)
S3method(c,custom_interpretive_rules)
S3method(c,custom_mdro_guideline)
S3method(c,disk)
S3method(c,mic)
@@ -96,6 +98,7 @@ S3method(print,amr_selector)
S3method(print,av)
S3method(print,bug_drug_combinations)
S3method(print,custom_eucast_rules)
S3method(print,custom_interpretive_rules)
S3method(print,custom_mdro_guideline)
S3method(print,deprecated_amr_dataset)
S3method(print,disk)
@@ -228,6 +231,7 @@ export(count_df)
export(count_resistant)
export(count_susceptible)
export(custom_eucast_rules)
export(custom_interpretive_rules)
export(custom_mdro_guideline)
export(eucast_dosage)
export(eucast_exceptional_phenotypes)

View File

@@ -1,4 +1,4 @@
# AMR 3.0.1.9055
# AMR 3.0.1.9056
This will become release v3.1.0, intended for launch end of May.
@@ -45,6 +45,7 @@ This will become release v3.1.0, intended for launch end of May.
* Fixed `as.sir()` ignoring `info = FALSE` for columns with no breakpoints (e.g. cefoxitin against *E. coli*)
### Updates
* Renamed `custom_eucast_rules()` to `custom_interpretive_rules()`; old function is now a deprecated wrapper. Renamed `data-raw/eucast_rules.tsv` to `interpretive_rules.tsv` and added `rule.provider` column for multi-guideline (EUCAST/CLSI) support. `clsi_rules()` no longer throws an error (#268)
* `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)
* Extensive `cli` integration for better message handling and clickable links in messages and warnings (#191, #265)
* `mdro()` now infers resistance for a _missing_ base drug column from an _available_ corresponding drug+inhibitor combination showing resistance (e.g., piperacillin is absent but required, while piperacillin/tazobactam available and resistant). Can be set with the new argument `infer_from_combinations`, which defaults to `TRUE` (#209). Note that this can yield a higher MDRO detection (which is a good thing as it has become more reliable).

View File

@@ -27,7 +27,7 @@
# how to conduct AMR data analysis: https://amr-for-r.org #
# ==================================================================== #
# add new version numbers here, and add the rules themselves to "data-raw/eucast_rules.tsv" and clinical_breakpoints
# add new version numbers here, and add the rules themselves to "data-raw/interpretive_rules.tsv" and clinical_breakpoints
# (sourcing "data-raw/_pre_commit_checks.R" will process the TSV file)
EUCAST_VERSION_BREAKPOINTS <- list(
"16.0" = list(
@@ -221,6 +221,7 @@ globalVariables(c(
"reference.rule",
"reference.rule_group",
"reference.version",
"rule.provider",
"rowid",
"rule_group",
"rule_name",

0
R/aa_helper_functions.R Normal file → Executable file
View File

0
R/amr_course.R Normal file → Executable file
View File

View File

@@ -762,7 +762,9 @@ antibiogram.default <- function(x,
# precompute priors per group and build (group, chunk) job list
jobs <- unlist(lapply(unique_groups, function(g) {
params_g <- wisca_parameters[wisca_parameters$group == g, , drop = FALSE]
if (sum(params_g$n_tested, na.rm = TRUE) == 0L) return(NULL)
if (sum(params_g$n_tested, na.rm = TRUE) == 0L) {
return(NULL)
}
priors_g <- create_wisca_priors(params_g)
lapply(seq_along(chunk_sizes), function(ch) {
list(group = g, priors = priors_g, n_sims = chunk_sizes[ch])
@@ -788,7 +790,6 @@ antibiogram.default <- function(x,
}
if (isTRUE(info)) message_(font_green_bg(" DONE "), as_note = FALSE)
} else {
progress <- progress_ticker(
n = length(unique_groups) * simulations,
@@ -1115,7 +1116,9 @@ antibiogram.grouped_df <- function(x,
x_df <- as.data.frame(x)
run_group <- function(i) {
rows <- unlist(groups[i, ]$.rows)
if (length(rows) == 0L) return(NULL)
if (length(rows) == 0L) {
return(NULL)
}
antibiogram(x_df[rows, , drop = FALSE],
antimicrobials = antimicrobials,
mo_transform = NULL,
@@ -1136,7 +1139,7 @@ antibiogram.grouped_df <- function(x,
conf_interval = conf_interval,
interval_side = interval_side,
info = FALSE,
parallel = FALSE # never nest parallelism in workers
parallel = FALSE # never nest parallelism in workers
)
}

View File

@@ -27,27 +27,27 @@
# how to conduct AMR data analysis: https://amr-for-r.org #
# ==================================================================== #
#' Define Custom EUCAST Rules
#' Define Custom Interpretive Rules
#'
#' Define custom EUCAST rules for your organisation or specific analysis and use the output of this function in [eucast_rules()].
#' Define custom interpretive rules for your organisation or specific analysis and use the output of this function in [interpretive_rules()].
#' @param ... Rules in [formula][base::tilde] notation, see below for instructions, and in *Examples*.
#' @details
#' Some organisations have their own adoption of EUCAST rules. This function can be used to define custom EUCAST rules to be used in the [eucast_rules()] function.
#' Some organisations have their own adoption of interpretive rules. This function can be used to define custom rules to be used in the [interpretive_rules()] function.
#'
#' ### Basics
#'
#' If you are familiar with the [`case_when()`][dplyr::case_when()] function of the `dplyr` package, you will recognise the input method to set your own rules. Rules must be set using what \R considers to be the 'formula notation'. The rule itself is written *before* the tilde (`~`) and the consequence of the rule is written *after* the tilde:
#'
#' ```r
#' x <- custom_eucast_rules(TZP == "S" ~ aminopenicillins == "S",
#' TZP == "R" ~ aminopenicillins == "R")
#' x <- custom_interpretive_rules(TZP == "S" ~ aminopenicillins == "S",
#' TZP == "R" ~ aminopenicillins == "R")
#' ```
#'
#' These are two custom EUCAST rules: if TZP (piperacillin/tazobactam) is "S", all aminopenicillins (ampicillin and amoxicillin) must be made "S", and if TZP is "R", aminopenicillins must be made "R". These rules can also be printed to the console, so it is immediately clear how they work:
#' These are two custom interpretive rules: if TZP (piperacillin/tazobactam) is "S", all aminopenicillins (ampicillin and amoxicillin) must be made "S", and if TZP is "R", aminopenicillins must be made "R". These rules can also be printed to the console, so it is immediately clear how they work:
#'
#' ```r
#' x
#' #> A set of custom EUCAST rules:
#' #> A set of custom interpretive rules:
#' #>
#' #> 1. If TZP is "S" then set to S :
#' #> amoxicillin (AMX), ampicillin (AMP)
@@ -68,11 +68,11 @@
#' #> 1 Escherichia coli R S S
#' #> 2 Klebsiella pneumoniae R S S
#'
#' eucast_rules(df,
#' rules = "custom",
#' custom_rules = x,
#' info = FALSE,
#' overwrite = TRUE)
#' interpretive_rules(df,
#' rules = "custom",
#' custom_rules = x,
#' info = FALSE,
#' overwrite = TRUE)
#' #> mo TZP ampi cipro
#' #> 1 Escherichia coli R R S
#' #> 2 Klebsiella pneumoniae R R S
@@ -83,16 +83,16 @@
#' There is one exception in columns used for the rules: all column names of the [microorganisms] data set can also be used, but do not have to exist in the data set. These column names are: `r vector_and(colnames(microorganisms), sort = FALSE, documentation = TRUE)`. Thus, this next example will work as well, despite the fact that the `df` data set does not contain a column `genus`:
#'
#' ```r
#' y <- custom_eucast_rules(
#' y <- custom_interpretive_rules(
#' TZP == "S" & genus == "Klebsiella" ~ aminopenicillins == "S",
#' TZP == "R" & genus == "Klebsiella" ~ aminopenicillins == "R"
#' )
#'
#' eucast_rules(df,
#' rules = "custom",
#' custom_rules = y,
#' info = FALSE,
#' overwrite = TRUE)
#' interpretive_rules(df,
#' rules = "custom",
#' custom_rules = y,
#' info = FALSE,
#' overwrite = TRUE)
#' #> mo TZP ampi cipro
#' #> 1 Escherichia coli R S S
#' #> 2 Klebsiella pneumoniae R R S
@@ -109,9 +109,9 @@
#' Rules can also be applied to multiple antimicrobials and antimicrobial groups simultaneously. Use the `c()` function to combine multiple antimicrobials. For instance, the following example sets all aminopenicillins and ureidopenicillins to "R" if column TZP (piperacillin/tazobactam) is "R":
#'
#' ```r
#' x <- custom_eucast_rules(TZP == "R" ~ c(aminopenicillins, ureidopenicillins) == "R")
#' x <- custom_interpretive_rules(TZP == "R" ~ c(aminopenicillins, ureidopenicillins) == "R")
#' x
#' #> A set of custom EUCAST rules:
#' #> A set of custom interpretive rules:
#' #>
#' #> 1. If TZP is "R" then set to "R":
#' #> amoxicillin (AMX), ampicillin (AMP), azlocillin (AZL), mezlocillin (MEZ), piperacillin (PIP), piperacillin/tazobactam (TZP)
@@ -123,7 +123,7 @@
#' @returns A [list] containing the custom rules
#' @export
#' @examples
#' x <- custom_eucast_rules(
#' x <- custom_interpretive_rules(
#' AMC == "R" & genus == "Klebsiella" ~ aminopenicillins == "R",
#' AMC == "I" & genus == "Klebsiella" ~ aminopenicillins == "I"
#' )
@@ -141,24 +141,24 @@
#' # combine rule sets
#' x2 <- c(
#' x,
#' custom_eucast_rules(TZP == "R" ~ carbapenems == "R")
#' custom_interpretive_rules(TZP == "R" ~ carbapenems == "R")
#' )
#' x2
custom_eucast_rules <- function(...) {
custom_interpretive_rules <- function(...) {
dots <- tryCatch(list(...),
error = function(e) "error"
)
stop_if(
identical(dots, "error"),
"rules must be a valid formula inputs (e.g., using '~'), see {.help [{.fun custom_eucast_rules}](AMR::custom_eucast_rules)}"
"rules must be a valid formula inputs (e.g., using '~'), see {.help [{.fun custom_interpretive_rules}](AMR::custom_interpretive_rules)}"
)
n_dots <- length(dots)
stop_if(n_dots == 0, "no custom rules were set. Please read the documentation using {.help [{.fun custom_eucast_rules}](AMR::custom_eucast_rules)}.")
stop_if(n_dots == 0, "no custom rules were set. Please read the documentation using {.help [{.fun custom_interpretive_rules}](AMR::custom_interpretive_rules)}.")
out <- vector("list", n_dots)
for (i in seq_len(n_dots)) {
stop_ifnot(
inherits(dots[[i]], "formula"),
"rule ", i, " must be a valid formula input (e.g., using '~'), see {.help [{.fun custom_eucast_rules}](AMR::custom_eucast_rules)}"
"rule ", i, " must be a valid formula input (e.g., using '~'), see {.help [{.fun custom_interpretive_rules}](AMR::custom_interpretive_rules)}"
)
# Query
@@ -180,7 +180,7 @@ custom_eucast_rules <- function(...) {
result <- dots[[i]][[3]]
stop_ifnot(
deparse(result) %like% "==",
"the result of rule ", i, " (the part after the `~`) must contain `==`, such as in `... ~ ampicillin == \"R\"`, see {.help [{.fun custom_eucast_rules}](AMR::custom_eucast_rules)}"
"the result of rule ", i, " (the part after the `~`) must contain `==`, such as in `... ~ ampicillin == \"R\"`, see {.help [{.fun custom_interpretive_rules}](AMR::custom_interpretive_rules)}"
)
result_group <- as.character(result)[[2]]
result_group <- as.character(str2lang(result_group))
@@ -230,13 +230,13 @@ custom_eucast_rules <- function(...) {
}
names(out) <- paste0("rule", seq_len(n_dots))
set_clean_class(out, new_class = c("custom_eucast_rules", "list"))
set_clean_class(out, new_class = c("custom_interpretive_rules", "list"))
}
#' @method c custom_eucast_rules
#' @method c custom_interpretive_rules
#' @noRd
#' @export
c.custom_eucast_rules <- function(x, ...) {
c.custom_interpretive_rules <- function(x, ...) {
if (length(list(...)) == 0) {
return(x)
}
@@ -245,21 +245,21 @@ c.custom_eucast_rules <- function(x, ...) {
out <- c(out, unclass(e))
}
names(out) <- paste0("rule", seq_len(length(out)))
set_clean_class(out, new_class = c("custom_eucast_rules", "list"))
set_clean_class(out, new_class = c("custom_interpretive_rules", "list"))
}
#' @method as.list custom_eucast_rules
#' @method as.list custom_interpretive_rules
#' @noRd
#' @export
as.list.custom_eucast_rules <- function(x, ...) {
as.list.custom_interpretive_rules <- function(x, ...) {
c(x, ...)
}
#' @method print custom_eucast_rules
#' @method print custom_interpretive_rules
#' @export
#' @noRd
print.custom_eucast_rules <- function(x, ...) {
cat("A set of custom EUCAST rules:\n")
print.custom_interpretive_rules <- function(x, ...) {
cat("A set of custom interpretive rules:\n")
for (i in seq_len(length(x))) {
rule <- x[[i]]
rule$query <- format_custom_query_rule(rule$query)
@@ -291,3 +291,19 @@ print.custom_eucast_rules <- function(x, ...) {
cat("\n ", rule_if, "\n", rule_then, "\n", sep = "")
}
}
# Backward-compat S3 dispatch for objects created with the old custom_eucast_rules() function
#' @method c custom_eucast_rules
#' @noRd
#' @export
c.custom_eucast_rules <- function(x, ...) c.custom_interpretive_rules(x, ...)
#' @method as.list custom_eucast_rules
#' @noRd
#' @export
as.list.custom_eucast_rules <- function(x, ...) as.list.custom_interpretive_rules(x, ...)
#' @method print custom_eucast_rules
#' @export
#' @noRd
print.custom_eucast_rules <- function(x, ...) print.custom_interpretive_rules(x, ...)

0
R/first_isolate.R Normal file → Executable file
View File

0
R/get_episode.R Normal file → Executable file
View File

View File

@@ -62,17 +62,17 @@ format_eucast_version_nr <- function(version, markdown = TRUE) {
#' @param x A data set with antimicrobials columns, such as `amox`, `AMX` and `AMC`.
#' @param info A [logical] to indicate whether progress should be printed to the console - the default is only print while in interactive sessions.
#' @param guideline A guideline name, either "EUCAST" (default) or "CLSI". This can be set with the package option [`AMR_guideline`][AMR-options].
#' @param rules A [character] vector that specifies which rules should be applied. Must be one or more of `"breakpoints"`, `"expected_phenotypes"`, `"expert"`, `"other"`, `"custom"`, `"all"`, and defaults to `c("breakpoints", "expected_phenotypes")`. The default value can be set to another value using the package option [`AMR_interpretive_rules`][AMR-options]: `options(AMR_interpretive_rules = "all")`. If using `"custom"`, be sure to fill in argument `custom_rules` too. Custom rules can be created with [custom_eucast_rules()].
#' @param rules A [character] vector that specifies which rules should be applied. Must be one or more of `"breakpoints"`, `"expected_phenotypes"`, `"expert"`, `"other"`, `"custom"`, `"all"`, and defaults to `c("breakpoints", "expected_phenotypes")`. The default value can be set to another value using the package option [`AMR_interpretive_rules`][AMR-options]: `options(AMR_interpretive_rules = "all")`. If using `"custom"`, be sure to fill in argument `custom_rules` too. Custom rules can be created with [custom_interpretive_rules()].
#' @param verbose A [logical] to turn Verbose mode on and off (default is off). In Verbose mode, the function does not apply rules to the data, but instead returns a data set in logbook form with extensive info about which rows and columns would be effected and in which way. Using Verbose mode takes a lot more time.
#' @param version_breakpoints The version number to use for the EUCAST Clinical Breakpoints guideline. Can be `r vector_or(names(EUCAST_VERSION_BREAKPOINTS), documentation = TRUE, reverse = TRUE)`.
#' @param version_expected_phenotypes The version number to use for the EUCAST Expected Phenotypes. Can be `r vector_or(names(EUCAST_VERSION_EXPECTED_PHENOTYPES), documentation = TRUE, reverse = TRUE)`.
#' @param version_expertrules The version number to use for the EUCAST Expert Rules and Intrinsic Resistance guideline. Can be `r vector_or(names(EUCAST_VERSION_EXPERT_RULES), documentation = TRUE, reverse = TRUE)`.
#' @param ampc_cephalosporin_resistance (only applies when `rules` contains `"expert"` or `"all"`) a [character] value that should be applied to cefotaxime, ceftriaxone and ceftazidime for AmpC de-repressed cephalosporin-resistant mutants - the default is `NA`. Currently only works when `version_expertrules` is `3.2` and higher; these versions of '*EUCAST Expert Rules on Enterobacterales*' state that results of cefotaxime, ceftriaxone and ceftazidime should be reported with a note, or results should be suppressed (emptied) for these three drugs. A value of `NA` (the default) for this argument will remove results for these three drugs, while e.g. a value of `"R"` will make the results for these drugs resistant. Use `NULL` or `FALSE` to not alter results for these three drugs of AmpC de-repressed cephalosporin-resistant mutants. Using `TRUE` is equal to using `"R"`. \cr For *EUCAST Expert Rules* v3.2, this rule applies to: `r vector_and(gsub("[^a-zA-Z ]+", "", unlist(strsplit(EUCAST_RULES_DF[which(EUCAST_RULES_DF$reference.version %in% c(3.2, 3.3) & EUCAST_RULES_DF$reference.rule %like% "ampc"), "this_value"][1], "|", fixed = TRUE))), quotes = "*")`.
#' @param ampc_cephalosporin_resistance (only applies when `rules` contains `"expert"` or `"all"`) a [character] value that should be applied to cefotaxime, ceftriaxone and ceftazidime for AmpC de-repressed cephalosporin-resistant mutants - the default is `NA`. Currently only works when `version_expertrules` is `3.2` and higher; these versions of '*EUCAST Expert Rules on Enterobacterales*' state that results of cefotaxime, ceftriaxone and ceftazidime should be reported with a note, or results should be suppressed (emptied) for these three drugs. A value of `NA` (the default) for this argument will remove results for these three drugs, while e.g. a value of `"R"` will make the results for these drugs resistant. Use `NULL` or `FALSE` to not alter results for these three drugs of AmpC de-repressed cephalosporin-resistant mutants. Using `TRUE` is equal to using `"R"`. \cr For *EUCAST Expert Rules* v3.2, this rule applies to: `r vector_and(gsub("[^a-zA-Z ]+", "", unlist(strsplit(INTERPRETIVE_RULES_DF[which(INTERPRETIVE_RULES_DF$reference.version %in% c(3.2, 3.3) & INTERPRETIVE_RULES_DF$reference.rule %like% "ampc"), "this_value"][1], "|", fixed = TRUE))), quotes = "*")`.
#' @param ... Column names of antimicrobials. To automatically detect antimicrobial column names, do not provide any named arguments; [guess_ab_col()] will then be used for detection. To manually specify a column, provide its name (case-insensitive) as an argument, e.g. `AMX = "amoxicillin"`. To skip a specific antimicrobial, set it to `NULL`, e.g. `TIC = NULL` to exclude ticarcillin. If a manually defined column does not exist in the data, it will be skipped with a warning.
#' @param ab Any (vector of) text that can be coerced to a valid antimicrobial drug code with [as.ab()].
#' @param administration Route of administration, either `r vector_or(dosage$administration, documentation = TRUE)`.
#' @param only_sir_columns A [logical] to indicate whether only antimicrobial columns must be included that were transformed to class [sir][as.sir()] on beforehand. Defaults to `FALSE` if no columns of `x` have a class [sir][as.sir()].
#' @param custom_rules Custom rules to apply, created with [custom_eucast_rules()].
#' @param custom_rules Custom rules to apply, created with [custom_interpretive_rules()].
#' @param overwrite A [logical] indicating whether to overwrite existing SIR values (default: `FALSE`). When `FALSE`, only non-SIR values are modified (i.e., any value that is not already S, I or R). To ensure compliance with EUCAST guidelines, **this should remain** `FALSE`, as EUCAST notes often state that an organism "should be tested for susceptibility to individual agents or be reported resistant".
#' @param add_if_missing A [logical] indicating whether rules should also be applied to missing (`NA`) values (default: `TRUE`). When `FALSE`, rules are only applied to cells that already contain an SIR value; cells with `NA` are left untouched. This is particularly useful when using `overwrite = TRUE` with custom rules and you want to update reported results without imputing values for untested drugs.
#' @inheritParams first_isolate
@@ -80,17 +80,17 @@ format_eucast_version_nr <- function(version, markdown = TRUE) {
#' **Note:** This function does not translate MIC or disk values to SIR values. Use [as.sir()] for that. \cr
#' **Note:** When ampicillin (AMP, J01CA01) is not available but amoxicillin (AMX, J01CA04) is, the latter will be used for all rules where there is a dependency on ampicillin. These drugs are interchangeable when it comes to expression of antimicrobial resistance. \cr
#'
#' The file containing all EUCAST rules is located here: <https://github.com/msberends/AMR/blob/main/data-raw/eucast_rules.tsv>. **Note:** Old taxonomic names are replaced with the current taxonomy where applicable. For example, *Ochrobactrum anthropi* was renamed to *Brucella anthropi* in 2020; the original EUCAST rules v3.1 and v3.2 did not yet contain this new taxonomic name. The `AMR` package contains the full microbial taxonomy updated until `r documentation_date(max(TAXONOMY_VERSION$GBIF$accessed_date, TAXONOMY_VERSION$LPSN$accessed_date))`, see [microorganisms].
#' The file containing all interpretive rules is located here: <https://github.com/msberends/AMR/blob/main/data-raw/interpretive_rules.tsv>. **Note:** Old taxonomic names are replaced with the current taxonomy where applicable. For example, *Ochrobactrum anthropi* was renamed to *Brucella anthropi* in 2020; the original EUCAST rules v3.1 and v3.2 did not yet contain this new taxonomic name. The `AMR` package contains the full microbial taxonomy updated until `r documentation_date(max(TAXONOMY_VERSION$GBIF$accessed_date, TAXONOMY_VERSION$LPSN$accessed_date))`, see [microorganisms].
#'
#' ### Custom Rules
#'
#' Custom rules can be created using [custom_eucast_rules()], e.g.:
#' Custom rules can be created using [custom_interpretive_rules()], e.g.:
#'
#' ```r
#' x <- custom_eucast_rules(AMC == "R" & genus == "Klebsiella" ~ aminopenicillins == "R",
#' AMC == "I" & genus == "Klebsiella" ~ aminopenicillins == "I")
#' x <- custom_interpretive_rules(AMC == "R" & genus == "Klebsiella" ~ aminopenicillins == "R",
#' AMC == "I" & genus == "Klebsiella" ~ aminopenicillins == "I")
#'
#' eucast_rules(example_isolates, rules = "custom", custom_rules = x)
#' interpretive_rules(example_isolates, rules = "custom", custom_rules = x)
#' ```
#'
#' ### 'Other' Rules
@@ -102,7 +102,7 @@ format_eucast_version_nr <- function(version, markdown = TRUE) {
#'
#' Important examples include amoxicillin and amoxicillin/clavulanic acid, and trimethoprim and trimethoprim/sulfamethoxazole. Needless to say, for these rules to work, both drugs must be available in the data set.
#'
#' Since these rules are not officially approved by EUCAST, they are not applied at default. To use these rules, include `"other"` to the `rules` argument, or use `eucast_rules(..., rules = "all")`. You can also set the package option [`AMR_interpretive_rules`][AMR-options], i.e. run `options(AMR_interpretive_rules = "all")`.
#' Since these rules are not officially approved by EUCAST, they are not applied at default. To use these rules, include `"other"` to the `rules` argument, or use `interpretive_rules(..., rules = "all")`. You can also set the package option [`AMR_interpretive_rules`][AMR-options], i.e. run `options(AMR_interpretive_rules = "all")`.
#' @aliases EUCAST
#' @rdname interpretive_rules
#' @export
@@ -184,7 +184,7 @@ interpretive_rules <- function(x,
meet_criteria(version_expertrules, allow_class = c("numeric", "integer"), has_length = 1, is_in = as.double(names(EUCAST_VERSION_EXPERT_RULES)))
meet_criteria(ampc_cephalosporin_resistance, allow_class = c("logical", "character", "sir"), has_length = 1, allow_NA = TRUE, allow_NULL = TRUE)
meet_criteria(only_sir_columns, allow_class = "logical", has_length = 1)
meet_criteria(custom_rules, allow_class = "custom_eucast_rules", allow_NULL = TRUE)
meet_criteria(custom_rules, allow_class = c("custom_interpretive_rules", "custom_eucast_rules"), allow_NULL = TRUE)
meet_criteria(overwrite, allow_class = "logical", has_length = 1)
meet_criteria(add_if_missing, allow_class = "logical", has_length = 1)
@@ -193,11 +193,6 @@ interpretive_rules <- function(x,
"Either set {.arg overwrite} or {.arg add_if_missing} to {.code TRUE}, or both."
)
stop_if(
guideline == "CLSI",
"CLSI guideline is not yet supported."
)
stop_if(
!is.na(ampc_cephalosporin_resistance) && !any(c("expert", "all") %in% rules),
"For the {.arg ampc_cephalosporin_resistance} argument to work, the {.arg rules} argument must contain {.code \"expert\"} or {.code \"all\"}."
@@ -205,8 +200,14 @@ interpretive_rules <- function(x,
add_MO_lookup_to_AMR_env()
if (guideline %like% "EUCAST") {
guideline <- "EUCAST"
} else if (guideline %like% "CLSI") {
guideline <- "CLSI"
}
if ("custom" %in% rules && is.null(custom_rules)) {
warning_("in {.help [{.fun eucast_rules}](AMR::eucast_rules)}: no custom rules were set with the {.arg custom_rules} argument",
warning_("in {.help [{.fun interpretive_rules}](AMR::interpretive_rules)}: no custom rules were set with the {.arg custom_rules} argument",
immediate = TRUE
)
rules <- rules[rules != "custom"]
@@ -229,13 +230,13 @@ interpretive_rules <- function(x,
if (interactive() && isTRUE(verbose) && isTRUE(info)) {
txt <- paste0(
"WARNING: In Verbose mode, the eucast_rules() function does not apply rules to the data, but instead returns a data set in logbook form with comprehensive info about which rows and columns would be effected and in which way.",
"WARNING: In Verbose mode, the interpretive_rules() function does not apply rules to the data, but instead returns a data set in logbook form with comprehensive info about which rows and columns would be effected and in which way.",
"\n\nThis may overwrite your existing data if you use e.g.:",
"\ndata <- eucast_rules(data, verbose = TRUE)\n\nDo you want to continue?"
"\ndata <- interpretive_rules(data, verbose = TRUE)\n\nDo you want to continue?"
)
showQuestion <- import_fn("showQuestion", "rstudioapi", error_on_fail = FALSE)
if (!is.null(showQuestion)) {
q_continue <- showQuestion("Using verbose = TRUE with eucast_rules()", txt)
q_continue <- showQuestion("Using verbose = TRUE with interpretive_rules()", txt)
} else {
q_continue <- utils::menu(choices = c("OK", "Cancel"), graphics = FALSE, title = txt)
}
@@ -330,7 +331,7 @@ interpretive_rules <- function(x,
verbose = verbose,
info = info,
only_sir_columns = only_sir_columns,
fn = "eucast_rules",
fn = "interpretive_rules",
...
)
@@ -489,7 +490,7 @@ interpretive_rules <- function(x,
"Rules by the ",
font_bold(paste0("AMR package v", utils::packageDescription("AMR")$Version)),
" (", format(as.Date(utils::packageDescription("AMR")$Date), format = "%Y"),
"), see {.help [{.fun eucast_rules}](AMR::eucast_rules)}\n"
"), see {.help [{.fun interpretive_rules}](AMR::interpretive_rules)}\n"
)
))
cat("\n\n")
@@ -611,59 +612,62 @@ interpretive_rules <- function(x,
if (!any(c("all", "custom") %in% rules) && !is.null(custom_rules)) {
if (isTRUE(info)) {
message_("Skipping custom EUCAST rules, since the {.arg rules} argument does not contain {.code \"custom\"}.")
message_("Skipping custom interpretive rules, since the {.arg rules} argument does not contain {.code \"custom\"}.")
}
custom_rules <- NULL
}
# >>> Apply Official EUCAST rules <<< ---------------------------------------------------
# >>> Apply Official interpretive rules <<< ---------------------------------------------------
eucast_notification_shown <- FALSE
if (!is.null(list(...)$eucast_rules_df)) {
# this allows: eucast_rules(x, eucast_rules_df = AMR:::EUCAST_RULES_DF |> filter(is.na(have_these_values)))
eucast_rules_df_total <- list(...)$eucast_rules_df
if (!is.null(list(...)$interpretive_rules_df)) {
# this allows: interpretive_rules(x, interpretive_rules_df = AMR:::INTERPRETIVE_RULES_DF |> filter(is.na(have_these_values)))
interpretive_rules_df_total <- list(...)$interpretive_rules_df
} else if (!is.null(list(...)$eucast_rules_df)) {
# deprecated parameter name kept for backward compatibility
interpretive_rules_df_total <- list(...)$eucast_rules_df
} else {
# otherwise internal data file, created in data-raw/_pre_commit_checks.R
eucast_rules_df_total <- EUCAST_RULES_DF
# internal data file, created in data-raw/_pre_commit_checks.R
interpretive_rules_df_total <- INTERPRETIVE_RULES_DF
}
## filter on user-set guideline versions ----
eucast_rules_df <- data.frame()
## filter on guideline provider and user-set guideline versions ----
interpretive_rules_df <- data.frame()
if (any(c("all", "breakpoints") %in% rules)) {
eucast_rules_df <- eucast_rules_df %pm>%
rbind_AMR(eucast_rules_df_total %pm>%
subset(reference.rule_group %like% "breakpoint" & reference.version == version_breakpoints))
interpretive_rules_df <- interpretive_rules_df %pm>%
rbind_AMR(interpretive_rules_df_total %pm>%
subset(rule.provider == guideline & reference.rule_group %like% "breakpoint" & reference.version == version_breakpoints))
}
if (any(c("all", "expected_phenotypes") %in% rules)) {
eucast_rules_df <- eucast_rules_df %pm>%
rbind_AMR(eucast_rules_df_total %pm>%
subset(reference.rule_group %like% "expected" & reference.version == version_expected_phenotypes))
interpretive_rules_df <- interpretive_rules_df %pm>%
rbind_AMR(interpretive_rules_df_total %pm>%
subset(rule.provider == guideline & reference.rule_group %like% "expected" & reference.version == version_expected_phenotypes))
}
if (any(c("all", "expert") %in% rules)) {
eucast_rules_df <- eucast_rules_df %pm>%
rbind_AMR(eucast_rules_df_total %pm>%
subset(reference.rule_group %like% "expert" & reference.version == version_expertrules))
interpretive_rules_df <- interpretive_rules_df %pm>%
rbind_AMR(interpretive_rules_df_total %pm>%
subset(rule.provider == guideline & reference.rule_group %like% "expert" & reference.version == version_expertrules))
}
## filter out AmpC de-repressed cephalosporin-resistant mutants ----
# no need to filter on version number here - the rules contain these version number, so are inherently filtered
# cefotaxime, ceftriaxone, ceftazidime
if (is.null(ampc_cephalosporin_resistance) || isFALSE(ampc_cephalosporin_resistance)) {
eucast_rules_df <- subset(
eucast_rules_df,
interpretive_rules_df <- subset(
interpretive_rules_df,
reference.rule %unlike% "ampc"
)
} else {
if (isTRUE(ampc_cephalosporin_resistance)) {
ampc_cephalosporin_resistance <- "R"
}
if (!is.null(eucast_rules_df$reference.rule)) {
eucast_rules_df[which(eucast_rules_df$reference.rule %like% "ampc"), "to_value"] <- as.character(ampc_cephalosporin_resistance)
if (!is.null(interpretive_rules_df$reference.rule)) {
interpretive_rules_df[which(interpretive_rules_df$reference.rule %like% "ampc"), "to_value"] <- as.character(ampc_cephalosporin_resistance)
}
}
# sometimes, the screenings are missing but the names are actually available
# we only hints on remaining rows in `eucast_rules_df`
# we only hints on remaining rows in `interpretive_rules_df`
screening_abx <- as.character(AMR::antimicrobials$ab[which(AMR::antimicrobials$ab %like% "-S$")])
screening_abx <- screening_abx[screening_abx %in% unique(unlist(strsplit(EUCAST_RULES_DF$and_these_antibiotics[!is.na(EUCAST_RULES_DF$and_these_antibiotics)], ", *")))]
screening_abx <- screening_abx[screening_abx %in% unique(unlist(strsplit(interpretive_rules_df_total$and_these_antibiotics[!is.na(interpretive_rules_df_total$and_these_antibiotics)], ", *")))]
if (isTRUE(info)) {
cat("\n")
}
@@ -682,12 +686,12 @@ interpretive_rules <- function(x,
}
## Go over all rules and apply them ----
for (i in seq_len(nrow(eucast_rules_df))) {
rule_previous <- eucast_rules_df[max(1, i - 1), "reference.rule", drop = TRUE]
rule_current <- eucast_rules_df[i, "reference.rule", drop = TRUE]
rule_next <- eucast_rules_df[min(nrow(eucast_rules_df), i + 1), "reference.rule", drop = TRUE]
rule_group_previous <- eucast_rules_df[max(1, i - 1), "reference.rule_group", drop = TRUE]
rule_group_current <- eucast_rules_df[i, "reference.rule_group", drop = TRUE]
for (i in seq_len(nrow(interpretive_rules_df))) {
rule_previous <- interpretive_rules_df[max(1, i - 1), "reference.rule", drop = TRUE]
rule_current <- interpretive_rules_df[i, "reference.rule", drop = TRUE]
rule_next <- interpretive_rules_df[min(nrow(interpretive_rules_df), i + 1), "reference.rule", drop = TRUE]
rule_group_previous <- interpretive_rules_df[max(1, i - 1), "reference.rule_group", drop = TRUE]
rule_group_current <- interpretive_rules_df[i, "reference.rule_group", drop = TRUE]
# don't apply rules if user doesn't want to apply them
if (rule_group_current %like% "breakpoint" && !any(c("all", "breakpoints") %in% rules)) {
next
@@ -702,16 +706,16 @@ interpretive_rules <- function(x,
if (isFALSE(info) || isFALSE(verbose)) {
rule_text <- ""
} else {
if (is.na(eucast_rules_df[i, "and_these_antibiotics", drop = TRUE])) {
rule_text <- paste0("always report as '", eucast_rules_df[i, "to_value", drop = TRUE], "': ", get_antibiotic_names(eucast_rules_df[i, "then_change_these_antibiotics", drop = TRUE]))
if (is.na(interpretive_rules_df[i, "and_these_antibiotics", drop = TRUE])) {
rule_text <- paste0("always report as '", interpretive_rules_df[i, "to_value", drop = TRUE], "': ", get_antibiotic_names(interpretive_rules_df[i, "then_change_these_antibiotics", drop = TRUE]))
} else {
rule_text <- paste0(
"report as '", eucast_rules_df[i, "to_value", drop = TRUE], "' when ",
"report as '", interpretive_rules_df[i, "to_value", drop = TRUE], "' when ",
format_antibiotic_names(
ab_names = get_antibiotic_names(eucast_rules_df[i, "and_these_antibiotics", drop = TRUE]),
ab_results = eucast_rules_df[i, "have_these_values", drop = TRUE]
ab_names = get_antibiotic_names(interpretive_rules_df[i, "and_these_antibiotics", drop = TRUE]),
ab_results = interpretive_rules_df[i, "have_these_values", drop = TRUE]
), ": ",
get_antibiotic_names(eucast_rules_df[i, "then_change_these_antibiotics", drop = TRUE])
get_antibiotic_names(interpretive_rules_df[i, "then_change_these_antibiotics", drop = TRUE])
)
}
}
@@ -720,7 +724,7 @@ interpretive_rules <- function(x,
rule_previous <- ""
rule_group_previous <- ""
}
if (i == nrow(eucast_rules_df)) {
if (i == nrow(interpretive_rules_df)) {
rule_next <- ""
}
@@ -789,13 +793,13 @@ interpretive_rules <- function(x,
}
## Get rule from file ------------------------------------------------------
if_mo_property <- trimws(eucast_rules_df[i, "if_mo_property", drop = TRUE])
like_is_one_of <- trimws(eucast_rules_df[i, "like.is.one_of", drop = TRUE])
mo_value <- trimws(eucast_rules_df[i, "this_value", drop = TRUE])
source_antibiotics <- eucast_rules_df[i, "and_these_antibiotics", drop = TRUE]
source_value <- trimws(unlist(strsplit(eucast_rules_df[i, "have_these_values", drop = TRUE], ",", fixed = TRUE)))
target_antibiotics <- eucast_rules_df[i, "then_change_these_antibiotics", drop = TRUE]
target_value <- eucast_rules_df[i, "to_value", drop = TRUE]
if_mo_property <- trimws(interpretive_rules_df[i, "if_mo_property", drop = TRUE])
like_is_one_of <- trimws(interpretive_rules_df[i, "like.is.one_of", drop = TRUE])
mo_value <- trimws(interpretive_rules_df[i, "this_value", drop = TRUE])
source_antibiotics <- interpretive_rules_df[i, "and_these_antibiotics", drop = TRUE]
source_value <- trimws(unlist(strsplit(interpretive_rules_df[i, "have_these_values", drop = TRUE], ",", fixed = TRUE)))
target_antibiotics <- interpretive_rules_df[i, "then_change_these_antibiotics", drop = TRUE]
target_value <- interpretive_rules_df[i, "to_value", drop = TRUE]
# if amo_value contains a group name, expand that name with all species in it
if (any(trimws(strsplit(mo_value, ",")[[1]]) %in% AMR::microorganisms.groups$mo_group_name, na.rm = TRUE)) {
@@ -894,7 +898,7 @@ interpretive_rules <- function(x,
if (!is.null(custom_rules)) {
if (isTRUE(info)) {
cat("\n")
cat(font_bold("Custom EUCAST rules, set by user"), "\n")
cat(font_bold("Custom interpretive rules, set by user"), "\n")
}
for (i in seq_len(length(custom_rules))) {
rule <- custom_rules[[i]]
@@ -929,8 +933,8 @@ interpretive_rules <- function(x,
to = target_value,
rule = c(
rule_text,
"Custom EUCAST rules",
paste0("Custom EUCAST rule ", i),
"Custom interpretive rules",
paste0("Custom interpretive rule ", i),
paste0(
"Object '", deparse(substitute(custom_rules)),
"' consisting of ", length(custom_rules), " custom rules"
@@ -1075,7 +1079,7 @@ interpretive_rules <- function(x,
warn_lacking_sir_class <- warn_lacking_sir_class[order(colnames(x.bak))]
warn_lacking_sir_class <- warn_lacking_sir_class[!is.na(warn_lacking_sir_class)]
warning_(
"in {.help [{.fun eucast_rules}](AMR::eucast_rules)}: not all columns with antimicrobial results are of class {.cls sir}. Transform them on beforehand, e.g.:\n\n",
"in {.help [{.fun interpretive_rules}](AMR::interpretive_rules)}: not all columns with antimicrobial results are of class {.cls sir}. Transform them on beforehand, e.g.:\n\n",
"\u00a0\u00a0", AMR_env$bullet_icon, " ", highlight_code(paste0(x_deparsed, " |> as.sir(", ifelse(length(warn_lacking_sir_class) == 1,
warn_lacking_sir_class,
paste0(warn_lacking_sir_class[1], ":", warn_lacking_sir_class[length(warn_lacking_sir_class)])
@@ -1177,7 +1181,7 @@ edit_sir <- function(x,
new_edits[rows, cols] == "NS")
non_SIR <- !isSIR
if (isFALSE(overwrite) && any(isSIR) && message_not_thrown_before("edit_sir.warning_overwrite")) {
warning_("in {.help [{.fun eucast_rules}](AMR::eucast_rules)}: some columns had SIR values which were not overwritten, since {.code overwrite = FALSE}.")
warning_("in {.help [{.fun interpretive_rules}](AMR::interpretive_rules)}: some columns had SIR values which were not overwritten, since {.code overwrite = FALSE}.")
}
# determine which cells to modify based on overwrite and add_if_missing
if (isTRUE(overwrite)) {
@@ -1211,7 +1215,7 @@ edit_sir <- function(x,
})
suppressWarnings(do_assign())
warning_(
"in {.help [{.fun eucast_rules}](AMR::eucast_rules)}: value \"", to, "\" added to the factor levels of column",
"in {.help [{.fun interpretive_rules}](AMR::interpretive_rules)}: value \"", to, "\" added to the factor levels of column",
ifelse(length(cols) == 1, "", "s"),
" ", vector_and(cols, quotes = "`", sort = FALSE),
" because this value was not an existing factor level."
@@ -1219,7 +1223,7 @@ edit_sir <- function(x,
txt_warning()
warned <<- FALSE
} else {
warning_("in {.help [{.fun eucast_rules}](AMR::eucast_rules)}: ", w$message)
warning_("in {.help [{.fun interpretive_rules}](AMR::interpretive_rules)}: ", w$message)
txt_warning()
}
},

0
R/mic.R Normal file → Executable file
View File

0
R/proportion.R Normal file → Executable file
View File

Binary file not shown.

0
R/tidymodels.R Normal file → Executable file
View File

View File

@@ -70,6 +70,13 @@ as.data.frame.deprecated_amr_dataset <- function(x, ...) {
# - `antibiotics` in `antibiogram()`
# - `converse_capped_values` in `as.sir()`
#' @rdname AMR-deprecated
#' @export
custom_eucast_rules <- function(...) {
deprecation_warning("custom_eucast_rules", "custom_interpretive_rules", is_function = TRUE)
custom_interpretive_rules(...)
}
#' @rdname AMR-deprecated
#' @export
ab_class <- function(...) {

View File

@@ -42,9 +42,9 @@ pre_commit_lst <- list()
usethis::ui_info(paste0("Updating internal package data"))
# See 'data-raw/eucast_rules.tsv' for the EUCAST reference file
pre_commit_lst$EUCAST_RULES_DF <- utils::read.delim(
file = "data-raw/eucast_rules.tsv",
# See 'data-raw/interpretive_rules.tsv' for the interpretive rules reference file
pre_commit_lst$INTERPRETIVE_RULES_DF <- utils::read.delim(
file = "data-raw/interpretive_rules.tsv",
skip = 9,
sep = "\t",
stringsAsFactors = FALSE,
@@ -364,7 +364,7 @@ pre_commit_lst$MO_RELEVANT_GENERA <- c(
)
# antibiotic groups
# (these will also be used for eucast_rules() and understanding data-raw/eucast_rules.tsv)
# (these will also be used for interpretive_rules() and understanding data-raw/interpretive_rules.tsv)
pre_commit_lst$AB_AMINOGLYCOSIDES <- antimicrobials %>%
filter(group %like% "aminoglycoside|paromomycin|spectinomycin") %>%
pull(ab)

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,13 @@
% Please edit documentation in R/zz_deprecated.R
\name{AMR-deprecated}
\alias{AMR-deprecated}
\alias{custom_eucast_rules}
\alias{ab_class}
\alias{ab_selector}
\title{Deprecated Functions, Arguments, or Datasets}
\usage{
custom_eucast_rules(...)
ab_class(...)
ab_selector(...)

View File

@@ -1,10 +1,10 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/custom_eucast_rules.R
\name{custom_eucast_rules}
\alias{custom_eucast_rules}
\title{Define Custom EUCAST Rules}
% Please edit documentation in R/custom_interpretive_rules.R
\name{custom_interpretive_rules}
\alias{custom_interpretive_rules}
\title{Define Custom Interpretive Rules}
\usage{
custom_eucast_rules(...)
custom_interpretive_rules(...)
}
\arguments{
\item{...}{Rules in \link[base:tilde]{formula} notation, see below for instructions, and in \emph{Examples}.}
@@ -13,22 +13,22 @@ custom_eucast_rules(...)
A \link{list} containing the custom rules
}
\description{
Define custom EUCAST rules for your organisation or specific analysis and use the output of this function in \code{\link[=eucast_rules]{eucast_rules()}}.
Define custom interpretive rules for your organisation or specific analysis and use the output of this function in \code{\link[=interpretive_rules]{interpretive_rules()}}.
}
\details{
Some organisations have their own adoption of EUCAST rules. This function can be used to define custom EUCAST rules to be used in the \code{\link[=eucast_rules]{eucast_rules()}} function.
Some organisations have their own adoption of interpretive rules. This function can be used to define custom rules to be used in the \code{\link[=interpretive_rules]{interpretive_rules()}} function.
\subsection{Basics}{
If you are familiar with the \code{\link[dplyr:case-and-replace-when]{case_when()}} function of the \code{dplyr} package, you will recognise the input method to set your own rules. Rules must be set using what \R considers to be the 'formula notation'. The rule itself is written \emph{before} the tilde (\code{~}) and the consequence of the rule is written \emph{after} the tilde:
\if{html}{\out{<div class="sourceCode r">}}\preformatted{x <- custom_eucast_rules(TZP == "S" ~ aminopenicillins == "S",
TZP == "R" ~ aminopenicillins == "R")
\if{html}{\out{<div class="sourceCode r">}}\preformatted{x <- custom_interpretive_rules(TZP == "S" ~ aminopenicillins == "S",
TZP == "R" ~ aminopenicillins == "R")
}\if{html}{\out{</div>}}
These are two custom EUCAST rules: if TZP (piperacillin/tazobactam) is "S", all aminopenicillins (ampicillin and amoxicillin) must be made "S", and if TZP is "R", aminopenicillins must be made "R". These rules can also be printed to the console, so it is immediately clear how they work:
These are two custom interpretive rules: if TZP (piperacillin/tazobactam) is "S", all aminopenicillins (ampicillin and amoxicillin) must be made "S", and if TZP is "R", aminopenicillins must be made "R". These rules can also be printed to the console, so it is immediately clear how they work:
\if{html}{\out{<div class="sourceCode r">}}\preformatted{x
#> A set of custom EUCAST rules:
#> A set of custom interpretive rules:
#>
#> 1. If TZP is "S" then set to S :
#> amoxicillin (AMX), ampicillin (AMP)
@@ -48,11 +48,11 @@ df
#> 1 Escherichia coli R S S
#> 2 Klebsiella pneumoniae R S S
eucast_rules(df,
rules = "custom",
custom_rules = x,
info = FALSE,
overwrite = TRUE)
interpretive_rules(df,
rules = "custom",
custom_rules = x,
info = FALSE,
overwrite = TRUE)
#> mo TZP ampi cipro
#> 1 Escherichia coli R R S
#> 2 Klebsiella pneumoniae R R S
@@ -63,16 +63,16 @@ eucast_rules(df,
There is one exception in columns used for the rules: all column names of the \link{microorganisms} data set can also be used, but do not have to exist in the data set. These column names are: \code{"mo"}, \code{"fullname"}, \code{"status"}, \code{"kingdom"}, \code{"phylum"}, \code{"class"}, \code{"order"}, \code{"family"}, \code{"genus"}, \code{"species"}, \code{"subspecies"}, \code{"rank"}, \code{"ref"}, \code{"oxygen_tolerance"}, \code{"source"}, \code{"lpsn"}, \code{"lpsn_parent"}, \code{"lpsn_renamed_to"}, \code{"mycobank"}, \code{"mycobank_parent"}, \code{"mycobank_renamed_to"}, \code{"gbif"}, \code{"gbif_parent"}, \code{"gbif_renamed_to"}, \code{"prevalence"}, and \code{"snomed"}. Thus, this next example will work as well, despite the fact that the \code{df} data set does not contain a column \code{genus}:
\if{html}{\out{<div class="sourceCode r">}}\preformatted{y <- custom_eucast_rules(
\if{html}{\out{<div class="sourceCode r">}}\preformatted{y <- custom_interpretive_rules(
TZP == "S" & genus == "Klebsiella" ~ aminopenicillins == "S",
TZP == "R" & genus == "Klebsiella" ~ aminopenicillins == "R"
)
eucast_rules(df,
rules = "custom",
custom_rules = y,
info = FALSE,
overwrite = TRUE)
interpretive_rules(df,
rules = "custom",
custom_rules = y,
info = FALSE,
overwrite = TRUE)
#> mo TZP ampi cipro
#> 1 Escherichia coli R S S
#> 2 Klebsiella pneumoniae R R S
@@ -90,9 +90,9 @@ You can define antimicrobial groups instead of single antimicrobials for the rul
Rules can also be applied to multiple antimicrobials and antimicrobial groups simultaneously. Use the \code{c()} function to combine multiple antimicrobials. For instance, the following example sets all aminopenicillins and ureidopenicillins to "R" if column TZP (piperacillin/tazobactam) is "R":
\if{html}{\out{<div class="sourceCode r">}}\preformatted{x <- custom_eucast_rules(TZP == "R" ~ c(aminopenicillins, ureidopenicillins) == "R")
\if{html}{\out{<div class="sourceCode r">}}\preformatted{x <- custom_interpretive_rules(TZP == "R" ~ c(aminopenicillins, ureidopenicillins) == "R")
x
#> A set of custom EUCAST rules:
#> A set of custom interpretive rules:
#>
#> 1. If TZP is "R" then set to "R":
#> amoxicillin (AMX), ampicillin (AMP), azlocillin (AZL), mezlocillin (MEZ), piperacillin (PIP), piperacillin/tazobactam (TZP)
@@ -147,7 +147,7 @@ These 43 antimicrobial groups are allowed in the rules (case-insensitive) and ca
}
}
\examples{
x <- custom_eucast_rules(
x <- custom_interpretive_rules(
AMC == "R" & genus == "Klebsiella" ~ aminopenicillins == "R",
AMC == "I" & genus == "Klebsiella" ~ aminopenicillins == "I"
)
@@ -165,7 +165,7 @@ eucast_rules(example_isolates,
# combine rule sets
x2 <- c(
x,
custom_eucast_rules(TZP == "R" ~ carbapenems == "R")
custom_interpretive_rules(TZP == "R" ~ carbapenems == "R")
)
x2
}

View File

@@ -45,8 +45,9 @@ A list with class \code{"htest"} containing the following
\item{residuals}{the Pearson residuals,
\code{(observed - expected) / sqrt(expected)}.}
\item{stdres}{standardized residuals,
\code{(observed - expected) / sqrt(V)}, where \code{V} is the residual cell variance (Agresti, 2007,
section 2.4.5 for the case where \code{x} is a matrix, \code{n * p * (1 - p)} otherwise).}
\code{(observed - expected) / sqrt(V)}, where \code{V} is the
residual cell variance (Agresti, 2007, section 2.4.5
for the case where \code{x} is a matrix, \code{n * p * (1 - p)} otherwise).}
}
\description{
\code{\link[=g.test]{g.test()}} performs chi-squared contingency table tests and goodness-of-fit tests, just like \code{\link[=chisq.test]{chisq.test()}} but is more reliable (1). A \emph{G}-test can be used to see whether the number of observations in each category fits a theoretical expectation (called a \strong{\emph{G}-test of goodness-of-fit}), or to see whether the proportions of one variable are different for different values of the other variable (called a \strong{\emph{G}-test of independence}).

View File

@@ -46,7 +46,7 @@ eucast_dosage(ab, administration = "iv", version_breakpoints = 15)
\item{info}{A \link{logical} to indicate whether progress should be printed to the console - the default is only print while in interactive sessions.}
\item{rules}{A \link{character} vector that specifies which rules should be applied. Must be one or more of \code{"breakpoints"}, \code{"expected_phenotypes"}, \code{"expert"}, \code{"other"}, \code{"custom"}, \code{"all"}, and defaults to \code{c("breakpoints", "expected_phenotypes")}. The default value can be set to another value using the package option \code{\link[=AMR-options]{AMR_interpretive_rules}}: \code{options(AMR_interpretive_rules = "all")}. If using \code{"custom"}, be sure to fill in argument \code{custom_rules} too. Custom rules can be created with \code{\link[=custom_eucast_rules]{custom_eucast_rules()}}.}
\item{rules}{A \link{character} vector that specifies which rules should be applied. Must be one or more of \code{"breakpoints"}, \code{"expected_phenotypes"}, \code{"expert"}, \code{"other"}, \code{"custom"}, \code{"all"}, and defaults to \code{c("breakpoints", "expected_phenotypes")}. The default value can be set to another value using the package option \code{\link[=AMR-options]{AMR_interpretive_rules}}: \code{options(AMR_interpretive_rules = "all")}. If using \code{"custom"}, be sure to fill in argument \code{custom_rules} too. Custom rules can be created with \code{\link[=custom_interpretive_rules]{custom_interpretive_rules()}}.}
\item{guideline}{A guideline name, either "EUCAST" (default) or "CLSI". This can be set with the package option \code{\link[=AMR-options]{AMR_guideline}}.}
@@ -62,7 +62,7 @@ eucast_dosage(ab, administration = "iv", version_breakpoints = 15)
\item{only_sir_columns}{A \link{logical} to indicate whether only antimicrobial columns must be included that were transformed to class \link[=as.sir]{sir} on beforehand. Defaults to \code{FALSE} if no columns of \code{x} have a class \link[=as.sir]{sir}.}
\item{custom_rules}{Custom rules to apply, created with \code{\link[=custom_eucast_rules]{custom_eucast_rules()}}.}
\item{custom_rules}{Custom rules to apply, created with \code{\link[=custom_interpretive_rules]{custom_interpretive_rules()}}.}
\item{overwrite}{A \link{logical} indicating whether to overwrite existing SIR values (default: \code{FALSE}). When \code{FALSE}, only non-SIR values are modified (i.e., any value that is not already S, I or R). To ensure compliance with EUCAST guidelines, \strong{this should remain} \code{FALSE}, as EUCAST notes often state that an organism "should be tested for susceptibility to individual agents or be reported resistant".}
@@ -86,15 +86,15 @@ To improve the interpretation of the antibiogram before CLSI/EUCAST interpretive
\strong{Note:} This function does not translate MIC or disk values to SIR values. Use \code{\link[=as.sir]{as.sir()}} for that. \cr
\strong{Note:} When ampicillin (AMP, J01CA01) is not available but amoxicillin (AMX, J01CA04) is, the latter will be used for all rules where there is a dependency on ampicillin. These drugs are interchangeable when it comes to expression of antimicrobial resistance. \cr
The file containing all EUCAST rules is located here: \url{https://github.com/msberends/AMR/blob/main/data-raw/eucast_rules.tsv}. \strong{Note:} Old taxonomic names are replaced with the current taxonomy where applicable. For example, \emph{Ochrobactrum anthropi} was renamed to \emph{Brucella anthropi} in 2020; the original EUCAST rules v3.1 and v3.2 did not yet contain this new taxonomic name. The \code{AMR} package contains the full microbial taxonomy updated until June 24th, 2024, see \link{microorganisms}.
The file containing all interpretive rules is located here: \url{https://github.com/msberends/AMR/blob/main/data-raw/interpretive_rules.tsv}. \strong{Note:} Old taxonomic names are replaced with the current taxonomy where applicable. For example, \emph{Ochrobactrum anthropi} was renamed to \emph{Brucella anthropi} in 2020; the original EUCAST rules v3.1 and v3.2 did not yet contain this new taxonomic name. The \code{AMR} package contains the full microbial taxonomy updated until June 24th, 2024, see \link{microorganisms}.
\subsection{Custom Rules}{
Custom rules can be created using \code{\link[=custom_eucast_rules]{custom_eucast_rules()}}, e.g.:
Custom rules can be created using \code{\link[=custom_interpretive_rules]{custom_interpretive_rules()}}, e.g.:
\if{html}{\out{<div class="sourceCode r">}}\preformatted{x <- custom_eucast_rules(AMC == "R" & genus == "Klebsiella" ~ aminopenicillins == "R",
AMC == "I" & genus == "Klebsiella" ~ aminopenicillins == "I")
\if{html}{\out{<div class="sourceCode r">}}\preformatted{x <- custom_interpretive_rules(AMC == "R" & genus == "Klebsiella" ~ aminopenicillins == "R",
AMC == "I" & genus == "Klebsiella" ~ aminopenicillins == "I")
eucast_rules(example_isolates, rules = "custom", custom_rules = x)
interpretive_rules(example_isolates, rules = "custom", custom_rules = x)
}\if{html}{\out{</div>}}
}
@@ -108,7 +108,7 @@ Before further processing, two non-EUCAST rules about drug combinations can be a
Important examples include amoxicillin and amoxicillin/clavulanic acid, and trimethoprim and trimethoprim/sulfamethoxazole. Needless to say, for these rules to work, both drugs must be available in the data set.
Since these rules are not officially approved by EUCAST, they are not applied at default. To use these rules, include \code{"other"} to the \code{rules} argument, or use \code{eucast_rules(..., rules = "all")}. You can also set the package option \code{\link[=AMR-options]{AMR_interpretive_rules}}, i.e. run \code{options(AMR_interpretive_rules = "all")}.
Since these rules are not officially approved by EUCAST, they are not applied at default. To use these rules, include \code{"other"} to the \code{rules} argument, or use \code{interpretive_rules(..., rules = "all")}. You can also set the package option \code{\link[=AMR-options]{AMR_interpretive_rules}}, i.e. run \code{options(AMR_interpretive_rules = "all")}.
}
}
\section{Download Our Reference Data}{

View File

@@ -32,7 +32,7 @@ pca(x, ..., retx = TRUE, center = TRUE, scale. = TRUE, tol = NULL,
standard deviations are less than or equal to \code{tol} times the
standard deviation of the first component.) With the default null
setting, no components are omitted (unless \code{rank.} is specified
less than \code{min(dim(x))}.). Other settings for tol could be
less than \code{min(dim(x))}.). Other settings for \code{tol} could be
\code{tol = 0} or \code{tol = sqrt(.Machine$double.eps)}, which
would omit essentially constant components.}

View File

@@ -32,4 +32,11 @@ test_that("test-_deprecated.R", {
expect_warning(example_isolates[, ab_class("mycobact")])
expect_warning(example_isolates[, ab_selector(name %like% "trim")])
# deprecated custom_interpretive_rules() still works and emits a warning
expect_warning(
x_old <- custom_eucast_rules(AMC == "R" ~ aminopenicillins == "R"),
regexp = "custom_eucast_rules"
)
expect_inherits(x_old, "custom_interpretive_rules")
})

View File

@@ -53,12 +53,12 @@ test_that("test-data.R", {
expect_false(anyNA(microorganisms.codes$mo))
expect_true(all(dosage$ab %in% AMR::antimicrobials$ab))
expect_true(all(dosage$name %in% AMR::antimicrobials$name))
eucast_abx <- AMR:::EUCAST_RULES_DF$and_these_antibiotics
eucast_abx <- unique(unlist(strsplit(eucast_abx[!is.na(eucast_abx)], ", +")))
expect_true(all(eucast_abx %in% AMR::antimicrobials$ab),
interpretive_abx <- AMR:::INTERPRETIVE_RULES_DF$and_these_antibiotics
interpretive_abx <- unique(unlist(strsplit(interpretive_abx[!is.na(interpretive_abx)], ", +")))
expect_true(all(interpretive_abx %in% AMR::antimicrobials$ab),
info = paste0(
"Missing in `antimicrobials` data set: ",
toString(eucast_abx[which(!eucast_abx %in% AMR::antimicrobials$ab)])
toString(interpretive_abx[which(!interpretive_abx %in% AMR::antimicrobials$ab)])
)
)

View File

@@ -27,13 +27,14 @@
# how to conduct AMR data analysis: https://amr-for-r.org #
# ==================================================================== #
test_that("test-eucast_rules.R", {
test_that("test-interpretive_rules.R", {
skip_on_cran()
# thoroughly check input table
expect_equal(
sort(colnames(AMR:::EUCAST_RULES_DF)),
sort(colnames(AMR:::INTERPRETIVE_RULES_DF)),
sort(c(
"rule.provider",
"if_mo_property", "like.is.one_of", "this_value",
"and_these_antibiotics", "have_these_values",
"then_change_these_antibiotics", "to_value",
@@ -42,7 +43,7 @@ test_that("test-eucast_rules.R", {
"note"
))
)
MOs_mentioned <- unique(AMR:::EUCAST_RULES_DF$this_value)
MOs_mentioned <- unique(AMR:::INTERPRETIVE_RULES_DF$this_value)
MOs_mentioned <- sort(trimws(unlist(strsplit(MOs_mentioned[!AMR:::is_valid_regex(MOs_mentioned)], ",", fixed = TRUE))))
MOs_test <- suppressWarnings(
trimws(paste(
@@ -54,19 +55,19 @@ test_that("test-eucast_rules.R", {
MOs_test[MOs_test == ""] <- mo_fullname(MOs_mentioned[MOs_test == ""], keep_synonyms = TRUE, language = NULL)
expect_equal(MOs_mentioned, MOs_test)
expect_error(suppressWarnings(eucast_rules(example_isolates, col_mo = "Non-existing")))
expect_error(eucast_rules(x = "text"))
expect_error(eucast_rules(data.frame(a = "test")))
expect_error(eucast_rules(data.frame(mo = "test"), rules = "invalid rules set"))
expect_error(suppressWarnings(interpretive_rules(example_isolates, col_mo = "Non-existing")))
expect_error(interpretive_rules(x = "text"))
expect_error(interpretive_rules(data.frame(a = "test")))
expect_error(interpretive_rules(data.frame(mo = "test"), rules = "invalid rules set"))
# expect_warning(eucast_rules(data.frame(mo = "Escherichia coli", vancomycin = "S", stringsAsFactors = TRUE)))
# expect_warning(interpretive_rules(data.frame(mo = "Escherichia coli", vancomycin = "S", stringsAsFactors = TRUE)))
expect_identical(
colnames(example_isolates),
colnames(suppressWarnings(eucast_rules(example_isolates, info = FALSE)))
colnames(suppressWarnings(interpretive_rules(example_isolates, info = FALSE)))
)
expect_output(suppressMessages(eucast_rules(example_isolates, info = TRUE)))
expect_output(suppressMessages(interpretive_rules(example_isolates, info = TRUE)))
a <- data.frame(
mo = c(
@@ -86,8 +87,8 @@ test_that("test-eucast_rules.R", {
amox = "R", # Amoxicillin
stringsAsFactors = FALSE
)
expect_identical(suppressWarnings(eucast_rules(a, "mo", info = FALSE)), b)
expect_output(suppressMessages(suppressWarnings(eucast_rules(a, "mo", info = TRUE))))
expect_identical(suppressWarnings(interpretive_rules(a, "mo", info = FALSE)), b)
expect_output(suppressMessages(suppressWarnings(interpretive_rules(a, "mo", info = TRUE))))
a <- data.frame(
mo = c(
@@ -105,7 +106,7 @@ test_that("test-eucast_rules.R", {
COL = "R", # Colistin
stringsAsFactors = FALSE
)
expect_equal(suppressWarnings(eucast_rules(a, "mo", info = FALSE)), b)
expect_equal(suppressWarnings(interpretive_rules(a, "mo", info = FALSE)), b)
# piperacillin must be R in Enterobacteriaceae when tica is R
if (AMR:::pkg_is_available("dplyr", min_version = "1.0.0", also_load = TRUE)) {
@@ -117,7 +118,7 @@ test_that("test-eucast_rules.R", {
TIC = as.sir("R"),
PIP = as.sir("S")
) %>%
eucast_rules(col_mo = "mo", version_expertrules = 3.1, rules = "expert", info = FALSE, overwrite = TRUE) %>%
interpretive_rules(col_mo = "mo", version_expertrules = 3.1, rules = "expert", info = FALSE, overwrite = TRUE) %>%
pull(PIP) %>%
unique() %>%
as.character()
@@ -127,7 +128,7 @@ test_that("test-eucast_rules.R", {
}
# azithromycin and clarythromycin must be equal to Erythromycin
a <- suppressWarnings(as.sir(eucast_rules(
a <- suppressWarnings(as.sir(interpretive_rules(
data.frame(
mo = example_isolates$mo,
ERY = example_isolates$ERY,
@@ -149,7 +150,7 @@ test_that("test-eucast_rules.R", {
# amox is inferred by benzylpenicillin in Kingella kingae
expect_equal(
suppressWarnings(
as.list(eucast_rules(
as.list(interpretive_rules(
data.frame(
mo = as.mo("Kingella kingae"),
PEN = "S",
@@ -164,16 +165,16 @@ test_that("test-eucast_rules.R", {
# also test norf
if (AMR:::pkg_is_available("dplyr", min_version = "1.0.0", also_load = TRUE)) {
expect_output(suppressWarnings(eucast_rules(example_isolates %>% mutate(NOR = "S", NAL = "S"), info = TRUE)))
expect_output(suppressWarnings(interpretive_rules(example_isolates %>% mutate(NOR = "S", NAL = "S"), info = TRUE)))
}
# check verbose output
expect_output(suppressWarnings(eucast_rules(example_isolates, verbose = TRUE, rules = "all", info = TRUE)))
expect_output(suppressWarnings(interpretive_rules(example_isolates, verbose = TRUE, rules = "all", info = TRUE)))
# AmpC de-repressed cephalo mutants
expect_identical(
eucast_rules(
interpretive_rules(
data.frame(
mo = c("Escherichia coli", "Enterobacter cloacae"),
cefotax = as.sir(c("S", "S"))
@@ -187,7 +188,7 @@ test_that("test-eucast_rules.R", {
)
expect_identical(
eucast_rules(
interpretive_rules(
data.frame(
mo = c("Escherichia coli", "Enterobacter cloacae"),
cefotax = as.sir(c("S", "S"))
@@ -201,7 +202,7 @@ test_that("test-eucast_rules.R", {
)
expect_identical(
eucast_rules(
interpretive_rules(
data.frame(
mo = c("Escherichia coli", "Enterobacter cloacae"),
cefotax = as.sir(c("S", "S"))
@@ -219,7 +220,7 @@ test_that("test-eucast_rules.R", {
expect_inherits(eucast_dosage(c("tobra", "genta", "cipro")), "data.frame")
x <- custom_eucast_rules(
x <- custom_interpretive_rules(
AMC == "R" & genus == "Klebsiella" ~ aminopenicillins == "R",
AMC == "I" & genus == "Klebsiella" ~ aminopenicillins == "I",
AMX == "S" ~ AMC == "S"
@@ -230,7 +231,7 @@ test_that("test-eucast_rules.R", {
# this custom rules makes 8 changes
expect_equal(
nrow(eucast_rules(example_isolates,
nrow(interpretive_rules(example_isolates,
rules = "custom",
custom_rules = x,
info = FALSE,
@@ -240,4 +241,10 @@ test_that("test-eucast_rules.R", {
8,
tolerance = 0.5
)
# clsi_rules() no longer errors (returns data unchanged until CLSI rows are added)
expect_identical(
suppressWarnings(clsi_rules(example_isolates, info = FALSE)),
example_isolates
)
})