Merge remote-tracking branch 'origin/main' into breakpoints

This commit is contained in:
dr. M.S. (Matthijs) Berends committed 2026-11-02 18:26:08 +01:00
commit d904ea5afb
23 files changed
+288 -83

No files matched your search

+2 -1
View File
@@ -57,7 +57,8 @@ jobs:
# - {os: ubuntu-latest, r: '3.3', allowfail: false}
# - {os: ubuntu-latest, r: '3.2', allowfail: false}
# - {os: ubuntu-latest, r: '3.1', allowfail: false}
- {os: ubuntu-latest, r: '3.0', allowfail: false}
# R 3.0 cannot be resolved for ubuntu-latest (Ubuntu 26.04), so use Ubuntu 24.04
- {os: ubuntu-24.04, r: '3.0', allowfail: false}
env:
R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
+2 -1
View File
@@ -48,7 +48,8 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
# Python 3.9 is not available for ubuntu-latest (Ubuntu 26.04)
python-version: '3.12'
- name: Install build dependencies
run: |
+2 -2
View File
@@ -1,6 +1,6 @@
Package: AMR
Version: 3.1.0.9006
Date: 2026-10-23
Version: 3.1.0.9014
Date: 2026-10-30
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
+13 -1
View File
@@ -1,4 +1,16 @@
# AMR 3.1.0.9006
# AMR 3.1.0.9014
This version fixes a critical bug in WISCA that biased all coverage estimates of `wisca()` and `antibiogram(..., wisca = TRUE)` downwards since AMR 3.0.0. Users are strongly advised to re-run their WISCA analyses.
### Changed
* WISCA (`wisca()` and `antibiogram(..., wisca = TRUE)`)
* Fixed a critical bug that systematically underestimated the coverage of every WISCA since AMR 3.0.0. Pathogens absent from a syndromic group, and pathogens without any test result for a regimen, were still included in the model with only prior information: each added a prior weight of 1 to the pathogen incidence, and untested pathogens kept their full incidence weight while counting as about 50% susceptible. The bias was largest for stratified analyses (e.g., up to 14 percentage points for meropenem in random strata of `example_isolates`). Now, only pathogens that occur in a group and have at least one test result for the regimen are included. Please re-run all WISCA analyses (#337)
* `retrieve_wisca_parameters()` now returns only the parameters actually used in the model, and `n_total` in the `long_numeric` attribute now counts only isolates of the pathogens included for that regimen
* A new warning is given when the prior weight of 1 per pathogen (Bielicki *et al.*, 2016) accounts for more than 5% of the pathogen incidence, i.e., with rare pathogens or small groups, as this can strongly lower the estimated coverage; consider `top_n_microorganisms()` or fewer and larger groups in that case
* Microorganism coercion (`as.mo()`)
* Fixed the misaligned arrows in the message on taxonomically renamed microorganisms in colour-supporting consoles, for old names whose author reference lacks "*et al.*" (#336)
* `intrinsic_resistant` data set
* Species groups and complexes are now listed for every antimicrobial to which all their species are intrinsically resistant. This adds 62 bug-drug combinations for the *Burkholderia cepacia* complex, coryneform bacteria, the *Enterobacter cloacae* complex (e.g., amoxicillin and amoxicillin/clavulanic acid), the *Klebsiella pneumoniae* complex, and coagulase-negative staphylococci, which affects `mo_is_intrinsic_resistant()`, `not_intrinsic_resistant()`, and the priors of `wisca()` (#338)
### Other
* The manual index on the website was rewritten to improve findability, with sections ordered by task and a filter field
+29 -6
View File
@@ -156,6 +156,10 @@
#'
#' **Prior distributions:** Pathogen incidence uses a non-informative \eqn{Dirichlet(1, 1, \ldots, 1)} prior. Susceptibility proportions use the Jeffreys prior, \eqn{\beta(0.5, 0.5)}, except for pathogen-drug combinations with known intrinsic resistance, which use a strongly informative \eqn{\beta(1, 9999)} prior that forces near-zero susceptibility regardless of observed data. Intrinsic resistance is determined using the [intrinsic_resistant] data set, which is based on `r format_eucast_version_nr(names(EUCAST_VERSION_EXPECTED_PHENOTYPES[1]))`.
#'
#' **Pathogens included in the model:** For each regimen (and group), only pathogens that occur in the data and have at least one test result for the regimen are included. Pathogens without any test result for a regimen do not contribute to its coverage, nor to its `n_total` in `long_numeric`. Use [retrieve_wisca_parameters()] to get the parameters that were used.
#'
#' **Rare pathogens and small groups:** Since every pathogen receives a prior weight of 1 in the Dirichlet distribution, the prior weighs heavily when there are few isolates relative to the number of pathogens, i.e., with pathogens found only once or a few times, or in small groups. This pulls the incidence towards a uniform distribution, and the very uncertain susceptibility of rare pathogens can then strongly lower the estimated coverage, without reflecting the clinical expectation. A warning is therefore given when this prior weight accounts for more than 5% of the pathogen incidence in any regimen (or group). Consider limiting the analysis to the most common pathogens using [top_n_microorganisms()], or using fewer and larger groups.
#'
#' **Interpreting the output:** Overlapping credible intervals between regimens indicate no significant difference in coverage; if a narrower-spectrum regimen overlaps with a broader one, the narrower-spectrum option may be preferred on stewardship grounds. Non-overlapping intervals indicate a clinically meaningful difference. For small sample sizes, consider pooling data from multiple sites to improve precision, provided pathogen distributions are sufficiently similar (Bielicki *et al.*, 2016).
#'
#' For the full mathematical derivation and worked examples, see the [WISCA vignette](https://amr-for-r.org/articles/WISCA.html).
@@ -182,12 +186,13 @@
#'
#' # basic WISCA: empirical coverage per regimen, weighted by pathogen
#' # incidence, with 95% credible intervals
#' wisca(example_isolates,
#' # (10 most common species, since rare species can distort coverage estimates)
#' wisca(top_n_microorganisms(example_isolates, n = 10),
#' antimicrobials = c("AMC", "AMC+CIP", "AMC+GEN")
#' )
#'
#' # equivalent using antibiogram():
#' antibiogram(example_isolates,
#' antibiogram(top_n_microorganisms(example_isolates, n = 10),
#' antimicrobials = c("AMC", "AMC+CIP", "AMC+GEN"),
#' wisca = TRUE
#' )
@@ -272,7 +277,7 @@
#'
#' # Print the output for R Markdown / Quarto -----------------------------
#'
#' ureido <- wisca(example_isolates,
#' ureido <- wisca(top_n_microorganisms(example_isolates, n = 10),
#' antimicrobials = ureidopenicillins(),
#' syndromic_group = "ward"
#' )
@@ -290,7 +295,7 @@
#' antimicrobials = c("AMC", "CIP", "TZP", "TZP+TOB"),
#' mo_transform = "gramstain"
#' )
#' ab2 <- wisca(example_isolates,
#' ab2 <- wisca(top_n_microorganisms(example_isolates, n = 10),
#' antimicrobials = c("AMC", "CIP", "TZP", "TZP+TOB"),
#' syndromic_group = "ward"
#' )
@@ -744,7 +749,8 @@ antibiogram.default <- function(x,
coverage = NA_real_,
lower_ci = NA_real_,
upper_ci = NA_real_,
n_total = sum(n_total, na.rm = TRUE),
# only pathogens with test results for the regimen enter the WISCA model, see below
n_total = sum(n_total[which(n_tested > 0)]),
n_tested = sum(n_tested, na.rm = TRUE),
n_susceptible = sum(n_susceptible, na.rm = TRUE)
)
@@ -761,7 +767,24 @@ antibiogram.default <- function(x,
out_wisca$group <- out_wisca$ab
}
wisca_parameters <- out
# only pathogens that occur in the group (n_total > 0) and have at least one test result for the regimen (n_tested > 0) may contribute to WISCA; otherwise, absent or untested pathogens would enter with prior-only parameters and bias the coverage downwards (#337)
wisca_parameters <- out[which(out$n_total > 0 & out$n_tested > 0), , drop = FALSE]
# the Dirichlet prior of 1 per pathogen (Bielicki et al., 2016) weighs heavily when many pathogens are rare
prior_share <- vapply(
split(wisca_parameters$n_total, as.character(wisca_parameters$group)),
function(n) length(n) / (length(n) + sum(n)),
FUN.VALUE = numeric(1)
)
if (any(prior_share > 0.05)) {
warning_(
"In ", sum(prior_share > 0.05), " of ", length(prior_share), " WISCA regimen(s) or group(s), the prior weight of 1 per pathogen accounts for more than 5% of the pathogen incidence (up to ", round(max(prior_share) * 100, 1), "%), since there are few isolates relative to the number of pathogens, i.e., rare pathogens or a small group. ",
"Because WISCA gives every pathogen a prior weight of 1 (Bielicki et al., 2016), this can strongly lower the estimated coverage without reflecting the clinical expectation. ",
"Consider limiting the analysis to the most common pathogens with {.help [{.fun top_n_microorganisms}](AMR::top_n_microorganisms)}, or using fewer and larger groups.",
call = FALSE
)
}
wisca_draws <- list()
wisca_components <- list()
+4 -1
View File
@@ -1144,7 +1144,10 @@ print.mo_renamed <- function(x, extra_txt = "", n = 25, ...) {
rows <- seq_len(min(NROW(x), n))
message_("The following microorganism", ifelse(NROW(x) > 1, "s were", " was"), " taxonomically renamed", extra_txt, ":")
old_format <- format(paste0(font_italic(x$old[rows], collapse = NULL), x$ref_old[rows])) # format() will set trailing spaces for textual alignment
old_format <- paste0(font_italic(x$old[rows], collapse = NULL), x$ref_old[rows])
# pad based on the visible width, since format() counts ANSI codes as characters
visible_width <- nchar(font_stripstyle(old_format), type = "width")
old_format <- paste0(old_format, strrep(" ", max(visible_width) - visible_width))
old_format <- gsub(" ", "\u00a0", old_format, fixed = TRUE)
for (old_tax in rows) {
message_("\u00a0\u00a0", AMR_env$bullet_icon, " ", old_format[old_tax], " -> ", font_italic(x$new[old_tax]), x$ref_new[old_tax], as_note = FALSE)
@@ -58,6 +58,23 @@ int_resis3 <- int_resis2[which(!int_resis2$ab %in% untreatable), ]
int_resis3$ab <- as.ab(int_resis3$ab)
int_resis3
# add species groups (e.g., complexes) for every antimicrobial to which all their species are intrinsically resistant (#338)
groups <- microorganisms.groups[which(microorganisms$rank[match(microorganisms.groups$mo, microorganisms$mo)] == "species"), , drop = FALSE]
int_resis_keys <- paste(int_resis3$mo, int_resis3$ab)
groups_species <- split(as.character(groups$mo), as.character(groups$mo_group))
int_resis_groups <- do.call(rbind, Map(function(group, species) {
abx <- unique(as.character(int_resis3$ab[int_resis3$mo %in% species]))
abx <- abx[vapply(abx, function(ab) all(paste(species, ab) %in% int_resis_keys), logical(1))]
data.frame(mo = rep(group, length(abx)), ab = abx, stringsAsFactors = FALSE)
}, names(groups_species), groups_species))
int_resis_groups <- int_resis_groups[which(!paste(int_resis_groups$mo, int_resis_groups$ab) %in% int_resis_keys), , drop = FALSE]
int_resis_groups$mo <- as.mo(int_resis_groups$mo, keep_synonyms = TRUE)
int_resis_groups$ab <- as.ab(int_resis_groups$ab)
int_resis3 <- bind_rows(int_resis3, int_resis_groups)
# keep the order of the microorganisms and antimicrobials data sets
int_resis3 <- int_resis3[order(match(int_resis3$mo, microorganisms$mo), match(int_resis3$ab, antimicrobials$ab)), , drop = FALSE]
rownames(int_resis3) <- NULL
all(int_resis3$mo %in% microorganisms$mo)
all(int_resis3$ab %in% antimicrobials$ab)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+62
View File
@@ -31323,8 +31323,19 @@
"Burkholderia cepacia" "Zorbamycin"
"Burkholderia cepacia complex" "Acetylmidecamycin"
"Burkholderia cepacia complex" "Acetylspiramycin"
"Burkholderia cepacia complex" "Amikacin"
"Burkholderia cepacia complex" "Amikacin/fosfomycin"
"Burkholderia cepacia complex" "Amoxicillin"
"Burkholderia cepacia complex" "Amoxicillin/clavulanic acid"
"Burkholderia cepacia complex" "Ampicillin"
"Burkholderia cepacia complex" "Ampicillin/sulbactam"
"Burkholderia cepacia complex" "Apramycin"
"Burkholderia cepacia complex" "Arbekacin"
"Burkholderia cepacia complex" "Astromicin"
"Burkholderia cepacia complex" "Avoparcin"
"Burkholderia cepacia complex" "Azithromycin"
"Burkholderia cepacia complex" "Aztreonam"
"Burkholderia cepacia complex" "Bekanamycin"
"Burkholderia cepacia complex" "Benzylpenicillin"
"Burkholderia cepacia complex" "Bleomycin"
"Burkholderia cepacia complex" "Cadazolid"
@@ -31342,54 +31353,87 @@
"Burkholderia cepacia complex" "Cefmetazole"
"Burkholderia cepacia complex" "Cefonicid"
"Burkholderia cepacia complex" "Ceforanide"
"Burkholderia cepacia complex" "Cefotaxime"
"Burkholderia cepacia complex" "Cefotetan"
"Burkholderia cepacia complex" "Cefotiam"
"Burkholderia cepacia complex" "Cefoxitin"
"Burkholderia cepacia complex" "Cefprozil"
"Burkholderia cepacia complex" "Cefroxadine"
"Burkholderia cepacia complex" "Ceftezole"
"Burkholderia cepacia complex" "Ceftriaxone"
"Burkholderia cepacia complex" "Cefuroxime"
"Burkholderia cepacia complex" "Cefuroxime axetil"
"Burkholderia cepacia complex" "Cephradine"
"Burkholderia cepacia complex" "Chloramphenicol"
"Burkholderia cepacia complex" "Ciprofloxacin"
"Burkholderia cepacia complex" "Clarithromycin"
"Burkholderia cepacia complex" "Clindamycin"
"Burkholderia cepacia complex" "Colistin"
"Burkholderia cepacia complex" "Cycloserine"
"Burkholderia cepacia complex" "Dalbavancin"
"Burkholderia cepacia complex" "Dibekacin"
"Burkholderia cepacia complex" "Dirithromycin"
"Burkholderia cepacia complex" "Ertapenem"
"Burkholderia cepacia complex" "Erythromycin"
"Burkholderia cepacia complex" "Flurithromycin"
"Burkholderia cepacia complex" "Fosfomycin"
"Burkholderia cepacia complex" "Framycetin"
"Burkholderia cepacia complex" "Fusidic acid"
"Burkholderia cepacia complex" "Gamithromycin"
"Burkholderia cepacia complex" "Gentamicin"
"Burkholderia cepacia complex" "Habekacin"
"Burkholderia cepacia complex" "Hygromycin"
"Burkholderia cepacia complex" "Isepamicin"
"Burkholderia cepacia complex" "Josamycin"
"Burkholderia cepacia complex" "Kanamycin"
"Burkholderia cepacia complex" "Kanamycin/cephalexin"
"Burkholderia cepacia complex" "Kasugamycin"
"Burkholderia cepacia complex" "Kitasamycin"
"Burkholderia cepacia complex" "Lincomycin"
"Burkholderia cepacia complex" "Linezolid"
"Burkholderia cepacia complex" "Loracarbef"
"Burkholderia cepacia complex" "Meleumycin"
"Burkholderia cepacia complex" "Micronomicin"
"Burkholderia cepacia complex" "Midecamycin"
"Burkholderia cepacia complex" "Miocamycin"
"Burkholderia cepacia complex" "Nafithromycin"
"Burkholderia cepacia complex" "Neomycin"
"Burkholderia cepacia complex" "Netilmicin"
"Burkholderia cepacia complex" "Norvancomycin"
"Burkholderia cepacia complex" "Oleandomycin"
"Burkholderia cepacia complex" "Oritavancin"
"Burkholderia cepacia complex" "Ostreogrycin"
"Burkholderia cepacia complex" "Pentisomicin"
"Burkholderia cepacia complex" "Piperacillin"
"Burkholderia cepacia complex" "Piperacillin/tazobactam"
"Burkholderia cepacia complex" "Pirlimycin"
"Burkholderia cepacia complex" "Plazomicin"
"Burkholderia cepacia complex" "Polymyxin B"
"Burkholderia cepacia complex" "Primycin"
"Burkholderia cepacia complex" "Pristinamycin"
"Burkholderia cepacia complex" "Propikacin"
"Burkholderia cepacia complex" "Quinupristin/dalfopristin"
"Burkholderia cepacia complex" "Ramoplanin"
"Burkholderia cepacia complex" "Ribostamycin"
"Burkholderia cepacia complex" "Rifampicin"
"Burkholderia cepacia complex" "Rokitamycin"
"Burkholderia cepacia complex" "Roxithromycin"
"Burkholderia cepacia complex" "Sisomicin"
"Burkholderia cepacia complex" "Solithromycin"
"Burkholderia cepacia complex" "Spiramycin"
"Burkholderia cepacia complex" "Streptoduocin"
"Burkholderia cepacia complex" "Streptomycin"
"Burkholderia cepacia complex" "Tedizolid"
"Burkholderia cepacia complex" "Teicoplanin"
"Burkholderia cepacia complex" "Telavancin"
"Burkholderia cepacia complex" "Telithromycin"
"Burkholderia cepacia complex" "Thiacetazone"
"Burkholderia cepacia complex" "Ticarcillin"
"Burkholderia cepacia complex" "Ticarcillin/clavulanic acid"
"Burkholderia cepacia complex" "Tildipirosin"
"Burkholderia cepacia complex" "Tilmicosin"
"Burkholderia cepacia complex" "Tobramycin"
"Burkholderia cepacia complex" "Trimethoprim"
"Burkholderia cepacia complex" "Troleandomycin"
"Burkholderia cepacia complex" "Tulathromycin"
"Burkholderia cepacia complex" "Tylosin"
@@ -65522,6 +65566,7 @@
"Cnuibacter physcomitrellae" "Polymyxin B"
"Cnuibacter physcomitrellae" "Temocillin"
"Coagulase-negative Staphylococcus (CoNS)" "Aztreonam"
"Coagulase-negative Staphylococcus (CoNS)" "Ceftazidime"
"Coagulase-negative Staphylococcus (CoNS)" "Colistin"
"Coagulase-negative Staphylococcus (CoNS)" "Nalidixic acid"
"Coagulase-negative Staphylococcus (CoNS)" "Polymyxin B"
@@ -67762,6 +67807,11 @@
"Corynebacterium zhongnanshanii" "Nalidixic acid"
"Corynebacterium zhongnanshanii" "Polymyxin B"
"Corynebacterium zhongnanshanii" "Temocillin"
"Coryneform bacterium" "Aztreonam"
"Coryneform bacterium" "Colistin"
"Coryneform bacterium" "Nalidixic acid"
"Coryneform bacterium" "Polymyxin B"
"Coryneform bacterium" "Temocillin"
"Cosenzaea" "Acetylmidecamycin"
"Cosenzaea" "Acetylspiramycin"
"Cosenzaea" "Avoparcin"
@@ -76494,11 +76544,20 @@
"Enterobacter cloacae cloacae" "Zorbamycin"
"Enterobacter cloacae complex" "Acetylmidecamycin"
"Enterobacter cloacae complex" "Acetylspiramycin"
"Enterobacter cloacae complex" "Amoxicillin"
"Enterobacter cloacae complex" "Amoxicillin/clavulanic acid"
"Enterobacter cloacae complex" "Ampicillin"
"Enterobacter cloacae complex" "Ampicillin/sulbactam"
"Enterobacter cloacae complex" "Avoparcin"
"Enterobacter cloacae complex" "Azithromycin"
"Enterobacter cloacae complex" "Benzylpenicillin"
"Enterobacter cloacae complex" "Bleomycin"
"Enterobacter cloacae complex" "Cadazolid"
"Enterobacter cloacae complex" "Cefadroxil"
"Enterobacter cloacae complex" "Cefalexin"
"Enterobacter cloacae complex" "Cefalotin"
"Enterobacter cloacae complex" "Cefazolin"
"Enterobacter cloacae complex" "Cefoxitin"
"Enterobacter cloacae complex" "Clarithromycin"
"Enterobacter cloacae complex" "Clindamycin"
"Enterobacter cloacae complex" "Cycloserine"
@@ -102515,6 +102574,8 @@
"Klebsiella pneumoniae" "Zorbamycin"
"Klebsiella pneumoniae complex" "Acetylmidecamycin"
"Klebsiella pneumoniae complex" "Acetylspiramycin"
"Klebsiella pneumoniae complex" "Amoxicillin"
"Klebsiella pneumoniae complex" "Ampicillin"
"Klebsiella pneumoniae complex" "Avoparcin"
"Klebsiella pneumoniae complex" "Azithromycin"
"Klebsiella pneumoniae complex" "Benzylpenicillin"
@@ -102556,6 +102617,7 @@
"Klebsiella pneumoniae complex" "Telavancin"
"Klebsiella pneumoniae complex" "Telithromycin"
"Klebsiella pneumoniae complex" "Thiacetazone"
"Klebsiella pneumoniae complex" "Ticarcillin"
"Klebsiella pneumoniae complex" "Tildipirosin"
"Klebsiella pneumoniae complex" "Tilmicosin"
"Klebsiella pneumoniae complex" "Troleandomycin"
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
b32da210529081a8e0ab8874956739d1
e17c4cf3ac9495a9ed832b64da96770a
Binary file not shown.
+30 -28
View File
@@ -70,15 +70,15 @@ With the help of contributors from all corners of the world, the `AMR` package i
One of the most powerful functions of this package, aside from calculating and plotting AMR, is selecting and filtering based on antimicrobial columns. This can be done using the so-called [antimicrobial selectors](https://amr-for-r.org/reference/antimicrobial_selectors.html), which work in base R, `dplyr` and `data.table`.
```{r}
# AMR works great with dplyr, but it's not required or neccesary
# AMR works great with dplyr, but it's not required or necessary
library(AMR)
library(dplyr, warn.conflicts = FALSE)
example_isolates %>%
mutate(bacteria = mo_fullname()) %>%
example_isolates |>
mutate(bacteria = mo_fullname()) |>
# filtering functions for microorganisms:
filter(mo_is_gram_negative(),
mo_is_intrinsic_resistant(ab = "cefotax")) %>%
mo_is_intrinsic_resistant(ab = "cefotax")) |>
# antimicrobial selectors:
select(bacteria,
aminoglycosides(),
@@ -94,44 +94,46 @@ The `AMR` package supports four types of antibiograms, with support for `r lengt
**For empirical therapy guidance (i.e., coverage estimates), use WISCA** (Weighted-Incidence Syndromic Combination Antibiogram). When a clinician starts empirical treatment, the causative pathogen is unknown. The relevant question is not *"what percentage of E. coli is susceptible?"* but *"what is the probability that this regimen will cover whatever pathogen is causing the infection?"*. WISCA answers that question directly, weighting susceptibility by pathogen incidence and providing credible intervals via Bayesian simulation. See `vignette("WISCA")` for the full explanation.
```{r}
wisca(example_isolates,
antimicrobials = c("TZP", "TZP+TOB", "TZP+GEN"),
minimum = 10) # Recommended threshold: >=30
example_isolates |>
top_n_microorganisms(n = 10) |> # 10 most common species
wisca(antimicrobials = c("TZP", "TZP+TOB", "TZP+GEN"),
minimum = 10) # Recommended threshold: >=30
```
WISCA supports stratification by any clinical variable, so you can generate syndrome-specific or ward-specific coverage estimates:
```{r}
wisca(example_isolates,
antimicrobials = c("TZP", "TZP+TOB", "TZP+GEN"),
syndromic_group = "ward",
minimum = 10) # Recommended threshold: >=30
example_isolates |>
top_n_microorganisms(n = 10) |>
wisca(antimicrobials = c("TZP", "TZP+TOB", "TZP+GEN"),
syndromic_group = "ward",
minimum = 10) # Recommended threshold: >=30
```
**For AMR surveillance**, traditional antibiograms remain the right tool for tracking resistance per species over time:
```{r}
antibiogram(example_isolates,
mo_transform = "gramstain",
antimicrobials = c("AMC", carbapenems(), "TZP"))
example_isolates |>
antibiogram(mo_transform = "gramstain",
antimicrobials = c("AMC", carbapenems(), "TZP"))
```
Combination antibiograms show the additional coverage gained by adding a second agent, stratified by species:
```{r}
antibiogram(example_isolates,
mo_transform = "gramstain",
antimicrobials = c("TZP", "TZP+TOB", "TZP+GEN"))
example_isolates |>
antibiogram(mo_transform = "gramstain",
antimicrobials = c("TZP", "TZP+TOB", "TZP+GEN"))
```
Like many other functions in this package, `antibiogram()` and `wisca()` come with support for `r length(AMR:::LANGUAGES_SUPPORTED)` languages that are often detected automatically based on system language:
```{r}
antibiogram(example_isolates,
antimicrobials = c("cipro", "tobra", "genta"), # any arbitrary name or code will work
mo_transform = "gramstain",
ab_transform = "name",
language = "uk") # Ukrainian
example_isolates |>
antibiogram(antimicrobials = c("cipro", "tobra", "genta"), # any arbitrary name or code will work
mo_transform = "gramstain",
ab_transform = "name",
language = "uk") # Ukrainian
```
### Interpreting and plotting MIC and SIR values
@@ -176,9 +178,9 @@ ggplot(data.frame(mic = some_mic_values,
For a manual approach, you can use the `resistance` or `susceptibility()` function:
```{r}
example_isolates %>%
example_isolates |>
# group by ward:
group_by(ward) %>%
group_by(ward) |>
# calculate AMR using resistance() for gentamicin and tobramycin
# and get their 95% confidence intervals using sir_confidence_interval():
summarise(across(c(GEN, TOB),
@@ -192,9 +194,9 @@ Or use [antimicrobial selectors](https://amr-for-r.org/reference/antimicrobial_s
library(AMR)
library(dplyr)
out <- example_isolates %>%
out <- example_isolates |>
# group by ward:
group_by(ward) %>%
group_by(ward) |>
# calculate AMR using resistance(), over all aminoglycosides and polymyxins:
summarise(across(c(aminoglycosides(), polymyxins()),
resistance))
@@ -203,12 +205,12 @@ out
```{r}
# transform the antibiotic columns to names:
out %>% set_ab_names()
out |> set_ab_names()
```
```{r}
# transform the antibiotic column to ATC codes:
out %>% set_ab_names(property = "atc")
out |> set_ab_names(property = "atc")
```
## What else can you do with this package?
+45 -36
View File
@@ -59,8 +59,8 @@ times](https://scholar.google.com/citations?view_op=view_citation&hl=en&citation
in scientific research.
After installing this package, R knows [**~103 000 distinct microbial
species**](./reference/microorganisms.html) (updated April 2026) and all
[**~630 antimicrobial and antiviral
species**](./reference/microorganisms.html) (updated October 2026) and
all [**~630 antimicrobial and antiviral
drugs**](./reference/antimicrobials.html) by name and code (including
ATC, EARS-Net, ASIARS-Net, PubChem, LOINC and SNOMED CT), and knows all
about valid SIR and MIC values. The integral clinical breakpoint
@@ -154,15 +154,15 @@ selectors](https://amr-for-r.org/reference/antimicrobial_selectors.html),
which work in base R, `dplyr` and `data.table`.
``` r
# AMR works great with dplyr, but it's not required or neccesary
# AMR works great with dplyr, but it's not required or necessary
library(AMR)
library(dplyr, warn.conflicts = FALSE)
example_isolates %>%
mutate(bacteria = mo_fullname()) %>%
example_isolates |>
mutate(bacteria = mo_fullname()) |>
# filtering functions for microorganisms:
filter(mo_is_gram_negative(),
mo_is_intrinsic_resistant(ab = "cefotax")) %>%
mo_is_intrinsic_resistant(ab = "cefotax")) |>
# antimicrobial selectors:
select(bacteria,
aminoglycosides(),
@@ -217,38 +217,46 @@ credible intervals via Bayesian simulation. See `vignette("WISCA")` for
the full explanation.
``` r
wisca(example_isolates,
antimicrobials = c("TZP", "TZP+TOB", "TZP+GEN"),
minimum = 10) # Recommended threshold: >=30
example_isolates |>
top_n_microorganisms(n = 10) |> # 10 most common species
wisca(antimicrobials = c("TZP", "TZP+TOB", "TZP+GEN"),
minimum = 10) # Recommended threshold: >=30
```
| Piperacillin/tazobactam | Piperacillin/tazobactam + Gentamicin | Piperacillin/tazobactam + Tobramycin |
|:------------------------|:-------------------------------------|:-------------------------------------|
| 70.2% (65-75.4%) | 93.6% (92-95.1%) | 89.9% (87-92.5%) |
| 73% (67.7-79%) | 98.7% (98-99.2%) | 93.8% (90.5-96.6%) |
WISCA supports stratification by any clinical variable, so you can
generate syndrome-specific or ward-specific coverage estimates:
``` r
wisca(example_isolates,
antimicrobials = c("TZP", "TZP+TOB", "TZP+GEN"),
syndromic_group = "ward",
minimum = 10) # Recommended threshold: >=30
example_isolates |>
top_n_microorganisms(n = 10) |>
wisca(antimicrobials = c("TZP", "TZP+TOB", "TZP+GEN"),
syndromic_group = "ward",
minimum = 10) # Recommended threshold: >=30
#> Warning: In 3 of 9 WISCA regimen(s) or group(s), the prior weight of 1 per pathogen accounts for more than
#> 5% of the pathogen incidence (up to 6.4%), since there are few isolates relative to the number of
#> pathogens, i.e., rare pathogens or a small group. Because WISCA gives every pathogen a prior weight
#> of 1 (Bielicki et al., 2016), this can strongly lower the estimated coverage without reflecting the
#> clinical expectation. Consider limiting the analysis to the most common pathogens with
#> `top_n_microorganisms()`, or using fewer and larger groups.
```
| Syndromic Group | Piperacillin/tazobactam | Piperacillin/tazobactam + Gentamicin | Piperacillin/tazobactam + Tobramycin |
|:----------------|:------------------------|:-------------------------------------|:-------------------------------------|
| Clinical | 74.8% (69-80.3%) | 93.7% (92.1-95.1%) | 90.4% (86.8-93.3%) |
| ICU | 56.7% (48.2-65.4%) | 86.6% (83.3-89.6%) | 83.1% (78.2-87.5%) |
| Outpatient | 57.6% (46-69%) | 76.4% (69.8-81.7%) | 68% (57.7-77.5%) |
| Clinical | 78.9% (72.8-85.6%) | 98.7% (97.8-99.3%) | 94.5% (90.3-97.6%) |
| ICU | 57.3% (48.9-69.5%) | 97.5% (95.7-98.8%) | 91.7% (85.8-96.3%) |
| Outpatient | 68% (49.4-88.5%) | 95.7% (90.7-98.7%) | 79.3% (62.7-93.8%) |
**For AMR surveillance**, traditional antibiograms remain the right tool
for tracking resistance per species over time:
``` r
antibiogram(example_isolates,
mo_transform = "gramstain",
antimicrobials = c("AMC", carbapenems(), "TZP"))
example_isolates |>
antibiogram(mo_transform = "gramstain",
antimicrobials = c("AMC", carbapenems(), "TZP"))
#> ℹ For `carbapenems()` using columns IPM (imipenem) and MEM (meropenem)
```
@@ -261,9 +269,9 @@ Combination antibiograms show the additional coverage gained by adding a
second agent, stratified by species:
``` r
antibiogram(example_isolates,
mo_transform = "gramstain",
antimicrobials = c("TZP", "TZP+TOB", "TZP+GEN"))
example_isolates |>
antibiogram(mo_transform = "gramstain",
antimicrobials = c("TZP", "TZP+TOB", "TZP+GEN"))
```
| Pathogen | Piperacillin/tazobactam | Piperacillin/tazobactam + Gentamicin | Piperacillin/tazobactam + Tobramycin |
@@ -276,11 +284,11 @@ come with support for 28 languages that are often detected automatically
based on system language:
``` r
antibiogram(example_isolates,
antimicrobials = c("cipro", "tobra", "genta"), # any arbitrary name or code will work
mo_transform = "gramstain",
ab_transform = "name",
language = "uk") # Ukrainian
example_isolates |>
antibiogram(antimicrobials = c("cipro", "tobra", "genta"), # any arbitrary name or code will work
mo_transform = "gramstain",
ab_transform = "name",
language = "uk") # Ukrainian
```
| Збудник | Гентаміцин | Тобраміцин | Ципрофлоксацин |
@@ -334,9 +342,9 @@ For a manual approach, you can use the `resistance` or
`susceptibility()` function:
``` r
example_isolates %>%
example_isolates |>
# group by ward:
group_by(ward) %>%
group_by(ward) |>
# calculate AMR using resistance() for gentamicin and tobramycin
# and get their 95% confidence intervals using sir_confidence_interval():
summarise(across(c(GEN, TOB),
@@ -358,9 +366,9 @@ to select a series of antibiotic columns:
library(AMR)
library(dplyr)
out <- example_isolates %>%
out <- example_isolates |>
# group by ward:
group_by(ward) %>%
group_by(ward) |>
# calculate AMR using resistance(), over all aminoglycosides and polymyxins:
summarise(across(c(aminoglycosides(), polymyxins()),
resistance))
@@ -384,7 +392,7 @@ out
``` r
# transform the antibiotic columns to names:
out %>% set_ab_names()
out |> set_ab_names()
#> # A tibble: 3 × 6
#> ward gentamicin tobramycin amikacin kanamycin colistin
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
@@ -395,7 +403,7 @@ out %>% set_ab_names()
``` r
# transform the antibiotic column to ATC codes:
out %>% set_ab_names(property = "atc")
out |> set_ab_names(property = "atc")
#> # A tibble: 3 × 6
#> ward J01GB03 J01GB01 J01GB06 J01GB04 J01XB01
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
@@ -427,7 +435,7 @@ data analysis. This package can be used for:
organisms (MDRO) ([tutorial](./reference/mdro.html))
- Calculating (empirical) susceptibility of both mono therapy and
combination therapies ([tutorial](./articles/AMR.html))
- Apply AMR functions in predictive modelling
- Applying AMR functions in predictive modelling
([tutorial](./articles/AMR_with_tidymodels.html))
- Getting properties for any microorganism (like Gram stain, species,
genus or family) ([manual](./reference/mo_property.html))
@@ -435,7 +443,8 @@ data analysis. This package can be used for:
EARS-Net/ATC/LOINC/PubChem, defined daily dose or trade name)
([manual](./reference/ab_property.html))
- Plotting antimicrobial resistance ([tutorial](./articles/AMR.html))
- Applying EUCAST expert rules ([manual](./reference/eucast_rules.html))
- Applying EUCAST and CLSI interpretive rules
([manual](./reference/interpretive_rules.html))
- Getting SNOMED codes of a microorganism, or getting properties of a
microorganism based on a SNOMED code
([manual](./reference/mo_property.html))
+9 -4
View File
@@ -268,6 +268,10 @@ It does so by combining two quantities: the relative incidence of each pathogen
\strong{Prior distributions:} Pathogen incidence uses a non-informative \eqn{Dirichlet(1, 1, \ldots, 1)} prior. Susceptibility proportions use the Jeffreys prior, \eqn{\beta(0.5, 0.5)}, except for pathogen-drug combinations with known intrinsic resistance, which use a strongly informative \eqn{\beta(1, 9999)} prior that forces near-zero susceptibility regardless of observed data. Intrinsic resistance is determined using the \link{intrinsic_resistant} data set, which is based on \href{https://www.eucast.org/bacteria/important-additional-information/expert-rules/}{'EUCAST Expected Resistant Phenotypes' v1.2} (2023).
\strong{Pathogens included in the model:} For each regimen (and group), only pathogens that occur in the data and have at least one test result for the regimen are included. Pathogens without any test result for a regimen do not contribute to its coverage, nor to its \code{n_total} in \code{long_numeric}. Use \code{\link[=retrieve_wisca_parameters]{retrieve_wisca_parameters()}} to get the parameters that were used.
\strong{Rare pathogens and small groups:} Since every pathogen receives a prior weight of 1 in the Dirichlet distribution, the prior weighs heavily when there are few isolates relative to the number of pathogens, i.e., with pathogens found only once or a few times, or in small groups. This pulls the incidence towards a uniform distribution, and the very uncertain susceptibility of rare pathogens can then strongly lower the estimated coverage, without reflecting the clinical expectation. A warning is therefore given when this prior weight accounts for more than 5\% of the pathogen incidence in any regimen (or group). Consider limiting the analysis to the most common pathogens using \code{\link[=top_n_microorganisms]{top_n_microorganisms()}}, or using fewer and larger groups.
\strong{Interpreting the output:} Overlapping credible intervals between regimens indicate no significant difference in coverage; if a narrower-spectrum regimen overlaps with a broader one, the narrower-spectrum option may be preferred on stewardship grounds. Non-overlapping intervals indicate a clinically meaningful difference. For small sample sizes, consider pooling data from multiple sites to improve precision, provided pathogen distributions are sufficiently similar (Bielicki \emph{et al.}, 2016).
For the full mathematical derivation and worked examples, see the \href{https://amr-for-r.org/articles/WISCA.html}{WISCA vignette}.
@@ -353,12 +357,13 @@ example_isolates
# basic WISCA: empirical coverage per regimen, weighted by pathogen
# incidence, with 95\% credible intervals
wisca(example_isolates,
# (10 most common species, since rare species can distort coverage estimates)
wisca(top_n_microorganisms(example_isolates, n = 10),
antimicrobials = c("AMC", "AMC+CIP", "AMC+GEN")
)
# equivalent using antibiogram():
antibiogram(example_isolates,
antibiogram(top_n_microorganisms(example_isolates, n = 10),
antimicrobials = c("AMC", "AMC+CIP", "AMC+GEN"),
wisca = TRUE
)
@@ -443,7 +448,7 @@ antibiogram(ex1,
# Print the output for R Markdown / Quarto -----------------------------
ureido <- wisca(example_isolates,
ureido <- wisca(top_n_microorganisms(example_isolates, n = 10),
antimicrobials = ureidopenicillins(),
syndromic_group = "ward"
)
@@ -461,7 +466,7 @@ ab1 <- antibiogram(example_isolates,
antimicrobials = c("AMC", "CIP", "TZP", "TZP+TOB"),
mo_transform = "gramstain"
)
ab2 <- wisca(example_isolates,
ab2 <- wisca(top_n_microorganisms(example_isolates, n = 10),
antimicrobials = c("AMC", "CIP", "TZP", "TZP+TOB"),
syndromic_group = "ward"
)
+1 -1
View File
@@ -5,7 +5,7 @@
\alias{intrinsic_resistant}
\title{Data Set Denoting Bacterial Intrinsic Resistance}
\format{
A \link[tibble:tibble]{tibble} with 307 684 observations and 3 variables:
A \link[tibble:tibble]{tibble} with 307 746 observations and 3 variables:
\itemize{
\item \code{mo}\cr Microorganism ID which occurs in \code{\link[=microorganisms]{microorganisms$mo}}. Names can be retrieved using \code{\link[=mo_name]{mo_name()}}.
\item \code{ab}\cr Antimicrobial ID which occurs in \code{\link[=antimicrobials]{antimicrobials$ab}}. Names can be retrieved using \code{\link[=ab_name]{ab_name()}}.
+51 -1
View File
@@ -47,6 +47,44 @@ test_that("test-antibiogram.R", {
wisca_known <- suppressMessages(wisca(example_isolates[example_isolates$mo != "UNKNOWN", ], antimicrobials = "GEN", simulations = 100, info = FALSE, guideline = "EUCAST"))
expect_true("UNKNOWN" %in% attributes(wisca_all)$wisca_parameters$mo)
expect_false(identical(as.data.frame(wisca_all), as.data.frame(wisca_known)))
# WISCA only includes pathogens that occur in a group and were tested for the regimen (#337)
set.seed(337)
wisca_data <- example_isolates[mo_domain(example_isolates$mo) == "Bacteria", ]
wisca_data$grp <- sample(c("A", "B", "C"), nrow(wisca_data), replace = TRUE)
wisca_strat <- suppressWarnings(wisca(wisca_data, antimicrobials = "MEM", syndromic_group = "grp", simulations = 5000, info = FALSE))
wisca_params <- retrieve_wisca_parameters(wisca_strat)
# no absent or untested pathogens
expect_true(all(wisca_params$n_total > 0 & wisca_params$n_tested > 0))
# each stratum equals the unstratified WISCA of its subset
for (g in c("A", "B", "C")) {
wisca_subset <- suppressWarnings(wisca(wisca_data[wisca_data$grp == g, ], antimicrobials = "MEM", simulations = 5000, info = FALSE))
cov_strat <- attributes(wisca_strat)$long_numeric$coverage[attributes(wisca_strat)$long_numeric$syndromic_group == g]
expect_true(abs(cov_strat - attributes(wisca_subset)$long_numeric$coverage) < 0.01)
expect_identical(attributes(wisca_strat)$long_numeric$n_total[attributes(wisca_strat)$long_numeric$syndromic_group == g], attributes(wisca_subset)$long_numeric$n_total)
}
# untested pathogens have no influence
wisca_untested <- wisca_data[1:20, ]
wisca_untested$mo <- as.mo("Brucella melitensis")
wisca_untested$MEM <- NA_sir_
set.seed(337)
wisca_without <- suppressWarnings(wisca(wisca_data, antimicrobials = "MEM", simulations = 1000, info = FALSE))
set.seed(337)
wisca_with <- suppressWarnings(wisca(rbind(wisca_data, wisca_untested), antimicrobials = "MEM", simulations = 1000, info = FALSE))
expect_identical(attributes(wisca_with)$long_numeric, attributes(wisca_without)$long_numeric)
expect_false("B_BRCLL_MLTN" %in% retrieve_wisca_parameters(wisca_with)$mo)
# warning for a large prior weight of rare pathogens
expect_warning(wisca(wisca_data, antimicrobials = "MEM", syndromic_group = "grp", simulations = 100, info = FALSE), "prior weight")
# (expect_warning(..., NA) is not supported by tinytest)
wisca_warnings <- character(0)
withCallingHandlers(
wisca(example_isolates, antimicrobials = "MEM", simulations = 100, info = FALSE),
warning = function(w) {
wisca_warnings <<- c(wisca_warnings, conditionMessage(w))
invokeRestart("muffleWarning")
}
)
expect_false(any(grepl("prior weight", wisca_warnings)))
if (AMR:::pkg_is_available("dplyr", min_version = "1.0.0", also_load = TRUE)) {
ab_grouped <- tryCatch(
# pathogen names are translated, so test in English, which must also hold on systems in another language
@@ -230,7 +268,19 @@ test_that("test-antibiogram.R", {
expect_equal(nrow(ab_grp_seq), nrow(ab_grp_par))
}
# 6. parallel = TRUE without a plan raises an informative error
# 6. parallel and sequential use the same WISCA parameters and give the same coverage (#337)
set.seed(337)
wisca_data <- example_isolates[mo_domain(example_isolates$mo) == "Bacteria", ]
wisca_data$grp <- sample(c("A", "B", "C"), nrow(wisca_data), replace = TRUE)
future::plan(future::sequential)
wisca_strat_seq <- suppressWarnings(wisca(wisca_data, antimicrobials = "MEM", syndromic_group = "grp", simulations = 5000, info = FALSE))
future::plan(future::multicore)
wisca_strat_par <- suppressWarnings(wisca(wisca_data, antimicrobials = "MEM", syndromic_group = "grp", simulations = 5000, parallel = TRUE, info = FALSE))
expect_identical(retrieve_wisca_parameters(wisca_strat_par), retrieve_wisca_parameters(wisca_strat_seq))
expect_true(all(retrieve_wisca_parameters(wisca_strat_par)$n_tested > 0))
expect_true(all(abs(attributes(wisca_strat_par)$long_numeric$coverage - attributes(wisca_strat_seq)$long_numeric$coverage) < 0.01))
# 7. parallel = TRUE without a plan raises an informative error
future::plan(future::sequential)
expect_error(
suppressWarnings(wisca(example_isolates, antimicrobials = "TZP", parallel = TRUE, info = FALSE)),
+19
View File
@@ -547,6 +547,25 @@ test_that("intrinsic_resistant: synonyms have exactly the resistances of their c
expect_true(length(absent) == 0, info = paste("Current names missing while their synonym is listed:", describe_mo(absent)))
})
test_that("intrinsic_resistant: species groups have the resistances shared by all their species", {
skip_on_cran()
ir_keys <- paste(intrinsic_resistant$mo, intrinsic_resistant$ab)
groups <- microorganisms.groups[which(microorganisms$rank[match(microorganisms.groups$mo, microorganisms$mo)] == "species"), , drop = FALSE]
groups_species <- split(as.character(groups$mo), as.character(groups$mo_group))
missing <- unlist(Map(function(group, species) {
abx <- unique(as.character(intrinsic_resistant$ab[intrinsic_resistant$mo %in% species]))
abx <- abx[vapply(abx, function(ab) all(paste(species, ab) %in% ir_keys), logical(1))]
if (length(abx) == 0) {
return(NULL)
}
abx <- abx[!paste(group, abx) %in% ir_keys]
if (length(abx) > 0) paste0(group, " (", paste(abx, collapse = ", "), ")")
}, names(groups_species), groups_species), use.names = FALSE)
expect_true(length(missing) == 0, info = paste("Species groups missing resistances shared by all their species:", paste(missing, collapse = "; ")))
# Enterobacter cloacae complex (#338)
expect_true(all(mo_is_intrinsic_resistant(rep("B_ENTRBC_CLOC-C", 2), ab = c("AMX", "AMC"))))
})
test_that("interpretive rules name organisms that exist", {
skip_on_cran()
r <- AMR:::INTERPRETIVE_RULES_DF
+1
View File
@@ -270,6 +270,7 @@ Use the `wisca()` function, or equivalently `antibiogram(..., wisca = TRUE)`. WI
```{r wisca}
wisca_result <- example_isolates %>%
top_n_microorganisms(n = 10) %>%
wisca(
antimicrobials = c("TZP", "TZP+TOB", "TZP+GEN"),
minimum = 10