(v3.0.1.9148) Store zone diameters without S category as blocking rows for EUCAST and as R-only rows for CLSI instead of capping them at 50 mm, let as.mo() return the complex for names such as 'Acinetobacter calcoaceticus-baumannii complex', make the breakpoint reproduction scripts stop on unchecked data defects and year-independent, and document that as.disk() returns NA above 50 mm

The full rebuild of clinical_breakpoints also assigns Salmonella serovars and the Bacteroides fragilis complex correctly and adds ECOFFs for Neocosmospora solani. The rebuilt microorganisms.codes is not included, since it remaps WHONET codes that need review first.
This commit is contained in:
dr. M.S. (Matthijs) Berends committed 2026-10-15 17:05:41 +02:00
1 parent fcfaa87f5d
commit 179eec584f
20 files changed
+374 -287

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
Package: AMR
Version: 3.0.1.9147
Version: 3.0.1.9148
Date: 2026-10-15
Title: Antimicrobial Resistance Data Analysis
Description: Functions to simplify and standardise antimicrobial resistance (AMR)
+5 -3
View File
@@ -1,4 +1,4 @@
# AMR 3.0.1.9147
# AMR 3.0.1.9148
Planned as v3.1.0, mid October 2026.
@@ -74,6 +74,8 @@ Planned as v3.1.0, mid October 2026.
* Fixed streptomycin breakpoints imported from WHONET, which were coded as streptoduocin (`STR`) instead of streptomycin (`STR1`)
* Fixed CLSI breakpoints for sulbactam/durlobactam, which were coded as sulfadimethoxine (`SUD`), and added the missing EUCAST 2011 breakpoints for roxithromycin (#308)
* Fixed some EUCAST Expert Rules, mostly on *S. pneumoniae*
* Zone diameters for which the guideline has no S category (coded by WHONET as an S breakpoint of 80 or 99 mm) are now blocking rows for EUCAST, e.g. cefuroxime oral in *H. influenzae* in EUCAST 2011, and keep only the R breakpoint for CLSI, e.g. fluconazole in *C. glabrata*. Previously, these S breakpoints were stored as 50 mm.
* Breakpoints and ECOFFs for *Salmonella* serovars Typhi, Typhimurium, and Paratyphi, and ECOFFs for the *Bacteroides fragilis* complex, are now assigned to these taxa instead of to *S. enterica* subsp. *enterica* and *B. fragilis*, and ECOFFs of amphotericin B for *Neocosmospora solani* were added
* Microorganism coercion (`as.mo()`) and properties (`mo_*()`)
* Considerably faster through one consistent session cache design, which is cleared when lookup tables change, e.g., after `add_custom_microorganisms()` (#313)
* `mo_*()` functions now retrieve properties of valid `mo` input directly, and only for unique values
@@ -81,7 +83,7 @@ Planned as v3.1.0, mid October 2026.
* Previously coerced input is now recognised again, e.g., for an unknown species of a known genus, instead of being matched again on every call (#313)
* `mo_uncertainties()` now also lists uncertainties of previously coerced input
* Translated names in the set language are now recognised, such as the output of `mo_name(x, language = "fr")`, and cached per language
* Input of the form `"X complex"` now falls back to `"X"` when the complex is not a distinct taxon, preventing `NA` for valid clinical descriptions such as `"Proteus vulgaris complex"` (#287)
* Input of the form `"X complex"` now falls back to `"X"` when the complex is not a distinct taxon, preventing `NA` for valid clinical descriptions such as `"Proteus vulgaris complex"`, while a complex named after several of its species returns the complex of one of them, e.g., `"Acinetobacter calcoaceticus-baumannii complex"` returns *A. baumannii* complex (#287)
* Abbreviated genus input now ranks candidates with an exactly matching species epithet above more prevalent organisms, so that e.g. `"S. apiospermum"` returns *Scedosporium apiospermum* instead of a *Staphylococcus* species (#288)
* Abbreviated genus input for species with subspecies (e.g., `"P. ovale"`) now returns the species instead of a more prevalent organism, while explicit subspecies (e.g., `"P. ovale curtisi"`) are preserved (#288)
* *Salmonella* serovars written with the species, such as `"Salmonella enterica serovar Typhi"`, no longer return *Salmonella bongori* or another subspecies
@@ -120,7 +122,7 @@ Planned as v3.1.0, mid October 2026.
* MICs and disk diffusion
* New argument `round_to_next_log2` in `as.mic()` and `rescale_mic()` to round values up to the nearest log2 dilution level (#255)
* `as.mic()` now handles values in scientific notation (e.g., `1e-3`) correctly
* Input validation of `as.disk()` is now more strict, rejecting values that are not recognisable as a numeric disk zone diameter
* Input validation of `as.disk()` is now more strict, rejecting values that are not recognisable as a numeric disk zone diameter. Values higher than 50 mm now return `NA` with a warning, instead of being set to 50 mm.
* MDRO determination (`mdro()`)
* Resistance can now be inferred from a drug/inhibitor combination when the column of the base drug is absent (e.g., piperacillin inferred from piperacillin/tazobactam). This is controlled with the new argument `infer_from_combinations` (default: `TRUE`) (#209).
* BRMO classification now includes bacterial complexes (#275)
+3 -1
View File
@@ -313,7 +313,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, or an organism excluded with "except"), [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", 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 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.
#'
#' ### Imported From WHONET
#' Some breakpoints in this package were validated through and imported from [WHONET](https://whonet.org), a free desktop Windows application developed and supported by the WHO Collaborating Centre for Surveillance of Antimicrobial Resistance. More can be read on [their website](https://whonet.org). The developers of WHONET and this `AMR` package have been in contact about sharing their work. We highly appreciate their great development on the WHONET software.
+1 -1
View File
@@ -35,7 +35,7 @@
#' @param na.rm A [logical] indicating whether missing values should be removed.
#' @details Interpret disk values as SIR values with [as.sir()]. It supports guidelines from EUCAST and CLSI.
#'
#' Disk diffusion growth zone sizes must be between 0 and 50 millimetres. Values higher than 50 but lower than 100 will be maximised to 50. All others input values outside the 0-50 range will return `NA`.
#' Disk diffusion growth zone sizes must be between 0 and 50 millimetres. Decimal values are rounded up. All input values outside the 0-50 range, including values higher than 50, return `NA` with a warning.
#' @return An [integer] with additional class [`disk`]
#' @aliases disk
#' @export
+10
View File
@@ -344,6 +344,16 @@ as.mo <- function(x,
if (x_out %in% MO_lookup_current$fullname_lower) {
return(as.character(MO_lookup_current$mo[match(x_out, MO_lookup_current$fullname_lower)]))
}
# a complex named after several of its species, such as 'Acinetobacter calcoaceticus-baumannii complex', must return the complex of one of these species (here 'Acinetobacter baumannii complex') instead of being matched as one of the species after the " complex" was stripped
x_words <- strsplit(x_out, " ", fixed = TRUE)[[1]]
if (length(x_words) >= 2) {
x_epithets <- strsplit(paste(x_words[-1], collapse = " "), "[-/ ]+", perl = TRUE)[[1]]
x_complexes <- paste(x_words[1], x_epithets[x_epithets != ""], "complex")
x_complexes <- x_complexes[x_complexes %in% MO_lookup_current$fullname_lower]
if (length(x_complexes) > 0) {
return(as.character(MO_lookup_current$mo[match(x_complexes[1], MO_lookup_current$fullname_lower)]))
}
}
}
# Salmonella serovars are stored without species (e.g. 'Salmonella Typhi'), but are often written with it, as in
@@ -113,7 +113,7 @@ unmatched <- unmatched |>
keep = mo_name %like_case% first_part | ORGANISM %like% "Gram " | ORGANISM == "Other" | ORGANISM %like% "anaerobic"
) |>
arrange(keep)
unmatched |> View()
if (interactive()) View(unmatched)
unmatched <- unmatched |>
filter(keep == TRUE)
@@ -171,8 +171,11 @@ organisms <- organisms |> filter(code != "XXX")
organisms |> filter(code == "SGM")
# organisms <- organisms |>
# filter(!(code == "SGM" & name %like% "Streptococcus"))
# this must be empty:
organisms$code[organisms$code |> duplicated()]
# this must be empty, e.g. a species group code that as.mo() resolves to another taxon than its organism code:
if (anyDuplicated(organisms$code) > 0) {
print(organisms |> filter(code %in% code[duplicated(code)]))
stop("WHONET organism code(s) resolve to more than one microorganism, see above")
}
saveRDS(organisms, "data-raw/organisms.rds", version = 2)
@@ -230,22 +233,11 @@ devtools::load_all()
# now that we have the correct MO codes, get the breakpoints and convert them
whonet_breakpoints_raw |>
count(GUIDELINES, BREAKPOINT_TYPE) |>
pivot_wider(names_from = BREAKPOINT_TYPE, values_from = n) |>
janitor::adorn_totals(where = c("row", "col"))
whonet_breakpoints_raw |>
filter(YEAR == format(Sys.Date(), "%Y")) |>
count(GUIDELINES, YEAR, BREAKPOINT_TYPE) |>
pivot_wider(names_from = BREAKPOINT_TYPE, values_from = n) |>
janitor::adorn_totals(where = c("row", "col"))
# overview tables with row and column totals, for inspection only
with(whonet_breakpoints_raw, addmargins(table(GUIDELINES, BREAKPOINT_TYPE)))
with(whonet_breakpoints_raw |> filter(YEAR == format(Sys.Date(), "%Y")), addmargins(table(GUIDELINES, BREAKPOINT_TYPE)))
# compared to current
AMR::clinical_breakpoints |>
count(GUIDELINES = gsub("[^a-zA-Z]", "", guideline), type) |>
arrange(tolower(type)) |>
pivot_wider(names_from = type, values_from = n) |>
as.data.frame() |>
janitor::adorn_totals(where = c("row", "col"))
with(AMR::clinical_breakpoints, addmargins(table(GUIDELINES = gsub("[^a-zA-Z]", "", guideline), type)))
breakpoints <- whonet_breakpoints_raw |>
mutate(code = toupper(ORGANISM_CODE)) |>
@@ -301,8 +293,11 @@ whonet_code_check <- breakpoints |>
by_name = as.ab(ANTIBIOTIC, info = FALSE)
) |>
filter(is.na(by_code) | is.na(by_name) | by_code != by_name)
# this should be empty:
whonet_code_check
# this must be empty:
if (nrow(whonet_code_check) > 0) {
print(whonet_code_check)
stop("WHONET antimicrobial code(s) do not match WHONET's own name of the agent, add them to `whonet_ab_recode`")
}
## Build new breakpoints table ----
@@ -333,7 +328,8 @@ breakpoints_new <- breakpoints |>
breakpoint_S = ifelse(type == "ECOFF" & is.na(S) & !is.na(ECV_ECOFF), ECV_ECOFF, S),
breakpoint_R = ifelse(type == "ECOFF" & is.na(R) & !is.na(ECV_ECOFF), ECV_ECOFF, R),
uti = ifelse(is.na(site), FALSE, gsub(".*(UTI|urinary|urine).*", "UTI", site) == "UTI"),
is_SDD = !is.na(SDD)
is_SDD = !is.na(SDD),
note = NA_character_
) |>
# Greek symbols and EM dash symbols are not allowed by CRAN, so replace them with ASCII:
mutate(disk_dose = disk_dose %>%
@@ -346,9 +342,11 @@ breakpoints_new <- breakpoints |>
distinct(guideline, type, host, ab, mo, method, site, breakpoint_S, .keep_all = TRUE)
# fix reference table names
breakpoints_new |>
filter(guideline %like% "EUCAST", is.na(ref_tbl)) |>
View()
if (interactive()) {
breakpoints_new |>
filter(guideline %like% "EUCAST", is.na(ref_tbl)) |>
View()
}
breakpoints_new <- breakpoints_new |>
mutate(ref_tbl = case_when(
is.na(ref_tbl) & guideline %like% "EUCAST 202" ~ lead(ref_tbl),
@@ -357,6 +355,18 @@ breakpoints_new <- breakpoints_new |>
))
# clean disk zones
# WHONET uses an S breakpoint above 50 mm (e.g. 80 or 99) where the guideline has no S category for a zone diameter, e.g. EUCAST 2011 for cefuroxime oral in H. influenzae. These cannot be stored as zone diameters (as.disk() returns NA for values above 50 mm), so the WHONET value is kept in `S_above_50` and handled under "Zone diameters without S category" below, after the fixes for WHONET errors. Any other value outside 0-50 mm must stop the script.
breakpoints_new <- breakpoints_new |>
mutate(
S_above_50 = if_else(method == "DISK" & type != "ECOFF" & breakpoint_S > 50, breakpoint_S, NA_real_),
breakpoint_S = if_else(is.na(S_above_50), breakpoint_S, NA_real_)
)
invalid_disk <- breakpoints_new |>
filter(method == "DISK" & ((!is.na(breakpoint_S) & (breakpoint_S < 0 | breakpoint_S > 50)) | (!is.na(breakpoint_R) & (breakpoint_R < 0 | breakpoint_R > 50))))
if (nrow(invalid_disk) > 0) {
print(invalid_disk |> select(guideline, type, mo, ab, breakpoint_S, breakpoint_R))
stop(nrow(invalid_disk), " zone diameter breakpoint(s) outside 0-50 mm that are not an S breakpoint of a guideline without S category, see above")
}
breakpoints_new[which(breakpoints_new$method == "DISK"), "breakpoint_S"] <- as.double(as.disk(breakpoints_new[which(breakpoints_new$method == "DISK"), "breakpoint_S", drop = TRUE]))
breakpoints_new[which(breakpoints_new$method == "DISK"), "breakpoint_R"] <- as.double(as.disk(breakpoints_new[which(breakpoints_new$method == "DISK"), "breakpoint_R", drop = TRUE]))
@@ -384,8 +394,6 @@ breakpoints_new[which(breakpoints_new$breakpoint_R == 257), "breakpoint_R"] <- m
breakpoints_new[which(breakpoints_new$breakpoint_R == 513), "breakpoint_R"] <- m[which(m == 512) + 1]
breakpoints_new[which(breakpoints_new$breakpoint_R == 1025), "breakpoint_R"] <- m[which(m == 1024) + 1]
# S breakpoints must never be missing:
anyNA(breakpoints_new$breakpoint_S)
# a lot of R breakpoints are missing, but for CLSI this is required and can be set using as.sir(..., substitute_missing_r_breakpoint = TRUE/FALSE, ...)
# 2025-04-20/ For EUCAST, this should not be the case, only happens to old guideline now it seems
@@ -452,7 +460,7 @@ kleb_tmp_disk <- breakpoints_new |>
uncount(length(kleb_spp)) |>
mutate(mo = kleb_spp)
breakpoints_new <- breakpoints_new |>
filter(!(guideline == "EUCAST 2026" & method == "MIC" & ab == "TMP" & mo == as.mo("Klebsiella"))) |>
filter(!(guideline == "EUCAST 2026" & ab == "TMP" & mo == as.mo("Klebsiella"))) |>
bind_rows(
kleb_tmp_mic,
kleb_tmp_disk
@@ -472,13 +480,41 @@ breakpoints_new$breakpoint_R[breakpoints_new$guideline %like% "EUCAST" & breakpo
breakpoints_new$breakpoint_S[breakpoints_new$guideline %like% "EUCAST" & breakpoints_new$ab == "TMP" & breakpoints_new$mo == as.mo("Enterococcus") & breakpoints_new$type == "human" & breakpoints_new$method == "MIC"] <- 1
breakpoints_new$breakpoint_R[breakpoints_new$guideline %like% "EUCAST" & breakpoints_new$ab == "TMP" & breakpoints_new$mo == as.mo("Enterococcus") & breakpoints_new$type == "human" & breakpoints_new$method == "MIC"] <- 1
# Zone diameters without S category: for EUCAST, there is no breakpoint for the organism then, so these become blocking rows (see "Blocking rows" in the EUCAST script, reproduction_of_clinical_breakpoints_eucast.R). Other guidelines keep the R breakpoint, so that as.sir() can still return R, e.g. CLSI for fluconazole in C. glabrata (SDD >= 15 mm, R <= 14 mm, no S category). Rows whose S breakpoint was set by the fixes above are left alone.
no_S_category <- !is.na(breakpoints_new$S_above_50) & is.na(breakpoints_new$breakpoint_S)
breakpoints_new |>
filter(no_S_category) |>
select(guideline, type, mo, ab, S_above_50, breakpoint_R)
no_S_eucast <- no_S_category & breakpoints_new$guideline %like% "EUCAST"
breakpoints_new$note[no_S_eucast] <- paste0("[No breakpoint] EUCAST gives no S category for this zone diameter (S >= ", breakpoints_new$S_above_50[no_S_eucast], " mm in WHONET)")
breakpoints_new$breakpoint_R[no_S_eucast] <- NA_real_
breakpoints_new$site[no_S_eucast] <- NA_character_
no_S_other <- no_S_category & !no_S_eucast
breakpoints_new$note[no_S_other] <- paste0("[No S category] ", gsub(" .*", "", breakpoints_new$guideline[no_S_other]), " gives no S category for this zone diameter (S >= ", breakpoints_new$S_above_50[no_S_other], " mm in WHONET), so only R can be interpreted")
# a blocking row is only kept where the organism has no breakpoint of its own for that agent, method and UTI status
is_blocker <- grepl("^\\[No breakpoint\\] ", breakpoints_new$note)
has_own_bp <- breakpoints_new[!is_blocker, ] |> distinct(guideline, type, host, mo, ab, method, uti)
breakpoints_new <- bind_rows(
breakpoints_new[!is_blocker, ],
breakpoints_new[is_blocker, ] |> anti_join(has_own_bp, by = c("guideline", "type", "host", "mo", "ab", "method", "uti"))
) |>
select(-S_above_50)
# S breakpoints must never be missing, except in blocking rows and for zone diameters without S category
missing_S <- breakpoints_new |>
filter(is.na(breakpoint_S) & !grepl("^\\[(No breakpoint|No S category)\\] ", note))
if (nrow(missing_S) > 0) {
print(missing_S |> select(guideline, type, mo, ab, method, breakpoint_S, breakpoint_R))
stop(nrow(missing_S), " row(s) without S breakpoint, see above")
}
# WHONET still contains PK/PD rules for EUCAST >= 2024, but this was ended from v14 (2024) on
guideline_year <- function(x) as.integer(gsub("[^0-9]", "", x))
breakpoints_new <- breakpoints_new |>
filter(!(guideline %like% "EUCAST (2024|2025|2026)" & ref_tbl == "PK/PD"))
filter(!(guideline %like% "EUCAST" & guideline_year(guideline) >= 2024 & ref_tbl == "PK/PD"))
# WHONET still contains generic anaerobic rules for EUCAST >= 2021, but this was ended from v12 (2022) on
breakpoints_new <- breakpoints_new |>
filter(!(guideline %like% "EUCAST (2022|2023|2024|2025|2026)" & ref_tbl %like% "anaerob"))
filter(!(guideline %like% "EUCAST" & guideline_year(guideline) >= 2022 & ref_tbl %like% "anaerob"))
# WHONET adds one log2 level to the R breakpoint for their software, e.g. in AMC in Enterobacterales:
# EUCAST 2023 guideline: S <= 8 and R > 8
@@ -501,11 +537,13 @@ breakpoints_new <- breakpoints_new |>
# check the strange duplicates
breakpoints_new |>
mutate(id = paste(guideline, type, host, method, site, mo, ab, uti)) %>%
filter(id %in% .$id[which(duplicated(id))]) |>
arrange(desc(guideline)) |>
View()
if (interactive()) {
breakpoints_new |>
mutate(id = paste(guideline, type, host, method, site, mo, ab, uti)) %>%
filter(id %in% .$id[which(duplicated(id))]) |>
arrange(desc(guideline)) |>
View()
}
# 2024-06-19/ mostly ECOFFs, but there's no explanation in the whonet_breakpoints_raw df, we have to remove duplicates
# 2025-04-20/ same, most important one seems M. tuberculosis in CLSI (also in 2025)
breakpoints_new <- breakpoints_new |>
@@ -547,7 +585,9 @@ breakpoints_new <- breakpoints_new |>
mo_rank(mo, keep_synonyms = TRUE) == "order" ~ 5,
mo != "UNKNOWN" ~ 6, # for B_ANAER, etc.
TRUE ~ 7
)) |>
) +
# a breakpoint of the same organism always precedes its blocking row
if_else(grepl("^\\[No breakpoint\\] ", note), 0.1, 0)) |>
# and arrange
arrange(desc(guideline), mo, ab, type, host, method)
@@ -2750,10 +2750,9 @@ merge_eucast_breakpoints <- function(clinical_breakpoints, breakpoints_eucast, e
paste(sort(unique(paste(replaced$guideline, replaced$scope))), collapse = ", "),
") with ", nrow(breakpoints_eucast), " rows from the EUCAST workbooks"
)
# rows without any breakpoint are only kept as the blocking rows of the
# EUCAST workbooks (see "Blocking rows")
# rows without any breakpoint are only kept as blocking rows (see "Blocking rows"), which also come from the WHONET-based rows for zone diameters without S category, e.g. EUCAST 2011
kept |>
filter(!(is.na(breakpoint_S) & is.na(breakpoint_R)) & !is.na(mo) & !is.na(ab)) |>
filter((!(is.na(breakpoint_S) & is.na(breakpoint_R)) | grepl("^\\[No breakpoint\\] ", note)) & !is.na(mo) & !is.na(ab)) |>
bind_rows(breakpoints_eucast) |>
arrange(desc(guideline), mo, ab, type, host, method, rank_index) |>
distinct(guideline, type, host, ab, mo, method, site, breakpoint_S, .keep_all = TRUE) |>
+1 -1
View File
@@ -1 +1 @@
48ce51f62aa57d2fc24ef71500874b31
19deed740b29b33d288443a1528c0fcd
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -27,7 +27,7 @@ This transforms a vector to a new class \code{\link{disk}}, which is a disk diff
\details{
Interpret disk values as SIR values with \code{\link[=as.sir]{as.sir()}}. It supports guidelines from EUCAST and CLSI.
Disk diffusion growth zone sizes must be between 0 and 50 millimetres. Values higher than 50 but lower than 100 will be maximised to 50. All others input values outside the 0-50 range will return \code{NA}.
Disk diffusion growth zone sizes must be between 0 and 50 millimetres. Decimal values are rounded up. All input values outside the 0-50 range, including values higher than 50, return \code{NA} with a warning.
\code{NA_disk_} is a missing value of the new \code{disk} class.
}
+4 -2
View File
@@ -5,7 +5,7 @@
\alias{clinical_breakpoints}
\title{Data Set with Clinical Breakpoints for SIR Interpretation}
\format{
A \link[tibble:tibble]{tibble} with 61 367 observations and 15 variables:
A \link[tibble:tibble]{tibble} with 61 393 observations and 15 variables:
\itemize{
\item \code{guideline}\cr Name of the guideline
\item \code{type}\cr Breakpoint type, either \code{"ECOFF"}, \code{"animal"}, or \code{"human"}
@@ -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, or an organism excluded with "except"), \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 653 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", 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 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.
}
\subsection{Imported From WHONET}{
+4 -1
View File
@@ -69,7 +69,10 @@ test_that("test-data.R", {
expect_true(all(clinical_breakpoints$guideline[is_blocking] %like% "EUCAST"))
expect_false(anyNA(as.mic(clinical_breakpoints[which(clinical_breakpoints$method == "MIC" & clinical_breakpoints$ref_tbl != "ECOFF" & !is_blocking), "breakpoint_S", drop = TRUE])))
expect_true(anyNA(as.mic(clinical_breakpoints[which(clinical_breakpoints$method == "MIC" & clinical_breakpoints$ref_tbl != "ECOFF" & !is_blocking), "breakpoint_R", drop = TRUE])))
expect_false(anyNA(as.disk(clinical_breakpoints[which(clinical_breakpoints$method == "DISK" & clinical_breakpoints$ref_tbl != "ECOFF" & !is_blocking), "breakpoint_S", drop = TRUE])))
# zone diameters without S category in the guideline (e.g. CLSI for fluconazole in C. glabrata) only have an R breakpoint, and must state this
no_S_category <- grepl("^\\[No S category\\] ", clinical_breakpoints$note)
expect_true(all(is.na(clinical_breakpoints$breakpoint_S[no_S_category]) & !is.na(clinical_breakpoints$breakpoint_R[no_S_category]) & clinical_breakpoints$method[no_S_category] == "DISK"))
expect_false(anyNA(as.disk(clinical_breakpoints[which(clinical_breakpoints$method == "DISK" & clinical_breakpoints$ref_tbl != "ECOFF" & !is_blocking & !no_S_category), "breakpoint_S", drop = TRUE])))
expect_true(anyNA(as.disk(clinical_breakpoints[which(clinical_breakpoints$method == "DISK" & clinical_breakpoints$ref_tbl != "ECOFF" & !is_blocking), "breakpoint_R", drop = TRUE])))
# antibiotic names must always be coercible to their original AB code
+3
View File
@@ -87,6 +87,9 @@ test_that("test-mo.R", {
# Issue #287: "X complex" fallback to "X" when complex is not a distinct taxon
expect_identical(as.character(suppressWarnings(as.mo("Proteus vulgaris complex"))), as.character(suppressWarnings(as.mo("Proteus vulgaris"))))
expect_identical(as.character(suppressWarnings(as.mo("Enterobacter cloacae complex"))), as.character(as.mo("Enterobacter cloacae complex")))
# a complex named after several of its species returns the complex, not one of the species
expect_identical(as.character(as.mo(c("Acinetobacter calcoaceticus-baumannii complex", "Acinetobacter calcoaceticus/baumannii complex"))), c("B_ACNTB_BMNN-C", "B_ACNTB_BMNN-C"))
expect_identical(as.character(as.mo("Klebsiella pneumoniae-variicola complex")), "B_KLBSL_PNMN-C")
# Issue #288: abbreviated genus with exact species epithet match should win
expect_identical(