AMR/R/sir.R

1528 lines
72 KiB
R
Raw Permalink Normal View History

2018-08-10 15:01:05 +02:00
# ==================================================================== #
# TITLE: #
2022-10-05 09:12:22 +02:00
# AMR: An R Package for Working with Antimicrobial Resistance Data #
2018-08-10 15:01:05 +02:00
# #
# SOURCE CODE: #
2020-07-08 14:48:06 +02:00
# https://github.com/msberends/AMR #
2018-08-10 15:01:05 +02:00
# #
# PLEASE CITE THIS SOFTWARE AS: #
2022-10-05 09:12:22 +02:00
# Berends MS, Luz CF, Friedrich AW, Sinha BNM, Albers CJ, Glasner C #
# (2022). AMR: An R Package for Working with Antimicrobial Resistance #
# Data. Journal of Statistical Software, 104(3), 1-31. #
2023-05-27 10:39:22 +02:00
# https://doi.org/10.18637/jss.v104.i03 #
2022-10-05 09:12:22 +02:00
# #
2022-12-27 15:16:15 +01:00
# Developed at the University of Groningen and the University Medical #
# Center Groningen in The Netherlands, in collaboration with many #
# colleagues from around the world, see our website. #
2018-08-10 15:01:05 +02:00
# #
2019-01-02 23:24:07 +01:00
# This R package is free software; you can freely use and distribute #
# it for both personal and commercial purposes under the terms of the #
# GNU General Public License version 2.0 (GNU GPL-2), as published by #
# the Free Software Foundation. #
# We created this package for both routine data analysis and academic #
# research and it was publicly released in the hope that it will be #
# useful, but it comes WITHOUT ANY WARRANTY OR LIABILITY. #
2020-10-08 11:16:03 +02:00
# #
# Visit our website for the full manual and a complete tutorial about #
# how to conduct AMR data analysis: https://msberends.github.io/AMR/ #
2018-08-10 15:01:05 +02:00
# ==================================================================== #
2023-01-21 23:47:20 +01:00
#' Translate MIC and Disk Diffusion to SIR, or Clean Existing SIR Data
2018-08-10 15:01:05 +02:00
#'
#' @description Clean up existing SIR values, or interpret minimum inhibitory concentration (MIC) values and disk diffusion diameters according to EUCAST or CLSI. [as.sir()] transforms the input to a new class [`sir`], which is an ordered [factor] containing the levels `S`, `SDD`, `I`, `R`, `NI`.
#'
2024-05-31 21:24:35 +02:00
#' These breakpoints are currently implemented:
#' - For **clinical microbiology**: EUCAST `r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "EUCAST" & type == "human")$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "EUCAST" & type == "human")$guideline)))` and CLSI `r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI" & type == "human")$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI" & type == "human")$guideline)))`;
#' - For **veterinary microbiology**: EUCAST `r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "EUCAST" & type == "animal")$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "EUCAST" & type == "animal")$guideline)))` and CLSI `r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI" & type == "animal")$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI" & type == "animal")$guideline)))`;
#' - ECOFFs (Epidemiological cut-off values): EUCAST `r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "EUCAST" & type == "ECOFF")$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "EUCAST" & type == "ECOFF")$guideline)))` and CLSI `r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI" & type == "ECOFF")$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI" & type == "ECOFF")$guideline)))`.
#'
2024-05-20 15:27:04 +02:00
#' All breakpoints used for interpretation are available in our [clinical_breakpoints] data set.
2023-01-21 23:47:20 +01:00
#' @rdname as.sir
2021-05-12 18:15:03 +02:00
#' @param x vector of values (for class [`mic`]: MIC values in mg/L, for class [`disk`]: a disk diffusion radius in millimetres)
#' @param mo a vector (or column name) with [character]s that can be coerced to valid microorganism codes with [as.mo()], can be left empty to determine it automatically
#' @param ab a vector (or column name) with [character]s that can be coerced to a valid antimicrobial drug code with [as.ab()]
2024-05-31 21:24:35 +02:00
#' @param uti (Urinary Tract Infection) a vector (or column name) with [logical]s (`TRUE` or `FALSE`) to specify whether a UTI specific interpretation from the guideline should be chosen. For using [as.sir()] on a [data.frame], this can also be a column containing [logical]s or when left blank, the data set will be searched for a column 'specimen', and rows within this column containing 'urin' (such as 'urine', 'urina') will be regarded isolates from a UTI. See *Examples*.
2019-05-10 16:44:59 +02:00
#' @inheritParams first_isolate
#' @param guideline defaults to EUCAST `r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "EUCAST")$guideline)))` (the latest implemented EUCAST guideline in the [AMR::clinical_breakpoints] data set), but can be set with the [package option][AMR-options] [`AMR_guideline`][AMR-options]. Currently supports EUCAST (`r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "EUCAST")$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "EUCAST")$guideline)))`) and CLSI (`r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI")$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI")$guideline)))`), see *Details*.
2021-05-12 18:15:03 +02:00
#' @param conserve_capped_values a [logical] to indicate that MIC values starting with `">"` (but not `">="`) must always return "R" , and that MIC values starting with `"<"` (but not `"<="`) must always return "S"
#' @param add_intrinsic_resistance *(only useful when using a EUCAST guideline)* a [logical] to indicate whether intrinsic antibiotic resistance must also be considered for applicable bug-drug combinations, meaning that e.g. ampicillin will always return "R" in *Klebsiella* species. Determination is based on the [intrinsic_resistant] data set, that itself is based on `r format_eucast_version_nr(3.3)`.
#' @param include_screening a [logical] to indicate that clinical breakpoints for screening are allowed - the default is `FALSE`. Can also be set with the [package option][AMR-options] [`AMR_include_screening`][AMR-options].
#' @param include_PKPD a [logical] to indicate that PK/PD clinical breakpoints must be applied as a last resort - the default is `TRUE`. Can also be set with the [package option][AMR-options] [`AMR_include_PKPD`][AMR-options].
#' @param breakpoint_type the type of breakpoints to use, either `r vector_or(clinical_breakpoints$type)`. ECOFF stands for Epidemiological Cut-Off values. The default is `"human"`, which can also be set with the [package option][AMR-options] [`AMR_breakpoint_type`][AMR-options]. If `host` is set to values of veterinary species, this will automatically be set to `"animal"`.
#' @param host a vector (or column name) with [character]s to indicate the host. Only useful for veterinary breakpoints, as it requires `breakpoint_type = "animal"`. The values can be any text resembling the animal species, even in any of the `r length(LANGUAGES_SUPPORTED)` supported languages of this package. For foreign languages, be sure to set the language with [set_AMR_locale()] (though it will be automatically guessed based on the system language).
2023-01-21 23:47:20 +01:00
#' @param reference_data a [data.frame] to be used for interpretation, which defaults to the [clinical_breakpoints] data set. Changing this argument allows for using own interpretation guidelines. This argument must contain a data set that is equal in structure to the [clinical_breakpoints] data set (same column names and column types). Please note that the `guideline` argument will be ignored when `reference_data` is manually set.
#' @param threshold maximum fraction of invalid antimicrobial interpretations of `x`, see *Examples*
2023-01-21 23:47:20 +01:00
#' @param ... for using on a [data.frame]: names of columns to apply [as.sir()] on (supports tidy selection such as `column1:column4`). Otherwise: arguments passed on to methods.
2022-08-28 10:31:50 +02:00
#' @details
#' *Note: The clinical breakpoints in this package were validated through, and imported from, [WHONET](https://whonet.org). The public use of this `AMR` package has been endorsed by both CLSI and EUCAST. See [clinical_breakpoints] for more information.*
#'
2022-10-10 15:44:59 +02:00
#' ### How it Works
2022-08-28 10:31:50 +02:00
#'
#' The [as.sir()] function can work in four ways:
2022-08-28 10:31:50 +02:00
#'
#' 1. For **cleaning raw / untransformed data**. The data will be cleaned to only contain valid values, namely: **S** for susceptible, **I** for intermediate or 'susceptible, increased exposure', **R** for resistant, **NI** for non-interpretable, and **SDD** for susceptible dose-dependent. Each of these can be set using a [regular expression][base::regex]. Furthermore, [as.sir()] will try its best to clean with some intelligence. For example, mixed values with SIR interpretations and MIC values such as `"<0.25; S"` will be coerced to `"S"`. Combined interpretations for multiple test methods (as seen in laboratory records) such as `"S; S"` will be coerced to `"S"`, but a value like `"S; I"` will return `NA` with a warning that the input is invalid.
2022-08-28 10:31:50 +02:00
#'
2020-12-22 00:51:17 +01:00
#' 2. For **interpreting minimum inhibitory concentration (MIC) values** according to EUCAST or CLSI. You must clean your MIC values first using [as.mic()], that also gives your columns the new data class [`mic`]. Also, be sure to have a column with microorganism names or codes. It will be found automatically, but can be set manually using the `mo` argument.
2023-01-21 23:47:20 +01:00
#' * Using `dplyr`, SIR interpretation can be done very easily with either:
2024-05-31 21:24:35 +02:00
#' ```r
2023-01-21 23:47:20 +01:00
#' your_data %>% mutate_if(is.mic, as.sir)
#' your_data %>% mutate(across(where(is.mic), as.sir))
2024-05-31 21:24:35 +02:00
#' your_data %>% mutate_if(is.mic, as.sir, ab = "column_with_antibiotics", mo = "column_with_microorganisms")
#' your_data %>% mutate_if(is.mic, as.sir, ab = c("cipro", "ampicillin", ...), mo = c("E. coli", "K. pneumoniae", ...))
#'
#' # for veterinary breakpoints, also set `host`:
#' your_data %>% mutate_if(is.mic, as.sir, host = "column_with_animal_hosts", guideline = "CLSI")
#' ```
#' * Operators like "<=" will be stripped before interpretation. When using `conserve_capped_values = TRUE`, an MIC value of e.g. ">2" will always return "R", even if the breakpoint according to the chosen guideline is ">=4". This is to prevent that capped values from raw laboratory data would not be treated conservatively. The default behaviour (`conserve_capped_values = FALSE`) considers ">2" to be lower than ">=4" and might in this case return "S" or "I".
2020-12-22 00:51:17 +01:00
#' 3. For **interpreting disk diffusion diameters** according to EUCAST or CLSI. You must clean your disk zones first using [as.disk()], that also gives your columns the new data class [`disk`]. Also, be sure to have a column with microorganism names or codes. It will be found automatically, but can be set manually using the `mo` argument.
2023-01-21 23:47:20 +01:00
#' * Using `dplyr`, SIR interpretation can be done very easily with either:
2024-05-31 21:24:35 +02:00
#' ```r
2023-01-21 23:47:20 +01:00
#' your_data %>% mutate_if(is.disk, as.sir)
#' your_data %>% mutate(across(where(is.disk), as.sir))
2024-05-31 21:24:35 +02:00
#' your_data %>% mutate_if(is.disk, as.sir, ab = "column_with_antibiotics", mo = "column_with_microorganisms")
#' your_data %>% mutate_if(is.disk, as.sir, ab = c("cipro", "ampicillin", ...), mo = c("E. coli", "K. pneumoniae", ...))
#'
#' # for veterinary breakpoints, also set `host`:
#' your_data %>% mutate_if(is.disk, as.sir, host = "column_with_animal_hosts", guideline = "CLSI")
#' ```
2023-01-21 23:47:20 +01:00
#' 4. For **interpreting a complete data set**, with automatic determination of MIC values, disk diffusion diameters, microorganism names or codes, and antimicrobial test results. This is done very simply by running `as.sir(your_data)`.
2022-08-28 10:31:50 +02:00
#'
2023-01-23 15:01:21 +01:00
#' **For points 2, 3 and 4: Use [sir_interpretation_history()]** to retrieve a [data.frame] (or [tibble][tibble::tibble()] if the `tibble` package is installed) with all results of the last [as.sir()] call.
2022-09-01 15:20:57 +02:00
#'
2022-10-10 15:44:59 +02:00
#' ### Supported Guidelines
2022-08-28 10:31:50 +02:00
#'
#' For interpreting MIC values as well as disk diffusion diameters, currently implemented guidelines are for **clinical microbiology**: EUCAST `r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "EUCAST" & type == "human")$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "EUCAST" & type == "human")$guideline)))` and CLSI `r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI" & type == "human")$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI" & type == "human")$guideline)))`, and for **veterinary microbiology**: EUCAST `r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "EUCAST" & type == "animal")$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "EUCAST" & type == "animal")$guideline)))` and CLSI `r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI" & type == "animal")$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI" & type == "animal")$guideline)))`.
2022-08-28 10:31:50 +02:00
#'
2023-02-12 15:09:54 +01:00
#' Thus, the `guideline` argument must be set to e.g., ``r paste0('"', subset(AMR::clinical_breakpoints, guideline %like% "EUCAST")$guideline[1], '"')`` or ``r paste0('"', subset(AMR::clinical_breakpoints, guideline %like% "CLSI")$guideline[1], '"')``. By simply using `"EUCAST"` (the default) or `"CLSI"` as input, the latest included version of that guideline will automatically be selected. You can set your own data set using the `reference_data` argument. The `guideline` argument will then be ignored.
2023-01-23 15:01:21 +01:00
#'
#' You can set the default guideline with the [package option][AMR-options] [`AMR_guideline`][AMR-options] (e.g. in your `.Rprofile` file), such as:
2023-01-23 15:01:21 +01:00
#'
2022-11-24 20:29:00 +01:00
#' ```
#' options(AMR_guideline = "CLSI")
#' options(AMR_guideline = "CLSI 2018")
#' options(AMR_guideline = "EUCAST 2020")
#' # or to reset:
#' options(AMR_guideline = NULL)
#' ```
#'
#' For veterinary guidelines, these might be the best options:
#'
#' ```
#' options(AMR_guideline = "CLSI")
#' options(AMR_breakpoint_type = "animal")
#' ```
2022-08-28 10:31:50 +02:00
#'
2022-10-10 15:44:59 +02:00
#' ### After Interpretation
2022-08-28 10:31:50 +02:00
#'
2023-01-21 23:47:20 +01:00
#' After using [as.sir()], you can use the [eucast_rules()] defined by EUCAST to (1) apply inferred susceptibility and resistance based on results of other antimicrobials and (2) apply intrinsic resistance based on taxonomic properties of a microorganism.
2022-08-28 10:31:50 +02:00
#'
2023-02-12 15:09:54 +01:00
#' ### Machine-Readable Clinical Breakpoints
2022-08-28 10:31:50 +02:00
#'
#' The repository of this package [contains a machine-readable version](https://github.com/msberends/AMR/blob/main/data-raw/clinical_breakpoints.txt) of all guidelines. This is a CSV file consisting of `r format(nrow(AMR::clinical_breakpoints), big.mark = " ")` rows and `r ncol(AMR::clinical_breakpoints)` columns. This file is machine-readable, since it contains one row for every unique combination of the test method (MIC or disk diffusion), the antimicrobial drug and the microorganism. **This allows for easy implementation of these rules in laboratory information systems (LIS)**. Note that it only contains interpretation guidelines for humans - interpretation guidelines from CLSI for animals were removed.
2019-04-09 14:59:17 +02:00
#'
2022-10-10 15:44:59 +02:00
#' ### Other
2022-08-28 10:31:50 +02:00
#'
2023-01-21 23:47:20 +01:00
#' The function [is.sir()] detects if the input contains class `sir`. If the input is a [data.frame], it iterates over all columns and returns a [logical] vector.
#'
#' The base R function [as.double()] can be used to retrieve quantitative values from a `sir` object: `"S"` = 1, `"I"`/`"SDD"` = 2, `"R"` = 3. All other values are rendered `NA` . **Note:** Do not use `as.integer()`, since that (because of how R works internally) will return the factor level indices, and not these aforementioned quantitative values.
2019-05-10 16:44:59 +02:00
#'
#' The function [is_sir_eligible()] returns `TRUE` when a column contains at most 5% invalid antimicrobial interpretations (not S and/or I and/or R and/or NI and/or SDD), and `FALSE` otherwise. The threshold of 5% can be set with the `threshold` argument. If the input is a [data.frame], it iterates over all columns and returns a [logical] vector.
2023-01-21 23:47:20 +01:00
#' @section Interpretation of SIR:
2023-07-10 13:41:52 +02:00
#' In 2019, the European Committee on Antimicrobial Susceptibility Testing (EUCAST) has decided to change the definitions of susceptibility testing categories S, I, and R as shown below (<https://www.eucast.org/newsiandr>):
2019-05-13 10:10:16 +02:00
#'
2023-01-21 23:47:20 +01:00
#' - **S - Susceptible, standard dosing regimen**\cr
#' A microorganism is categorised as "Susceptible, standard dosing regimen", when there is a high likelihood of therapeutic success using a standard dosing regimen of the agent.
#' - **I - Susceptible, increased exposure** *\cr
#' A microorganism is categorised as "Susceptible, Increased exposure*" when there is a high likelihood of therapeutic success because exposure to the agent is increased by adjusting the dosing regimen or by its concentration at the site of infection.
2019-11-29 19:43:23 +01:00
#' - **R = Resistant**\cr
2023-01-21 23:47:20 +01:00
#' A microorganism is categorised as "Resistant" when there is a high likelihood of therapeutic failure even when there is increased exposure.
2023-01-23 15:01:21 +01:00
#'
2023-01-21 23:47:20 +01:00
#' * *Exposure* is a function of how the mode of administration, dose, dosing interval, infusion time, as well as distribution and excretion of the antimicrobial agent will influence the infecting organism at the site of infection.
2019-05-13 10:10:16 +02:00
#'
2022-11-13 13:44:25 +01:00
#' This AMR package honours this insight. Use [susceptibility()] (equal to [proportion_SI()]) to determine antimicrobial susceptibility and [count_susceptible()] (equal to [count_SI()]) to count susceptible isolates.
2023-01-21 23:47:20 +01:00
#' @return Ordered [factor] with new class `sir`
#' @aliases sir
2018-08-10 15:01:05 +02:00
#' @export
#' @seealso [as.mic()], [as.disk()], [as.mo()]
2022-10-22 22:00:15 +02:00
#' @source
#' For interpretations of minimum inhibitory concentration (MIC) values and disk diffusion diameters:
2022-10-30 14:31:45 +01:00
#'
#' - **CLSI M39: Analysis and Presentation of Cumulative Antimicrobial Susceptibility Test Data**, `r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI")$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI")$guideline)))`, *Clinical and Laboratory Standards Institute* (CLSI). <https://clsi.org/standards/products/microbiology/documents/m39/>.
#' - **CLSI M100: Performance Standard for Antimicrobial Susceptibility Testing**, `r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI" & type != "animal")$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI" & type != "animal")$guideline)))`, *Clinical and Laboratory Standards Institute* (CLSI). <https://clsi.org/standards/products/microbiology/documents/m100/>.
#' - **CLSI VET01: Performance Standards for Antimicrobial Disk and Dilution Susceptibility Tests for Bacteria Isolated From Animals**, `r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI" & type == "animal")$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "CLSI" & type == "animal")$guideline)))`, *Clinical and Laboratory Standards Institute* (CLSI). <https://clsi.org/standards/products/veterinary-medicine/documents/vet01//>.
#' - **EUCAST Breakpoint tables for interpretation of MICs and zone diameters**, `r min(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "EUCAST")$guideline)))`-`r max(as.integer(gsub("[^0-9]", "", subset(AMR::clinical_breakpoints, guideline %like% "EUCAST")$guideline)))`, *European Committee on Antimicrobial Susceptibility Testing* (EUCAST). <https://www.eucast.org/clinical_breakpoints>.
#' @inheritSection AMR Reference Data Publicly Available
#' @examples
2022-08-21 16:37:20 +02:00
#' example_isolates
2023-01-21 23:47:20 +01:00
#' summary(example_isolates) # see all SIR results at a glance
2022-08-28 10:31:50 +02:00
#'
#' # For INTERPRETING disk diffusion and MIC values -----------------------
2022-08-28 10:31:50 +02:00
#'
2020-02-20 13:19:23 +01:00
#' # a whole data set, even with combined MIC values and disk zones
2022-08-28 10:31:50 +02:00
#' df <- data.frame(
#' microorganism = "Escherichia coli",
#' AMP = as.mic(8),
#' CIP = as.mic(0.256),
#' GEN = as.disk(18),
#' TOB = as.disk(16),
#' ERY = "R"
#' )
2023-01-21 23:47:20 +01:00
#' as.sir(df)
2022-08-28 10:31:50 +02:00
#'
2022-09-01 15:20:57 +02:00
#' # return a 'logbook' about the results:
2023-01-21 23:47:20 +01:00
#' sir_interpretation_history()
2022-09-01 15:20:57 +02:00
#'
2020-02-20 13:19:23 +01:00
#' # for single values
2023-01-21 23:47:20 +01:00
#' as.sir(
2022-08-28 10:31:50 +02:00
#' x = as.mic(2),
#' mo = as.mo("S. pneumoniae"),
#' ab = "AMP",
#' guideline = "EUCAST"
#' )
2020-02-14 19:54:13 +01:00
#'
2023-01-21 23:47:20 +01:00
#' as.sir(
2022-08-28 10:31:50 +02:00
#' x = as.disk(18),
#' mo = "Strep pneu", # `mo` will be coerced with as.mo()
#' ab = "ampicillin", # and `ab` with as.ab()
#' guideline = "EUCAST"
#' )
#'
#' \donttest{
#' # the dplyr way
#' if (require("dplyr")) {
2023-01-21 23:47:20 +01:00
#' df %>% mutate_if(is.mic, as.sir)
#' df %>% mutate_if(function(x) is.mic(x) | is.disk(x), as.sir)
#' df %>% mutate(across(where(is.mic), as.sir))
#' df %>% mutate_at(vars(AMP:TOB), as.sir)
#' df %>% mutate(across(AMP:TOB, as.sir))
2022-08-28 10:31:50 +02:00
#'
#' df %>%
#' mutate_at(vars(AMP:TOB), as.sir, mo = "microorganism")
2022-08-28 10:31:50 +02:00
#'
#' # to include information about urinary tract infections (UTI)
2022-08-28 10:31:50 +02:00
#' data.frame(
#' mo = "E. coli",
#' NIT = c("<= 2", 32),
#' from_the_bladder = c(TRUE, FALSE)
#' ) %>%
2023-01-21 23:47:20 +01:00
#' as.sir(uti = "from_the_bladder")
2022-08-28 10:31:50 +02:00
#'
#' data.frame(
#' mo = "E. coli",
#' NIT = c("<= 2", 32),
#' specimen = c("urine", "blood")
#' ) %>%
2023-01-21 23:47:20 +01:00
#' as.sir() # automatically determines urine isolates
2022-08-28 10:31:50 +02:00
#'
#' df %>%
2023-01-21 23:47:20 +01:00
#' mutate_at(vars(AMP:TOB), as.sir, mo = "E. coli", uti = TRUE)
#' }
2019-05-10 16:44:59 +02:00
#'
2023-01-21 23:47:20 +01:00
#' # For CLEANING existing SIR values ------------------------------------
2022-08-28 10:31:50 +02:00
#'
#' as.sir(c("S", "SDD", "I", "R", "NI", "A", "B", "C"))
2023-01-21 23:47:20 +01:00
#' as.sir("<= 0.002; S") # will return "S"
#' sir_data <- as.sir(c(rep("S", 474), rep("I", 36), rep("R", 370)))
#' is.sir(sir_data)
#' plot(sir_data) # for percentages
#' barplot(sir_data) # for frequencies
#'
#' # as common in R, you can use as.integer() to return factor indices:
#' as.integer(as.sir(c("S", "SDD", "I", "R", "NI", NA)))
#' # but for computational use, as.double() will return 1 for S, 2 for I/SDD, and 3 for R:
#' as.double(as.sir(c("S", "SDD", "I", "R", "NI", NA)))
#'
#' # the dplyr way
#' if (require("dplyr")) {
#' example_isolates %>%
2023-01-21 23:47:20 +01:00
#' mutate_at(vars(PEN:RIF), as.sir)
2022-08-28 10:31:50 +02:00
#' # same:
#' example_isolates %>%
2023-01-21 23:47:20 +01:00
#' as.sir(PEN:RIF)
2022-08-28 10:31:50 +02:00
#'
2023-01-21 23:47:20 +01:00
#' # fastest way to transform all columns with already valid AMR results to class `sir`:
#' example_isolates %>%
2023-01-21 23:47:20 +01:00
#' mutate_if(is_sir_eligible, as.sir)
2022-08-28 10:31:50 +02:00
#'
#' # since dplyr 1.0.0, this can also be:
#' # example_isolates %>%
2023-01-21 23:47:20 +01:00
#' # mutate(across(where(is_sir_eligible), as.sir))
#' }
2020-05-16 21:40:50 +02:00
#' }
2023-01-21 23:47:20 +01:00
as.sir <- function(x, ...) {
UseMethod("as.sir")
2019-05-10 16:44:59 +02:00
}
2024-05-20 15:27:04 +02:00
as_sir_structure <- function(x) {
structure(factor(as.character(unlist(unname(x))),
levels = c("S", "SDD", "I", "R", "NI"),
2024-05-20 15:27:04 +02:00
ordered = TRUE),
class = c("sir", "ordered", "factor"))
}
2023-01-21 23:47:20 +01:00
#' @rdname as.sir
#' @details `NA_sir_` is a missing value of the new `sir` class, analogous to e.g. base \R's [`NA_character_`][base::NA].
#' @format NULL
#' @export
2024-05-20 15:27:04 +02:00
NA_sir_ <- as_sir_structure(NA_character_)
2023-01-21 23:47:20 +01:00
#' @rdname as.sir
2020-07-29 13:48:50 +02:00
#' @export
2023-01-21 23:47:20 +01:00
is.sir <- function(x) {
if (inherits(x, "data.frame")) {
2023-01-21 23:47:20 +01:00
unname(vapply(FUN.VALUE = logical(1), x, is.sir))
} else {
isTRUE(inherits(x, "sir"))
}
2020-07-29 13:48:50 +02:00
}
2023-01-21 23:47:20 +01:00
#' @rdname as.sir
2020-07-29 13:48:50 +02:00
#' @export
2023-01-21 23:47:20 +01:00
is_sir_eligible <- function(x, threshold = 0.05) {
meet_criteria(threshold, allow_class = "numeric", has_length = 1)
2022-08-28 10:31:50 +02:00
if (inherits(x, "data.frame")) {
# iterate this function over all columns
2023-01-21 23:47:20 +01:00
return(unname(vapply(FUN.VALUE = logical(1), x, is_sir_eligible)))
}
2022-08-28 10:31:50 +02:00
2020-07-29 13:48:50 +02:00
stop_if(NCOL(x) > 1, "`x` must be a one-dimensional vector.")
2022-08-28 10:31:50 +02:00
if (any(c(
"numeric",
"integer",
"mo",
"ab",
"Date",
"POSIXt",
"raw",
"hms",
"mic",
"disk"
)
%in% class(x))) {
2020-07-29 13:48:50 +02:00
# no transformation needed
return(FALSE)
} else if (all(x %in% c("S", "SDD", "I", "R", "NI", NA)) & !all(is.na(x))) {
return(TRUE)
} else if (!any(c("S", "SDD", "I", "R", "NI") %in% x, na.rm = TRUE) & !all(is.na(x))) {
return(FALSE)
2020-07-29 13:48:50 +02:00
} else {
x <- x[!is.na(x) & !is.null(x) & !x %in% c("", "-", "NULL")]
2020-07-29 13:48:50 +02:00
if (length(x) == 0) {
# no other values than empty
cur_col <- get_current_column()
if (!is.null(cur_col)) {
ab <- suppressWarnings(as.ab(cur_col, fast_mode = TRUE, info = FALSE))
if (!is.na(ab)) {
2022-11-13 13:44:25 +01:00
# this is a valid antibiotic drug code
2022-08-28 10:31:50 +02:00
message_(
2023-01-21 23:47:20 +01:00
"Column '", font_bold(cur_col), "' is SIR eligible (despite only having empty values), since it seems to be ",
2022-08-28 10:31:50 +02:00
ab_name(ab, language = NULL, tolower = TRUE), " (", ab, ")"
)
return(TRUE)
}
}
# all values empty and no antibiotic col name - return FALSE
2020-07-29 13:48:50 +02:00
return(FALSE)
}
# transform all values and see if it meets the set threshold
2023-01-21 23:47:20 +01:00
checked <- suppressWarnings(as.sir(x))
2020-07-29 13:48:50 +02:00
outcome <- sum(is.na(checked)) / length(x)
outcome <= threshold
}
}
2024-05-20 15:27:04 +02:00
#' @rdname as.sir
2019-05-10 16:44:59 +02:00
#' @export
#' @param S,I,R,NI,SDD a case-independent [regular expression][base::regex] to translate input to this result. This regular expression will be run *after* all non-letters and whitespaces are removed from the input.
# extra param: warn (logical, to never throw a warning)
as.sir.default <- function(x,
S = "^(S|U)+$",
I = "^(I)+$",
R = "^(R)+$",
NI = "^(N|NI|V)+$",
SDD = "^(SDD|D|H)+$",
...) {
if (inherits(x, "sir")) {
2024-05-20 18:58:35 +02:00
return(as_sir_structure(x))
}
2022-08-28 10:31:50 +02:00
x.bak <- x
x <- as.character(x) # this is needed to prevent the vctrs pkg from throwing an error
2023-01-23 15:01:21 +01:00
2024-04-05 16:44:43 +02:00
if (inherits(x.bak, c("numeric", "integer")) && all(x %in% c(1:3, NA))) {
2021-05-04 15:20:43 +02:00
# support haven package for importing e.g., from SPSS - it adds the 'labels' attribute
lbls <- attributes(x.bak)$labels
2023-01-21 23:47:20 +01:00
if (!is.null(lbls) && all(c("S", "I", "R") %in% names(lbls)) && all(c(1:3) %in% lbls)) {
2021-05-04 15:20:43 +02:00
x[x.bak == 1] <- names(lbls[lbls == 1])
x[x.bak == 2] <- names(lbls[lbls == 2])
x[x.bak == 3] <- names(lbls[lbls == 3])
} else {
x[x.bak == 1] <- "S"
x[x.bak == 2] <- "I"
2022-08-28 10:31:50 +02:00
x[x.bak == 3] <- "R"
2021-05-04 15:20:43 +02:00
}
2024-06-13 20:55:17 +02:00
} else if (inherits(x.bak, "character") && all(x %in% c("1", "2", "3", "S", "I", "R", NA_character_))) {
2022-11-17 15:23:29 +01:00
x[x.bak == "1"] <- "S"
x[x.bak == "2"] <- "I"
x[x.bak == "3"] <- "R"
} else if (inherits(x.bak, "character") && all(x %in% c("1", "2", "3", "4", "5", "S", "SDD", "I", "R", "NI", NA_character_))) {
2024-06-13 20:55:17 +02:00
x[x.bak == "1"] <- "S"
x[x.bak == "2"] <- "SDD"
x[x.bak == "3"] <- "I"
x[x.bak == "4"] <- "R"
x[x.bak == "5"] <- "NI"
} else if (!all(is.na(x)) && !identical(levels(x), c("S", "SDD", "I", "R", "NI")) && !all(x %in% c("S", "SDD", "I", "R", "NI", NA))) {
2024-04-07 20:22:59 +02:00
if (all(x %unlike% "(S|I|R)", na.rm = TRUE)) {
# check if they are actually MICs or disks
2020-09-24 00:30:11 +02:00
if (all_valid_mics(x)) {
2023-01-21 23:47:20 +01:00
warning_("in `as.sir()`: the input seems to contain MIC values. You can transform them with `as.mic()` before running `as.sir()` to interpret them.")
2020-09-24 00:30:11 +02:00
} else if (all_valid_disks(x)) {
2023-01-21 23:47:20 +01:00
warning_("in `as.sir()`: the input seems to contain disk diffusion values. You can transform them with `as.disk()` before running `as.sir()` to interpret them.")
2020-02-20 17:21:01 +01:00
}
}
2022-08-28 10:31:50 +02:00
# trim leading and trailing spaces, new lines, etc.
x <- trimws2(as.character(unlist(x)))
x[x %in% c(NA, "", "-", "NULL")] <- NA_character_
x.bak <- x
2023-01-23 15:01:21 +01:00
na_before <- length(x[is.na(x)])
2022-08-28 10:31:50 +02:00
# correct for translations
2022-08-28 10:31:50 +02:00
trans_R <- unlist(TRANSLATIONS[
which(TRANSLATIONS$pattern == "Resistant"),
LANGUAGES_SUPPORTED[LANGUAGES_SUPPORTED %in% colnames(TRANSLATIONS)]
])
trans_S <- unlist(TRANSLATIONS[
which(TRANSLATIONS$pattern == "Susceptible"),
LANGUAGES_SUPPORTED[LANGUAGES_SUPPORTED %in% colnames(TRANSLATIONS)]
])
trans_I <- unlist(TRANSLATIONS[
which(TRANSLATIONS$pattern %in% c("Incr. exposure", "Susceptible, incr. exp.", "Intermediate")),
LANGUAGES_SUPPORTED[LANGUAGES_SUPPORTED %in% colnames(TRANSLATIONS)]
])
x <- gsub(paste0(unique(trans_R[!is.na(trans_R)]), collapse = "|"), "R", x, ignore.case = TRUE)
x <- gsub(paste0(unique(trans_S[!is.na(trans_S)]), collapse = "|"), "S", x, ignore.case = TRUE)
x <- gsub(paste0(unique(trans_I[!is.na(trans_I)]), collapse = "|"), "I", x, ignore.case = TRUE)
# replace all English textual input
x[x %like% "([^a-z]|^)res(is(tant)?)?"] <- "R"
x[x %like% "([^a-z]|^)sus(cep(tible)?)?"] <- "S"
x[x %like% "not|non"] <- "NI"
x[x %like% "([^a-z]|^)int(er(mediate)?)?|incr.*exp"] <- "I"
x[x %like% "dose"] <- "SDD"
2022-10-05 09:12:22 +02:00
x <- gsub("[^A-Z]+", "", x, perl = TRUE)
2024-05-20 15:27:04 +02:00
# apply regexes set by user
x[x %like% S] <- "S"
x[x %like% I] <- "I"
x[x %like% R] <- "R"
x[x %like% NI] <- "NI"
2024-05-20 15:27:04 +02:00
x[x %like% SDD] <- "SDD"
x[!x %in% c("S", "SDD", "I", "R", "NI")] <- NA_character_
2020-06-26 10:21:22 +02:00
na_after <- length(x[is.na(x) | x == ""])
2022-08-28 10:31:50 +02:00
2023-01-21 23:47:20 +01:00
if (!isFALSE(list(...)$warn)) { # so as.sir(..., warn = FALSE) will never throw a warning
if (na_before != na_after) {
2023-02-09 13:07:39 +01:00
list_missing <- x.bak[is.na(x) & !is.na(x.bak) & x.bak != ""] %pm>%
unique() %pm>%
sort() %pm>%
vector_and(quotes = TRUE)
2022-10-05 09:12:22 +02:00
cur_col <- get_current_column()
2023-01-21 23:47:20 +01:00
warning_("in `as.sir()`: ", na_after - na_before, " result",
2022-10-05 09:12:22 +02:00
ifelse(na_after - na_before > 1, "s", ""),
ifelse(is.null(cur_col), "", paste0(" in column '", cur_col, "'")),
" truncated (",
2022-08-28 10:31:50 +02:00
round(((na_after - na_before) / length(x)) * 100),
"%) that were invalid antimicrobial interpretations: ",
list_missing,
call = FALSE
)
}
}
2018-08-23 00:40:36 +02:00
}
2022-08-28 10:31:50 +02:00
2024-05-20 15:27:04 +02:00
as_sir_structure(x)
}
2018-08-23 00:40:36 +02:00
2023-01-21 23:47:20 +01:00
#' @rdname as.sir
2019-05-10 16:44:59 +02:00
#' @export
2023-01-21 23:47:20 +01:00
as.sir.mic <- function(x,
2022-08-28 10:31:50 +02:00
mo = NULL,
ab = deparse(substitute(x)),
2022-11-24 20:29:00 +01:00
guideline = getOption("AMR_guideline", "EUCAST"),
2022-10-29 14:15:23 +02:00
uti = NULL,
conserve_capped_values = FALSE,
add_intrinsic_resistance = FALSE,
2023-01-21 23:47:20 +01:00
reference_data = AMR::clinical_breakpoints,
2023-02-12 15:09:54 +01:00
include_screening = getOption("AMR_include_screening", FALSE),
2023-01-23 20:07:57 +01:00
include_PKPD = getOption("AMR_include_PKPD", TRUE),
breakpoint_type = getOption("AMR_breakpoint_type", "human"),
host = NULL,
...) {
2023-01-21 23:47:20 +01:00
as_sir_method(
2022-08-28 10:31:50 +02:00
method_short = "mic",
method_long = "MIC values",
x = x,
mo = mo,
ab = ab,
guideline = guideline,
uti = uti,
conserve_capped_values = conserve_capped_values,
add_intrinsic_resistance = add_intrinsic_resistance,
reference_data = reference_data,
2023-02-12 15:09:54 +01:00
include_screening = include_screening,
2023-01-23 20:07:57 +01:00
include_PKPD = include_PKPD,
breakpoint_type = breakpoint_type,
host = host,
2022-08-28 10:31:50 +02:00
...
)
2019-05-10 16:44:59 +02:00
}
2023-01-21 23:47:20 +01:00
#' @rdname as.sir
2019-05-10 16:44:59 +02:00
#' @export
2023-01-21 23:47:20 +01:00
as.sir.disk <- function(x,
2022-08-28 10:31:50 +02:00
mo = NULL,
ab = deparse(substitute(x)),
2022-11-24 20:29:00 +01:00
guideline = getOption("AMR_guideline", "EUCAST"),
2022-10-29 14:15:23 +02:00
uti = NULL,
add_intrinsic_resistance = FALSE,
2023-01-21 23:47:20 +01:00
reference_data = AMR::clinical_breakpoints,
2023-02-12 15:09:54 +01:00
include_screening = getOption("AMR_include_screening", FALSE),
2023-01-23 20:07:57 +01:00
include_PKPD = getOption("AMR_include_PKPD", TRUE),
breakpoint_type = getOption("AMR_breakpoint_type", "human"),
host = NULL,
...) {
2023-01-21 23:47:20 +01:00
as_sir_method(
2022-08-28 10:31:50 +02:00
method_short = "disk",
method_long = "disk diffusion zones",
x = x,
mo = mo,
ab = ab,
guideline = guideline,
uti = uti,
conserve_capped_values = FALSE,
add_intrinsic_resistance = add_intrinsic_resistance,
reference_data = reference_data,
2023-02-12 15:09:54 +01:00
include_screening = include_screening,
2023-01-23 20:07:57 +01:00
include_PKPD = include_PKPD,
breakpoint_type = breakpoint_type,
host = NULL,
2022-08-28 10:31:50 +02:00
...
)
2020-02-20 13:19:23 +01:00
}
2023-01-21 23:47:20 +01:00
#' @rdname as.sir
2020-02-20 13:19:23 +01:00
#' @export
2023-01-21 23:47:20 +01:00
as.sir.data.frame <- function(x,
2022-08-28 10:31:50 +02:00
...,
col_mo = NULL,
2022-11-24 20:29:00 +01:00
guideline = getOption("AMR_guideline", "EUCAST"),
uti = NULL,
conserve_capped_values = FALSE,
add_intrinsic_resistance = FALSE,
2023-01-23 20:07:57 +01:00
reference_data = AMR::clinical_breakpoints,
2023-02-12 15:09:54 +01:00
include_screening = getOption("AMR_include_screening", FALSE),
include_PKPD = getOption("AMR_include_PKPD", TRUE),
breakpoint_type = getOption("AMR_breakpoint_type", "human"),
host = NULL) {
meet_criteria(x, allow_class = "data.frame") # will also check for dimensions > 0
meet_criteria(col_mo, allow_class = "character", is_in = colnames(x), allow_NULL = TRUE)
meet_criteria(guideline, allow_class = "character", has_length = 1)
2022-10-29 14:15:23 +02:00
meet_criteria(uti, allow_class = c("logical", "character"), allow_NULL = TRUE, allow_NA = TRUE)
meet_criteria(conserve_capped_values, allow_class = "logical", has_length = 1)
meet_criteria(add_intrinsic_resistance, allow_class = "logical", has_length = 1)
meet_criteria(reference_data, allow_class = "data.frame")
meet_criteria(include_screening, allow_class = "logical", has_length = 1)
meet_criteria(include_PKPD, allow_class = "logical", has_length = 1)
meet_criteria(breakpoint_type, allow_class = "character", is_in = reference_data$type, has_length = 1)
2024-02-24 19:26:35 +01:00
meet_criteria(host, allow_class = c("character", "factor"), allow_NULL = TRUE, allow_NA = TRUE)
2020-12-22 00:51:17 +01:00
x.bak <- x
for (i in seq_len(ncol(x))) {
# don't keep factors, overwriting them is hard
if (is.factor(x[, i, drop = TRUE])) {
x[, i] <- as.character(x[, i, drop = TRUE])
}
}
2022-08-28 10:31:50 +02:00
2020-10-20 21:00:57 +02:00
# -- MO
col_mo.bak <- col_mo
if (is.null(col_mo)) {
col_mo <- search_type_in_df(x = x, type = "mo", info = FALSE)
}
# -- host
if (missing(breakpoint_type) && any(host %in% AMR_env$host_preferred_order, na.rm = TRUE)) {
message_("Assuming `breakpoint_type = \"animal\"` since `host` contains animal species.")
breakpoint_type <- "animal"
} else if (any(!convert_host(host) %in% c("human", "ECOFF"), na.rm = TRUE)) {
message_("Assuming `breakpoint_type = \"animal\"`.")
breakpoint_type <- "animal"
}
if (breakpoint_type == "animal") {
if (is.null(host)) {
host <- search_type_in_df(x = x, type = "host", add_col_prefix = FALSE)
2024-02-24 19:26:35 +01:00
} else if (length(host) == 1 && as.character(host) %in% colnames(x)) {
host <- x[[as.character(host)]]
}
2024-05-31 09:50:54 +02:00
} else {
host <- breakpoint_type
}
2020-02-20 13:19:23 +01:00
# -- UTIs
col_uti <- uti
if (is.null(col_uti)) {
col_uti <- search_type_in_df(x = x, type = "uti", add_col_prefix = FALSE)
2020-02-20 13:19:23 +01:00
}
if (!is.null(col_uti)) {
if (is.logical(col_uti)) {
2024-05-31 21:24:35 +02:00
# already a logical vector as input
2020-02-20 13:19:23 +01:00
if (length(col_uti) == 1) {
uti <- rep(col_uti, NROW(x))
} else {
uti <- col_uti
}
} else {
# column found, transform to logical
2022-08-28 10:31:50 +02:00
stop_if(
length(col_uti) != 1 | !col_uti %in% colnames(x),
"argument `uti` must be a [logical] vector, of must be a single column name of `x`"
)
2020-02-20 13:19:23 +01:00
uti <- as.logical(x[, col_uti, drop = TRUE])
}
} else {
2022-10-29 14:15:23 +02:00
# col_uti is still NULL - look for specimen column and make logicals of the urines
2020-02-20 13:19:23 +01:00
col_specimen <- suppressMessages(search_type_in_df(x = x, type = "specimen"))
if (!is.null(col_specimen)) {
uti <- x[, col_specimen, drop = TRUE] %like% "urin"
values <- sort(unique(x[uti, col_specimen, drop = TRUE]))
if (length(values) > 1) {
plural <- c("s", "", "")
} else {
plural <- c("", "s", "a ")
}
2022-08-28 10:31:50 +02:00
message_(
"Assuming value", plural[1], " ",
vector_and(values, quotes = TRUE),
" in column '", font_bold(col_specimen),
"' reflect", plural[2], " ", plural[3], "urinary tract infection", plural[1],
2023-01-21 23:47:20 +01:00
".\n Use `as.sir(uti = FALSE)` to prevent this."
2022-08-28 10:31:50 +02:00
)
2020-02-20 13:19:23 +01:00
} else {
# no data about UTI's found
2022-10-29 14:15:23 +02:00
uti <- NULL
2020-02-20 13:19:23 +01:00
}
}
2022-08-28 10:31:50 +02:00
2020-02-20 13:19:23 +01:00
i <- 0
2021-12-05 23:11:10 +01:00
if (tryCatch(length(list(...)) > 0, error = function(e) TRUE)) {
2023-02-09 13:07:39 +01:00
sel <- colnames(pm_select(x, ...))
2021-12-05 23:11:10 +01:00
} else {
sel <- colnames(x)
}
2020-10-20 21:00:57 +02:00
if (!is.null(col_mo)) {
sel <- sel[sel != col_mo]
}
2022-08-28 10:31:50 +02:00
ab_cols <- colnames(x)[vapply(FUN.VALUE = logical(1), x, function(y) {
2020-02-20 13:19:23 +01:00
i <<- i + 1
check <- is.mic(y) | is.disk(y)
ab <- colnames(x)[i]
2020-10-20 21:00:57 +02:00
if (!is.null(col_mo) && ab == col_mo) {
return(FALSE)
}
if (!is.null(col_uti) && ab == col_uti) {
return(FALSE)
}
2020-09-24 12:38:13 +02:00
if (length(sel) == 0 || (length(sel) > 0 && ab %in% sel)) {
ab_coerced <- suppressWarnings(as.ab(ab))
if (is.na(ab_coerced) || (length(sel) > 0 & !ab %in% sel)) {
2020-09-24 12:38:13 +02:00
# not even a valid AB code
return(FALSE)
} else {
return(TRUE)
}
2020-02-20 13:19:23 +01:00
} else {
2020-09-24 12:38:13 +02:00
return(FALSE)
2020-02-20 13:19:23 +01:00
}
})]
2022-08-28 10:31:50 +02:00
stop_if(
length(ab_cols) == 0,
"no columns with MIC values, disk zones or antibiotic column names found in this data set. Use as.mic() or as.disk() to transform antimicrobial columns."
)
2020-02-20 13:19:23 +01:00
# set type per column
types <- character(length(ab_cols))
types[vapply(FUN.VALUE = logical(1), x.bak[, ab_cols, drop = FALSE], is.disk)] <- "disk"
types[vapply(FUN.VALUE = logical(1), x.bak[, ab_cols, drop = FALSE], is.mic)] <- "mic"
types[types == "" & vapply(FUN.VALUE = logical(1), x[, ab_cols, drop = FALSE], all_valid_disks)] <- "disk"
types[types == "" & vapply(FUN.VALUE = logical(1), x[, ab_cols, drop = FALSE], all_valid_mics)] <- "mic"
2023-01-21 23:47:20 +01:00
types[types == "" & !vapply(FUN.VALUE = logical(1), x.bak[, ab_cols, drop = FALSE], is.sir)] <- "sir"
if (any(types %in% c("mic", "disk"), na.rm = TRUE)) {
2020-10-20 21:00:57 +02:00
# now we need an mo column
stop_if(is.null(col_mo), "`col_mo` must be set")
# if not null, we already found it, now find again so a message will show
if (is.null(col_mo.bak)) {
col_mo <- search_type_in_df(x = x, type = "mo")
}
x_mo <- as.mo(x[, col_mo, drop = TRUE])
}
2022-08-28 10:31:50 +02:00
2020-02-20 13:19:23 +01:00
for (i in seq_len(length(ab_cols))) {
if (types[i] == "mic") {
2023-02-09 13:07:39 +01:00
x[, ab_cols[i]] <- x %pm>%
pm_pull(ab_cols[i]) %pm>%
as.character() %pm>%
as.mic() %pm>%
2023-01-21 23:47:20 +01:00
as.sir(
2022-09-01 15:20:57 +02:00
mo = x_mo,
mo.bak = x[, col_mo, drop = TRUE],
ab = ab_cols[i],
guideline = guideline,
uti = uti,
conserve_capped_values = conserve_capped_values,
add_intrinsic_resistance = add_intrinsic_resistance,
reference_data = reference_data,
2023-02-12 15:09:54 +01:00
include_screening = include_screening,
2023-01-23 20:07:57 +01:00
include_PKPD = include_PKPD,
breakpoint_type = breakpoint_type,
host = host,
2022-09-01 15:20:57 +02:00
is_data.frame = TRUE
)
2020-02-20 13:19:23 +01:00
} else if (types[i] == "disk") {
2023-02-09 13:07:39 +01:00
x[, ab_cols[i]] <- x %pm>%
pm_pull(ab_cols[i]) %pm>%
as.character() %pm>%
as.disk() %pm>%
2023-01-21 23:47:20 +01:00
as.sir(
2022-09-01 15:20:57 +02:00
mo = x_mo,
mo.bak = x[, col_mo, drop = TRUE],
ab = ab_cols[i],
guideline = guideline,
uti = uti,
add_intrinsic_resistance = add_intrinsic_resistance,
reference_data = reference_data,
2023-02-12 15:09:54 +01:00
include_screening = include_screening,
2023-01-23 20:07:57 +01:00
include_PKPD = include_PKPD,
breakpoint_type = breakpoint_type,
host = host,
2022-09-01 15:20:57 +02:00
is_data.frame = TRUE
)
2023-01-21 23:47:20 +01:00
} else if (types[i] == "sir") {
2020-12-22 00:51:17 +01:00
show_message <- FALSE
2020-10-20 21:00:57 +02:00
ab <- ab_cols[i]
ab_coerced <- suppressWarnings(as.ab(ab))
if (!all(x[, ab_cols[i], drop = TRUE] %in% c("S", "SDD", "I", "R", "NI", NA), na.rm = TRUE)) {
2020-12-22 00:51:17 +01:00
show_message <- TRUE
2020-11-23 21:50:27 +01:00
# only print message if values are not already clean
message_("Cleaning values in column '", font_bold(ab), "' (",
2022-08-28 10:31:50 +02:00
ifelse(ab_coerced != toupper(ab), paste0(ab_coerced, ", "), ""),
ab_name(ab_coerced, tolower = TRUE), ")... ",
appendLF = FALSE,
as_note = FALSE
)
2023-01-21 23:47:20 +01:00
} else if (!is.sir(x.bak[, ab_cols[i], drop = TRUE])) {
2020-12-22 00:51:17 +01:00
show_message <- TRUE
# only print message if class not already set
message_("Assigning class 'sir' to already clean column '", font_bold(ab), "' (",
2022-08-28 10:31:50 +02:00
ifelse(ab_coerced != toupper(ab), paste0(ab_coerced, ", "), ""),
2022-10-29 16:08:18 +02:00
ab_name(ab_coerced, tolower = TRUE, language = NULL), ")... ",
2022-08-28 10:31:50 +02:00
appendLF = FALSE,
as_note = FALSE
)
2020-11-23 21:50:27 +01:00
}
2023-01-21 23:47:20 +01:00
x[, ab_cols[i]] <- as.sir.default(x = as.character(x[, ab_cols[i], drop = TRUE]))
2020-12-22 00:51:17 +01:00
if (show_message == TRUE) {
message(font_green_bg(" OK "))
2020-11-23 21:50:27 +01:00
}
2020-02-20 13:19:23 +01:00
}
}
2022-08-28 10:31:50 +02:00
2020-02-20 13:19:23 +01:00
x
2019-05-10 16:44:59 +02:00
}
get_guideline <- function(guideline, reference_data) {
2023-01-21 23:47:20 +01:00
if (!identical(reference_data, AMR::clinical_breakpoints)) {
return(guideline)
}
2019-05-13 10:10:16 +02:00
guideline_param <- toupper(guideline)
if (guideline_param %in% c("CLSI", "EUCAST")) {
guideline_param <- rev(sort(subset(reference_data, guideline %like% guideline_param)$guideline))[1L]
2019-05-10 16:44:59 +02:00
}
if (guideline_param %unlike% " ") {
2020-02-21 13:13:34 +01:00
# like 'EUCAST2020', should be 'EUCAST 2020'
guideline_param <- gsub("([a-z]+)([0-9]+)", "\\1 \\2", guideline_param, ignore.case = TRUE)
}
2022-08-28 10:31:50 +02:00
stop_ifnot(guideline_param %in% reference_data$guideline,
2022-08-28 10:31:50 +02:00
"invalid guideline: '", guideline,
"'.\nValid guidelines are: ", vector_and(reference_data$guideline, quotes = TRUE, reverse = TRUE),
call = FALSE
)
guideline_param
}
convert_host <- function(x, lang = get_AMR_locale()) {
2024-02-24 19:26:35 +01:00
x <- trimws2(tolower(as.character(x)))
x_out <- rep(NA_character_, length(x))
2024-05-31 09:50:54 +02:00
x_out[trimws2(tolower(x)) == "human"] <- "human"
x_out[trimws2(tolower(x)) == "ecoff"] <- "ecoff"
# this order is based on: clinical_breakpoints |> filter(type == "animal") |> count(host, sort = TRUE)
x_out[is.na(x_out) & (x %like% "dog|canine" | x %like% translate_AMR("dog|dogs|canine", lang))] <- "dogs"
x_out[is.na(x_out) & (x %like% "cattle|bovine" | x %like% translate_AMR("cattle|bovine", lang))] <- "cattle"
x_out[is.na(x_out) & (x %like% "swine|suida(e)?" | x %like% translate_AMR("swine|swines", lang))] <- "swine"
x_out[is.na(x_out) & (x %like% "cat|feline" | x %like% translate_AMR("cat|cats|feline", lang))] <- "cats"
x_out[is.na(x_out) & (x %like% "horse|equine" | x %like% translate_AMR("horse|horses|equine", lang))] <- "horse"
x_out[is.na(x_out) & (x %like% "aqua|fish" | x %like% translate_AMR("aquatic|fish", lang))] <- "aquatic"
x_out[is.na(x_out) & (x %like% "bird|chicken|poultry|avia" | x %like% translate_AMR("bird|birds|poultry", lang))] <- "poultry"
2024-05-31 09:50:54 +02:00
x_out[x_out == "ecoff"] <- "ECOFF"
x_out
}
2023-01-21 23:47:20 +01:00
as_sir_method <- function(method_short,
2022-08-26 22:25:15 +02:00
method_long,
x,
mo,
ab,
guideline,
uti,
conserve_capped_values,
add_intrinsic_resistance,
reference_data,
2023-02-12 15:09:54 +01:00
include_screening,
2023-01-23 20:07:57 +01:00
include_PKPD,
breakpoint_type,
host,
...) {
2022-09-01 15:20:57 +02:00
meet_criteria(x, allow_NA = TRUE, .call_depth = -2)
2024-05-31 21:24:35 +02:00
meet_criteria(mo, allow_class = c("mo", "character"), has_length = c(1, length(x)), allow_NULL = TRUE, .call_depth = -2)
meet_criteria(ab, allow_class = c("ab", "character"), has_length = c(1, length(x)), .call_depth = -2)
2022-09-01 15:20:57 +02:00
meet_criteria(guideline, allow_class = "character", has_length = 1, .call_depth = -2)
2024-05-31 21:24:35 +02:00
meet_criteria(uti, allow_class = c("logical", "character"), has_length = c(1, length(x)), allow_NULL = TRUE, allow_NA = TRUE, .call_depth = -2)
2022-09-01 15:20:57 +02:00
meet_criteria(conserve_capped_values, allow_class = "logical", has_length = 1, .call_depth = -2)
meet_criteria(add_intrinsic_resistance, allow_class = "logical", has_length = 1, .call_depth = -2)
meet_criteria(reference_data, allow_class = "data.frame", .call_depth = -2)
2023-02-12 15:09:54 +01:00
meet_criteria(include_screening, allow_class = "logical", has_length = 1, .call_depth = -2)
2023-01-23 20:07:57 +01:00
meet_criteria(include_PKPD, allow_class = "logical", has_length = 1, .call_depth = -2)
2023-02-12 15:09:54 +01:00
check_reference_data(reference_data, .call_depth = -2)
meet_criteria(breakpoint_type, allow_class = "character", is_in = reference_data$type, has_length = 1, .call_depth = -2)
2024-02-24 19:26:35 +01:00
meet_criteria(host, allow_class = c("character", "factor"), allow_NULL = TRUE, allow_NA = TRUE, .call_depth = -2)
# backward compatibilty
dots <- list(...)
dots <- dots[which(!names(dots) %in% c("warn", "mo.bak", "is_data.frame"))]
if (length(dots) != 0) {
warning_("These arguments in `as.sir()` are no longer used: ", vector_and(names(dots), quotes = "`"), ".", call = FALSE)
}
2023-07-11 14:29:40 +02:00
guideline_coerced <- get_guideline(guideline, reference_data)
if (breakpoint_type == "animal") {
if (is.null(host)) {
host <- AMR_env$host_preferred_order[1]
if (message_not_thrown_before("as.sir", "host_missing")) {
message_("Animal hosts not set in `host`, assuming `host = \"", host, "\"`, since these have the highest breakpoint availability.\n\n")
}
}
} else {
2024-02-24 19:26:35 +01:00
if (!is.null(host) && !all(toupper(as.character(host)) %in% c("HUMAN", "ECOFF"))) {
if (message_not_thrown_before("as.sir", "assumed_breakpoint_animal")) {
message_("Assuming `breakpoint_type = \"animal\"`, since `host` is set.", ifelse(guideline_coerced %like% "EUCAST", " Do you also need to set `guideline = \"CLSI\"`?", ""), "\n\n")
}
breakpoint_type <- "animal"
} else {
2024-05-31 09:50:54 +02:00
host <- breakpoint_type
}
}
host <- convert_host(host)
2023-07-11 09:50:45 +02:00
if (message_not_thrown_before("as.sir", "sir_interpretation_history")) {
message_("Run `sir_interpretation_history()` afterwards to retrieve a logbook with all the details of the breakpoint interpretations. Note that some ", ifelse(breakpoint_type == "animal", "animal hosts and ", ""), "microorganisms might not have breakpoints for each antimicrobial drug in ", guideline_coerced, ".\n\n")
}
if (breakpoint_type == "animal" && message_not_thrown_before("as.sir", "host_preferred_order")) {
message_("Please note that in the absence of specific veterinary breakpoints for certain animal hosts, breakpoints for dogs, cattle, swine, cats, horse, aquatic, and poultry, in that order, are used as substitutes.\n\n")
2023-07-11 09:50:45 +02:00
}
current_df <- tryCatch(get_current_data(NA, 0), error = function(e) NULL)
2022-08-28 10:31:50 +02:00
# get ab
if (!is.null(current_df) && length(ab) == 1 && ab %in% colnames(current_df) && any(current_df[[ab]] %like% "[A-Z]", na.rm = TRUE)) {
ab <- current_df[[ab]]
2024-05-31 21:24:35 +02:00
} else if (length(ab) != length(x)) {
# for dplyr's across()
cur_column_dplyr <- import_fn("cur_column", "dplyr", error_on_fail = FALSE)
if (!is.null(cur_column_dplyr) && is.data.frame(current_df)) {
# try to get current column, which will only be available when in across()
ab <- tryCatch(cur_column_dplyr(),
error = function(e) ab
)
}
}
# get mo
if (!is.null(current_df) && length(mo) == 1 && mo %in% colnames(current_df)) {
mo_var_found <- paste0(" based on column '", font_bold(mo), "'")
mo <- current_df[[mo]]
2024-05-31 21:24:35 +02:00
} else if (length(mo) != length(x)) {
mo_var_found <- ""
if (is.null(mo)) {
tryCatch(
{
df <- get_current_data(arg_name = "mo", call = -3) # will return an error if not found
mo <- NULL
try(
{
2024-05-31 21:24:35 +02:00
mo <- suppressMessages(search_type_in_df(df, "mo", add_col_prefix = FALSE))
},
silent = TRUE
)
if (!is.null(df) && !is.null(mo) && is.data.frame(df)) {
mo_var_found <- paste0(" based on column '", font_bold(mo), "'")
mo <- df[, mo, drop = TRUE]
}
},
error = function(e) {
mo <- NULL
2022-08-28 10:31:50 +02:00
}
)
}
2024-06-04 20:17:54 +02:00
} else {
mo_var_found <- ""
}
if (is.null(mo)) {
2023-01-21 23:47:20 +01:00
stop_("No information was supplied about the microorganisms (missing argument `mo` and no column of class 'mo' found). See ?as.sir.\n\n",
"To transform certain columns with e.g. mutate(), use `data %>% mutate(across(..., as.sir, mo = x))`, where x is your column with microorganisms.\n",
2023-05-24 15:55:53 +02:00
"To transform all ", method_long, " in a data set, use `data %>% as.sir()` or `data %>% mutate_if(is.", method_short, ", as.sir)`.",
2022-08-28 10:31:50 +02:00
call = FALSE
)
}
2024-05-31 21:24:35 +02:00
# get uti
if (!is.null(current_df) && length(uti) == 1 && uti %in% colnames(current_df)) {
uti <- current_df[[uti]]
} else if (length(uti) != length(x)) {
if (is.null(uti)) {
tryCatch(
{
df <- get_current_data(arg_name = "uti", call = -3) # will return an error if not found
uti <- NULL
try(
{
uti <- suppressMessages(search_type_in_df(df, "uti", add_col_prefix = FALSE))
},
silent = TRUE
)
if (!is.null(df) && !is.null(uti) && is.data.frame(df)) {
uti <- df[, uti, drop = TRUE]
}
},
error = function(e) {
uti <- NULL
}
)
}
}
2022-08-28 10:31:50 +02:00
if (length(ab) == 1 && ab %like% paste0("as.", method_short)) {
2023-01-21 23:47:20 +01:00
stop_("No unambiguous name was supplied about the antibiotic (argument `ab`). See ?as.sir.", call = FALSE)
}
2022-08-28 10:31:50 +02:00
2022-09-01 15:20:57 +02:00
ab.bak <- ab
ab <- suppressWarnings(as.ab(ab))
if (!is.null(list(...)$mo.bak)) {
mo.bak <- list(...)$mo.bak
} else {
mo.bak <- mo
}
2023-03-11 14:24:34 +01:00
# be sure to take current taxonomy, as the 'clinical_breakpoints' data set only contains current taxonomy
mo <- suppressWarnings(suppressMessages(as.mo(mo, keep_synonyms = FALSE, info = FALSE)))
if (all(is.na(ab))) {
message_("Returning NAs for unknown antibiotic: ", vector_and(ab.bak, sort = FALSE, quotes = TRUE),
". Rename this column to a valid name or code, and check the output with `as.ab()`.",
2022-08-28 10:31:50 +02:00
add_fn = font_red,
as_note = FALSE
)
2023-01-21 23:47:20 +01:00
return(as.sir(rep(NA, length(x))))
}
2022-09-01 15:20:57 +02:00
if (length(mo) == 1) {
mo <- rep(mo, length(x))
}
if (length(ab) == 1) {
ab <- rep(ab, length(x))
ab.bak <- rep(ab.bak, length(ab))
}
2024-05-31 09:50:54 +02:00
if (length(host) == 1) {
host <- rep(host, length(x))
}
2022-10-29 14:15:23 +02:00
if (is.null(uti)) {
uti <- NA
}
if (length(uti) == 1) {
uti <- rep(uti, length(x))
}
2024-05-31 21:24:35 +02:00
uti[is.na(uti)] <- FALSE
2022-11-24 20:29:00 +01:00
if (isTRUE(add_intrinsic_resistance) && guideline_coerced %unlike% "EUCAST") {
2023-01-21 23:47:20 +01:00
if (message_not_thrown_before("as.sir", "intrinsic")) {
warning_("in `as.sir()`: using 'add_intrinsic_resistance' is only useful when using EUCAST guidelines, since the rules for intrinsic resistance are based on EUCAST.")
2022-11-24 20:29:00 +01:00
}
}
agent_formatted <- paste0("'", font_bold(ab.bak, collapse = NULL), "'")
2022-09-01 15:20:57 +02:00
agent_name <- ab_name(ab, tolower = TRUE, language = NULL)
same_ab <- generalise_antibiotic_name(ab) == generalise_antibiotic_name(agent_name)
same_ab.bak <- generalise_antibiotic_name(ab.bak) == generalise_antibiotic_name(agent_name)
2024-05-31 09:50:54 +02:00
agent_formatted[same_ab.bak] <- paste0(agent_formatted[same_ab.bak], " (", ab[same_ab.bak], ")")
2024-05-30 16:39:59 +02:00
agent_formatted[!same_ab.bak & !same_ab] <- paste0(agent_formatted[!same_ab.bak & !same_ab],
" (", ifelse(ab.bak[!same_ab.bak & !same_ab] == ab[!same_ab.bak & !same_ab],
"",
2024-05-30 16:39:59 +02:00
paste0(ab[!same_ab.bak & !same_ab], ", ")),
agent_name[!same_ab.bak & !same_ab],
")")
2024-05-30 16:39:59 +02:00
# this intro text will also be printed in the progress bar if the `progress` package is installed
intro_txt <- paste0("Interpreting ", method_long, ": ", ifelse(isTRUE(list(...)$is_data.frame), "column ", ""),
ifelse(length(unique(agent_formatted)) == 1, unique(agent_formatted), paste0(vector_and(agent_formatted, quotes = FALSE, sort = FALSE))),
mo_var_found,
ifelse(identical(reference_data, AMR::clinical_breakpoints),
paste0(", ", font_bold(guideline_coerced)),
""),
"... ")
2022-11-24 20:29:00 +01:00
msg_note <- function(messages) {
messages <- unique(messages)
2022-11-24 20:29:00 +01:00
for (i in seq_len(length(messages))) {
messages[i] <- word_wrap(extra_indent = 5, messages[i])
}
2023-01-23 15:01:21 +01:00
message(
2023-07-10 13:41:52 +02:00
font_yellow_bg(paste0(" NOTE", ifelse(length(messages) > 1, "S", ""), " \n")),
paste0(" ", font_black(AMR_env$bullet_icon), " ", font_black(messages, collapse = NULL), collapse = "\n")
2023-01-23 15:01:21 +01:00
)
2022-11-24 20:29:00 +01:00
}
2022-09-01 15:20:57 +02:00
method <- method_short
2022-10-05 09:12:22 +02:00
metadata_mo <- get_mo_uncertainties()
2022-08-28 10:31:50 +02:00
rise_warning <- FALSE
rise_note <- FALSE
method_coerced <- toupper(method)
ab_coerced <- as.ab(ab)
if (identical(reference_data, AMR::clinical_breakpoints)) {
breakpoints <- reference_data %pm>%
subset(guideline == guideline_coerced & method == method_coerced & ab %in% ab_coerced)
if (any(ab_coerced == "AMX") && nrow(breakpoints[breakpoints$ab == "AMX", , drop = FALSE]) == 0) {
ab_coerced[ab_coerced == "AMX"] <- "AMP"
breakpoints <- reference_data %pm>%
subset(guideline == guideline_coerced & method == method_coerced & ab %in% ab_coerced)
}
} else {
breakpoints <- reference_data %pm>%
subset(method == method_coerced & ab %in% ab_coerced)
}
# create the unique data frame to be filled to save time
2023-01-23 15:01:21 +01:00
df <- data.frame(
values = x,
mo = mo,
ab = ab,
2023-01-23 15:01:21 +01:00
result = NA_sir_,
uti = uti,
host = host,
2023-01-23 15:01:21 +01:00
stringsAsFactors = FALSE
)
if (method == "mic") {
2023-01-21 23:47:20 +01:00
# when as.sir.mic is called directly
2023-01-23 15:01:21 +01:00
df$values <- as.mic(df$values)
} else if (method == "disk") {
2023-01-21 23:47:20 +01:00
# when as.sir.disk is called directly
2022-11-24 20:29:00 +01:00
df$values <- as.disk(df$values)
}
df_unique <- unique(df[ , c("mo", "ab", "uti", "host"), drop = FALSE])
# get all breakpoints
breakpoints <- breakpoints %pm>%
subset(type == breakpoint_type)
2023-02-12 15:09:54 +01:00
if (isFALSE(include_screening)) {
# remove screening rules from the breakpoints table
breakpoints <- breakpoints %pm>%
subset(site %unlike% "screen" & ref_tbl %unlike% "screen")
}
2023-01-23 20:07:57 +01:00
if (isFALSE(include_PKPD)) {
# remove PKPD rules from the breakpoints table
2023-02-09 13:07:39 +01:00
breakpoints <- breakpoints %pm>%
2023-01-23 20:07:57 +01:00
subset(mo != "UNKNOWN" & ref_tbl %unlike% "PK.*PD")
}
2022-11-24 20:29:00 +01:00
msgs <- character(0)
2023-01-23 15:01:21 +01:00
2022-11-24 20:29:00 +01:00
if (guideline_coerced %like% "EUCAST") {
any_is_intrinsic_resistant <- FALSE
2022-10-30 21:05:46 +01:00
add_intrinsic_resistance_to_AMR_env()
2022-11-24 20:29:00 +01:00
}
2024-05-31 09:50:54 +02:00
if (nrow(df_unique) < 10) {
# only print intro under 10 items, otherwise progressbar will print this and then it will be printed double
message_(intro_txt, appendLF = FALSE, as_note = FALSE)
}
2023-07-10 13:41:52 +02:00
p <- progress_ticker(n = nrow(df_unique), n_min = 10, title = font_blue(intro_txt), only_bar_percent = TRUE)
has_progress_bar <- !is.null(import_fn("progress_bar", "progress", error_on_fail = FALSE)) && nrow(df_unique) >= 10
on.exit(close(p))
if (nrow(breakpoints) == 0) {
# apparently no breakpoints found
message(
paste0(font_rose_bg(" WARNING "), "\n"),
font_black(paste0(" ", AMR_env$bullet_icon, " No ", guideline_coerced, " ", method_coerced, " breakpoints available for ",
suppressMessages(suppressWarnings(ab_name(unique(ab_coerced), language = NULL, tolower = TRUE))),
" (", unique(ab_coerced), ")."), collapse = "\n"))
load_mo_uncertainties(metadata_mo)
return(rep(NA_sir_, nrow(df)))
}
# run the rules (df_unique is a row combination per mo/ab/uti/host)
2023-07-10 13:41:52 +02:00
for (i in seq_len(nrow(df_unique))) {
p$tick()
2023-07-10 13:41:52 +02:00
mo_current <- df_unique[i, "mo", drop = TRUE]
ab_current <- df_unique[i, "ab", drop = TRUE]
2024-05-31 09:50:54 +02:00
host_current <- df_unique[i, "host", drop = TRUE]
2023-07-10 13:41:52 +02:00
uti_current <- df_unique[i, "uti", drop = TRUE]
2024-05-31 21:24:35 +02:00
if (isFALSE(uti_current)) {
2023-07-12 12:41:25 +02:00
# no preference, so no filter on UTIs
2024-05-31 09:50:54 +02:00
rows <- which(df$mo == mo_current & df$ab == ab_current & df$host == host_current)
2023-07-10 13:41:52 +02:00
} else {
2024-05-31 09:50:54 +02:00
rows <- which(df$mo == mo_current & df$ab == ab_current & df$host == host_current & df$uti == uti_current)
2023-07-10 13:41:52 +02:00
}
2022-11-24 20:29:00 +01:00
values <- df[rows, "values", drop = TRUE]
2023-01-21 23:47:20 +01:00
new_sir <- rep(NA_sir_, length(rows))
2023-01-23 15:01:21 +01:00
# find different mo properties, as fast as possible
2023-07-11 09:50:45 +02:00
mo_current_genus <- AMR_env$MO_lookup$mo[match(AMR_env$MO_lookup$genus[match(mo_current, AMR_env$MO_lookup$mo)], AMR_env$MO_lookup$fullname)]
mo_current_family <- AMR_env$MO_lookup$mo[match(AMR_env$MO_lookup$family[match(mo_current, AMR_env$MO_lookup$mo)], AMR_env$MO_lookup$fullname)]
mo_current_order <- AMR_env$MO_lookup$mo[match(AMR_env$MO_lookup$order[match(mo_current, AMR_env$MO_lookup$mo)], AMR_env$MO_lookup$fullname)]
mo_current_class <- AMR_env$MO_lookup$mo[match(AMR_env$MO_lookup$class[match(mo_current, AMR_env$MO_lookup$mo)], AMR_env$MO_lookup$fullname)]
mo_current_rank <- AMR_env$MO_lookup$rank[match(mo_current, AMR_env$MO_lookup$mo)]
mo_current_name <- AMR_env$MO_lookup$fullname[match(mo_current, AMR_env$MO_lookup$mo)]
2023-07-10 13:41:52 +02:00
if (mo_current %in% AMR::microorganisms.groups$mo) {
2023-07-12 12:41:25 +02:00
# get the species group (might be more than 1 entry)
mo_current_species_group <- AMR::microorganisms.groups$mo_group[which(AMR::microorganisms.groups$mo == mo_current)]
2022-11-24 20:29:00 +01:00
} else {
2023-07-12 12:41:25 +02:00
mo_current_species_group <- NULL
}
mo_current_other <- structure("UNKNOWN", class = c("mo", "character"))
2022-11-24 20:29:00 +01:00
# formatted for notes
mo_formatted <- mo_current_name
if (!mo_current_rank %in% c("kingdom", "phylum", "class", "order")) {
mo_formatted <- font_italic(mo_formatted, collapse = NULL)
2022-11-24 20:29:00 +01:00
}
2023-01-23 15:01:21 +01:00
ab_formatted <- paste0(
suppressMessages(suppressWarnings(ab_name(ab_current, language = NULL, tolower = TRUE))),
" (", ab_current, ")"
2023-01-23 15:01:21 +01:00
)
# gather all available breakpoints for current MO
2023-02-09 13:07:39 +01:00
breakpoints_current <- breakpoints %pm>%
subset(ab == ab_current) %pm>%
2023-01-23 15:01:21 +01:00
subset(mo %in% c(
2023-07-12 12:41:25 +02:00
mo_current, mo_current_genus, mo_current_family,
2023-01-23 15:01:21 +01:00
mo_current_order, mo_current_class,
mo_current_species_group,
2023-01-23 15:01:21 +01:00
mo_current_other
))
# set the host index according to most available breakpoints (see R/zzz.R where this is set in the pkg environment)
breakpoints_current$host_index <- match(breakpoints_current$host, c("human", "ECOFF", AMR_env$host_preferred_order))
# sort on host and taxonomic rank
2024-05-31 21:24:35 +02:00
# (this will e.g. prefer 'species' breakpoints over 'order' breakpoints)
if (all(uti_current == FALSE, na.rm = TRUE)) {
2023-02-09 13:07:39 +01:00
breakpoints_current <- breakpoints_current %pm>%
2024-05-31 21:24:35 +02:00
# this will put UTI = FALSE first, then UTI = NA, then UTI = TRUE
2024-06-13 20:55:17 +02:00
pm_mutate(uti_index = ifelse(is.na(uti) & uti == FALSE, 1,
2024-05-31 21:24:35 +02:00
ifelse(is.na(uti), 2,
3))) %pm>%
2020-02-20 13:19:23 +01:00
# be as specific as possible (i.e. prefer species over genus):
2024-05-31 21:24:35 +02:00
pm_arrange(host_index, rank_index, uti_index)
} else if (all(uti_current == TRUE, na.rm = TRUE)) {
2023-02-09 13:07:39 +01:00
breakpoints_current <- breakpoints_current %pm>%
2024-05-31 21:24:35 +02:00
subset(uti == TRUE) %pm>%
2023-07-10 13:41:52 +02:00
# be as specific as possible (i.e. prefer species over genus):
pm_arrange(host_index, rank_index)
2020-02-20 13:19:23 +01:00
}
if (NROW(breakpoints_current) == 0) {
# no note about missing breakpoints - it's already in the header before the interpretation starts
next
}
# veterinary host check
host_current <- unique(df_unique[i, "host", drop = TRUE])[1]
breakpoints_current$host_match <- breakpoints_current$host == host_current
if (breakpoint_type == "animal") {
if (any(breakpoints_current$host_match == TRUE, na.rm = TRUE)) {
breakpoints_current <- breakpoints_current %pm>%
subset(host_match == TRUE)
} else {
# no breakpoint found for this host, so sort on mostly available guidelines
msgs <- c(msgs, paste0("No breakpoints available for ", font_bold(host_current), " for ", ab_formatted, " in ", mo_formatted, " - using ", font_bold(breakpoints_current$host[1]), " instead."))
}
}
2022-11-24 20:29:00 +01:00
# throw notes for different body sites
2023-07-10 13:41:52 +02:00
site <- breakpoints_current[1L, "site", drop = FALSE] # this is the one we'll take
if (is.na(site)) {
site <- paste0("an unspecified body site")
} else {
site <- paste0("body site '", site, "'")
}
2024-06-13 20:55:17 +02:00
if (nrow(breakpoints_current) == 1 && all(breakpoints_current$uti == TRUE) && any(uti_current %in% c(FALSE, NA)) && message_not_thrown_before("as.sir", "uti", ab_current)) {
2022-11-24 20:29:00 +01:00
# only UTI breakpoints available
2023-01-21 23:47:20 +01:00
warning_("in `as.sir()`: interpretation of ", font_bold(ab_formatted), " is only available for (uncomplicated) urinary tract infections (UTI) for some microorganisms, thus assuming `uti = TRUE`. See `?as.sir`.")
2022-10-29 14:15:23 +02:00
rise_warning <- TRUE
2024-06-13 20:55:17 +02:00
} else if (nrow(breakpoints_current) > 1 && length(unique(breakpoints_current$site)) > 1 && any(is.na(uti_current)) && all(c(TRUE, FALSE) %in% breakpoints_current$uti, na.rm = TRUE) && message_not_thrown_before("as.sir", "siteUTI", mo_current, ab_current)) {
2022-11-24 20:29:00 +01:00
# both UTI and Non-UTI breakpoints available
msgs <- c(msgs, paste0("Breakpoints for UTI ", font_bold("and"), " non-UTI available for ", ab_formatted, " in ", mo_formatted, " - assuming ", site, ". Use argument `uti` to set which isolates are from urine. See `?as.sir`."))
2023-02-09 13:07:39 +01:00
breakpoints_current <- breakpoints_current %pm>%
pm_filter(uti == FALSE)
2024-06-13 20:55:17 +02:00
} else if (nrow(breakpoints_current) > 1 && length(unique(breakpoints_current$site)) > 1 && all(breakpoints_current$uti == FALSE, na.rm = TRUE) && message_not_thrown_before("as.sir", "siteOther", mo_current, ab_current)) {
2022-11-24 20:29:00 +01:00
# breakpoints for multiple body sites available
msgs <- c(msgs, paste0("Multiple breakpoints available for ", ab_formatted, " in ", mo_formatted, " - assuming ", site, "."))
2022-10-22 22:00:15 +02:00
}
2023-07-11 14:29:40 +02:00
2022-11-24 20:29:00 +01:00
# first check if mo is intrinsic resistant
2024-06-13 20:55:17 +02:00
if (isTRUE(add_intrinsic_resistance) && guideline_coerced %like% "EUCAST" && paste(mo_current, ab_current) %in% AMR_env$intrinsic_resistant) {
2022-11-24 20:29:00 +01:00
msgs <- c(msgs, paste0("Intrinsic resistance applied for ", ab_formatted, " in ", mo_formatted, ""))
2023-01-21 23:47:20 +01:00
new_sir <- rep(as.sir("R"), length(rows))
2023-01-23 20:07:57 +01:00
} else if (nrow(breakpoints_current) == 0) {
# no rules available
new_sir <- rep(NA_sir_, length(rows))
2022-11-24 20:29:00 +01:00
} else {
# then run the rules
breakpoints_current <- breakpoints_current[1L, , drop = FALSE]
2023-01-23 15:01:21 +01:00
2023-02-12 15:09:54 +01:00
if (any(breakpoints_current$mo == "UNKNOWN", na.rm = TRUE) | any(breakpoints_current$ref_tbl %like% "PK.*PD", na.rm = TRUE)) {
2023-07-11 14:29:40 +02:00
msgs <- c(msgs, "Some PK/PD breakpoints were applied - use `include_PKPD = FALSE` to prevent this")
2023-01-23 20:07:57 +01:00
}
2023-02-12 15:09:54 +01:00
if (any(breakpoints_current$site %like% "screen", na.rm = TRUE) | any(breakpoints_current$ref_tbl %like% "screen", na.rm = TRUE)) {
2023-07-11 14:29:40 +02:00
msgs <- c(msgs, "Some screening breakpoints were applied - use `include_screening = FALSE` to prevent this")
2023-02-12 15:09:54 +01:00
}
2023-01-23 20:07:57 +01:00
2022-11-24 20:29:00 +01:00
if (method == "mic") {
2023-03-12 13:02:37 +01:00
new_sir <- case_when_AMR(
2023-01-21 23:47:20 +01:00
is.na(values) ~ NA_sir_,
values <= breakpoints_current$breakpoint_S ~ as.sir("S"),
guideline_coerced %like% "EUCAST" & values > breakpoints_current$breakpoint_R ~ as.sir("R"),
guideline_coerced %like% "CLSI" & values >= breakpoints_current$breakpoint_R ~ as.sir("R"),
2022-11-24 20:29:00 +01:00
# return "I" when breakpoints are in the middle
2023-01-21 23:47:20 +01:00
!is.na(breakpoints_current$breakpoint_S) & !is.na(breakpoints_current$breakpoint_R) ~ as.sir("I"),
2022-08-28 10:31:50 +02:00
# and NA otherwise
2023-01-21 23:47:20 +01:00
TRUE ~ NA_sir_
2022-08-28 10:31:50 +02:00
)
2019-05-10 16:44:59 +02:00
} else if (method == "disk") {
2023-03-12 13:02:37 +01:00
new_sir <- case_when_AMR(
2023-01-21 23:47:20 +01:00
is.na(values) ~ NA_sir_,
as.double(values) >= as.double(breakpoints_current$breakpoint_S) ~ as.sir("S"),
guideline_coerced %like% "EUCAST" & as.double(values) < as.double(breakpoints_current$breakpoint_R) ~ as.sir("R"),
guideline_coerced %like% "CLSI" & as.double(values) <= as.double(breakpoints_current$breakpoint_R) ~ as.sir("R"),
2022-11-24 20:29:00 +01:00
# return "I" when breakpoints are in the middle
2023-01-21 23:47:20 +01:00
!is.na(breakpoints_current$breakpoint_S) & !is.na(breakpoints_current$breakpoint_R) ~ as.sir("I"),
2022-08-28 10:31:50 +02:00
# and NA otherwise
2023-01-21 23:47:20 +01:00
TRUE ~ NA_sir_
2022-08-28 10:31:50 +02:00
)
2019-05-10 16:44:59 +02:00
}
2022-09-01 15:20:57 +02:00
# write to verbose output
2023-03-12 13:02:37 +01:00
AMR_env$sir_interpretation_history <- rbind_AMR(
2023-01-21 23:47:20 +01:00
AMR_env$sir_interpretation_history,
2022-11-24 20:29:00 +01:00
# recycling 1 to 2 rows does not seem to work, which is why rep() was added
2022-09-01 15:20:57 +02:00
data.frame(
2022-11-24 20:29:00 +01:00
datetime = rep(Sys.time(), length(rows)),
index = rows,
ab_user = rep(ab.bak[match(ab_current, df$ab)][1], length(rows)),
2023-07-11 09:50:45 +02:00
mo_user = rep(mo.bak[match(mo_current, df$mo)][1], length(rows)),
ab = rep(ab_current, length(rows)),
2023-07-11 09:50:45 +02:00
mo = rep(breakpoints_current[, "mo", drop = TRUE], length(rows)),
2022-11-24 20:29:00 +01:00
input = as.double(values),
2023-01-21 23:47:20 +01:00
outcome = as.sir(new_sir),
2023-07-11 09:50:45 +02:00
method = rep(method_coerced, length(rows)),
2022-11-24 20:29:00 +01:00
breakpoint_S_R = rep(paste0(breakpoints_current[, "breakpoint_S", drop = TRUE], "-", breakpoints_current[, "breakpoint_R", drop = TRUE]), length(rows)),
2023-07-11 09:50:45 +02:00
guideline = rep(guideline_coerced, length(rows)),
host = rep(breakpoints_current[, "host", drop = TRUE], length(rows)),
2023-07-11 09:50:45 +02:00
ref_table = rep(breakpoints_current[, "ref_tbl", drop = TRUE], length(rows)),
uti = rep(breakpoints_current[, "uti", drop = TRUE], length(rows)),
2022-09-01 15:20:57 +02:00
stringsAsFactors = FALSE
)
)
2019-05-10 16:44:59 +02:00
}
2023-01-23 15:01:21 +01:00
2023-01-21 23:47:20 +01:00
df[rows, "result"] <- new_sir
2019-05-10 16:44:59 +02:00
}
close(p)
2023-01-23 15:01:21 +01:00
# printing messages
2023-07-10 13:41:52 +02:00
if (has_progress_bar == TRUE) {
# the progress bar has overwritten the intro text, so:
message_(intro_txt, appendLF = FALSE, as_note = FALSE)
}
2022-11-24 20:29:00 +01:00
if (isTRUE(rise_warning)) {
2023-07-10 17:02:28 +02:00
message(font_rose_bg(" WARNING "))
2022-11-24 20:29:00 +01:00
} else if (length(msgs) == 0) {
2023-07-10 13:41:52 +02:00
message(font_green_bg(" OK "))
2022-11-24 20:29:00 +01:00
} else {
msg_note(sort(msgs))
2020-02-20 13:19:23 +01:00
}
2023-01-23 15:01:21 +01:00
2022-10-05 09:12:22 +02:00
load_mo_uncertainties(metadata_mo)
2023-01-23 15:01:21 +01:00
2022-11-24 20:29:00 +01:00
df$result
2019-05-10 16:44:59 +02:00
}
2023-01-21 23:47:20 +01:00
#' @rdname as.sir
2022-09-01 15:20:57 +02:00
#' @param clean a [logical] to indicate whether previously stored results should be forgotten after returning the 'logbook' with results
#' @export
2023-01-21 23:47:20 +01:00
sir_interpretation_history <- function(clean = FALSE) {
2022-09-01 15:20:57 +02:00
meet_criteria(clean, allow_class = "logical", has_length = 1)
out <- AMR_env$sir_interpretation_history
2022-09-01 15:20:57 +02:00
if (NROW(out) == 0) {
2023-01-21 23:47:20 +01:00
message_("No results to return. Run `as.sir()` on MIC values or disk diffusion zones first to see a 'logbook' data set here.")
2022-11-14 15:20:39 +01:00
return(invisible(NULL))
2022-09-01 15:20:57 +02:00
}
2023-01-21 23:47:20 +01:00
out$outcome <- as.sir(out$outcome)
2022-09-01 15:20:57 +02:00
# keep stored for next use
if (isTRUE(clean)) {
2023-01-21 23:47:20 +01:00
AMR_env$sir_interpretation_history <- AMR_env$sir_interpretation_history[0, , drop = FALSE]
2022-09-01 15:20:57 +02:00
}
2023-01-23 15:01:21 +01:00
# sort descending on time
out <- out[order(out$datetime, decreasing = TRUE), , drop = FALSE]
2023-02-18 14:56:06 +01:00
if (pkg_is_available("tibble")) {
2022-09-01 15:20:57 +02:00
import_fn("as_tibble", "tibble")(out)
} else {
out
}
}
# will be exported using s3_register() in R/zzz.R
2023-01-21 23:47:20 +01:00
pillar_shaft.sir <- function(x, ...) {
2020-08-26 11:33:54 +02:00
out <- trimws(format(x))
if (has_colour()) {
# colours will anyway not work when has_colour() == FALSE,
# but then the indentation should also not be applied
2022-11-24 20:29:00 +01:00
out[is.na(x)] <- font_grey(" NA")
out[x == "NI"] <- font_grey_bg(" NI ")
2022-10-05 09:12:22 +02:00
out[x == "S"] <- font_green_bg(" S ")
out[x == "I"] <- font_orange_bg(" I ")
2024-05-20 15:27:04 +02:00
out[x == "SDD"] <- font_orange_bg(" SDD ")
if (is_dark()) {
out[x == "R"] <- font_red_bg(" R ")
} else {
out[x == "R"] <- font_rose_bg(" R ")
}
}
create_pillar_column(out, align = "left", width = 5)
2020-08-26 11:33:54 +02:00
}
# will be exported using s3_register() in R/zzz.R
2023-01-21 23:47:20 +01:00
type_sum.sir <- function(x, ...) {
"sir"
2020-08-26 11:33:54 +02:00
}
# will be exported using s3_register() in R/zzz.R
2023-01-21 23:47:20 +01:00
freq.sir <- function(x, ...) {
x_name <- deparse(substitute(x))
x_name <- gsub(".*[$]", "", x_name)
if (x_name %in% c("x", ".")) {
# try again going through system calls
2022-08-28 10:31:50 +02:00
x_name <- stats::na.omit(vapply(
FUN.VALUE = character(1),
sys.calls(),
function(call) {
call_txt <- as.character(call)
ifelse(call_txt[1] %like% "freq$", call_txt[length(call_txt)], character(0))
}
))[1L]
}
ab <- suppressMessages(suppressWarnings(as.ab(x_name)))
digits <- list(...)$digits
if (is.null(digits)) {
digits <- 2
}
if (!is.na(ab)) {
2022-08-28 10:31:50 +02:00
cleaner::freq.default(
x = x, ...,
.add_header = list(
Drug = paste0(ab_name(ab, language = NULL), " (", ab, ", ", paste(ab_atc(ab), collapse = "/"), ")"),
`Drug group` = ab_group(ab, language = NULL),
`%SI` = trimws(percentage(susceptibility(x, minimum = 0, as_percent = FALSE),
digits = digits
))
)
)
} else {
2022-08-28 10:31:50 +02:00
cleaner::freq.default(
x = x, ...,
.add_header = list(
`%SI` = trimws(percentage(susceptibility(x, minimum = 0, as_percent = FALSE),
digits = digits
))
)
)
}
}
2020-09-28 01:08:55 +02:00
# will be exported using s3_register() in R/zzz.R
2023-01-21 23:47:20 +01:00
get_skimmers.sir <- function(column) {
2020-09-28 11:00:59 +02:00
# get the variable name 'skim_variable'
name_call <- function(.data) {
2020-09-28 01:08:55 +02:00
calls <- sys.calls()
frms <- sys.frames()
2020-09-28 11:00:59 +02:00
calls_txt <- vapply(calls, function(x) paste(deparse(x), collapse = ""), FUN.VALUE = character(1))
if (any(calls_txt %like% "skim_variable", na.rm = TRUE)) {
ind <- which(calls_txt %like% "skim_variable")[1L]
2023-01-21 23:47:20 +01:00
vars <- tryCatch(eval(parse(text = ".data$skim_variable$sir"), envir = frms[[ind]]),
2022-08-28 10:31:50 +02:00
error = function(e) NULL
)
tryCatch(ab_name(as.character(calls[[length(calls)]][[2]]), language = NULL),
2022-08-28 10:31:50 +02:00
error = function(e) NA_character_
)
2020-09-28 11:00:59 +02:00
} else {
2020-09-28 01:08:55 +02:00
NA_character_
}
}
2022-08-28 10:31:50 +02:00
2020-12-17 16:22:25 +01:00
skimr::sfl(
2023-01-21 23:47:20 +01:00
skim_type = "sir",
2020-09-28 11:00:59 +02:00
ab_name = name_call,
2020-09-28 01:08:55 +02:00
count_R = count_R,
count_S = count_susceptible,
count_I = count_I,
2022-08-28 10:31:50 +02:00
prop_R = ~ proportion_R(., minimum = 0),
prop_S = ~ susceptibility(., minimum = 0),
prop_I = ~ proportion_I(., minimum = 0)
2020-09-28 01:08:55 +02:00
)
}
2023-01-21 23:47:20 +01:00
#' @method print sir
#' @export
#' @noRd
2023-01-21 23:47:20 +01:00
print.sir <- function(x, ...) {
cat("Class 'sir'\n")
print(as.character(x), quote = FALSE)
}
#' @method as.double sir
#' @export
as.double.sir <- function(x, ...) {
dbls <- rep(NA_real_, length(x))
dbls[x == "S"] <- 1
dbls[x %in% c("SDD", "I")] <- 2
dbls[x == "R"] <- 3
dbls
}
2023-01-21 23:47:20 +01:00
#' @method droplevels sir
2018-12-29 22:24:19 +01:00
#' @export
#' @noRd
2023-01-21 23:47:20 +01:00
droplevels.sir <- function(x, exclude = if (any(is.na(levels(x)))) NULL else NA, ...) {
2018-12-29 22:24:19 +01:00
x <- droplevels.factor(x, exclude = exclude, ...)
2023-01-21 23:47:20 +01:00
class(x) <- c("sir", "ordered", "factor")
2018-12-29 22:24:19 +01:00
x
}
2023-01-21 23:47:20 +01:00
#' @method summary sir
#' @export
#' @noRd
2023-01-21 23:47:20 +01:00
summary.sir <- function(object, ...) {
x <- object
n <- sum(!is.na(x))
S <- sum(x == "S", na.rm = TRUE)
SDD <- sum(x == "SDD", na.rm = TRUE)
I <- sum(x == "I", na.rm = TRUE)
R <- sum(x == "R", na.rm = TRUE)
NI <- sum(x == "NI", na.rm = TRUE)
pad <- function(x) {
2023-01-23 20:07:57 +01:00
if (is.na(x)) {
return("??")
}
if (x == "0%") {
x <- " 0.0%"
}
if (nchar(x) < 5) {
x <- paste0(rep(" ", 5 - nchar(x)), x)
}
x
}
value <- c(
2023-01-21 23:47:20 +01:00
"Class" = "sir",
"%S" = paste0(pad(percentage(S / n, digits = 1)), " (n=", S, ")"),
"%SDD" = paste0(pad(percentage(SDD / n, digits = 1)), " (n=", SDD, ")"),
"%I" = paste0(pad(percentage(I / n, digits = 1)), " (n=", I, ")"),
"%R" = paste0(pad(percentage(R / n, digits = 1)), " (n=", R, ")"),
"%NI" = paste0(pad(percentage(NI / n, digits = 1)), " (n=", NI, ")")
2018-08-10 15:01:05 +02:00
)
class(value) <- c("summaryDefault", "table")
value
}
2018-08-10 15:01:05 +02:00
2023-01-21 23:47:20 +01:00
#' @method [<- sir
2020-04-13 21:09:56 +02:00
#' @export
#' @noRd
2023-01-21 23:47:20 +01:00
"[<-.sir" <- function(i, j, ..., value) {
value <- as.sir(value)
2020-04-13 21:09:56 +02:00
y <- NextMethod()
attributes(y) <- attributes(i)
y
}
2023-01-21 23:47:20 +01:00
#' @method [[<- sir
2020-04-13 21:09:56 +02:00
#' @export
#' @noRd
2023-01-21 23:47:20 +01:00
"[[<-.sir" <- function(i, j, ..., value) {
value <- as.sir(value)
2020-04-13 21:09:56 +02:00
y <- NextMethod()
attributes(y) <- attributes(i)
y
}
2023-01-21 23:47:20 +01:00
#' @method c sir
2020-04-13 21:09:56 +02:00
#' @export
#' @noRd
2023-01-21 23:47:20 +01:00
c.sir <- function(...) {
as.sir(unlist(lapply(list(...), as.character)))
2020-04-13 21:09:56 +02:00
}
2023-01-21 23:47:20 +01:00
#' @method unique sir
#' @export
#' @noRd
2023-01-21 23:47:20 +01:00
unique.sir <- function(x, incomparables = FALSE, ...) {
y <- NextMethod()
attributes(y) <- attributes(x)
y
}
2023-01-21 23:47:20 +01:00
#' @method rep sir
2021-07-06 16:35:14 +02:00
#' @export
#' @noRd
2023-01-21 23:47:20 +01:00
rep.sir <- function(x, ...) {
2021-07-06 16:35:14 +02:00
y <- NextMethod()
attributes(y) <- attributes(x)
y
}
2023-02-12 15:09:54 +01:00
check_reference_data <- function(reference_data, .call_depth) {
2023-01-21 23:47:20 +01:00
if (!identical(reference_data, AMR::clinical_breakpoints)) {
2023-02-12 15:09:54 +01:00
class_sir <- vapply(FUN.VALUE = character(1), AMR::clinical_breakpoints, function(x) paste0("<", class(x), ">", collapse = " and "))
class_ref <- vapply(FUN.VALUE = character(1), reference_data, function(x) paste0("<", class(x), ">", collapse = " and "))
2023-01-21 23:47:20 +01:00
if (!all(names(class_sir) == names(class_ref))) {
2023-02-12 15:09:54 +01:00
stop_("`reference_data` must have the same column names as the 'clinical_breakpoints' data set.", call = .call_depth)
}
2023-01-21 23:47:20 +01:00
if (!all(class_sir == class_ref)) {
2023-02-12 15:09:54 +01:00
stop_("`reference_data` must be the same structure as the 'clinical_breakpoints' data set. Column '", names(class_ref[class_sir != class_ref][1]), "' is of class ", class_ref[class_sir != class_ref][1], ", but should be of class ", class_sir[class_sir != class_ref][1], ".", call = .call_depth)
}
}
}