diff --git a/R/data.R b/R/data.R index 3337b2167..83d944a9e 100755 --- a/R/data.R +++ b/R/data.R @@ -314,7 +314,9 @@ #' The default is `"human"`, which can also be set with the package option [`AMR_breakpoint_type`][AMR-options]. Use [`as.sir(..., breakpoint_type = ...)`][as.sir()] to interpret raw data using a specific breakpoint type, e.g. `as.sir(..., breakpoint_type = "ECOFF")` to use ECOFFs. #' #' ### Blocking Rows -#' Where EUCAST lists an organism but gives no breakpoint for it (e.g. "IE", "-", "Note", breakpoints in brackets, an organism excluded with "except", or no S category for a zone diameter), [as.sir()] must not fall back to the breakpoint of a broader taxon, such as the breakpoint for *Vibrio* spp. where EUCAST gives "IE" for *V. fluvialis*. Such cases are included as "blocking rows": rows in which `breakpoint_S` and `breakpoint_R` are both `NA` and the `note` starts with `"[No breakpoint]"` followed by the reason. [as.sir()] returns `NA` for these. This currently applies to `r format(sum(is.na(clinical_breakpoints$breakpoint_S) & is.na(clinical_breakpoints$breakpoint_R)), big.mark = " ")` rows. Remove them with `subset(clinical_breakpoints, !is.na(breakpoint_S))` if only actual breakpoints are needed. +#' Where EUCAST lists an organism but gives no breakpoint for it (e.g. "IE", "-", "Note", an organism excluded with "except", or no S category for a zone diameter), [as.sir()] must not fall back to the breakpoint of a broader taxon, such as the breakpoint for *Vibrio* spp. where EUCAST gives "IE" for *V. fluvialis*. Such cases are included as "blocking rows": rows in which `breakpoint_S` and `breakpoint_R` are both `NA` and the `note` starts with `"[No breakpoint]"` followed by the reason. [as.sir()] returns `NA` for these. This currently applies to `r format(sum(is.na(clinical_breakpoints$breakpoint_S) & is.na(clinical_breakpoints$breakpoint_R)), big.mark = " ")` rows. Remove them with `subset(clinical_breakpoints, !is.na(breakpoint_S))` if only actual breakpoints are needed. +#' +#' **EUCAST's breakpoints in brackets** are not in this data set. EUCAST bases these on ECOFFs, and states that reporting S or I should be avoided. [as.sir()] therefore treats them as absent, and can use PK/PD breakpoints or, for unknown UTI status, UTI breakpoints instead. All bracketed values are kept in the [raw import of the EUCAST tables](https://github.com/msberends/AMR/blob/main/data-raw/breakpoints_eucast_raw.csv) (column `is_bracketed`). #' #' Where another guideline has no S category for a zone diameter, such as CLSI for fluconazole in *C. glabrata* (SDD and R only), only `breakpoint_R` is given, and the `note` starts with `"[No S category]"`. [as.sir()] then returns R where applicable, and `NA` otherwise. #' diff --git a/R/sir.R b/R/sir.R index 809f5fe98..62bff077f 100755 --- a/R/sir.R +++ b/R/sir.R @@ -1962,7 +1962,9 @@ as_sir_method <- function(method_short, taxonomic_level <- round(breakpoints_current$rank_index * 2) / 2 is_blocking <- is.na(breakpoints_current$breakpoint_S) & is.na(breakpoints_current$breakpoint_R) is_uti <- breakpoints_current$uti %in% TRUE - too_broad <- !is_uti & is_blocking & taxonomic_level >= min(taxonomic_level[is_uti]) + # a UTI row without breakpoints is no breakpoint either, so it does not take precedence over anything + is_uti_breakpoint <- is_uti & !is_blocking + too_broad <- !is_uti & is_blocking & any(is_uti_breakpoint) & taxonomic_level >= suppressWarnings(min(taxonomic_level[is_uti_breakpoint])) breakpoints_current <- breakpoints_current[!too_broad, , drop = FALSE] if (any(!breakpoints_current$uti %in% TRUE)) { if (length(unique(breakpoints_current$site)) > 1) { diff --git a/man/clinical_breakpoints.Rd b/man/clinical_breakpoints.Rd index c5e13e3c9..cf48a8c3a 100644 --- a/man/clinical_breakpoints.Rd +++ b/man/clinical_breakpoints.Rd @@ -49,7 +49,9 @@ The default is \code{"human"}, which can also be set with the package option \co \subsection{Blocking Rows}{ -Where EUCAST lists an organism but gives no breakpoint for it (e.g. "IE", "-", "Note", breakpoints in brackets, an organism excluded with "except", or no S category for a zone diameter), \code{\link[=as.sir]{as.sir()}} must not fall back to the breakpoint of a broader taxon, such as the breakpoint for \emph{Vibrio} spp. where EUCAST gives "IE" for \emph{V. fluvialis}. Such cases are included as "blocking rows": rows in which \code{breakpoint_S} and \code{breakpoint_R} are both \code{NA} and the \code{note} starts with \code{"[No breakpoint]"} followed by the reason. \code{\link[=as.sir]{as.sir()}} returns \code{NA} for these. This currently applies to 15 660 rows. Remove them with \code{subset(clinical_breakpoints, !is.na(breakpoint_S))} if only actual breakpoints are needed. +Where EUCAST lists an organism but gives no breakpoint for it (e.g. "IE", "-", "Note", an organism excluded with "except", or no S category for a zone diameter), \code{\link[=as.sir]{as.sir()}} must not fall back to the breakpoint of a broader taxon, such as the breakpoint for \emph{Vibrio} spp. where EUCAST gives "IE" for \emph{V. fluvialis}. Such cases are included as "blocking rows": rows in which \code{breakpoint_S} and \code{breakpoint_R} are both \code{NA} and the \code{note} starts with \code{"[No breakpoint]"} followed by the reason. \code{\link[=as.sir]{as.sir()}} returns \code{NA} for these. This currently applies to 15 660 rows. Remove them with \code{subset(clinical_breakpoints, !is.na(breakpoint_S))} if only actual breakpoints are needed. + +\strong{EUCAST's breakpoints in brackets} are not in this data set. EUCAST bases these on ECOFFs, and states that reporting S or I should be avoided. \code{\link[=as.sir]{as.sir()}} therefore treats them as absent, and can use PK/PD breakpoints or, for unknown UTI status, UTI breakpoints instead. All bracketed values are kept in the \href{https://github.com/msberends/AMR/blob/main/data-raw/breakpoints_eucast_raw.csv}{raw import of the EUCAST tables} (column \code{is_bracketed}). Where another guideline has no S category for a zone diameter, such as CLSI for fluconazole in \emph{C. glabrata} (SDD and R only), only \code{breakpoint_R} is given, and the \code{note} starts with \code{"[No S category]"}. \code{\link[=as.sir]{as.sir()}} then returns R where applicable, and \code{NA} otherwise. } diff --git a/tests/testthat/test-data.R b/tests/testthat/test-data.R index aa8408914..b0b7c12d9 100644 --- a/tests/testthat/test-data.R +++ b/tests/testthat/test-data.R @@ -582,6 +582,16 @@ test_that("microorganisms.codes: unique codes for existing names", { expect_true(all(is.na(microorganisms.codes$source) | grepl("^WHONET \\(.+\\)$", microorganisms.codes$source))) }) +test_that("clinical_breakpoints: no EUCAST breakpoints in brackets (#327)", { + skip_on_cran() + expect_false(any(grepl("brackets", clinical_breakpoints$note, fixed = TRUE))) + expect_false(any(grepl("[( )]", clinical_breakpoints$note, fixed = TRUE))) + # e.g., systemic amikacin for Enterobacterales in EUCAST 2026, with only the breakpoint for infections originating from the urinary tract left + amk <- subset(clinical_breakpoints, guideline == "EUCAST 2026" & type == "human" & ab == "AMK" & method == "MIC" & mo == "B_[ORD]_ENTRBCTR") + expect_identical(nrow(amk), 1L) + expect_true(amk$uti) +}) + test_that("clinical_breakpoints: EUCAST MIC breakpoints are stored as published (#331)", { skip_on_cran() cb <- as.data.frame(clinical_breakpoints, stringsAsFactors = FALSE) diff --git a/tests/testthat/test-sir.R b/tests/testthat/test-sir.R index faa2f286d..83f0792c3 100644 --- a/tests/testthat/test-sir.R +++ b/tests/testthat/test-sir.R @@ -826,10 +826,24 @@ test_that("EUCAST blocking rows prevent falling back to broader breakpoints", { expect_identical(sir(as.mic(0.064), "Haemophilus influenzae", "LVX", "EUCAST 2026"), "S") # high-level gentamicin resistance (screening test given in the notes) expect_identical(sir(as.mic(256), "Enterococcus faecium", "GEH", "EUCAST 2026"), "R") - # breakpoints in brackets for systemic infections are not replaced by the UTI breakpoint if uti is unknown - expect_identical(sir(as.mic(4), "Acinetobacter baumannii", "AMK", "EUCAST 2026"), NA_character_) + # breakpoints in brackets are not in the data set (#327), so a UTI breakpoint is used if uti is unknown and nothing else is available, which is noted every time + sir_interpretation_history(clean = TRUE) + expect_identical(sir(as.mic(4), "Acinetobacter baumannii", "AMK", "EUCAST 2026"), "S") + sir_history <- sir_interpretation_history(clean = TRUE) + expect_match(sir_history$notes, "only available for (uncomplicated) urinary tract infections", fixed = TRUE) expect_identical(sir(as.mic(4), "Acinetobacter baumannii", "AMK", "EUCAST 2026", uti = FALSE), NA_character_) expect_identical(sir(as.mic(4), "Acinetobacter baumannii", "AMK", "EUCAST 2026", uti = TRUE), "S") + # without a breakpoint in brackets, PK-PD breakpoints apply if EUCAST has these, as in EUCAST 2023 for aminoglycosides + sir_interpretation_history(clean = TRUE) + expect_identical(sir(as.mic(0.5), "Escherichia coli", "AMK", "EUCAST 2023", uti = FALSE), "S") + sir_history <- sir_interpretation_history(clean = TRUE) + expect_match(sir_history$ref_table, "PK PD", fixed = TRUE) + # and the breakpoints of a broader taxon for another route, of which the site is noted + sir_interpretation_history(clean = TRUE) + expect_identical(sir(as.mic(4), "Enterococcus faecalis", "AMX", "EUCAST 2026", uti = FALSE), "S") + expect_identical(sir_interpretation_history(clean = TRUE)$site, "Intravenous") + # an organism that a table excludes stays excluded, also if EUCAST has breakpoints in brackets for it + expect_identical(sir(as.mic(1), "Bacillus anthracis", "VAN", "EUCAST 2026"), NA_character_) # UTI breakpoints are only used for unknown uti if there are no non-UTI breakpoints, which is noted every time for (i in 1:2) { sir_interpretation_history(clean = TRUE)