1
0
mirror of https://github.com/msberends/AMR.git synced 2026-03-07 14:14:37 +01:00

mdro(): infer base drug resistance from drug+inhibitor combination columns (#209)

When a base beta-lactam column (e.g., piperacillin/PIP) is absent but a
corresponding drug+inhibitor combination (e.g., piperacillin/tazobactam/TZP)
is present and resistant, resistance in the base drug is now correctly
inferred. This is clinically sound: resistance in a combination implies the
inhibitor provided no benefit, so the base drug is also resistant.

Susceptibility in a combination is NOT propagated to the base drug (the
inhibitor may be responsible for susceptibility), so only R values are
inferred; missing base drugs remain NA otherwise.

Implementation details:
- Uses AB_BETALACTAMS_WITH_INHIBITOR to identify all beta-lactam+inhibitor
  combinations present in the user's data
- Derives base drug AB codes by stripping the "/inhibitor" part from names
- Creates synthetic proxy columns (.sir_proxy_<AB>) in x, set to "R" when
  any matching combination is R, otherwise NA
- Proxy columns are added to cols_ab before drug variable assignment,
  so all existing guideline logic benefits without any changes
- Multiple combos for the same base drug are OR-ed (any R → R)
- Adds internal ab_without_inhibitor() helper for the name->base mapping
- Verbose mode reports which combinations are used for inference

Bumps version: 3.0.1.9028 -> 3.0.1.9029

https://claude.ai/code/session_01Cp154UtssHg84bw38xiiTG
This commit is contained in:
Claude
2026-03-06 19:16:21 +00:00
parent daab605ca4
commit 485ae25e09
3 changed files with 57 additions and 2 deletions

View File

@@ -1,5 +1,5 @@
Package: AMR
Version: 3.0.1.9028
Version: 3.0.1.9029
Date: 2026-03-06
Title: Antimicrobial Resistance Data Analysis
Description: Functions to simplify and standardise antimicrobial resistance (AMR)

View File

@@ -1,4 +1,4 @@
# AMR 3.0.1.9028
# AMR 3.0.1.9029
### New
* Integration with the **tidymodels** framework to allow seamless use of SIR, MIC and disk data in modelling pipelines via `recipes`
@@ -18,6 +18,7 @@
* Two new `NA` objects, `NA_ab_` and `NA_mo_`, analogous to base R's `NA_character_` and `NA_integer_`, for use in pipelines that require typed missing values
### Fixes
* `mdro()`: when a base beta-lactam drug column is missing but a corresponding drug+inhibitor combination is present in the data and resistant (e.g., piperacillin/tazobactam = R while piperacillin is absent), the base drug is now correctly inferred as resistant. This ensures MDRO classification is not missed due to test-ordering differences in the laboratory. The reverse direction is also valid: susceptibility in a combination does not imply susceptibility in the base drug (the inhibitor may be responsible), so only resistance is propagated. Closes #209
* Fixed a bug in `as.ab()` where certain AB codes containing "PH" or "TH" (such as `ETH`, `MTH`, `PHE`, `PHN`, `STH`, `THA`, `THI1`) would incorrectly return `NA` when combined in a vector with any untranslatable value (#245)
* Fixed a bug in `antibiogram()` for when no antimicrobials are set
* Fixed a bug in `as.sir()` where for numeric input the arguments `S`, `I`, and `R` would not be considered (#244)

View File

@@ -480,6 +480,50 @@ mdro <- function(x = NULL,
}
cols_ab <- cols_ab[!duplicated(cols_ab)]
# Infer resistance for missing base drugs from available drug+inhibitor combination columns.
# Clinical principle: resistance in drug+inhibitor (e.g., piperacillin/tazobactam = R)
# always implies resistance in the base drug (e.g., piperacillin = R), because the
# enzyme inhibitor adds nothing when the organism is truly resistant to the base drug.
# NOTE: susceptibility in a combination does NOT imply susceptibility in the base drug
# (the inhibitor may be responsible), so synthetic proxy columns only propagate R, not S/I.
.combos_in_data <- AB_BETALACTAMS_WITH_INHIBITOR[AB_BETALACTAMS_WITH_INHIBITOR %in% names(cols_ab)]
if (length(.combos_in_data) > 0) {
.base_drugs <- suppressMessages(
as.ab(gsub("/.*", "", ab_name(as.character(.combos_in_data), language = NULL)))
)
.unique_bases <- unique(.base_drugs[!is.na(.base_drugs)])
for (.base in .unique_bases) {
.base_code <- as.character(.base)
if (!.base_code %in% names(cols_ab)) {
# Base drug column absent; find all available combo columns for this base drug
.combos <- .combos_in_data[!is.na(.base_drugs) & as.character(.base_drugs) == .base_code]
.combo_cols <- unname(cols_ab[as.character(.combos)])
.combo_cols <- .combo_cols[!is.na(.combo_cols)]
if (length(.combo_cols) > 0) {
# Vectorised: if ANY combination is R, infer base drug as R; otherwise NA
.sir_chars <- as.data.frame(
lapply(x[, .combo_cols, drop = FALSE], function(col) as.character(as.sir(col))),
stringsAsFactors = FALSE
)
.new_col <- paste0(".sir_proxy_", .base_code)
x[[.new_col]] <- ifelse(rowSums(.sir_chars == "R", na.rm = TRUE) > 0L, "R", NA_character_)
cols_ab <- c(cols_ab, setNames(.new_col, .base_code))
if (isTRUE(verbose)) {
message_(
"Inferring resistance for ", ab_name(.base_code, language = NULL),
" from available drug+inhibitor combination(s): ",
paste(ab_name(as.character(.combos), language = NULL), collapse = ", "),
" (resistance in a combination always implies resistance in the base drug)",
add_fn = font_blue
)
}
}
}
}
cols_ab <- cols_ab[!duplicated(names(cols_ab))]
}
rm(list = intersect(ls(), c(".combos_in_data", ".base_drugs", ".unique_bases", ".base", ".base_code", ".combos", ".combo_cols", ".sir_chars", ".new_col")))
# nolint start
AMC <- cols_ab["AMC"]
AMK <- cols_ab["AMK"]
@@ -674,6 +718,16 @@ mdro <- function(x = NULL,
x
}
ab_without_inhibitor <- function(ab_codes) {
# Get the base drug AB code from a drug+inhibitor combination.
# e.g., AMC (amoxicillin/clavulanic acid) -> AMX (amoxicillin)
# TZP (piperacillin/tazobactam) -> PIP (piperacillin)
# SAM (ampicillin/sulbactam) -> AMP (ampicillin)
combo_names <- ab_name(ab_codes, language = NULL)
base_names <- gsub("/.*", "", combo_names)
suppressMessages(as.ab(base_names))
}
# antimicrobial classes
# nolint start
aminoglycosides <- c(TOB, GEN)