From 485ae25e09c3c5bc10a5501271b594fd6ac64f81 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Mar 2026 19:16:21 +0000 Subject: [PATCH] mdro(): infer base drug resistance from drug+inhibitor combination columns (#209) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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_) 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 --- DESCRIPTION | 2 +- NEWS.md | 3 ++- R/mdro.R | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 8757581ea..49aa54457 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -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) diff --git a/NEWS.md b/NEWS.md index 4dff7d59f..5b2941c9c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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) diff --git a/R/mdro.R b/R/mdro.R index e583ddea4..49b98b000 100755 --- a/R/mdro.R +++ b/R/mdro.R @@ -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)