From 2461631bcefa78ebdb37bdfad359be74cdd9165a Mon Sep 17 00:00:00 2001 From: Matthijs Berends Date: Thu, 1 May 2025 14:38:51 +0200 Subject: [PATCH] (v2.1.1.9268) WISCA vignette, antibiogram sorting, fix translations --- DESCRIPTION | 2 +- NEWS.md | 2 +- R/antibiogram.R | 31 +++-- R/translate.R | 20 +++- man/antibiogram.Rd | 13 +- tests/testthat/test-ab_property.R | 2 +- tests/testthat/test-atc_online.R | 4 +- tests/testthat/test-data.R | 1 - vignettes/AMR.Rmd | 4 +- vignettes/AMR_with_tidymodels.Rmd | 2 +- vignettes/EUCAST.Rmd | 4 +- vignettes/MDR.Rmd | 4 +- vignettes/PCA.Rmd | 4 +- vignettes/WHONET.Rmd | 4 +- vignettes/WISCA.Rmd | 189 ++++++++++++++---------------- vignettes/datasets.Rmd | 4 +- 16 files changed, 156 insertions(+), 134 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 8ca8214de..1896345aa 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: AMR -Version: 2.1.1.9267 +Version: 2.1.1.9268 Date: 2025-05-01 Title: Antimicrobial Resistance Data Analysis Description: Functions to simplify and standardise antimicrobial resistance (AMR) diff --git a/NEWS.md b/NEWS.md index 58fa9655b..ba000ddde 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# AMR 2.1.1.9267 +# AMR 2.1.1.9268 *(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://amr-for-r.org/#get-this-package).)* diff --git a/R/antibiogram.R b/R/antibiogram.R index 68f7b0e92..2f2340434 100755 --- a/R/antibiogram.R +++ b/R/antibiogram.R @@ -59,6 +59,7 @@ #' @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*. #' @param combine_SI A [logical] to indicate whether all susceptibility should be determined by results of either S, SDD, or I, instead of only S (default is `TRUE`). #' @param sep A separating character for antimicrobial columns in combination antibiograms. +#' @param sort_columns A [logical] to indicate whether the antimicrobial columns must be sorted on name. #' @param wisca A [logical] to indicate whether a Weighted-Incidence Syndromic Combination Antibiogram (WISCA) must be generated (default is `FALSE`). This will use a Bayesian decision model to estimate regimen coverage probabilities using [Monte Carlo simulations](https://en.wikipedia.org/wiki/Monte_Carlo_method). Set `simulations`, `conf_interval`, and `interval_side` to adjust. #' @param simulations (for WISCA) a numerical value to set the number of Monte Carlo simulations. #' @param conf_interval A numerical value to set confidence interval (default is `0.95`). @@ -405,6 +406,7 @@ antibiogram <- function(x, minimum = 30, combine_SI = TRUE, sep = " + ", + sort_columns = TRUE, wisca = FALSE, simulations = 1000, conf_interval = 0.95, @@ -430,6 +432,7 @@ antibiogram.default <- function(x, minimum = 30, combine_SI = TRUE, sep = " + ", + sort_columns = TRUE, wisca = FALSE, simulations = 1000, conf_interval = 0.95, @@ -449,6 +452,7 @@ antibiogram.default <- function(x, deprecation_warning("antibiotics", "antimicrobials", fn = "antibiogram", is_argument = TRUE) antimicrobials <- list(...)$antibiotics } + meet_criteria(antimicrobials, allow_class = "character", allow_NA = FALSE, allow_NULL = FALSE) if (!is.function(mo_transform)) { meet_criteria(mo_transform, allow_class = "character", has_length = 1, is_in = c("name", "shortname", "gramstain", colnames(AMR::microorganisms)), allow_NULL = TRUE, allow_NA = TRUE) } @@ -468,6 +472,7 @@ antibiogram.default <- function(x, meet_criteria(minimum, allow_class = c("numeric", "integer"), has_length = 1, is_positive_or_zero = TRUE, is_finite = TRUE) meet_criteria(combine_SI, allow_class = "logical", has_length = 1) meet_criteria(sep, allow_class = "character", has_length = 1) + meet_criteria(sort_columns, allow_class = "logical", has_length = 1) meet_criteria(simulations, allow_class = c("numeric", "integer"), has_length = 1, is_finite = TRUE, is_positive = TRUE) meet_criteria(conf_interval, allow_class = c("numeric", "integer"), has_length = 1, is_finite = TRUE, is_positive = TRUE) meet_criteria(interval_side, allow_class = "character", has_length = 1, is_in = c("two-tailed", "left", "right")) @@ -591,6 +596,8 @@ antibiogram.default <- function(x, ) colnames(out)[colnames(out) == "total"] <- "n_tested" colnames(out)[colnames(out) == "total_rows"] <- "n_total" + out$ab <- factor(out$ab, levels = antimicrobials, ordered = TRUE) + out <- out[order(out$mo, out$ab), , drop = FALSE] counts <- out @@ -824,7 +831,7 @@ antibiogram.default <- function(x, # transform names of antimicrobials ab_naming_function <- function(x, t, l, s) { - x <- strsplit(x, s, fixed = TRUE) + x <- strsplit(as.character(x), s, fixed = TRUE) out <- character(length = length(x)) for (i in seq_along(x)) { a <- x[[i]] @@ -869,6 +876,12 @@ antibiogram.default <- function(x, attr(out, "groups") <- NULL class(out) <- class(out)[!class(out) %in% c("grouped_df", "grouped_data")] + if (isTRUE(sort_columns)) { + sort_fn <- base::sort + } else { + sort_fn <- function(x) x + } + if (isTRUE(has_syndromic_group)) { grps <- unique(out$syndromic_group) for (i in seq_along(grps)) { @@ -886,25 +899,25 @@ antibiogram.default <- function(x, # sort rows new_df <- new_df %pm>% pm_arrange(syndromic_group) # sort columns - new_df <- new_df[, c("syndromic_group", sort(colnames(new_df)[colnames(new_df) != "syndromic_group"])), drop = FALSE] + new_df <- new_df[, c("syndromic_group", sort_fn(colnames(new_df)[colnames(new_df) != "syndromic_group"])), drop = FALSE] colnames(new_df)[1] <- translate_AMR("Syndromic Group", language = language) } else { # sort rows new_df <- new_df %pm>% pm_arrange(mo, syndromic_group) # sort columns - new_df <- new_df[, c("syndromic_group", "mo", sort(colnames(new_df)[!colnames(new_df) %in% c("syndromic_group", "mo")])), drop = FALSE] + new_df <- new_df[, c("syndromic_group", "mo", sort_fn(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) if (wisca == TRUE) { # sort columns - new_df <- new_df[, c(sort(colnames(new_df))), drop = FALSE] + new_df <- new_df[, c(sort_fn(colnames(new_df))), drop = FALSE] } else { # sort rows new_df <- new_df %pm>% pm_arrange(mo) # sort columns - new_df <- new_df[, c("mo", sort(colnames(new_df)[colnames(new_df) != "mo"])), drop = FALSE] + new_df <- new_df[, c("mo", sort_fn(colnames(new_df)[colnames(new_df) != "mo"])), drop = FALSE] colnames(new_df)[1] <- translate_AMR("Pathogen", language = language) } } @@ -966,6 +979,7 @@ antibiogram.grouped_df <- function(x, minimum = 30, combine_SI = TRUE, sep = " + ", + sort_columns = TRUE, wisca = FALSE, simulations = 1000, conf_interval = 0.95, @@ -1008,6 +1022,7 @@ antibiogram.grouped_df <- function(x, minimum = minimum, combine_SI = combine_SI, sep = sep, + sort_columns = sort_columns, wisca = wisca, simulations = simulations, conf_interval = conf_interval, @@ -1084,6 +1099,7 @@ wisca <- function(x, language = get_AMR_locale(), combine_SI = TRUE, sep = " + ", + sort_columns = TRUE, simulations = 1000, conf_interval = 0.95, interval_side = "two-tailed", @@ -1103,6 +1119,7 @@ wisca <- function(x, language = language, combine_SI = combine_SI, sep = sep, + sort_columns = sort_columns, wisca = TRUE, simulations = simulations, conf_interval = conf_interval, @@ -1143,8 +1160,8 @@ simulate_coverage <- function(params) { n_pathogens <- length(params$gamma_posterior) # random draws per pathogen - random_incidence <- runif(n = n_pathogens) - random_susceptibility <- runif(n = n_pathogens) + random_incidence <- stats::runif(n = n_pathogens) + random_susceptibility <- stats::runif(n = n_pathogens) simulated_incidence <- stats::qgamma( p = random_incidence, diff --git a/R/translate.R b/R/translate.R index fe711d8d9..032981b99 100755 --- a/R/translate.R +++ b/R/translate.R @@ -203,7 +203,25 @@ translate_into_language <- function(from, df_trans <- TRANSLATIONS # internal data file from.bak <- from from_unique <- unique(from) - from_unique_translated <- from_unique + from_split_combined <- function(vec) { + sapply(vec, function(x) { + if (grepl("/", x, fixed = TRUE)) { + parts <- strsplit(x, "/", fixed = TRUE)[[1]] + # Translate each part separately + translated_parts <- translate_into_language( + parts, + language = lang, + only_unknown = only_unknown, + only_affect_ab_names = only_affect_ab_names, + only_affect_mo_names = only_affect_mo_names + ) + paste(translated_parts, collapse = "/") + } else { + x + } + }, USE.NAMES = FALSE) + } + from_unique_translated <- from_split_combined(from_unique) # only keep lines where translation is available for this language df_trans <- df_trans[which(!is.na(df_trans[, lang, drop = TRUE])), , drop = FALSE] diff --git a/man/antibiogram.Rd b/man/antibiogram.Rd index 76d0481ef..d55e2d74e 100644 --- a/man/antibiogram.Rd +++ b/man/antibiogram.Rd @@ -23,16 +23,17 @@ antibiogram(x, antimicrobials = where(is.sir), mo_transform = "shortname", only_all_tested = FALSE, digits = ifelse(wisca, 1, 0), formatting_type = getOption("AMR_antibiogram_formatting_type", ifelse(wisca, 14, 18)), col_mo = NULL, language = get_AMR_locale(), - minimum = 30, combine_SI = TRUE, sep = " + ", wisca = FALSE, - simulations = 1000, conf_interval = 0.95, interval_side = "two-tailed", - info = interactive(), ...) + minimum = 30, combine_SI = TRUE, sep = " + ", sort_columns = TRUE, + wisca = FALSE, simulations = 1000, conf_interval = 0.95, + interval_side = "two-tailed", info = interactive(), ...) wisca(x, antimicrobials = where(is.sir), ab_transform = "name", syndromic_group = NULL, only_all_tested = FALSE, digits = 1, formatting_type = getOption("AMR_antibiogram_formatting_type", 14), col_mo = NULL, language = get_AMR_locale(), combine_SI = TRUE, - sep = " + ", simulations = 1000, conf_interval = 0.95, - interval_side = "two-tailed", info = interactive(), ...) + sep = " + ", sort_columns = TRUE, simulations = 1000, + conf_interval = 0.95, interval_side = "two-tailed", + info = interactive(), ...) retrieve_wisca_parameters(wisca_model, ...) @@ -90,6 +91,8 @@ retrieve_wisca_parameters(wisca_model, ...) \item{sep}{A separating character for antimicrobial columns in combination antibiograms.} +\item{sort_columns}{A \link{logical} to indicate whether the antimicrobial columns must be sorted on name.} + \item{wisca}{A \link{logical} to indicate whether a Weighted-Incidence Syndromic Combination Antibiogram (WISCA) must be generated (default is \code{FALSE}). This will use a Bayesian decision model to estimate regimen coverage probabilities using \href{https://en.wikipedia.org/wiki/Monte_Carlo_method}{Monte Carlo simulations}. Set \code{simulations}, \code{conf_interval}, and \code{interval_side} to adjust.} \item{simulations}{(for WISCA) a numerical value to set the number of Monte Carlo simulations.} diff --git a/tests/testthat/test-ab_property.R b/tests/testthat/test-ab_property.R index 0cd165cad..559d4cd8d 100644 --- a/tests/testthat/test-ab_property.R +++ b/tests/testthat/test-ab_property.R @@ -33,7 +33,7 @@ test_that("test-ab_property.R", { ab_reset_session() expect_identical(ab_name("AMX", language = NULL), "Amoxicillin") - expect_identical(ab_atc("AMX"), "J01CA04") + expect_identical(ab_atc("AMX"), c("J01CA04", "QG51AA03", "QJ01CA04")) expect_identical(ab_cid("AMX"), as.integer(33613)) expect_inherits(ab_tradenames("AMX"), "character") diff --git a/tests/testthat/test-atc_online.R b/tests/testthat/test-atc_online.R index d35ee9ddc..7f5680c85 100644 --- a/tests/testthat/test-atc_online.R +++ b/tests/testthat/test-atc_online.R @@ -35,8 +35,8 @@ test_that("test-atc_online.R", { AMR:::pkg_is_available("xml2") && tryCatch(curl::has_internet(), error = function(e) FALSE)) { expect_true(length(atc_online_groups(ab_atc("AMX"))) >= 1) - expect_equal(atc_online_ddd(ab_atc("AMX"), administration = "O"), 1.5) - expect_equal(atc_online_ddd(ab_atc("AMX"), administration = "P"), 3) + expect_equal(atc_online_ddd(ab_atc("AMX", only_first = TRUE), administration = "O"), 1.5) + expect_equal(atc_online_ddd(ab_atc("AMX", only_first = TRUE), administration = "P"), 3) expect_equal(atc_online_ddd_units("AMX", administration = "P"), "g") } }) diff --git a/tests/testthat/test-data.R b/tests/testthat/test-data.R index 181bfc8f5..8e0b3fa87 100644 --- a/tests/testthat/test-data.R +++ b/tests/testthat/test-data.R @@ -34,7 +34,6 @@ test_that("test-data.R", { expect_identical(nrow(microorganisms), length(unique(microorganisms$mo))) expect_identical(class(microorganisms$mo), c("mo", "character")) expect_identical(nrow(antimicrobials), length(unique(AMR::antimicrobials$ab))) - expect_true(all(is.na(AMR::antimicrobials$atc[duplicated(AMR::antimicrobials$atc)]))) expect_identical(class(AMR::antimicrobials$ab), c("ab", "character")) diff --git a/vignettes/AMR.Rmd b/vignettes/AMR.Rmd index 390557c1f..de64a7cc2 100755 --- a/vignettes/AMR.Rmd +++ b/vignettes/AMR.Rmd @@ -1,11 +1,11 @@ --- -title: "How to conduct AMR data analysis" +title: "Conduct AMR data analysis" output: rmarkdown::html_vignette: toc: true toc_depth: 3 vignette: > - %\VignetteIndexEntry{How to conduct AMR data analysis} + %\VignetteIndexEntry{Conduct AMR data analysis} %\VignetteEncoding{UTF-8} %\VignetteEngine{knitr::rmarkdown} editor_options: diff --git a/vignettes/AMR_with_tidymodels.Rmd b/vignettes/AMR_with_tidymodels.Rmd index db63455f4..eca38f93f 100644 --- a/vignettes/AMR_with_tidymodels.Rmd +++ b/vignettes/AMR_with_tidymodels.Rmd @@ -22,7 +22,7 @@ knitr::opts_chunk$set( ) ``` -> This page was entirely written by our [AMR for R Assistant](https://chat.amr-for-r.org), a ChatGPT manually-trained model able to answer any question about the AMR package. +> This page was entirely written by our [AMR for R Assistant](https://chat.amr-for-r.org), a ChatGPT manually-trained model able to answer any question about the `AMR` package. Antimicrobial resistance (AMR) is a global health crisis, and understanding resistance patterns is crucial for managing effective treatments. The `AMR` R package provides robust tools for analysing AMR data, including convenient antimicrobial selector functions like `aminoglycosides()` and `betalactams()`. diff --git a/vignettes/EUCAST.Rmd b/vignettes/EUCAST.Rmd index e6fea7c7a..78627f2fd 100644 --- a/vignettes/EUCAST.Rmd +++ b/vignettes/EUCAST.Rmd @@ -1,11 +1,11 @@ --- -title: "How to apply EUCAST rules" +title: "Apply EUCAST rules" output: rmarkdown::html_vignette: toc: true toc_depth: 3 vignette: > - %\VignetteIndexEntry{How to apply EUCAST rules} + %\VignetteIndexEntry{Apply EUCAST rules} %\VignetteEncoding{UTF-8} %\VignetteEngine{knitr::rmarkdown} editor_options: diff --git a/vignettes/MDR.Rmd b/vignettes/MDR.Rmd index 12e820611..63435a363 100644 --- a/vignettes/MDR.Rmd +++ b/vignettes/MDR.Rmd @@ -1,10 +1,10 @@ --- -title: "How to determine multi-drug resistance (MDR)" +title: "Determine multi-drug resistance (MDR)" output: rmarkdown::html_vignette: toc: true vignette: > - %\VignetteIndexEntry{How to determine multi-drug resistance (MDR)} + %\VignetteIndexEntry{Determine multi-drug resistance (MDR)} %\VignetteEncoding{UTF-8} %\VignetteEngine{knitr::rmarkdown} editor_options: diff --git a/vignettes/PCA.Rmd b/vignettes/PCA.Rmd index 0dc8103b2..4cd5e4cde 100755 --- a/vignettes/PCA.Rmd +++ b/vignettes/PCA.Rmd @@ -1,11 +1,11 @@ --- -title: "How to conduct principal component analysis (PCA) for AMR" +title: "Conduct principal component analysis (PCA) for AMR" output: rmarkdown::html_vignette: toc: true toc_depth: 3 vignette: > - %\VignetteIndexEntry{How to conduct principal component analysis (PCA) for AMR} + %\VignetteIndexEntry{Conduct principal component analysis (PCA) for AMR} %\VignetteEncoding{UTF-8} %\VignetteEngine{knitr::rmarkdown} editor_options: diff --git a/vignettes/WHONET.Rmd b/vignettes/WHONET.Rmd index 1aca7e582..ec0cf2465 100644 --- a/vignettes/WHONET.Rmd +++ b/vignettes/WHONET.Rmd @@ -1,11 +1,11 @@ --- -title: "How to work with WHONET data" +title: "Work with WHONET data" output: rmarkdown::html_vignette: toc: true toc_depth: 3 vignette: > - %\VignetteIndexEntry{How to work with WHONET data} + %\VignetteIndexEntry{Work with WHONET data} %\VignetteEncoding{UTF-8} %\VignetteEngine{knitr::rmarkdown} editor_options: diff --git a/vignettes/WISCA.Rmd b/vignettes/WISCA.Rmd index e3d31b12b..14d41ae1e 100644 --- a/vignettes/WISCA.Rmd +++ b/vignettes/WISCA.Rmd @@ -22,202 +22,187 @@ knitr::opts_chunk$set( ) ``` +> This explainer was largely written by our [AMR for R Assistant](https://chat.amr-for-r.org), a ChatGPT manually-trained model able to answer any question about the `AMR` package. + ## Introduction Clinical guidelines for empirical antimicrobial therapy require *probabilistic reasoning*: what is the chance that a regimen will cover the likely infecting organisms, before culture results are available? -This is the purpose of **WISCA**, or: - -> **Weighted-Incidence Syndromic Combination Antibiogram** +This is the purpose of **WISCA**, or **Weighted-Incidence Syndromic Combination Antibiogram**. WISCA is a Bayesian approach that integrates: + - **Pathogen prevalence** (how often each species causes the syndrome), - **Regimen susceptibility** (how often a regimen works *if* the pathogen is known), -to estimate the **overall empirical coverage** of antimicrobial regimens — with quantified uncertainty. +to estimate the **overall empirical coverage** of antimicrobial regimens, with quantified uncertainty. -This vignette explains how WISCA works, why it is useful, and how to apply it in **AMR**. - ---- +This vignette explains how WISCA works, why it is useful, and how to apply it using the `AMR` package. ## Why traditional antibiograms fall short A standard antibiogram gives you: -``` Species → Antibiotic → Susceptibility % +``` +Species → Antibiotic → Susceptibility % +``` -But clinicians don’t know the species *a priori*. They need to choose a regimen that covers the **likely pathogens** — without knowing which one is present. +But clinicians don’t know the species *a priori*. They need to choose a regimen that covers the **likely pathogens**, without knowing which one is present. + +Traditional antibiograms calculate the susceptibility % as just the number of resistant isolates divided by the total number of tested isolates. Therefore, traditional antibiograms: -Traditional antibiograms: - Fragment information by organism, - Do not weight by real-world prevalence, - Do not account for combination therapy or sample size, - Do not provide uncertainty. ---- - ## The idea of WISCA WISCA asks: -> “What is the **probability** that this regimen **will cover** the pathogen, given the syndrome?” +> "What is the **probability** that this regimen **will cover** the pathogen, given the syndrome?" This means combining two things: + - **Incidence** of each pathogen in the syndrome, - **Susceptibility** of each pathogen to the regimen. We can write this as: -``` coverage = ∑ (pathogen incidence × susceptibility) +$$\text{Coverage} = \sum_i (\text{Incidence}_i \times \text{Susceptibility}_i)$$ For example, suppose: -- E. coli causes 60% of cases, and 90% of *E. coli* are susceptible to a drug. -- Klebsiella causes 40% of cases, and 70% of *Klebsiella* are susceptible. + +- *E. coli* causes 60% of cases, and 90% of *E. coli* are susceptible to a drug. +- *Klebsiella* causes 40% of cases, and 70% of *Klebsiella* are susceptible. Then: -``` coverage = (0.6 × 0.9) + (0.4 × 0.7) = 0.82 +$$\text{Coverage} = (0.6 \times 0.9) + (0.4 \times 0.7) = 0.82$$ -But in real data, incidence and susceptibility are **estimated from samples** — so they carry uncertainty. WISCA models this **probabilistically**, using conjugate Bayesian distributions. - ---- +But in real data, incidence and susceptibility are **estimated from samples**, so they carry uncertainty. WISCA models this **probabilistically**, using conjugate Bayesian distributions. ## The Bayesian engine behind WISCA ### Pathogen incidence Let: -- K be the number of pathogens, -- ``` α = (1, 1, ..., 1) be a **Dirichlet** prior (uniform), -- ``` n = (n₁, ..., nₖ) be the observed counts per species. -Then the posterior incidence follows: +- $K$ be the number of pathogens, +- $\alpha = (1, 1, \ldots, 1)$ be a **Dirichlet** prior (uniform), +- $n = (n_1, \ldots, n_K)$ be the observed counts per species. -``` incidence ∼ Dirichlet(α + n) +Then the posterior incidence is: -In simulations, we draw from this posterior using: +$$p \sim \text{Dirichlet}(\alpha_1 + n_1, \ldots, \alpha_K + n_K)$$ -``` xᵢ ∼ Gamma(αᵢ + nᵢ, 1) +To simulate from this, we use: -``` incidenceᵢ = xᵢ / ∑ xⱼ - ---- +$$x_i \sim \text{Gamma}(\alpha_i + n_i,\ 1), \quad p_i = \frac{x_i}{\sum_{j=1}^{K} x_j}$$ ### Susceptibility -Each pathogen–regimen pair has: -- ``` prior: Beta(1, 1) -- ``` data: S susceptible out of N tested +Each pathogen–regimen pair has a prior and data: -Then: +- Prior: $\text{Beta}(\alpha_0, \beta_0)$, with default $\alpha_0 = \beta_0 = 1$ +- Data: $S$ susceptible out of $N$ tested -``` susceptibility ∼ Beta(1 + S, 1 + (N - S)) +The $S$ category could also include values SDD (susceptible, dose-dependent) and I (intermediate [CLSI], or susceptible, increased exposure [EUCAST]). -In each simulation, we draw random susceptibility per species from this Beta distribution. +Then the posterior is: ---- +$$\theta \sim \text{Beta}(\alpha_0 + S,\ \beta_0 + N - S)$$ ### Final coverage estimate Putting it together: -``` For each simulation: - - Draw incidence ∼ Dirichlet - - Draw susceptibility ∼ Beta - - Multiply → coverage estimate +1. Simulate pathogen incidence: $\boldsymbol{p} \sim \text{Dirichlet}$ +2. Simulate susceptibility: $\theta_i \sim \text{Beta}(1 + S_i,\ 1 + R_i)$ +3. Combine: -We repeat this (e.g. 1000×) and summarise: -- **Mean**: expected coverage -- **Quantiles**: credible interval (default 95%) +$$\text{Coverage} = \sum_{i=1}^{K} p_i \cdot \theta_i$$ ---- +Repeat this simulation (e.g. 1000×) and summarise: -## Practical use in AMR +- **Mean** = expected coverage +- **Quantiles** = credible interval -### Simulate a synthetic syndrome +## Practical use in the `AMR` package + +### Prepare data and simulate synthetic syndrome ```{r} library(AMR) data <- example_isolates -# Add a fake syndrome column for stratification -data$syndrome <- ifelse(data$mo %like% "coli", "UTI", "Other") +# Structure of our data +data +# Add a fake syndrome column +data$syndrome <- ifelse(data$mo %like% "coli", "UTI", "No UTI") ``` ### Basic WISCA antibiogram ```{r} -antibiogram(data, - wisca = TRUE) +wisca(data, + antimicrobials = c("AMC", "CIP", "GEN")) +``` + +### Use combination regimens + +```{r} +wisca(data, + antimicrobials = c("AMC", "AMC + CIP", "AMC + GEN")) ``` ### Stratify by syndrome ```{r} -antibiogram(data, - syndromic_group = "syndrome", - wisca = TRUE) +wisca(data, + antimicrobials = c("AMC", "AMC + CIP", "AMC + GEN"), + syndromic_group = "syndrome") ``` -### Use combination regimens - -The `antibiogram()` function supports combination regimens: +The `AMR` package is available in `r length(AMR:::LANGUAGES_SUPPORTED)` languages, which can all be used for the `wisca()` function too: ```{r} -antibiogram(data, - antimicrobials = c("AMC", "GEN", "AMC + GEN", "CIP"), - wisca = TRUE) +wisca(data, + antimicrobials = c("AMC", "AMC + CIP", "AMC + GEN"), + syndromic_group = gsub("UTI", "UCI", data$syndrome), + language = "Spanish") ``` ---- -## Interpretation +## Sensible defaults, which can be customised -Suppose you get this output: - -| Regimen | Coverage | Lower_CI | Upper_CI | -|-------------|----------|----------|----------| -| AMC | 0.72 | 0.65 | 0.78 | -| AMC + GEN | 0.88 | 0.83 | 0.93 | - -Interpretation: - -> *“AMC + GEN covers 88% of expected pathogens for this syndrome, with 95% certainty that the true coverage lies between 83% and 93%.”* - -Regimens with few tested isolates will show **wider intervals**. - ---- - -## Sensible defaults, but you can customise - -- `minimum = 30`: exclude regimens with <30 isolates tested. -- `simulations = 1000`: number of Monte Carlo samples. -- `conf_interval = 0.95`: coverage interval width. -- `combine_SI = TRUE`: count “I”/“SDD” as susceptible. - ---- +- `simulations = 1000`: number of Monte Carlo draws +- `conf_interval = 0.95`: coverage interval width +- `combine_SI = TRUE`: count "I" and "SDD" as susceptible ## Limitations -- WISCA does not model time trends or temporal resistance shifts. -- It assumes data are representative of current clinical practice. -- It does not account for patient-level covariates (yet). -- Species-specific data are abstracted into syndrome-level estimates. +- It assumes your data are representative +- No adjustment for patient-level covariates, although these could be passed onto the `syndromic_group` argument +- WISCA does not model resistance over time, you might want to use `tidymodels` for that, for which we [wrote a basic introduction](https://amr-for-r.org/articles/AMR_with_tidymodels.html) ---- +## Summary + +WISCA enables: + +- Empirical regimen comparison, +- Syndrome-specific coverage estimation, +- Fully probabilistic interpretation. + +It is available in the `AMR` package via either: + +```r +wisca(...) + +antibiogram(..., wisca = TRUE) +``` ## Reference -Bielicki JA et al. (2016). -*Weighted-incidence syndromic combination antibiograms to guide empiric treatment in pediatric bloodstream infections.* -**J Antimicrob Chemother**, 71(2):529–536. doi:10.1093/jac/dkv397 - ---- - -## Conclusion - -WISCA shifts empirical therapy from simple percent susceptible toward **probabilistic, syndrome-based decision support**. It is a statistically principled, clinically intuitive method to guide regimen selection — and easy to use via the `antibiogram()` function in the **AMR** package. - -For antimicrobial stewardship teams, it enables **disease-specific, reproducible, and data-driven guidance** — even in the face of sparse data. - +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.* **J Antimicrob Chemother**. 71(3):794-802. https://doi.org/10.1093/jac/dkv397 diff --git a/vignettes/datasets.Rmd b/vignettes/datasets.Rmd index 5ce8b0329..1e65b6c37 100644 --- a/vignettes/datasets.Rmd +++ b/vignettes/datasets.Rmd @@ -1,12 +1,12 @@ --- -title: "Data sets for download / own use" +title: "Download data sets for download / own use" date: '`r format(Sys.Date(), "%d %B %Y")`' output: rmarkdown::html_vignette: toc: true toc_depth: 1 vignette: > - %\VignetteIndexEntry{Data sets for download / own use} + %\VignetteIndexEntry{Download data sets for download / own use} %\VignetteEncoding{UTF-8} %\VignetteEngine{knitr::rmarkdown} editor_options: