diff --git a/DESCRIPTION b/DESCRIPTION index 2f30e48e..49fa4af6 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: AMR -Version: 2.1.1.9077 -Date: 2024-09-19 +Version: 2.1.1.9078 +Date: 2024-09-22 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/NEWS.md b/NEWS.md index 8b27cb74..b30aa439 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# AMR 2.1.1.9077 +# AMR 2.1.1.9078 *(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! Install this beta using [the instructions here](https://msberends.github.io/AMR/#latest-development-version).)* @@ -37,6 +37,10 @@ This package now supports not only tools for AMR data analysis in clinical setti * It is now possible to use column names for argument `ab`, `mo`, and `uti`: `as.sir(..., ab = "column1", mo = "column2", uti = "column3")`. This greatly improves the flexibility for users. * Users can now set their own criteria (using regular expressions) as to what should be considered S, I, R, SDD, and NI. * To get quantitative values, `as.double()` on a `sir` object will return 1 for S, 2 for SDD/I, and 3 for R (NI will become `NA`). Other functions using `sir` classes (e.g., `summary()`) are updated to reflect the change to contain NI and SDD. +* `antibiogram()` function + * New argument `formatting_type` to set any of the 12 options for the formatting of all 'cells'. This defaults to `10`, changing the output of antibiograms to cells with `5% (15/300)` instead of the previous standard of just `5`. + * For this reason, `add_total_n` is now `FALSE` at default since the denominators are added to the cells + * The `ab_transform` argument now defaults to `"name"`, displaying antibiotic column names instead of codes * `antibiotics` data set * Added "clindamycin inducible screening" as `CLI1`. Since clindamycin is a lincosamide, the antibiotic selector `lincosamides()` now contains the argument `only_treatable = TRUE` (similar to other antibiotic selectors that contain non-treatable drugs) * Added Amorolfine (`AMO`, D01AE16), which is now also part of the `antifungals()` selector diff --git a/R/aa_options.R b/R/aa_options.R index 0830ab12..cb4d1021 100755 --- a/R/aa_options.R +++ b/R/aa_options.R @@ -31,6 +31,7 @@ #' #' This is an overview of all the package-specific [options()] you can set in the `AMR` package. #' @section Options: +#' * `AMR_antibiogram_formatting_type` \cr A [numeric] (1-12) to use in [antibiogram()], to indicate which formatting type to use. #' * `AMR_breakpoint_type` \cr A [character] to use in [as.sir()], to indicate which breakpoint type to use. This must be either `r vector_or(clinical_breakpoints$type)`. #' * `AMR_cleaning_regex` \cr A [regular expression][base::regex] (case-insensitive) to use in [as.mo()] and all [`mo_*`][mo_property()] functions, to clean the user input. The default is the outcome of [mo_cleaning_regex()], which removes texts between brackets and texts such as "species" and "serovar". #' * `AMR_custom_ab` \cr A file location to an RDS file, to use custom antimicrobial drugs with this package. This is explained in [add_custom_antimicrobials()]. diff --git a/R/antibiogram.R b/R/antibiogram.R index 4d112eb8..fbd86aac 100755 --- a/R/antibiogram.R +++ b/R/antibiogram.R @@ -27,17 +27,18 @@ # how to conduct AMR data analysis: https://msberends.github.io/AMR/ # # ==================================================================== # -#' Generate Antibiogram: Traditional, Combined, Syndromic, or Weighted-Incidence Syndromic Combination (WISCA) +#' Generate Traditional, Combination, Syndromic, or WISCA Antibiograms #' -#' Generate an antibiogram, and communicate the results in plots or tables. These functions follow the logic of Klinker *et al.* and Barbieri *et al.* (see *Source*), and allow reporting in e.g. R Markdown and Quarto as well. +#' Create detailed antibiograms with options for traditional, combination, syndromic, and Bayesian WISCA methods. Based on the approaches of Klinker *et al.*, Barbieri *et al.*, and the Bayesian WISCA model (Weighted-Incidence Syndromic Combination Antibiogram) by Bielicki *et al.*, this function provides flexible output formats including plots and tables, ideal for integration with R Markdown and Quarto reports. #' @param x a [data.frame] containing at least a column with microorganisms and columns with antibiotic results (class 'sir', see [as.sir()]) #' @param antibiotics vector of any antibiotic name or code (will be evaluated with [as.ab()], column name of `x`, or (any combinations of) [antibiotic selectors][antibiotic_class_selectors] such as [aminoglycosides()] or [carbapenems()]. For combination antibiograms, this can also be set to values separated with `"+"`, such as "TZP+TOB" or "cipro + genta", given that columns resembling such antibiotics exist in `x`. See *Examples*. -#' @param mo_transform a character to transform microorganism input - must be "name", "shortname", "gramstain", or one of the column names of the [microorganisms] data set: `r vector_or(colnames(microorganisms), sort = FALSE, quotes = TRUE)`. Can also be `NULL` to not transform the input. -#' @param ab_transform a character to transform antibiotic input - must be one of the column names of the [antibiotics] data set: `r vector_or(colnames(antibiotics), sort = FALSE, quotes = TRUE)`. Can also be `NULL` to not transform the input. +#' @param mo_transform a character to transform microorganism input - must be `"name"`, `"shortname"` (default), `"gramstain"`, or one of the column names of the [microorganisms] data set: `r vector_or(colnames(microorganisms), sort = FALSE, quotes = TRUE)`. Can also be `NULL` to not transform the input. +#' @param ab_transform a character to transform antibiotic input - must be one of the column names of the [antibiotics] data set (defaults to "name"): `r vector_or(colnames(antibiotics), sort = FALSE, quotes = TRUE)`. Can also be `NULL` to not transform the input. #' @param syndromic_group a column name of `x`, or values calculated to split rows of `x`, e.g. by using [ifelse()] or [`case_when()`][dplyr::case_when()]. See *Examples*. #' @param add_total_n a [logical] to indicate whether total available numbers per pathogen should be added to the table (default is `TRUE`). This will add the lowest and highest number of available isolate per antibiotic (e.g, if for *E. coli* 200 isolates are available for ciprofloxacin and 150 for amoxicillin, the returned number will be "150-200"). #' @param only_all_tested (for combination antibiograms): a [logical] to indicate that isolates must be tested for all antibiotics, see *Details* -#' @param digits number of digits to use for rounding +#' @param digits number of digits to use for rounding the susceptibility percentage +#' @param formatting_type numeric value (1–12) indicating how the 'cells' of the antibiogram table should be formatted. See *Details* > *Formatting Type* for a list of options. #' @param col_mo column name of the names or codes of the microorganisms (see [as.mo()]) - the default is the first column of class [`mo`]. Values will be coerced using [as.mo()]. #' @param language language to translate text, which defaults to the system language (see [get_AMR_locale()]) #' @param minimum the minimum allowed number of available (tested) isolates. Any isolate count lower than `minimum` will return `NA` with a warning. The default number of `30` isolates is advised by the Clinical and Laboratory Standards Institute (CLSI) as best practice, see *Source*. @@ -47,14 +48,35 @@ #' @param object an [antibiogram()] object #' @param ... when used in [R Markdown or Quarto][knitr::kable()]: arguments passed on to [knitr::kable()] (otherwise, has no use) #' @details This function returns a table with values between 0 and 100 for *susceptibility*, not resistance. -#' +#' #' **Remember that you should filter your data to let it contain only first isolates!** This is needed to exclude duplicates and to reduce selection bias. Use [first_isolate()] to determine them in your data set with one of the four available algorithms. -#' -#' All types of antibiograms as listed below can be plotted (using [ggplot2::autoplot()] or base \R [plot()]/[barplot()]). The `antibiogram` object can also be used directly in R Markdown / Quarto (i.e., `knitr`) for reports. In this case, [knitr::kable()] will be applied automatically and microorganism names will even be printed in italics at default (see argument `italicise`). You can also use functions from specific 'table reporting' packages to transform the output of [antibiogram()] to your needs, e.g. with `flextable::as_flextable()` or `gt::gt()`. +#' +#' ### Formatting Type +#' +#' The formatting of the 'cells' of the table can be set with the argument `formatting_type`. In these examples, `5` is the susceptibility percentage, `15` the numerator, and `300` the denominator: +#' +#' 1. 5 +#' 2. 15 +#' 3. 300 +#' 4. 15/300 +#' 5. 5 (300) +#' 6. 5% (300) +#' 7. 5 (N=300) +#' 8. 5% (N=300) +#' 9. 5 (15/300) +#' 10. 5% (15/300) +#' 11. 5 (N=15/300) +#' 12. 5% (N=15/300) +#' +#' The default is `10`, which can be set globally with the [package option][AMR-options] [`AMR_antibiogram_formatting_type`][AMR-options], e.g. `options(AMR_antibiogram_formatting_type = 5)`. +#' +#' Set `digits` (defaults to `0`) to alter the rounding of the susceptibility percentage. #' #' ### Antibiogram Types #' -#' There are four antibiogram types, as proposed by Klinker *et al.* (2021, \doi{10.1177/20499361211011373}), and they are all supported by [antibiogram()]: +#' There are four antibiogram types, as summarised by Klinker *et al.* (2021, \doi{10.1177/20499361211011373}), and they are all supported by [antibiogram()]. Use WISCA whenever possible, since it provides precise coverage estimates by accounting for pathogen incidence and antimicrobial susceptibility. See the section *Why Use WISCA?* on this page. +#' +#' The four antibiogram types: #' #' 1. **Traditional Antibiogram** #' @@ -91,6 +113,8 @@ #' ``` #' #' 4. **Weighted-Incidence Syndromic Combination Antibiogram (WISCA)** +#' +#' WISCA enhances empirical antibiotic selection by weighting the incidence of pathogens in specific clinical syndromes and combining them with their susceptibility data. It provides an estimation of regimen coverage by aggregating pathogen incidences and susceptibilities across potential causative organisms. See also the section *Why Use WISCA?* on this page. #' #' Case example: Susceptibility of *Pseudomonas aeruginosa* to TZP among respiratory specimens (obtained among ICU patients only) for male patients age >=65 years with heart failure #' @@ -106,8 +130,12 @@ #' .$condition == "Heart Disease", #' "Study Group", "Control Group")) #' ``` +#' +#' WISCA uses a sophisticated Bayesian decision model to combine both local and pooled antimicrobial resistance data. This approach not only evaluates local patterns but can also draw on multi-centre datasets to improve regimen accuracy, even in low-incidence infections like paediatric bloodstream infections (BSIs). #' -#' Note that for combination antibiograms, it is important to realise that susceptibility can be calculated in two ways, which can be set with the `only_all_tested` argument (default is `FALSE`). See this example for two antibiotics, Drug A and Drug B, about how [antibiogram()] works to calculate the %SI: +#' ### Inclusion in Combination Antibiogram and Syndromic Antibiogram +#' +#' Note that for types 2 and 3 (Combination Antibiogram and Syndromic Antibiogram), it is important to realise that susceptibility can be calculated in two ways, which can be set with the `only_all_tested` argument (default is `FALSE`). See this example for two antibiotics, Drug A and Drug B, about how [antibiogram()] works to calculate the %SI: #' #' ``` #' -------------------------------------------------------------------- @@ -127,8 +155,28 @@ #' - - - - #' -------------------------------------------------------------------- #' ``` +#' +#' ### Plotting +#' +#' All types of antibiograms as listed above can be plotted (using [ggplot2::autoplot()] or base \R's [plot()] and [barplot()]). +#' +#' THe outcome of [antibiogram()] can also be used directly in R Markdown / Quarto (i.e., `knitr`) for reports. In this case, [knitr::kable()] will be applied automatically and microorganism names will even be printed in italics at default (see argument `italicise`). +#' +#' You can also use functions from specific 'table reporting' packages to transform the output of [antibiogram()] to your needs, e.g. with `flextable::as_flextable()` or `gt::gt()`. #' +#' @section Why Use WISCA?: +#' WISCA is a powerful tool for guiding empirical antibiotic therapy because it provides precise coverage estimates by accounting for pathogen incidence and antimicrobial susceptibility. This is particularly important in empirical treatment, where the causative pathogen is often unknown at the outset. Traditional antibiograms do not reflect the weighted likelihood of specific pathogens based on clinical syndromes, which can lead to suboptimal treatment choices. +#' +#' The Bayesian WISCA, as described by Bielicki *et al.* (2016), improves on earlier methods by handling uncertainties common in smaller datasets, such as low-incidence infections. This method offers a significant advantage by: +#' +#' 1. Pooling Data from Multiple Sources:\cr WISCA uses pooled data from multiple hospitals or surveillance sources to overcome limitations of small sample sizes at individual institutions, allowing for more confident selection of narrow-spectrum antibiotics or combinations. +#' 2. Bayesian Framework:\cr The Bayesian decision tree model accounts for both local data and prior knowledge (such as inherent resistance patterns) to estimate regimen coverage. It allows for a more precise estimation of coverage, even in cases where susceptibility data is missing or incomplete. +#' 3. Incorporating Pathogen and Regimen Uncertainty:\cr WISCA allows clinicians to see the likelihood that an empirical regimen will be effective against all relevant pathogens, taking into account uncertainties related to both pathogen prevalence and antimicrobial resistance. This leads to better-informed, data-driven clinical decisions. +#' 4. Scenarios for Optimising Treatment:\cr For hospitals or settings with low-incidence infections, WISCA helps determine whether local data is sufficient or if pooling with external data is necessary. It also identifies statistically significant differences or similarities between antibiotic regimens, enabling clinicians to choose optimal therapies with greater confidence. +#' +#' WISCA is essential in optimising empirical treatment by shifting away from broad-spectrum antibiotics, which are often overused in empirical settings. By offering precise estimates based on syndromic patterns and pooled data, WISCA supports antimicrobial stewardship by guiding more targeted therapy, reducing unnecessary broad-spectrum use, and combating the rise of antimicrobial resistance. #' @source +#' * Bielicki JA *et al.* (2016). **Selecting appropriate empirical antibiotic regimens for paediatric bloodstream infections: application of a Bayesian decision model to local and pooled antimicrobial resistance surveillance data** *Journal of Antimicrobial Chemotherapy* 71(3); \doi{10.1093/jac/dkv397} #' * Klinker KP *et al.* (2021). **Antimicrobial stewardship and antibiograms: importance of moving beyond traditional antibiograms**. *Therapeutic Advances in Infectious Disease*, May 5;8:20499361211011373; \doi{10.1177/20499361211011373} #' * Barbieri E *et al.* (2021). **Development of a Weighted-Incidence Syndromic Combination Antibiogram (WISCA) to guide the choice of the empiric antibiotic treatment for urinary tract infection in paediatric patients: a Bayesian approach** *Antimicrobial Resistance & Infection Control* May 1;10(1):74; \doi{10.1186/s13756-021-00939-2} #' * **M39 Analysis and Presentation of Cumulative Antimicrobial Susceptibility Test Data, 5th Edition**, 2022, *Clinical and Laboratory Standards Institute (CLSI)*. . @@ -253,11 +301,12 @@ antibiogram <- function(x, antibiotics = where(is.sir), mo_transform = "shortname", - ab_transform = NULL, + ab_transform = "name", syndromic_group = NULL, - add_total_n = TRUE, + add_total_n = FALSE, only_all_tested = FALSE, digits = 0, + formatting_type = getOption("AMR_antibiogram_formatting_type", 10), col_mo = NULL, language = get_AMR_locale(), minimum = 30, @@ -271,6 +320,7 @@ antibiogram <- function(x, meet_criteria(add_total_n, allow_class = "logical", has_length = 1) meet_criteria(only_all_tested, allow_class = "logical", has_length = 1) meet_criteria(digits, allow_class = c("numeric", "integer"), has_length = 1, is_finite = TRUE) + meet_criteria(formatting_type, allow_class = c("numeric", "integer"), has_length = 1, is_in = c(1:12)) meet_criteria(col_mo, allow_class = "character", has_length = 1, allow_NULL = TRUE, is_in = colnames(x)) language <- validate_language(language) meet_criteria(minimum, allow_class = c("numeric", "integer"), has_length = 1, is_positive_or_zero = TRUE, is_finite = TRUE) @@ -388,7 +438,7 @@ antibiogram <- function(x, counts <- out if (isTRUE(combine_SI)) { - out$numerator <- out$S + out$I + out$numerator <- out$S + out$I + out$SDD } else { out$numerator <- out$S } @@ -413,8 +463,36 @@ antibiogram <- function(x, pm_group_by(mo, ab) } - out <- out %pm>% - pm_summarise(SI = numerator / total) + # formatting type: + # 1. 5 + # 2. 15 + # 3. 300 + # 4. 15/300 + # 5. 5 (300) + # 6. 5% (300) + # 7. 5 (N=300) + # 8. 5% (N=300) + # 9. 5 (15/300) + # 10. 5% (15/300) + # 11. 5 (N=15/300) + # 12. 5% (N=15/300) + out_numeric <- out %pm>% + pm_summarise(percentage = numerator / total, + numerator = numerator, + total = total) + out$digits <- digits # since pm_sumarise() cannot work with an object outside the current frame + if (formatting_type == 1) out <- out %pm>% pm_summarise(out_value = round((numerator / total) * 100, digits = digits)) + if (formatting_type == 2) out <- out %pm>% pm_summarise(out_value = numerator) + if (formatting_type == 3) out <- out %pm>% pm_summarise(out_value = total) + if (formatting_type == 4) out <- out %pm>% pm_summarise(out_value = paste0(numerator, "/", total)) + if (formatting_type == 5) out <- out %pm>% pm_summarise(out_value = paste0(round((numerator / total) * 100, digits = digits), " (", total, ")")) + if (formatting_type == 6) out <- out %pm>% pm_summarise(out_value = paste0(round((numerator / total) * 100, digits = digits), "% (", total, ")")) + if (formatting_type == 7) out <- out %pm>% pm_summarise(out_value = paste0(round((numerator / total) * 100, digits = digits), " (N=", total, ")")) + if (formatting_type == 8) out <- out %pm>% pm_summarise(out_value = paste0(round((numerator / total) * 100, digits = digits), "% (N=", total, ")")) + if (formatting_type == 9) out <- out %pm>% pm_summarise(out_value = paste0(round((numerator / total) * 100, digits = digits), " (", numerator, "/", total, ")")) + if (formatting_type == 10) out <- out %pm>% pm_summarise(out_value = paste0(round((numerator / total) * 100, digits = digits), "% (", numerator, "/", total, ")")) + if (formatting_type == 11) out <- out %pm>% pm_summarise(out_value = paste0(round((numerator / total) * 100, digits = digits), " (N=", numerator, "/", total, ")")) + if (formatting_type == 12) out <- out %pm>% pm_summarise(out_value = paste0(round((numerator / total) * 100, digits = digits), "% (N=", numerator, "/", total, ")")) # transform names of antibiotics ab_naming_function <- function(x, t, l, s) { @@ -437,15 +515,15 @@ antibiogram <- function(x, out } out$ab <- ab_naming_function(out$ab, t = ab_transform, l = language, s = sep) - + out_numeric$ab <- ab_naming_function(out_numeric$ab, t = ab_transform, l = language, s = sep) + # transform long to wide - long_to_wide <- function(object, digs) { - object$SI <- round(object$SI * 100, digits = digs) + long_to_wide <- function(object) { object <- object %pm>% # an unclassed data.frame is required for stats::reshape() as.data.frame(stringsAsFactors = FALSE) %pm>% - stats::reshape(direction = "wide", idvar = "mo", timevar = "ab", v.names = "SI") - colnames(object) <- gsub("^SI?[.]", "", colnames(object)) + stats::reshape(direction = "wide", idvar = "mo", timevar = "ab", v.names = "out_value") + colnames(object) <- gsub("^out_value?[.]", "", colnames(object)) return(object) } @@ -453,18 +531,17 @@ antibiogram <- function(x, attr(out, "pm_groups") <- NULL attr(out, "groups") <- NULL class(out) <- class(out)[!class(out) %in% c("grouped_df", "grouped_data")] - long <- out if (isTRUE(has_syndromic_group)) { grps <- unique(out$syndromic_group) for (i in seq_len(length(grps))) { grp <- grps[i] if (i == 1) { - new_df <- long_to_wide(out[which(out$syndromic_group == grp), , drop = FALSE], digs = digits) + new_df <- long_to_wide(out[which(out$syndromic_group == grp), , drop = FALSE]) } else { new_df <- rbind_AMR( new_df, - long_to_wide(out[which(out$syndromic_group == grp), , drop = FALSE], digs = digits) + long_to_wide(out[which(out$syndromic_group == grp), , drop = FALSE]) ) } } @@ -474,7 +551,7 @@ antibiogram <- function(x, new_df <- new_df[, c("syndromic_group", "mo", sort(colnames(new_df)[!colnames(new_df) %in% c("syndromic_group", "mo")])), drop = FALSE] colnames(new_df)[1:2] <- translate_AMR(c("Syndromic Group", "Pathogen"), language = language) } else { - new_df <- long_to_wide(out, digs = digits) + new_df <- long_to_wide(out) # sort rows new_df <- new_df %pm>% pm_arrange(mo) # sort columns @@ -514,19 +591,14 @@ antibiogram <- function(x, rownames(out) <- NULL structure(out, has_syndromic_group = has_syndromic_group, - long = long, + out_numeric = out_numeric, combine_SI = combine_SI ) } # will be exported in R/zzz.R tbl_sum.antibiogram <- function(x, ...) { - if (isTRUE(base::l10n_info()$`UTF-8`)) { - cross <- "\u00d7" - } else { - cross <- "x" - } - dims <- paste(format(NROW(x), big.mark = ","), cross, format(NCOL(x), big.mark = ",")) + dims <- paste(format(NROW(x), big.mark = ","), AMR_env$cross_icon, format(NCOL(x), big.mark = ",")) names(dims) <- "An Antibiogram" dims } @@ -545,7 +617,7 @@ tbl_format_footer.antibiogram <- function(x, ...) { #' @export #' @rdname antibiogram plot.antibiogram <- function(x, ...) { - df <- attributes(x)$long + df <- attributes(x)$out_numeric if ("syndromic_group" %in% colnames(df)) { # barplot in base R does not support facets - paste columns together df$mo <- paste(df$mo, "-", df$syndromic_group) @@ -561,7 +633,7 @@ plot.antibiogram <- function(x, ...) { df_sub <- df[df$mo == mo, , drop = FALSE] barplot( - height = df_sub$SI * 100, + height = df_sub$percentage * 100, xlab = NULL, ylab = ifelse(isTRUE(attributes(x)$combine_SI), "%SI", "%S"), names.arg = df_sub$ab, @@ -584,12 +656,12 @@ barplot.antibiogram <- function(height, ...) { #' @rdname antibiogram # will be exported using s3_register() in R/zzz.R autoplot.antibiogram <- function(object, ...) { - df <- attributes(object)$long + df <- attributes(object)$out_numeric ggplot2::ggplot(df) + ggplot2::geom_col( ggplot2::aes( x = ab, - y = SI * 100, + y = percentage * 100, fill = if ("syndromic_group" %in% colnames(df)) { syndromic_group } else { diff --git a/R/bug_drug_combinations.R b/R/bug_drug_combinations.R index 777036ac..ab722e96 100755 --- a/R/bug_drug_combinations.R +++ b/R/bug_drug_combinations.R @@ -108,7 +108,6 @@ bug_drug_combinations <- function(x, SDD = integer(0), I = integer(0), R = integer(0), - N = integer(0), total = integer(0), stringsAsFactors = FALSE ) diff --git a/inst/tinytest/test-antibiogram.R b/inst/tinytest/test-antibiogram.R index 73a82c67..886f0ea2 100644 --- a/inst/tinytest/test-antibiogram.R +++ b/inst/tinytest/test-antibiogram.R @@ -36,20 +36,22 @@ ab1 <- antibiogram(example_isolates, ab2 <- antibiogram(example_isolates, antibiotics = aminoglycosides(), ab_transform = "atc", - mo_transform = "gramstain") + mo_transform = "gramstain", + add_total_n = TRUE) ab3 <- antibiogram(example_isolates, antibiotics = carbapenems(), - ab_transform = "name", - mo_transform = "name") + ab_transform = "ab", + mo_transform = "name", + formatting_type = 1) expect_inherits(ab1, "antibiogram") expect_inherits(ab2, "antibiogram") expect_inherits(ab3, "antibiogram") -expect_equal(colnames(ab1), c("Pathogen (N min-max)", "AMK", "GEN", "IPM", "KAN", "MEM", "TOB")) +expect_equal(colnames(ab1), c("Pathogen", "Amikacin", "Gentamicin", "Imipenem", "Kanamycin", "Meropenem", "Tobramycin")) expect_equal(colnames(ab2), c("Pathogen (N min-max)", "J01GB01", "J01GB03", "J01GB04", "J01GB06")) -expect_equal(colnames(ab3), c("Pathogen (N min-max)", "Imipenem", "Meropenem")) -expect_equal(ab3$Meropenem, c(52, NA, 100, 100, NA)) +expect_equal(colnames(ab3), c("Pathogen", "IPM", "MEM")) +expect_equal(ab3$MEM, c(52, NA, 100, 100, NA)) # Combined antibiogram ------------------------------------------------- @@ -67,7 +69,7 @@ ab5 <- antibiogram(example_isolates, expect_inherits(ab4, "antibiogram") expect_inherits(ab5, "antibiogram") -expect_equal(colnames(ab4), c("Pathogen (N min-max)", "TZP", "TZP + GEN", "TZP + TOB")) +expect_equal(colnames(ab4), c("Pathogen", "Piperacillin/tazobactam", "Piperacillin/tazobactam + Gentamicin", "Piperacillin/tazobactam + Tobramycin")) expect_equal(colnames(ab5), c("Pathogen", "Piperacillin/tazobactam", "Piperacillin/tazobactam & Tobramycin")) # Syndromic antibiogram ------------------------------------------------ @@ -75,7 +77,8 @@ expect_equal(colnames(ab5), c("Pathogen", "Piperacillin/tazobactam", "Piperacill # the data set could contain a filter for e.g. respiratory specimens ab6 <- antibiogram(example_isolates, antibiotics = c(aminoglycosides(), carbapenems()), - syndromic_group = "ward") + syndromic_group = "ward", + ab_transform = NULL) # with a custom language, though this will be determined automatically # (i.e., this table will be in Dutch on Dutch systems) @@ -85,11 +88,12 @@ ab7 <- antibiogram(ex1, ab_transform = "name", syndromic_group = ifelse(ex1$ward == "ICU", "IC", "Geen IC"), - language = "nl") + language = "nl", + add_total_n = TRUE) expect_inherits(ab6, "antibiogram") expect_inherits(ab7, "antibiogram") -expect_equal(colnames(ab6), c("Syndromic Group", "Pathogen (N min-max)", "AMK", "GEN", "IPM", "KAN", "MEM", "TOB")) +expect_equal(colnames(ab6), c("Syndromic Group", "Pathogen", "AMK", "GEN", "IPM", "KAN", "MEM", "TOB")) expect_equal(colnames(ab7), c("Syndroomgroep", "Pathogeen (N min-max)", "Amikacine", "Gentamicine", "Tobramycine")) # Weighted-incidence syndromic combination antibiogram (WISCA) --------- @@ -101,10 +105,11 @@ ab8 <- antibiogram(example_isolates, minimum = 10, # this should be >= 30, but now just as example syndromic_group = ifelse(example_isolates$age >= 65 & example_isolates$gender == "M", - "WISCA Group 1", "WISCA Group 2")) + "WISCA Group 1", "WISCA Group 2"), + ab_transform = NULL) expect_inherits(ab8, "antibiogram") -expect_equal(colnames(ab8), c("Syndromic Group", "Pathogen (N min-max)", "AMC", "AMC + CIP", "TZP", "TZP + TOB")) +expect_equal(colnames(ab8), c("Syndromic Group", "Pathogen", "AMC", "AMC + CIP", "TZP", "TZP + TOB")) # Generate plots with ggplot2 or base R -------------------------------- diff --git a/man/AMR-options.Rd b/man/AMR-options.Rd index 67649159..e31e9865 100644 --- a/man/AMR-options.Rd +++ b/man/AMR-options.Rd @@ -9,6 +9,7 @@ This is an overview of all the package-specific \code{\link[=options]{options()} \section{Options}{ \itemize{ +\item \code{AMR_antibiogram_formatting_type} \cr A \link{numeric} (1-12) to use in \code{\link[=antibiogram]{antibiogram()}}, to indicate which formatting type to use. \item \code{AMR_breakpoint_type} \cr A \link{character} to use in \code{\link[=as.sir]{as.sir()}}, to indicate which breakpoint type to use. This must be either "ECOFF", "animal", or "human". \item \code{AMR_cleaning_regex} \cr A \link[base:regex]{regular expression} (case-insensitive) to use in \code{\link[=as.mo]{as.mo()}} and all \code{\link[=mo_property]{mo_*}} functions, to clean the user input. The default is the outcome of \code{\link[=mo_cleaning_regex]{mo_cleaning_regex()}}, which removes texts between brackets and texts such as "species" and "serovar". \item \code{AMR_custom_ab} \cr A file location to an RDS file, to use custom antimicrobial drugs with this package. This is explained in \code{\link[=add_custom_antimicrobials]{add_custom_antimicrobials()}}. diff --git a/man/antibiogram.Rd b/man/antibiogram.Rd index 518f9c9b..737e6bdf 100644 --- a/man/antibiogram.Rd +++ b/man/antibiogram.Rd @@ -5,9 +5,10 @@ \alias{plot.antibiogram} \alias{autoplot.antibiogram} \alias{knit_print.antibiogram} -\title{Generate Antibiogram: Traditional, Combined, Syndromic, or Weighted-Incidence Syndromic Combination (WISCA)} +\title{Generate Traditional, Combination, Syndromic, or WISCA Antibiograms} \source{ \itemize{ +\item Bielicki JA \emph{et al.} (2016). \strong{Selecting appropriate empirical antibiotic regimens for paediatric bloodstream infections: application of a Bayesian decision model to local and pooled antimicrobial resistance surveillance data} \emph{Journal of Antimicrobial Chemotherapy} 71(3); \doi{10.1093/jac/dkv397} \item Klinker KP \emph{et al.} (2021). \strong{Antimicrobial stewardship and antibiograms: importance of moving beyond traditional antibiograms}. \emph{Therapeutic Advances in Infectious Disease}, May 5;8:20499361211011373; \doi{10.1177/20499361211011373} \item Barbieri E \emph{et al.} (2021). \strong{Development of a Weighted-Incidence Syndromic Combination Antibiogram (WISCA) to guide the choice of the empiric antibiotic treatment for urinary tract infection in paediatric patients: a Bayesian approach} \emph{Antimicrobial Resistance & Infection Control} May 1;10(1):74; \doi{10.1186/s13756-021-00939-2} \item \strong{M39 Analysis and Presentation of Cumulative Antimicrobial Susceptibility Test Data, 5th Edition}, 2022, \emph{Clinical and Laboratory Standards Institute (CLSI)}. \url{https://clsi.org/standards/products/microbiology/documents/m39/}. @@ -18,11 +19,12 @@ antibiogram( x, antibiotics = where(is.sir), mo_transform = "shortname", - ab_transform = NULL, + ab_transform = "name", syndromic_group = NULL, - add_total_n = TRUE, + add_total_n = FALSE, only_all_tested = FALSE, digits = 0, + formatting_type = getOption("AMR_antibiogram_formatting_type", 10), col_mo = NULL, language = get_AMR_locale(), minimum = 30, @@ -47,9 +49,9 @@ antibiogram( \item{antibiotics}{vector of any antibiotic name or code (will be evaluated with \code{\link[=as.ab]{as.ab()}}, column name of \code{x}, or (any combinations of) \link[=antibiotic_class_selectors]{antibiotic selectors} such as \code{\link[=aminoglycosides]{aminoglycosides()}} or \code{\link[=carbapenems]{carbapenems()}}. For combination antibiograms, this can also be set to values separated with \code{"+"}, such as "TZP+TOB" or "cipro + genta", given that columns resembling such antibiotics exist in \code{x}. See \emph{Examples}.} -\item{mo_transform}{a character to transform microorganism input - must be "name", "shortname", "gramstain", or one of the column names of the \link{microorganisms} data set: "mo", "fullname", "status", "kingdom", "phylum", "class", "order", "family", "genus", "species", "subspecies", "rank", "ref", "oxygen_tolerance", "source", "lpsn", "lpsn_parent", "lpsn_renamed_to", "mycobank", "mycobank_parent", "mycobank_renamed_to", "gbif", "gbif_parent", "gbif_renamed_to", "prevalence", or "snomed". Can also be \code{NULL} to not transform the input.} +\item{mo_transform}{a character to transform microorganism input - must be \code{"name"}, \code{"shortname"} (default), \code{"gramstain"}, or one of the column names of the \link{microorganisms} data set: "mo", "fullname", "status", "kingdom", "phylum", "class", "order", "family", "genus", "species", "subspecies", "rank", "ref", "oxygen_tolerance", "source", "lpsn", "lpsn_parent", "lpsn_renamed_to", "mycobank", "mycobank_parent", "mycobank_renamed_to", "gbif", "gbif_parent", "gbif_renamed_to", "prevalence", or "snomed". Can also be \code{NULL} to not transform the input.} -\item{ab_transform}{a character to transform antibiotic input - must be one of the column names of the \link{antibiotics} data set: "ab", "cid", "name", "group", "atc", "atc_group1", "atc_group2", "abbreviations", "synonyms", "oral_ddd", "oral_units", "iv_ddd", "iv_units", or "loinc". Can also be \code{NULL} to not transform the input.} +\item{ab_transform}{a character to transform antibiotic input - must be one of the column names of the \link{antibiotics} data set (defaults to "name"): "ab", "cid", "name", "group", "atc", "atc_group1", "atc_group2", "abbreviations", "synonyms", "oral_ddd", "oral_units", "iv_ddd", "iv_units", or "loinc". Can also be \code{NULL} to not transform the input.} \item{syndromic_group}{a column name of \code{x}, or values calculated to split rows of \code{x}, e.g. by using \code{\link[=ifelse]{ifelse()}} or \code{\link[dplyr:case_when]{case_when()}}. See \emph{Examples}.} @@ -57,7 +59,9 @@ antibiogram( \item{only_all_tested}{(for combination antibiograms): a \link{logical} to indicate that isolates must be tested for all antibiotics, see \emph{Details}} -\item{digits}{number of digits to use for rounding} +\item{digits}{number of digits to use for rounding the susceptibility percentage} + +\item{formatting_type}{numeric value (1–12) indicating how the 'cells' of the antibiogram table should be formatted. See \emph{Details} > \emph{Formatting Type} for a list of options.} \item{col_mo}{column name of the names or codes of the microorganisms (see \code{\link[=as.mo]{as.mo()}}) - the default is the first column of class \code{\link{mo}}. Values will be coerced using \code{\link[=as.mo]{as.mo()}}.} @@ -80,17 +84,40 @@ antibiogram( \item{na}{character to use for showing \code{NA} values} } \description{ -Generate an antibiogram, and communicate the results in plots or tables. These functions follow the logic of Klinker \emph{et al.} and Barbieri \emph{et al.} (see \emph{Source}), and allow reporting in e.g. R Markdown and Quarto as well. +Create detailed antibiograms with options for traditional, combination, syndromic, and Bayesian WISCA methods. Based on the approaches of Klinker \emph{et al.}, Barbieri \emph{et al.}, and the Bayesian WISCA model (Weighted-Incidence Syndromic Combination Antibiogram) by Bielicki \emph{et al.}, this function provides flexible output formats including plots and tables, ideal for integration with R Markdown and Quarto reports. } \details{ This function returns a table with values between 0 and 100 for \emph{susceptibility}, not resistance. \strong{Remember that you should filter your data to let it contain only first isolates!} This is needed to exclude duplicates and to reduce selection bias. Use \code{\link[=first_isolate]{first_isolate()}} to determine them in your data set with one of the four available algorithms. +\subsection{Formatting Type}{ + +The formatting of the 'cells' of the table can be set with the argument \code{formatting_type}. In these examples, \code{5} is the susceptibility percentage, \code{15} the numerator, and \code{300} the denominator: +\enumerate{ +\item 5 +\item 15 +\item 300 +\item 15/300 +\item 5 (300) +\item 5\% (300) +\item 5 (N=300) +\item 5\% (N=300) +\item 5 (15/300) +\item 5\% (15/300) +\item 5 (N=15/300) +\item 5\% (N=15/300) +} + +The default is \code{10}, which can be set globally with the \link[=AMR-options]{package option} \code{\link[=AMR-options]{AMR_antibiogram_formatting_type}}, e.g. \code{options(AMR_antibiogram_formatting_type = 5)}. + +Set \code{digits} (defaults to \code{0}) to alter the rounding of the susceptibility percentage. +} -All types of antibiograms as listed below can be plotted (using \code{\link[ggplot2:autoplot]{ggplot2::autoplot()}} or base \R \code{\link[=plot]{plot()}}/\code{\link[=barplot]{barplot()}}). The \code{antibiogram} object can also be used directly in R Markdown / Quarto (i.e., \code{knitr}) for reports. In this case, \code{\link[knitr:kable]{knitr::kable()}} will be applied automatically and microorganism names will even be printed in italics at default (see argument \code{italicise}). You can also use functions from specific 'table reporting' packages to transform the output of \code{\link[=antibiogram]{antibiogram()}} to your needs, e.g. with \code{flextable::as_flextable()} or \code{gt::gt()}. \subsection{Antibiogram Types}{ -There are four antibiogram types, as proposed by Klinker \emph{et al.} (2021, \doi{10.1177/20499361211011373}), and they are all supported by \code{\link[=antibiogram]{antibiogram()}}: +There are four antibiogram types, as summarised by Klinker \emph{et al.} (2021, \doi{10.1177/20499361211011373}), and they are all supported by \code{\link[=antibiogram]{antibiogram()}}. Use WISCA whenever possible, since it provides precise coverage estimates by accounting for pathogen incidence and antimicrobial susceptibility. See the section \emph{Why Use WISCA?} on this page. + +The four antibiogram types: \enumerate{ \item \strong{Traditional Antibiogram} @@ -122,6 +149,8 @@ Code example: }\if{html}{\out{}} \item \strong{Weighted-Incidence Syndromic Combination Antibiogram (WISCA)} +WISCA enhances empirical antibiotic selection by weighting the incidence of pathogens in specific clinical syndromes and combining them with their susceptibility data. It provides an estimation of regimen coverage by aggregating pathogen incidences and susceptibilities across potential causative organisms. See also the section \emph{Why Use WISCA?} on this page. + Case example: Susceptibility of \emph{Pseudomonas aeruginosa} to TZP among respiratory specimens (obtained among ICU patients only) for male patients age >=65 years with heart failure Code example: @@ -135,9 +164,14 @@ your_data \%>\% .$condition == "Heart Disease", "Study Group", "Control Group")) }\if{html}{\out{}} + +WISCA uses a sophisticated Bayesian decision model to combine both local and pooled antimicrobial resistance data. This approach not only evaluates local patterns but can also draw on multi-centre datasets to improve regimen accuracy, even in low-incidence infections like paediatric bloodstream infections (BSIs). +} } -Note that for combination antibiograms, it is important to realise that susceptibility can be calculated in two ways, which can be set with the \code{only_all_tested} argument (default is \code{FALSE}). See this example for two antibiotics, Drug A and Drug B, about how \code{\link[=antibiogram]{antibiogram()}} works to calculate the \%SI: +\subsection{Inclusion in Combination Antibiogram and Syndromic Antibiogram}{ + +Note that for types 2 and 3 (Combination Antibiogram and Syndromic Antibiogram), it is important to realise that susceptibility can be calculated in two ways, which can be set with the \code{only_all_tested} argument (default is \code{FALSE}). See this example for two antibiotics, Drug A and Drug B, about how \code{\link[=antibiogram]{antibiogram()}} works to calculate the \%SI: \if{html}{\out{
}}\preformatted{-------------------------------------------------------------------- only_all_tested = FALSE only_all_tested = TRUE @@ -157,7 +191,31 @@ Note that for combination antibiograms, it is important to realise that suscepti -------------------------------------------------------------------- }\if{html}{\out{
}} } + +\subsection{Plotting}{ + +All types of antibiograms as listed above can be plotted (using \code{\link[ggplot2:autoplot]{ggplot2::autoplot()}} or base \R's \code{\link[=plot]{plot()}} and \code{\link[=barplot]{barplot()}}). + +THe outcome of \code{\link[=antibiogram]{antibiogram()}} can also be used directly in R Markdown / Quarto (i.e., \code{knitr}) for reports. In this case, \code{\link[knitr:kable]{knitr::kable()}} will be applied automatically and microorganism names will even be printed in italics at default (see argument \code{italicise}). + +You can also use functions from specific 'table reporting' packages to transform the output of \code{\link[=antibiogram]{antibiogram()}} to your needs, e.g. with \code{flextable::as_flextable()} or \code{gt::gt()}. } +} +\section{Why Use WISCA?}{ + +WISCA is a powerful tool for guiding empirical antibiotic therapy because it provides precise coverage estimates by accounting for pathogen incidence and antimicrobial susceptibility. This is particularly important in empirical treatment, where the causative pathogen is often unknown at the outset. Traditional antibiograms do not reflect the weighted likelihood of specific pathogens based on clinical syndromes, which can lead to suboptimal treatment choices. + +The Bayesian WISCA, as described by Bielicki \emph{et al.} (2016), improves on earlier methods by handling uncertainties common in smaller datasets, such as low-incidence infections. This method offers a significant advantage by: +\enumerate{ +\item Pooling Data from Multiple Sources:\cr WISCA uses pooled data from multiple hospitals or surveillance sources to overcome limitations of small sample sizes at individual institutions, allowing for more confident selection of narrow-spectrum antibiotics or combinations. +\item Bayesian Framework:\cr The Bayesian decision tree model accounts for both local data and prior knowledge (such as inherent resistance patterns) to estimate regimen coverage. It allows for a more precise estimation of coverage, even in cases where susceptibility data is missing or incomplete. +\item Incorporating Pathogen and Regimen Uncertainty:\cr WISCA allows clinicians to see the likelihood that an empirical regimen will be effective against all relevant pathogens, taking into account uncertainties related to both pathogen prevalence and antimicrobial resistance. This leads to better-informed, data-driven clinical decisions. +\item Scenarios for Optimising Treatment:\cr For hospitals or settings with low-incidence infections, WISCA helps determine whether local data is sufficient or if pooling with external data is necessary. It also identifies statistically significant differences or similarities between antibiotic regimens, enabling clinicians to choose optimal therapies with greater confidence. +} + +WISCA is essential in optimising empirical treatment by shifting away from broad-spectrum antibiotics, which are often overused in empirical settings. By offering precise estimates based on syndromic patterns and pooled data, WISCA supports antimicrobial stewardship by guiding more targeted therapy, reducing unnecessary broad-spectrum use, and combating the rise of antimicrobial resistance. +} + \examples{ # example_isolates is a data set available in the AMR package. # run ?example_isolates for more info.