EUCAST import: blocking rows, note restrictions, PK-PD and parser fixes (#310)

Make the EUCAST Clinical Breakpoint Tables import (2019-2026) fail-safe:

- Add blocking rows (NA breakpoints, reason in `note`) where EUCAST gives
  no breakpoint for an organism (IE, "-", Note, brackets, "except"
  clauses, table scope), so as.sir() no longer falls back to broader taxa
- Apply restrictions from EUCAST notes via a curated rule table with a
  completeness guard (e.g. zone diameters for E. coli only)
- Import PK-PD (non-species related) breakpoints 2019-2023, H.
  parainfluenzae MIC breakpoints, and explicit screening thresholds from
  notes (high-level aminoglycosides, cefoxitin MIC, unidentified CoNS)
- Parser fixes: lost Staphylococcus sheets 2019/2020, note keys and
  bodies, struck-through cells, hidden sheets, missing disk contents,
  organism groups, site vocabulary, MIC dilution levels, off-scale
  breakpoints stored as 0.0001
- Keep EUCAST antifungal breakpoints 2019-2025 from WHONET; code WHONET
  streptomycin as STR1 instead of STR (streptoduocin)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Matthijs BerendsandClaude Opus 5.5 authored and GitHub committed 2026-10-02 06:01:58 +02:00
1 parent 5d9974e2b9
commit dab4c47e86
25 files changed
+83693 -58604

No files matched your search

+4
View File
@@ -28,6 +28,9 @@ Planned as v3.1.0, mid October 2026.
### New
* EUCAST 2026 and CLSI 2026 breakpoints: over 5,700 new breakpoints added to the `clinical_breakpoints` data set; EUCAST 2026 is now the default for all MIC and disk diffusion interpretations
* Human EUCAST breakpoints from 2019 (v9.0) on, are now retrieved directly from EUCAST Excel files instead of relying on the WHONET repository.
* `clinical_breakpoints` now contains blocking rows (`NA` breakpoints, reason in `note`) where EUCAST gives no breakpoint for an organism, so `as.sir()` no longer falls back to the breakpoint of a broader taxon
* Restrictions in EUCAST notes are applied, e.g. zone diameter breakpoints for *E. coli* only
* EUCAST PK-PD (non-species related) breakpoints 2019-2023 and *H. parainfluenzae* MIC breakpoints are included
* Wildtype/Non-wildtype (WT/NWT) output when using ECOFF-based interpretation, by setting `breakpoint_type = "ECOFF"` in `as.sir()`; WT/NWT results are fully supported in all resistance/susceptibility functions and plots (#254)
* *tidymodels* integration for using SIR, MIC and disk data in modelling pipelines: `step_mic_log2()`, `step_sir_numeric()`, and new column selectors `all_sir()`, `all_mic()`, `all_disk()`
* New `esbl_isolates` data set for practising AMR modelling
@@ -40,6 +43,7 @@ Planned as v3.1.0, mid October 2026.
* New `wisca_plot()` to assess the susceptibility and incidence distributions from the Monte Carlo simulations
### Fixed
* Streptomycin breakpoints imported from WHONET were coded as streptoduocin (`STR`) instead of streptomycin (`STR1`)
* Setting `options(AMR_guideline = "EUCAST 2012")` or any year-qualified value no longer causes errors or silent wrong behaviour in `interpretive_rules()`, `resistance()`, `susceptibility()`, `count_resistant()`, `count_susceptible()`, and SIR plotting/printing functions (#298)
* `as.sir()`
* On data frames: already-converted SIR columns no longer dropped on re-run (#278)
+9 -4
View File
@@ -297,20 +297,24 @@
#' - `method`\cr Testing method, either `r vector_or(clinical_breakpoints$method, documentation = TRUE)`
#' - `site`\cr Body site for which the breakpoint must be applied, e.g. "Oral" or "Respiratory"
#' - `mo`\cr Microbial ID, see [as.mo()]
#' - `rank_index`\cr Taxonomic rank index of `mo` from 1 (subspecies/infraspecies) to 5 (unknown microorganism)
#' - `rank_index`\cr Taxonomic rank index of `mo` from 1 (subspecies/infraspecies) to 7 (non-species related breakpoints), used to choose the most specific breakpoint; blocking rows (see *Details*) have the rank index of their organism + 0.1
#' - `ab`\cr Antimicrobial code as used by this package, EARS-Net and WHONET, see [as.ab()]
#' - `ref_tbl`\cr Info about where the guideline rule can be found
#' - `disk_dose`\cr Dose of the used disk diffusion method
#' - `breakpoint_S`\cr Lowest MIC value or highest number of millimetres that leads to "S"
#' - `breakpoint_S`\cr Lowest MIC value or highest number of millimetres that leads to "S", is `NA` for blocking rows (see *Details*)
#' - `breakpoint_R`\cr Highest MIC value or lowest number of millimetres that leads to "R", can be `NA`
#' - `uti`\cr A [logical] value (`TRUE`/`FALSE`) to indicate whether the rule applies to a urinary tract infection (UTI)
#' - `is_SDD`\cr A [logical] value (`TRUE`/`FALSE`) to indicate whether the intermediate range between "S" and "R" should be interpreted as "SDD", instead of "I". This currently applies to `r sum(clinical_breakpoints$is_SDD)` breakpoints.
#' - `note`\cr Notes from the guideline that apply to the breakpoint, if available
#' @details
#' ### Different Types of Breakpoints
#' Supported types of breakpoints are `r vector_and(clinical_breakpoints$type, quotes = FALSE)`. ECOFF (Epidemiological cut-off) values are used in antimicrobial susceptibility testing to differentiate between wild-type and non-wild-type strains of bacteria or fungi.
#'
#' 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.
#'
#' ### 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.
#'
@@ -319,9 +323,10 @@
#' * All CLSI breakpoints, including ECOFF
#' * All non-human EUCAST breakpoints, including ECOFF
#' * Human EUCAST breakpoints between `r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "EUCAST" & type == "human")$guideline)))` and 2018
#' * Human EUCAST antifungal breakpoints between 2019 and 2025
#'
#' Human EUCAST breakpoints from 2019 onwards (antifungal breakpoints from 2026 onwards) were retrieved directly from the EUCAST Clinical Breakpoint Tables published on <https://www.eucast.org>, using [this script](https://github.com/msberends/AMR/blob/main/data-raw/_reproduction_scripts/reproduction_of_clinical_breakpoints_eucast.R).
#'
#' Human EUCAST breakpoints from 2019 onwards, were retrieved directly from <https://www.eucast.org> using [this script](https://github.com/msberends/AMR/blob/main/data-raw/_reproduction_scripts/reproduction_of_clinical_breakpoints_eucast.R).
#'
#' Our WHONET import script can be [found here](https://github.com/msberends/AMR/blob/main/data-raw/_reproduction_scripts/reproduction_of_clinical_breakpoints.R).
#'
#' ### Response From CLSI and EUCAST
BIN
View File
Binary file not shown.
+2 -1
View File
@@ -653,7 +653,8 @@ if (changed_md5(clin_break)) {
try(saveRDS(clin_break, "data-raw/datasets/clinical_breakpoints.rds", version = 2, compress = "xz"), silent = TRUE)
try(write.table(clinical_breakpoints, "data-raw/datasets/clinical_breakpoints.txt", sep = "\t", na = "", row.names = FALSE), silent = TRUE)
try(haven::write_sav(clin_break, "data-raw/datasets/clinical_breakpoints.sav"), silent = TRUE)
try(haven::write_dta(clin_break, "data-raw/datasets/clinical_breakpoints.dta"), silent = TRUE)
# long notes as strL, otherwise Stata's fixed-width strings make the file exceed 100 MB; a missing string is "" in Stata
try(haven::write_dta(clin_break %>% mutate(note = if_else(is.na(note), "", note)), "data-raw/datasets/clinical_breakpoints.dta", strl_threshold = 255), silent = TRUE)
try(openxlsx2::write_xlsx(clin_break, "data-raw/datasets/clinical_breakpoints.xlsx"), silent = TRUE)
try(arrow::write_feather(clin_break, "data-raw/datasets/clinical_breakpoints.feather"), silent = TRUE)
try(arrow::write_parquet(clin_break, "data-raw/datasets/clinical_breakpoints.parquet"), silent = TRUE)
@@ -282,6 +282,24 @@ breakpoints |>
breakpoints <- breakpoints |>
filter(!is.na(WHONET_ABX_CODE))
# a WHONET code can also exist in `antimicrobials` as the code of another
# agent, e.g. WHONET "STR" (streptomycin) is "STR" (streptoduocin) here, so
# compare the code with WHONET's own name of the agent:
whonet_code_check <- breakpoints |>
distinct(WHONET_ABX_CODE) |>
left_join(whonet_antibiotics_raw |> select(WHONET_ABX_CODE, ANTIBIOTIC), by = "WHONET_ABX_CODE") |>
filter(!is.na(ANTIBIOTIC)) |>
mutate(
by_code = as.ab(if_else(WHONET_ABX_CODE == "STR", "STR1", WHONET_ABX_CODE), info = FALSE),
by_name = as.ab(ANTIBIOTIC, info = FALSE)
) |>
filter(is.na(by_code) | is.na(by_name) | by_code != by_name)
# 2026-10-01: SUD (WHONET: sulbactam-durlobactam, CLSI 2024-2026) is
# sulfadimethoxine here, and sulbactam-durlobactam is not in `antimicrobials`
# yet; this must be resolved before these breakpoints can be used. This
# should otherwise be empty:
whonet_code_check
## Build new breakpoints table ----
@@ -304,7 +322,10 @@ breakpoints_new <- breakpoints |>
mo_rank(mo, keep_synonyms = TRUE) == "order" ~ 5,
TRUE ~ 6
),
ab = as.ab(WHONET_ABX_CODE),
# WHONET code "STR" is streptomycin, but in this package "STR" is
# streptoduocin (streptomycin is "STR1"), so as.ab("STR") would return
# the wrong agent
ab = as.ab(if_else(WHONET_ABX_CODE == "STR", "STR1", WHONET_ABX_CODE)),
ref_tbl = ifelse(type == "ECOFF" & is.na(REFERENCE_TABLE), "ECOFF", REFERENCE_TABLE),
disk_dose = POTENCY,
breakpoint_S = ifelse(type == "ECOFF" & is.na(S) & !is.na(ECV_ECOFF), ECV_ECOFF, S),
@@ -505,6 +526,9 @@ dim(clinical_breakpoints)
# Retrieve >= EUCAST 2019 directly from EUCAST ----
# This parses the EUCAST Clinical Breakpoint Tables in data-raw/ and creates
# `breakpoints_eucast`, `eucast_coverage` and merge_eucast_breakpoints(), used
# below to replace the WHONET-based human EUCAST rows that these tables cover
source("data-raw/_reproduction_scripts/reproduction_of_clinical_breakpoints_eucast.R")
@@ -525,8 +549,7 @@ breakpoints_new <- breakpoints_new |>
# and arrange
arrange(desc(guideline), mo, ab, type, host, method)
clinical_breakpoints <- breakpoints_new
clinical_breakpoints <- clinical_breakpoints |> dataset_UTF8_to_ASCII()
clinical_breakpoints <- merge_eucast_breakpoints(breakpoints_new, breakpoints_eucast, eucast_coverage)
usethis::use_data(clinical_breakpoints, overwrite = TRUE, compress = "xz", version = 2)
rm(clinical_breakpoints)
devtools::load_all(".")
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
634c5e23bed1e92783eeb4739c0d1486
c23b72fd46f4a08b2033b104cef0d4e1
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.
Binary file not shown.
Binary file not shown.
+15 -18
View File
@@ -27,12 +27,9 @@
<div style="display: flex; font-size: 0.8em;">
<p style="text-align:left; width: 50%;">
<small><a href="https://amr-for-r.org/">amr-for-r.org</a></small>
</p>
<p style="text-align:right; width: 50%;">
<small><a href="https://doi.org/10.18637/jss.v104.i03" target="_blank">doi.org/10.18637/jss.v104.i03</a></small>
</p>
@@ -62,7 +59,7 @@ times](https://scholar.google.com/citations?view_op=view_citation&hl=en&citation
in scientific research.
After installing this package, R knows [**~97 000 distinct microbial
species**](./reference/microorganisms.html) (updated May 2026) and all
species**](./reference/microorganisms.html) (updated mei 2026) and all
[**~620 antimicrobial and antiviral
drugs**](./reference/antimicrobials.html) by name and code (including
ATC, EARS-Net, ASIARS-Net, PubChem, LOINC and SNOMED CT), and knows all
@@ -227,8 +224,8 @@ wisca(example_isolates,
```
| Piperacillin/tazobactam | Piperacillin/tazobactam + Gentamicin | Piperacillin/tazobactam + Tobramycin |
|:---|:---|:---|
| 70.2% (64.6-75.4%) | 93.6% (92.1-95%) | 89.9% (87-92.5%) |
|:------------------------|:-------------------------------------|:-------------------------------------|
| 70% (64.9-75.3%) | 93.6% (92.1-95%) | 89.9% (87.1-92.3%) |
WISCA supports stratification by any clinical variable, so you can
generate syndrome-specific or ward-specific coverage estimates:
@@ -242,10 +239,10 @@ wisca(example_isolates,
```
| Syndromic Group | Piperacillin/tazobactam | Piperacillin/tazobactam + Gentamicin | Piperacillin/tazobactam + Tobramycin |
|:---|:---|:---|:---|
| Clinical | 74.7% (68.9-80.8%) | 93.7% (92.1-95.2%) | 90.5% (87-93.2%) |
| ICU | 57% (48.2-65.7%) | 86.8% (83.4-89.8%) | 83% (78-87.6%) |
| Outpatient | 57.2% (46.2-68.9%) | 76.6% (70.1-82.1%) | 67.7% (57.6-77.5%) |
|:----------------|:------------------------|:-------------------------------------|:-------------------------------------|
| Clinical | 74.8% (69.1-80.2%) | 93.6% (92.1-95.1%) | 90.4% (87.2-93.1%) |
| ICU | 57.2% (49.2-65.6%) | 86.7% (83.4-89.9%) | 82.9% (78.1-87.2%) |
| Outpatient | 57.6% (46.3-69.2%) | 76.5% (70.5-82%) | 67.6% (57.3-77.1%) |
**For AMR surveillance**, traditional antibiograms remain the right tool
for tracking resistance per species over time:
@@ -257,10 +254,10 @@ antibiogram(example_isolates,
#> ℹ For `carbapenems()` using columns IPM (imipenem) and MEM (meropenem)
```
| Pathogen | Amoxicillin/clavulanic acid | Imipenem | Meropenem | Piperacillin/tazobactam |
|:---|:---|:---|:---|:---|
| Gram-negative | 76% (73-79%,N=726) | 99% (98-100%,N=631) | 100% (99-100%,N=626) | 88% (85-91%,N=641) |
| Gram-positive | 76% (74-79%,N=1138) | 81% (75-85%,N=257) | 77% (70-82%,N=203) | 86% (82-89%,N=345) |
| Pathogen | Amoxicillin/clavulanic acid | Imipenem | Meropenem | Piperacillin/tazobactam |
|:--------------|:----------------------------|:--------------------|:---------------------|:------------------------|
| Gram-negative | 76% (73-79%,N=726) | 99% (98-100%,N=631) | 100% (99-100%,N=626) | 88% (85-91%,N=641) |
| Gram-positive | 76% (74-79%,N=1138) | 81% (75-85%,N=257) | 77% (70-82%,N=203) | 86% (82-89%,N=345) |
Combination antibiograms show the additional coverage gained by adding a
second agent, stratified by species:
@@ -271,10 +268,10 @@ antibiogram(example_isolates,
antimicrobials = c("TZP", "TZP+TOB", "TZP+GEN"))
```
| Pathogen | Piperacillin/tazobactam | Piperacillin/tazobactam + Gentamicin | Piperacillin/tazobactam + Tobramycin |
|:---|:---|:---|:---|
| Gram-negative | 88% (85-91%,N=641) | 99% (97-99%,N=691) | 98% (97-99%,N=693) |
| Gram-positive | 86% (82-89%,N=345) | 98% (96-98%,N=1044) | 95% (93-97%,N=550) |
| Pathogen | Piperacillin/tazobactam | Piperacillin/tazobactam + Gentamicin | Piperacillin/tazobactam + Tobramycin |
|:--------------|:------------------------|:-------------------------------------|:-------------------------------------|
| Gram-negative | 88% (85-91%,N=641) | 99% (97-99%,N=691) | 98% (97-99%,N=693) |
| Gram-positive | 86% (82-89%,N=345) | 98% (96-98%,N=1044) | 95% (93-97%,N=550) |
Like many other functions in this package, `antibiogram()` and `wisca()`
come with support for 28 languages that are often detected automatically
+11 -4
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 44 089 observations and 15 variables:
A \link[tibble:tibble]{tibble} with 60 173 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"}
@@ -13,14 +13,15 @@ A \link[tibble:tibble]{tibble} with 44 089 observations and 15 variables:
\item \code{method}\cr Testing method, either \code{"DISK"} or \code{"MIC"}
\item \code{site}\cr Body site for which the breakpoint must be applied, e.g. "Oral" or "Respiratory"
\item \code{mo}\cr Microbial ID, see \code{\link[=as.mo]{as.mo()}}
\item \code{rank_index}\cr Taxonomic rank index of \code{mo} from 1 (subspecies/infraspecies) to 5 (unknown microorganism)
\item \code{rank_index}\cr Taxonomic rank index of \code{mo} from 1 (subspecies/infraspecies) to 7 (non-species related breakpoints), used to choose the most specific breakpoint; blocking rows (see \emph{Details}) have the rank index of their organism + 0.1
\item \code{ab}\cr Antimicrobial code as used by this package, EARS-Net and WHONET, see \code{\link[=as.ab]{as.ab()}}
\item \code{ref_tbl}\cr Info about where the guideline rule can be found
\item \code{disk_dose}\cr Dose of the used disk diffusion method
\item \code{breakpoint_S}\cr Lowest MIC value or highest number of millimetres that leads to "S"
\item \code{breakpoint_S}\cr Lowest MIC value or highest number of millimetres that leads to "S", is \code{NA} for blocking rows (see \emph{Details})
\item \code{breakpoint_R}\cr Highest MIC value or lowest number of millimetres that leads to "R", can be \code{NA}
\item \code{uti}\cr A \link{logical} value (\code{TRUE}/\code{FALSE}) to indicate whether the rule applies to a urinary tract infection (UTI)
\item \code{is_SDD}\cr A \link{logical} value (\code{TRUE}/\code{FALSE}) to indicate whether the intermediate range between "S" and "R" should be interpreted as "SDD", instead of "I". This currently applies to 72 breakpoints.
\item \code{note}\cr Notes from the guideline that apply to the breakpoint, if available
}
}
\usage{
@@ -46,6 +47,11 @@ Supported types of breakpoints are ECOFF, animal, and human. ECOFF (Epidemiologi
The default is \code{"human"}, which can also be set with the package option \code{\link[=AMR-options]{AMR_breakpoint_type}}. Use \code{\link[=as.sir]{as.sir(..., breakpoint_type = ...)}} to interpret raw data using a specific breakpoint type, e.g. \code{as.sir(..., breakpoint_type = "ECOFF")} to use ECOFFs.
}
\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 14 523 rows. Remove them with \code{subset(clinical_breakpoints, !is.na(breakpoint_S))} if only actual breakpoints are needed.
}
\subsection{Imported From WHONET}{
Some breakpoints in this package were validated through and imported from \href{https://whonet.org}{WHONET}, a free desktop Windows application developed and supported by the WHO Collaborating Centre for Surveillance of Antimicrobial Resistance. More can be read on \href{https://whonet.org}{their website}. The developers of WHONET and this \code{AMR} package have been in contact about sharing their work. We highly appreciate their great development on the WHONET software.
@@ -55,9 +61,10 @@ From WHONET, imported were:
\item All CLSI breakpoints, including ECOFF
\item All non-human EUCAST breakpoints, including ECOFF
\item Human EUCAST breakpoints between 2011 and 2018
\item Human EUCAST antifungal breakpoints between 2019 and 2025
}
Human EUCAST breakpoints from 2019 onwards, were retrieved directly from \url{https://www.eucast.org} using \href{https://github.com/msberends/AMR/blob/main/data-raw/_reproduction_scripts/reproduction_of_clinical_breakpoints_eucast.R}{this script}.
Human EUCAST breakpoints from 2019 onwards (antifungal breakpoints from 2026 onwards) were retrieved directly from the EUCAST Clinical Breakpoint Tables published on \url{https://www.eucast.org}, using \href{https://github.com/msberends/AMR/blob/main/data-raw/_reproduction_scripts/reproduction_of_clinical_breakpoints_eucast.R}{this script}.
Our WHONET import script can be \href{https://github.com/msberends/AMR/blob/main/data-raw/_reproduction_scripts/reproduction_of_clinical_breakpoints.R}{found here}.
}
+1 -1
View File
@@ -46,7 +46,7 @@ A list with class \code{"htest"} containing the following
\code{(observed - expected) / sqrt(expected)}.}
\item{stdres}{standardized residuals,
\code{(observed - expected) / sqrt(V)}, where \code{V} is the
residual cell variance (Agresti, 2007, section 2.4.5
residual cell variance {(\if{html}{\out{<a href="#reference+chisq.test.Rd+R+3AAgresti+3A2007" class="citation">}}Agresti 2007\if{html}{\out{</a>}}, section 2.4.5)}
for the case where \code{x} is a matrix, \code{n * p * (1 - p)} otherwise).}
}
\description{
+3 -2
View File
@@ -59,8 +59,9 @@ ggplot_pca(
}
\item{pc.biplot}{
If true, use what Gabriel (1971) refers to as a "principal component
biplot", with \code{lambda = 1} and observations scaled up by sqrt(n) and
If true, use what {\if{html}{\cite{}\out{<a href="#reference+biplot.princomp.Rd+R+3AGabriel+3A1971" class="citation">}}Gabriel (1971)\if{html}{\out{</a>}}} refers to as a
\dQuote{principal component biplot},
with \code{lambda = 1} and observations scaled up by sqrt(n) and
variables scaled down by sqrt(n). Then inner products between
variables approximate covariances and distances between observations
approximate Mahalanobis distance.
+8 -4
View File
@@ -63,10 +63,14 @@ test_that("test-data.R", {
)
# check valid disks/MICs
expect_false(anyNA(as.mic(clinical_breakpoints[which(clinical_breakpoints$method == "MIC" & clinical_breakpoints$ref_tbl != "ECOFF"), "breakpoint_S", drop = TRUE])))
expect_true(anyNA(as.mic(clinical_breakpoints[which(clinical_breakpoints$method == "MIC" & clinical_breakpoints$ref_tbl != "ECOFF"), "breakpoint_R", drop = TRUE])))
expect_false(anyNA(as.disk(clinical_breakpoints[which(clinical_breakpoints$method == "DISK" & clinical_breakpoints$ref_tbl != "ECOFF"), "breakpoint_S", drop = TRUE])))
expect_true(anyNA(as.disk(clinical_breakpoints[which(clinical_breakpoints$method == "DISK" & clinical_breakpoints$ref_tbl != "ECOFF"), "breakpoint_R", drop = TRUE])))
# rows without any breakpoint are EUCAST's blocking rows ("no breakpoint" for that organism), which must state their reason
is_blocking <- is.na(clinical_breakpoints$breakpoint_S) & is.na(clinical_breakpoints$breakpoint_R)
expect_true(all(grepl("^\\[No breakpoint\\] ", clinical_breakpoints$note[is_blocking])))
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])))
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
expect_identical(as.ab(AMR::antimicrobials$name), AMR::antimicrobials$ab)
+42
View File
@@ -602,3 +602,45 @@ test_that("custom reference_data: host = NA acts as host-agnostic fallback", {
)
expect_equal(as.character(result), "R")
})
test_that("EUCAST blocking rows prevent falling back to broader breakpoints", {
skip_on_cran()
sir <- function(x, mo, ab, guideline, include_screening = FALSE) {
as.character(suppressMessages(suppressWarnings(
as.sir(x, mo = mo, ab = ab, guideline = guideline, include_screening = include_screening)
)))
}
# IE for V. fluvialis, while Vibrio spp. has breakpoints
expect_identical(sir(as.mic(0.125), "Vibrio fluvialis", "CTX", "EUCAST 2026"), NA_character_)
expect_identical(sir(as.mic(0.125), "Vibrio cholerae", "CTX", "EUCAST 2026"), "S")
# fosfomycin zone diameter breakpoints apply to E. coli only
expect_identical(sir(as.disk(25), "Klebsiella pneumoniae", "FOS", "EUCAST 2019"), NA_character_)
expect_identical(sir(as.disk(25), "Escherichia coli", "FOS", "EUCAST 2019"), "S")
# "-" for enterococci must not fall back to the PK-PD breakpoints
expect_identical(sir(as.mic(1), "Enterococcus faecalis", "CTX", "EUCAST 2021"), NA_character_)
# exceptions: "Klebsiella spp. (except K. aerogenes)"
expect_identical(sir(as.mic(2), "Klebsiella aerogenes", "CXM", "EUCAST 2026"), NA_character_)
# agent listed in the Enterobacterales table for other organisms only
expect_identical(sir(as.mic(0.5), "Salmonella enterica", "CXM", "EUCAST 2026"), NA_character_)
# no EUCAST breakpoints at all, so no PK-PD breakpoints either
expect_identical(sir(as.mic(0.5), "Burkholderia cepacia complex", "MEM", "EUCAST 2020"), NA_character_)
# PK-PD breakpoints only until EUCAST 2023
expect_identical(sir(as.mic(0.25), "Nocardia asteroides", "AMX", "EUCAST 2020"), "S")
expect_identical(sir(as.mic(0.25), "Nocardia asteroides", "AMX", "EUCAST 2024"), NA_character_)
# H. influenzae MIC breakpoints apply to H. parainfluenzae, zone diameters do not
expect_identical(sir(as.mic(0.5), "Haemophilus parainfluenzae", "CIP", "EUCAST 2026"), "R")
expect_identical(sir(as.disk(30), "Haemophilus parainfluenzae", "CIP", "EUCAST 2026"), NA_character_)
# cefoxitin screen: CoNS not identified to species level use S >= 25 mm
expect_identical(sir(as.disk(23), "B_STPHY_CONS", "FOX", "EUCAST 2026", include_screening = TRUE), "R")
expect_identical(sir(as.disk(23), "Staphylococcus hominis", "FOX", "EUCAST 2026", include_screening = TRUE), "S")
expect_identical(sir(as.disk(26), "Staphylococcus epidermidis", "FOX", "EUCAST 2026", include_screening = TRUE), "R")
expect_identical(sir(as.disk(26), "Staphylococcus epidermidis", "FOX", "EUCAST 2026"), NA_character_)
# topical screening cut-off values do not replace systemic breakpoints
expect_identical(sir(as.mic(0.5), "Staphylococcus aureus", "FUS", "EUCAST 2026"), "S")
# off-scale breakpoint S <= 0.001 mg/L categorises wild type isolates as I
expect_identical(sir(as.mic(0.0005), "Pseudomonas aeruginosa", "CIP", "EUCAST 2026"), "I")
# EUCAST's 0.06 mg/L is the dilution 0.0625, i.e. 0.064 in this package
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")
})