Leave EUCAST breakpoints in brackets out of clinical_breakpoints, and document where they are kept (#327)

as.sir() treats them as absent, so it can use PK/PD breakpoints or, for unknown `uti`, UTI breakpoints. The values stay available in data-raw/breakpoints_eucast_raw.csv, to which the manual now points. Organisms that a table excludes stay excluded, since these have their own blocking rows.

This also fixes the change for unknown `uti` of the previous commit: a blocking row only gives way to an actual UTI breakpoint, not to a UTI row without breakpoints.
This commit is contained in:
dr. M.S. (Matthijs) Berends committed 2026-11-02 17:03:47 +01:00
1 parent 4c4a7d4392
commit fab20c5eaa
5 files changed
+35 -5

No files matched your search

+3 -1
View File
@@ -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.
#'
+3 -1
View File
@@ -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) {
+3 -1
View File
@@ -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.
}
+10
View File
@@ -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)
+16 -2
View File
@@ -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)