2018-02-21 11:52:31 +01:00
# ==================================================================== #
2023-07-08 17:30:05 +02:00
# TITLE: #
2022-10-05 09:12:22 +02:00
# AMR: An R Package for Working with Antimicrobial Resistance Data #
2018-02-21 11:52:31 +01:00
# #
2023-07-08 17:30:05 +02:00
# SOURCE CODE: #
2020-07-08 14:48:06 +02:00
# https://github.com/msberends/AMR #
2018-02-21 11:52:31 +01:00
# #
2023-07-08 17:30:05 +02:00
# PLEASE CITE THIS SOFTWARE AS: #
2024-07-16 14:51:57 +02:00
# Berends MS, Luz CF, Friedrich AW, et al. (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-02-21 11:52:31 +01: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. #
2020-01-05 17:22:09 +01:00
# 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 #
2021-02-02 23:57:35 +01:00
# how to conduct AMR data analysis: https://msberends.github.io/AMR/ #
2018-02-21 11:52:31 +01:00
# ==================================================================== #
2021-11-28 23:01:26 +01:00
# ====================================================== #
# || Change the EUCAST version numbers in R/globals.R || #
# ====================================================== #
2020-12-03 22:30:14 +01:00
format_eucast_version_nr <- function ( version , markdown = TRUE ) {
2020-11-12 11:07:23 +01:00
# for documentation - adds title, version number, year and url in markdown language
lst <- c ( EUCAST_VERSION_BREAKPOINTS , EUCAST_VERSION_EXPERT_RULES )
2021-01-18 16:57:56 +01:00
version <- format ( unique ( version ) , nsmall = 1 )
txt <- character ( 0 )
for ( i in seq_len ( length ( version ) ) ) {
v <- version [i ]
if ( markdown == TRUE ) {
2022-08-28 10:31:50 +02:00
txt <- c ( txt , paste0 (
" [" , lst [ [v ] ] $ title , " " , lst [ [v ] ] $ version_txt , " ](" , lst [ [v ] ] $ url , " )" ,
" (" , lst [ [v ] ] $ year , " )"
) )
2021-01-18 16:57:56 +01:00
} else {
2022-08-28 10:31:50 +02:00
txt <- c ( txt , paste0 (
lst [ [version ] ] $ title , " " , lst [ [v ] ] $ version_txt ,
" (" , lst [ [v ] ] $ year , " )"
) )
2021-01-18 16:57:56 +01:00
}
2020-12-03 22:30:14 +01:00
}
2021-02-04 16:48:16 +01:00
vector_and ( txt , quotes = FALSE )
2020-11-12 11:07:23 +01:00
}
2019-04-05 18:47:39 +02:00
2021-01-18 16:57:56 +01:00
#' Apply EUCAST Rules
2022-08-28 10:31:50 +02:00
#'
2019-11-15 15:25:03 +01:00
#' @description
2022-10-05 09:12:22 +02:00
#' Apply rules for clinical breakpoints and intrinsic resistance as defined by the European Committee on Antimicrobial Susceptibility Testing (EUCAST, <https://www.eucast.org>), see *Source*. Use [eucast_dosage()] to get a [data.frame] with advised dosages of a certain bug-drug combination, which is based on the [dosage] data set.
2022-08-28 10:31:50 +02:00
#'
2021-01-18 16:57:56 +01:00
#' To improve the interpretation of the antibiogram before EUCAST rules are applied, some non-EUCAST rules can applied at default, see *Details*.
2022-08-27 20:49:37 +02:00
#' @param x a data set with antibiotic columns, such as `amox`, `AMX` and `AMC`
2023-02-22 14:38:57 +01:00
#' @param info a [logical] to indicate whether progress should be printed to the console - the default is only print while in interactive sessions
2024-09-24 15:34:12 +02:00
#' @param rules a [character] vector that specifies which rules should be applied. Must be one or more of `"breakpoints"`, `"expert"`, `"other"`, `"custom"`, `"all"`, and defaults to `c("breakpoints", "expert")`. The default value can be set to another value using the package option [`AMR_eucastrules`][AMR-options]: `options(AMR_eucastrules = "all")`. If using `"custom"`, be sure to fill in argument `custom_rules` too. Custom rules can be created with [custom_eucast_rules()].
2020-09-24 00:30:11 +02:00
#' @param verbose a [logical] to turn Verbose mode on and off (default is off). In Verbose mode, the function does not apply rules to the data, but instead returns a data set in logbook form with extensive info about which rows and columns would be effected and in which way. Using Verbose mode takes a lot more time.
2023-06-22 15:10:59 +02:00
#' @param version_breakpoints the version number to use for the EUCAST Clinical Breakpoints guideline. Can be `r vector_or(names(EUCAST_VERSION_BREAKPOINTS), reverse = TRUE)`.
#' @param version_expertrules the version number to use for the EUCAST Expert Rules and Intrinsic Resistance guideline. Can be `r vector_or(names(EUCAST_VERSION_EXPERT_RULES), reverse = TRUE)`.
2023-07-08 17:30:05 +02:00
# @param version_resistant_phenotypes the version number to use for the EUCAST Expected Resistant Phenotypes. Can be `r vector_or(names(EUCAST_VERSION_RESISTANTPHENOTYPES), reverse = TRUE)`.
2023-02-22 14:38:57 +01:00
#' @param ampc_cephalosporin_resistance a [character] value that should be applied to cefotaxime, ceftriaxone and ceftazidime for AmpC de-repressed cephalosporin-resistant mutants - the default is `NA`. Currently only works when `version_expertrules` is `3.2` and higher; these version of '*EUCAST Expert Rules on Enterobacterales*' state that results of cefotaxime, ceftriaxone and ceftazidime should be reported with a note, or results should be suppressed (emptied) for these three drugs. A value of `NA` (the default) for this argument will remove results for these three drugs, while e.g. a value of `"R"` will make the results for these drugs resistant. Use `NULL` or `FALSE` to not alter results for these three drugs of AmpC de-repressed cephalosporin-resistant mutants. Using `TRUE` is equal to using `"R"`. \cr For *EUCAST Expert Rules* v3.2, this rule applies to: `r vector_and(gsub("[^a-zA-Z ]+", "", unlist(strsplit(EUCAST_RULES_DF[which(EUCAST_RULES_DF$reference.version %in% c(3.2, 3.3) & EUCAST_RULES_DF$reference.rule %like% "ampc"), "this_value"][1], "|", fixed = TRUE))), quotes = "*")`.
2021-01-18 16:57:56 +01:00
#' @param ... column name of an antibiotic, see section *Antibiotics* below
2022-11-13 13:44:25 +01:00
#' @param ab any (vector of) text that can be coerced to a valid antibiotic drug code with [as.ab()]
2021-01-12 22:08:04 +01:00
#' @param administration route of administration, either `r vector_or(dosage$administration)`
2023-02-22 14:38:57 +01:00
#' @param only_sir_columns a [logical] to indicate whether only antibiotic columns must be detected that were transformed to class `sir` (see [as.sir()]) on beforehand (default is `FALSE`)
2021-04-07 08:37:42 +02:00
#' @param custom_rules custom rules to apply, created with [custom_eucast_rules()]
2018-11-01 20:50:10 +01:00
#' @inheritParams first_isolate
2019-04-05 18:47:39 +02:00
#' @details
2023-01-21 23:47:20 +01:00
#' **Note:** This function does not translate MIC values to SIR values. Use [as.sir()] for that. \cr
2021-04-16 14:59:57 +02:00
#' **Note:** When ampicillin (AMP, J01CA01) is not available but amoxicillin (AMX, J01CA04) is, the latter will be used for all rules where there is a dependency on ampicillin. These drugs are interchangeable when it comes to expression of antimicrobial resistance. \cr
2019-04-09 14:59:17 +02:00
#'
2022-10-05 09:12:22 +02:00
#' The file containing all EUCAST rules is located here: <https://github.com/msberends/AMR/blob/main/data-raw/eucast_rules.tsv>. **Note:** Old taxonomic names are replaced with the current taxonomy where applicable. For example, *Ochrobactrum anthropi* was renamed to *Brucella anthropi* in 2020; the original EUCAST rules v3.1 and v3.2 did not yet contain this new taxonomic name. The `AMR` package contains the full microbial taxonomy updated until `r documentation_date(max(TAXONOMY_VERSION$GBIF$accessed_date, TAXONOMY_VERSION$LPSN$accessed_date))`, see [microorganisms].
2022-08-28 10:31:50 +02:00
#'
2022-10-05 09:12:22 +02:00
#' ### Custom Rules
2022-08-28 10:31:50 +02:00
#'
2021-04-07 08:37:42 +02:00
#' Custom rules can be created using [custom_eucast_rules()], e.g.:
2022-08-28 10:31:50 +02:00
#'
2022-10-05 09:12:22 +02:00
#' ```r
2021-04-07 08:37:42 +02:00
#' x <- custom_eucast_rules(AMC == "R" & genus == "Klebsiella" ~ aminopenicillins == "R",
#' AMC == "I" & genus == "Klebsiella" ~ aminopenicillins == "I")
#'
2022-10-05 09:12:22 +02:00
#' eucast_rules(example_isolates, rules = "custom", custom_rules = x)
2021-04-07 08:37:42 +02:00
#' ```
2022-08-28 10:31:50 +02:00
#'
2022-10-05 09:12:22 +02:00
#' ### 'Other' Rules
2022-08-28 10:31:50 +02:00
#'
2020-09-24 00:30:11 +02:00
#' Before further processing, two non-EUCAST rules about drug combinations can be applied to improve the efficacy of the EUCAST rules, and the reliability of your data (analysis). These rules are:
2022-08-28 10:31:50 +02:00
#'
2020-09-24 00:30:11 +02:00
#' 1. A drug **with** enzyme inhibitor will be set to S if the same drug **without** enzyme inhibitor is S
#' 2. A drug **without** enzyme inhibitor will be set to R if the same drug **with** enzyme inhibitor is R
2022-08-28 10:31:50 +02:00
#'
2020-09-24 00:30:11 +02:00
#' Important examples include amoxicillin and amoxicillin/clavulanic acid, and trimethoprim and trimethoprim/sulfamethoxazole. Needless to say, for these rules to work, both drugs must be available in the data set.
2022-08-28 10:31:50 +02:00
#'
2024-09-24 15:34:12 +02:00
#' Since these rules are not officially approved by EUCAST, they are not applied at default. To use these rules, include `"other"` to the `rules` argument, or use `eucast_rules(..., rules = "all")`. You can also set the package option [`AMR_eucastrules`][AMR-options], i.e. run `options(AMR_eucastrules = "all")`.
2018-08-31 13:36:19 +02:00
#' @section Antibiotics:
2019-11-28 22:32:17 +01:00
#' To define antibiotics column names, leave as it is to determine it automatically with [guess_ab_col()] or input a text (case-insensitive), or use `NULL` to skip a column (e.g. `TIC = NULL` to skip ticarcillin). Manually defined but non-existing columns will be skipped with a warning.
2018-12-07 12:04:55 +01:00
#'
2024-03-03 23:24:57 +01:00
#' The following antibiotics are eligible for the functions [eucast_rules()] and [mdro()]. These are shown below in the format 'name (`antimicrobial ID`, [ATC code](https://atcddd.fhi.no/atc/structure_and_principles/))', sorted alphabetically:
2018-07-26 16:30:42 +02:00
#'
2021-05-18 11:29:31 +02:00
#' `r create_eucast_ab_documentation()`
2019-11-06 14:43:23 +01:00
#' @aliases EUCAST
2018-11-16 20:50:50 +01:00
#' @rdname eucast_rules
2018-02-21 11:52:31 +01:00
#' @export
2020-09-18 16:05:53 +02:00
#' @return The input of `x`, possibly with edited values of antibiotics. Or, if `verbose = TRUE`, a [data.frame] with all original and new values of the affected bug-drug combinations.
2018-02-21 11:52:31 +01:00
#' @source
2020-09-29 23:35:46 +02:00
#' - EUCAST Expert Rules. Version 2.0, 2012.\cr
2021-01-06 11:16:17 +01:00
#' Leclercq et al. **EUCAST expert rules in antimicrobial susceptibility testing.** *Clin Microbiol Infect.* 2013;19(2):141-60; \doi{https://doi.org/10.1111/j.1469-0691.2011.03703.x}
2020-09-29 23:35:46 +02:00
#' - EUCAST Expert Rules, Intrinsic Resistance and Exceptional Phenotypes Tables. Version 3.1, 2016. [(link)](https://www.eucast.org/fileadmin/src/media/PDFs/EUCAST_files/Expert_Rules/Expert_rules_intrinsic_exceptional_V3.1.pdf)
#' - EUCAST Intrinsic Resistance and Unusual Phenotypes. Version 3.2, 2020. [(link)](https://www.eucast.org/fileadmin/src/media/PDFs/EUCAST_files/Expert_Rules/2020/Intrinsic_Resistance_and_Unusual_Phenotypes_Tables_v3.2_20200225.pdf)
2021-11-28 23:01:26 +01:00
#' - EUCAST Intrinsic Resistance and Unusual Phenotypes. Version 3.3, 2021. [(link)](https://www.eucast.org/fileadmin/src/media/PDFs/EUCAST_files/Expert_Rules/2021/Intrinsic_Resistance_and_Unusual_Phenotypes_Tables_v3.3_20211018.pdf)
2020-09-29 23:35:46 +02:00
#' - EUCAST Breakpoint tables for interpretation of MICs and zone diameters. Version 9.0, 2019. [(link)](https://www.eucast.org/fileadmin/src/media/PDFs/EUCAST_files/Breakpoint_tables/v_9.0_Breakpoint_Tables.xlsx)
#' - EUCAST Breakpoint tables for interpretation of MICs and zone diameters. Version 10.0, 2020. [(link)](https://www.eucast.org/fileadmin/src/media/PDFs/EUCAST_files/Breakpoint_tables/v_10.0_Breakpoint_Tables.xlsx)
2021-01-12 22:08:04 +01:00
#' - EUCAST Breakpoint tables for interpretation of MICs and zone diameters. Version 11.0, 2021. [(link)](https://www.eucast.org/fileadmin/src/media/PDFs/EUCAST_files/Breakpoint_tables/v_11.0_Breakpoint_Tables.xlsx)
2022-08-21 16:37:20 +02:00
#' - EUCAST Breakpoint tables for interpretation of MICs and zone diameters. Version 12.0, 2022. [(link)](https://www.eucast.org/fileadmin/src/media/PDFs/EUCAST_files/Breakpoint_tables/v_12.0_Breakpoint_Tables.xlsx)
2021-01-18 16:57:56 +01:00
#' @inheritSection AMR Reference Data Publicly Available
2018-02-21 11:52:31 +01:00
#' @examples
2019-11-18 12:10:47 +01:00
#' \donttest{
2022-08-28 10:31:50 +02:00
#' a <- data.frame(
#' mo = c(
#' "Staphylococcus aureus",
#' "Enterococcus faecalis",
#' "Escherichia coli",
#' "Klebsiella pneumoniae",
#' "Pseudomonas aeruginosa"
#' ),
#' VAN = "-", # Vancomycin
#' AMX = "-", # Amoxicillin
#' COL = "-", # Colistin
#' CAZ = "-", # Ceftazidime
#' CXM = "-", # Cefuroxime
#' PEN = "S", # Benzylpenicillin
#' FOX = "S", # Cefoxitin
#' stringsAsFactors = FALSE
#' )
2018-10-18 12:10:10 +02:00
#'
2022-08-21 16:37:20 +02:00
#' head(a)
2018-10-18 12:10:10 +02:00
#'
2019-02-08 16:06:54 +01:00
#'
2020-09-24 00:30:11 +02:00
#' # apply EUCAST rules: some results wil be changed
2019-02-08 16:06:54 +01:00
#' b <- eucast_rules(a)
2018-04-02 16:05:09 +02:00
#'
2022-08-21 16:37:20 +02:00
#' head(b)
2019-02-08 16:06:54 +01:00
#'
#'
2019-07-30 13:12:40 +02:00
#' # do not apply EUCAST rules, but rather get a data.frame
2020-09-24 00:30:11 +02:00
#' # containing all details about the transformations:
2019-02-08 16:06:54 +01:00
#' c <- eucast_rules(a, verbose = TRUE)
2022-08-21 16:37:20 +02:00
#' head(c)
2019-08-09 14:28:46 +02:00
#' }
2022-08-28 10:31:50 +02:00
#'
2022-08-21 16:37:20 +02:00
#' # Dosage guidelines:
2022-08-28 10:31:50 +02:00
#'
2021-01-12 22:08:04 +01:00
#' eucast_dosage(c("tobra", "genta", "cipro"), "iv")
2022-08-28 10:31:50 +02:00
#'
2022-08-21 16:37:20 +02:00
#' eucast_dosage(c("tobra", "genta", "cipro"), "iv", version_breakpoints = 10)
2019-04-05 18:47:39 +02:00
eucast_rules <- function ( x ,
2018-11-01 20:23:33 +01:00
col_mo = NULL ,
2020-02-21 21:13:38 +01:00
info = interactive ( ) ,
2020-09-24 00:30:11 +02:00
rules = getOption ( " AMR_eucastrules" , default = c ( " breakpoints" , " expert" ) ) ,
2018-11-01 20:23:33 +01:00
verbose = FALSE ,
2022-11-14 15:20:39 +01:00
version_breakpoints = 12.0 ,
2021-11-28 23:01:26 +01:00
version_expertrules = 3.3 ,
2023-07-08 17:30:05 +02:00
# TODO version_resistant_phenotypes = 1.2,
2020-12-27 14:23:11 +01:00
ampc_cephalosporin_resistance = NA ,
2023-01-21 23:47:20 +01:00
only_sir_columns = FALSE ,
2021-04-07 08:37:42 +02:00
custom_rules = NULL ,
2019-04-05 18:47:39 +02:00
... ) {
2020-10-19 17:09:19 +02:00
meet_criteria ( x , allow_class = " data.frame" )
meet_criteria ( col_mo , allow_class = " character" , has_length = 1 , is_in = colnames ( x ) , allow_NULL = TRUE )
meet_criteria ( info , allow_class = " logical" , has_length = 1 )
2021-04-07 08:37:42 +02:00
meet_criteria ( rules , allow_class = " character" , has_length = c ( 1 , 2 , 3 , 4 , 5 ) , is_in = c ( " breakpoints" , " expert" , " other" , " all" , " custom" ) )
2020-10-19 17:09:19 +02:00
meet_criteria ( verbose , allow_class = " logical" , has_length = 1 )
2021-01-14 14:41:44 +01:00
meet_criteria ( version_breakpoints , allow_class = c ( " numeric" , " integer" ) , has_length = 1 , is_in = as.double ( names ( EUCAST_VERSION_BREAKPOINTS ) ) )
meet_criteria ( version_expertrules , allow_class = c ( " numeric" , " integer" ) , has_length = 1 , is_in = as.double ( names ( EUCAST_VERSION_EXPERT_RULES ) ) )
2023-07-08 17:30:05 +02:00
# meet_criteria(version_resistant_phenotypes, allow_class = c("numeric", "integer"), has_length = 1, is_in = as.double(names(EUCAST_VERSION_RESISTANTPHENOTYPES)))
2023-01-21 23:47:20 +01:00
meet_criteria ( ampc_cephalosporin_resistance , allow_class = c ( " logical" , " character" , " sir" ) , has_length = 1 , allow_NA = TRUE , allow_NULL = TRUE )
meet_criteria ( only_sir_columns , allow_class = " logical" , has_length = 1 )
2021-04-07 08:37:42 +02:00
meet_criteria ( custom_rules , allow_class = " custom_eucast_rules" , allow_NULL = TRUE )
2022-08-28 10:31:50 +02:00
2023-01-21 23:47:20 +01:00
add_MO_lookup_to_AMR_env ( )
2022-11-14 15:20:39 +01:00
if ( " custom" %in% rules && is.null ( custom_rules ) ) {
2022-03-02 15:38:55 +01:00
warning_ ( " in `eucast_rules()`: no custom rules were set with the `custom_rules` argument" ,
2022-08-28 10:31:50 +02:00
immediate = TRUE
)
2021-04-07 08:37:42 +02:00
rules <- rules [rules != " custom" ]
if ( length ( rules ) == 0 ) {
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) ) {
2021-04-07 08:37:42 +02:00
message_ ( " No other rules were set, returning original data" , add_fn = font_red , as_note = FALSE )
}
return ( x )
}
}
2022-08-28 10:31:50 +02:00
2020-09-24 12:38:13 +02:00
x_deparsed <- deparse ( substitute ( x ) )
2021-04-23 09:59:36 +02:00
if ( length ( x_deparsed ) > 1 || any ( x_deparsed %unlike% " [a-z]+" ) ) {
2020-09-24 12:38:13 +02:00
x_deparsed <- " your_data"
}
2022-08-28 10:31:50 +02:00
2020-09-24 00:30:11 +02:00
breakpoints_info <- EUCAST_VERSION_BREAKPOINTS [ [which ( as.double ( names ( EUCAST_VERSION_BREAKPOINTS ) ) == version_breakpoints ) ] ]
expertrules_info <- EUCAST_VERSION_EXPERT_RULES [ [which ( as.double ( names ( EUCAST_VERSION_EXPERT_RULES ) ) == version_expertrules ) ] ]
2023-07-08 17:30:05 +02:00
# resistantphenotypes_info <- EUCAST_VERSION_RESISTANTPHENOTYPES[[which(as.double(names(EUCAST_VERSION_RESISTANTPHENOTYPES)) == version_resistant_phenotypes)]]
2023-06-22 15:10:59 +02:00
2020-09-24 00:30:11 +02:00
# support old setting (until AMR v1.3.0)
2022-12-27 15:16:15 +01:00
if ( missing ( rules ) && ! is.null ( getOption ( " AMR.eucast_rules" ) ) ) {
2020-09-24 00:30:11 +02:00
rules <- getOption ( " AMR.eucast_rules" )
}
2022-08-28 10:31:50 +02:00
2022-11-14 15:20:39 +01:00
if ( interactive ( ) && isTRUE ( verbose ) && isTRUE ( info ) ) {
2022-08-28 10:31:50 +02:00
txt <- paste0 (
" WARNING: In Verbose mode, the eucast_rules() function does not apply rules to the data, but instead returns a data set in logbook form with extensive info about which rows and columns would be effected and in which way." ,
" \n\nThis may overwrite your existing data if you use e.g.:" ,
" \ndata <- eucast_rules(data, verbose = TRUE)\n\nDo you want to continue?"
)
2020-09-24 00:30:11 +02:00
showQuestion <- import_fn ( " showQuestion" , " rstudioapi" , error_on_fail = FALSE )
2020-09-24 12:38:13 +02:00
if ( ! is.null ( showQuestion ) ) {
2020-05-16 21:40:50 +02:00
q_continue <- showQuestion ( " Using verbose = TRUE with eucast_rules()" , txt )
2019-08-06 14:39:22 +02:00
} else {
2020-05-16 13:05:47 +02:00
q_continue <- utils :: menu ( choices = c ( " OK" , " Cancel" ) , graphics = FALSE , title = txt )
2019-08-06 14:39:22 +02:00
}
if ( q_continue %in% c ( FALSE , 2 ) ) {
2020-10-27 15:56:51 +01:00
message_ ( " Cancelled, returning original data" , add_fn = font_red , as_note = FALSE )
2019-08-20 11:40:54 +02:00
return ( x )
2019-08-06 14:39:22 +02:00
}
}
2022-08-28 10:31:50 +02:00
2018-11-01 20:23:33 +01:00
# try to find columns based on type
# -- mo
2019-01-15 12:45:24 +01:00
if ( is.null ( col_mo ) ) {
2020-09-24 00:30:11 +02:00
col_mo <- search_type_in_df ( x = x , type = " mo" , info = info )
2021-01-15 22:44:52 +01:00
stop_if ( is.null ( col_mo ) , " `col_mo` must be set" )
2018-12-22 22:39:34 +01:00
}
2022-08-28 10:31:50 +02:00
2019-06-07 22:47:37 +02:00
decimal.mark <- getOption ( " OutDec" )
2023-02-06 11:57:22 +01:00
big.mark <- ifelse ( decimal.mark != " ," , " ," , " " )
2020-05-16 13:05:47 +02:00
formatnr <- function ( x , big = big.mark , dec = decimal.mark ) {
trimws ( format ( x , big.mark = big , decimal.mark = dec ) )
2019-06-07 22:47:37 +02:00
}
2022-08-28 10:31:50 +02:00
2018-10-17 17:32:34 +02:00
warned <- FALSE
2023-01-21 23:47:20 +01:00
warn_lacking_sir_class <- character ( 0 )
2020-09-24 00:30:11 +02:00
txt_ok <- function ( n_added , n_changed , warned = FALSE ) {
2019-10-11 17:21:02 +02:00
if ( warned == FALSE ) {
2020-09-24 00:30:11 +02:00
if ( n_added + n_changed == 0 ) {
2020-05-16 13:05:47 +02:00
cat ( font_subtle ( " (no changes)\n" ) )
2018-10-17 17:32:34 +02:00
} else {
2019-08-09 14:28:46 +02:00
# opening
2022-11-14 15:20:39 +01:00
if ( n_added > 0 && n_changed == 0 ) {
2021-05-13 15:56:12 +02:00
cat ( font_green ( " (" ) )
2022-11-14 15:20:39 +01:00
} else if ( n_added == 0 && n_changed > 0 ) {
2021-05-13 15:56:12 +02:00
cat ( font_blue ( " (" ) )
} else {
cat ( font_grey ( " (" ) )
}
2019-08-09 14:28:46 +02:00
# additions
2020-09-24 00:30:11 +02:00
if ( n_added > 0 ) {
if ( n_added == 1 ) {
2020-05-16 13:05:47 +02:00
cat ( font_green ( " 1 value added" ) )
2019-08-09 14:28:46 +02:00
} else {
2020-09-24 00:30:11 +02:00
cat ( font_green ( formatnr ( n_added ) , " values added" ) )
2019-08-09 14:28:46 +02:00
}
}
# separator
2022-11-14 15:20:39 +01:00
if ( n_added > 0 && n_changed > 0 ) {
2020-05-16 13:05:47 +02:00
cat ( font_grey ( " , " ) )
2019-08-09 14:28:46 +02:00
}
# changes
2020-09-24 00:30:11 +02:00
if ( n_changed > 0 ) {
if ( n_changed == 1 ) {
2020-05-16 13:05:47 +02:00
cat ( font_blue ( " 1 value changed" ) )
2019-08-09 14:28:46 +02:00
} else {
2020-09-24 00:30:11 +02:00
cat ( font_blue ( formatnr ( n_changed ) , " values changed" ) )
2019-08-09 14:28:46 +02:00
}
2022-08-28 10:31:50 +02:00
}
2019-08-09 14:28:46 +02:00
# closing
2022-11-14 15:20:39 +01:00
if ( n_added > 0 && n_changed == 0 ) {
2021-05-13 15:56:12 +02:00
cat ( font_green ( " )\n" ) )
2022-11-14 15:20:39 +01:00
} else if ( n_added == 0 && n_changed > 0 ) {
2021-05-13 15:56:12 +02:00
cat ( font_blue ( " )\n" ) )
} else {
cat ( font_grey ( " )\n" ) )
}
2018-10-17 17:32:34 +02:00
}
warned <<- FALSE
}
}
2022-08-28 10:31:50 +02:00
cols_ab <- get_column_abx (
x = x ,
soft_dependencies = c (
" AMC" ,
" AMP" ,
" AMX" ,
" CIP" ,
" ERY" ,
" FOX1" ,
" GEN" ,
" MFX" ,
" NAL" ,
" NOR" ,
" PEN" ,
" PIP" ,
" TCY" ,
" TIC" ,
" TOB"
) ,
hard_dependencies = NULL ,
verbose = verbose ,
info = info ,
2023-01-21 23:47:20 +01:00
only_sir_columns = only_sir_columns ,
2022-08-28 10:31:50 +02:00
fn = " eucast_rules" ,
...
)
2022-11-14 15:20:39 +01:00
if ( ! " AMP" %in% names ( cols_ab ) && " AMX" %in% names ( cols_ab ) ) {
2018-12-31 01:48:53 +01:00
# ampicillin column is missing, but amoxicillin is available
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) ) {
2021-04-07 08:37:42 +02:00
message_ ( " Using column '" , cols_ab [names ( cols_ab ) == " AMX" ] , " ' as input for ampicillin since many EUCAST rules depend on it." )
2020-09-24 00:30:11 +02:00
}
2021-04-07 08:37:42 +02:00
cols_ab <- c ( cols_ab , c ( AMP = unname ( cols_ab [names ( cols_ab ) == " AMX" ] ) ) )
2018-10-17 17:32:34 +02:00
}
2022-08-28 10:31:50 +02:00
2020-09-24 00:30:11 +02:00
# data preparation ----
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) && NROW ( x ) > 10000 ) {
2020-10-27 15:56:51 +01:00
message_ ( " Preparing data..." , appendLF = FALSE , as_note = FALSE )
2020-09-24 00:30:11 +02:00
}
2022-08-28 10:31:50 +02:00
2020-09-24 00:30:11 +02:00
# Some helper functions ---------------------------------------------------
2019-07-09 13:36:03 +02:00
get_antibiotic_names <- function ( x ) {
2023-02-09 13:07:39 +01:00
x <- x %pm>%
strsplit ( " ," ) %pm>%
unlist ( ) %pm>%
trimws2 ( ) %pm>%
vapply ( FUN.VALUE = character ( 1 ) , function ( x ) if ( x %in% AMR :: antibiotics $ ab ) ab_name ( x , language = NULL , tolower = TRUE , fast_mode = TRUE ) else x ) %pm>%
sort ( ) %pm>%
2019-07-09 13:36:03 +02:00
paste ( collapse = " , " )
2019-11-28 22:32:17 +01:00
x <- gsub ( " _" , " " , x , fixed = TRUE )
x <- gsub ( " except CAZ" , paste ( " except" , ab_name ( " CAZ" , language = NULL , tolower = TRUE ) ) , x , fixed = TRUE )
2021-05-03 10:47:32 +02:00
x <- gsub ( " except TGC" , paste ( " except" , ab_name ( " TGC" , language = NULL , tolower = TRUE ) ) , x , fixed = TRUE )
2020-09-24 12:38:13 +02:00
x <- gsub ( " cephalosporins (1st|2nd|3rd|4th|5th)" , " cephalosporins (\\1 gen.)" , x )
2019-11-28 22:32:17 +01:00
x
2019-07-09 13:36:03 +02:00
}
2019-08-09 14:28:46 +02:00
format_antibiotic_names <- function ( ab_names , ab_results ) {
2022-10-05 09:12:22 +02:00
ab_names <- trimws2 ( unlist ( strsplit ( ab_names , " ," ) ) )
ab_results <- trimws2 ( unlist ( strsplit ( ab_results , " ," ) ) )
2019-08-09 14:28:46 +02:00
if ( length ( ab_results ) == 1 ) {
if ( length ( ab_names ) == 1 ) {
# like FOX S
x <- paste ( ab_names , " is" )
} else if ( length ( ab_names ) == 2 ) {
# like PEN,FOX S
x <- paste ( paste0 ( ab_names , collapse = " and " ) , " are both" )
} else {
# like PEN,FOX,GEN S (although dependency on > 2 ABx does not exist at the moment)
2020-12-13 13:44:04 +01:00
# nolint start
# x <- paste(paste0(ab_names, collapse = " and "), "are all")
# nolint end
2019-08-09 14:28:46 +02:00
}
return ( paste0 ( x , " '" , ab_results , " '" ) )
} else {
if ( length ( ab_names ) == 2 ) {
# like PEN,FOX S,R
2022-08-28 10:31:50 +02:00
paste0 (
ab_names [1 ] , " is '" , ab_results [1 ] , " ' and " ,
ab_names [2 ] , " is '" , ab_results [2 ] , " '"
)
2019-08-09 14:28:46 +02:00
} else {
# like PEN,FOX,GEN S,R,R (although dependency on > 2 ABx does not exist at the moment)
2022-08-28 10:31:50 +02:00
paste0 (
ab_names [1 ] , " is '" , ab_results [1 ] , " ' and " ,
ab_names [2 ] , " is '" , ab_results [2 ] , " ' and " ,
ab_names [3 ] , " is '" , ab_results [3 ] , " '"
)
2019-08-09 14:28:46 +02:00
}
}
}
2023-01-21 23:47:20 +01:00
as.sir_no_warning <- function ( x ) {
if ( is.sir ( x ) ) {
2020-09-24 00:30:11 +02:00
return ( x )
}
2023-01-21 23:47:20 +01:00
suppressWarnings ( as.sir ( x ) )
2020-09-24 00:30:11 +02:00
}
2022-08-28 10:31:50 +02:00
2020-09-29 10:40:25 +02:00
# Preparing the data ------------------------------------------------------
2022-08-28 10:31:50 +02:00
verbose_info <- data.frame (
rowid = character ( 0 ) ,
col = character ( 0 ) ,
mo_fullname = character ( 0 ) ,
2023-01-21 23:47:20 +01:00
old = as.sir ( character ( 0 ) ) ,
new = as.sir ( character ( 0 ) ) ,
2022-08-28 10:31:50 +02:00
rule = character ( 0 ) ,
rule_group = character ( 0 ) ,
rule_name = character ( 0 ) ,
rule_source = character ( 0 ) ,
stringsAsFactors = FALSE
)
2020-09-24 00:30:11 +02:00
old_cols <- colnames ( x )
old_attributes <- attributes ( x )
x <- as.data.frame ( x , stringsAsFactors = FALSE ) # no tibbles, data.tables, etc.
rownames ( x ) <- NULL # will later be restored with old_attributes
# create unique row IDs - combination of the MO and all ABx columns (so they will only run once per unique combination)
2022-08-28 10:31:50 +02:00
x $ `.rowid` <- vapply (
FUN.VALUE = character ( 1 ) ,
as.list ( as.data.frame ( t ( x [ , c ( col_mo , cols_ab ) , drop = FALSE ] ) ,
stringsAsFactors = FALSE
) ) ,
function ( x ) {
x [is.na ( x ) ] <- " ."
paste0 ( x , collapse = " " )
}
)
2021-05-13 15:56:12 +02:00
# save original table, with the new .rowid column
2020-09-24 00:30:11 +02:00
x.bak <- x
# keep only unique rows for MO and ABx
2023-02-09 13:07:39 +01:00
x <- x %pm>%
pm_arrange ( `.rowid` ) %pm>%
2020-09-24 00:30:11 +02:00
# big speed gain! only analyse unique rows:
2023-02-09 13:07:39 +01:00
pm_distinct ( `.rowid` , .keep_all = TRUE ) %pm>%
2020-09-24 00:30:11 +02:00
as.data.frame ( stringsAsFactors = FALSE )
2022-10-05 09:12:22 +02:00
x [ , col_mo ] <- as.mo ( as.character ( x [ , col_mo , drop = TRUE ] ) , info = info )
2021-04-23 09:59:36 +02:00
# rename col_mo to prevent interference with joined columns
colnames ( x ) [colnames ( x ) == col_mo ] <- " .col_mo"
col_mo <- " .col_mo"
# join to microorganisms data set
x <- left_join_microorganisms ( x , by = col_mo , suffix = c ( " _oldcols" , " " ) )
2022-10-05 09:12:22 +02:00
x $ gramstain <- mo_gramstain ( x [ , col_mo , drop = TRUE ] , language = NULL , info = FALSE )
2021-05-13 15:56:12 +02:00
x $ genus_species <- trimws ( paste ( x $ genus , x $ species ) )
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) && NROW ( x ) > 10000 ) {
2020-11-11 16:49:27 +01:00
message_ ( " OK." , add_fn = list ( font_green , font_bold ) , as_note = FALSE )
2020-09-24 00:30:11 +02:00
}
2022-08-28 10:31:50 +02:00
2020-09-24 00:30:11 +02:00
if ( any ( x $ genus == " Staphylococcus" , na.rm = TRUE ) ) {
2022-10-14 13:02:50 +02:00
all_staph <- AMR_env $ MO_lookup [which ( AMR_env $ MO_lookup $ genus == " Staphylococcus" ) , , drop = FALSE ]
2022-10-05 09:12:22 +02:00
all_staph $ CNS_CPS <- suppressWarnings ( mo_name ( all_staph $ mo , Becker = " all" , language = NULL , info = FALSE ) )
2020-09-24 00:30:11 +02:00
}
if ( any ( x $ genus == " Streptococcus" , na.rm = TRUE ) ) {
2022-10-14 13:02:50 +02:00
all_strep <- AMR_env $ MO_lookup [which ( AMR_env $ MO_lookup $ genus == " Streptococcus" ) , , drop = FALSE ]
2022-10-05 09:12:22 +02:00
all_strep $ Lancefield <- suppressWarnings ( mo_name ( all_strep $ mo , Lancefield = TRUE , language = NULL , info = FALSE ) )
2020-09-24 00:30:11 +02:00
}
2022-08-28 10:31:50 +02:00
2020-09-24 00:30:11 +02:00
n_added <- 0
n_changed <- 0
2022-08-28 10:31:50 +02:00
2020-05-27 16:37:49 +02:00
# Other rules: enzyme inhibitors ------------------------------------------
if ( any ( c ( " all" , " other" ) %in% rules ) ) {
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) ) {
2020-12-11 12:17:23 +01:00
cat ( " \n" )
cat ( word_wrap (
2022-08-28 10:31:50 +02:00
font_bold ( paste0 (
" Rules by this AMR package (" ,
font_red ( paste0 (
" v" , utils :: packageDescription ( " AMR" ) $ Version , " , " ,
format ( as.Date ( utils :: packageDescription ( " AMR" ) $ Date ) , format = " %Y" )
2024-06-15 15:33:49 +02:00
) ) , " ), see `?eucast_rules`\n"
2022-08-28 10:31:50 +02:00
) )
) )
2020-05-27 16:37:49 +02:00
}
2022-10-05 09:12:22 +02:00
ab_enzyme <- subset ( AMR :: antibiotics , name %like% " /" ) [ , c ( " ab" , " name" ) , drop = FALSE ]
2021-04-12 12:35:13 +02:00
colnames ( ab_enzyme ) <- c ( " enzyme_ab" , " enzyme_name" )
ab_enzyme $ base_name <- gsub ( " ^([a-zA-Z0-9]+).*" , " \\1" , ab_enzyme $ enzyme_name )
2022-10-05 09:12:22 +02:00
ab_enzyme $ base_ab <- AMR :: antibiotics [match ( ab_enzyme $ base_name , AMR :: antibiotics $ name ) , " ab" , drop = TRUE ]
2021-04-12 12:35:13 +02:00
ab_enzyme <- subset ( ab_enzyme , ! is.na ( base_ab ) )
# make ampicillin and amoxicillin interchangable
ampi <- subset ( ab_enzyme , base_ab == " AMX" )
ampi $ base_ab <- " AMP"
ampi $ base_name <- ab_name ( " AMP" , language = NULL )
amox <- subset ( ab_enzyme , base_ab == " AMP" )
amox $ base_ab <- " AMX"
amox $ base_name <- ab_name ( " AMX" , language = NULL )
# merge and sort
2023-03-12 13:02:37 +01:00
ab_enzyme <- rbind_AMR ( ab_enzyme , ampi , amox )
2022-08-27 20:49:37 +02:00
ab_enzyme <- ab_enzyme [order ( ab_enzyme $ enzyme_name ) , , drop = FALSE ]
2022-08-28 10:31:50 +02:00
2020-05-27 16:37:49 +02:00
for ( i in seq_len ( nrow ( ab_enzyme ) ) ) {
2021-04-12 12:35:13 +02:00
# check if both base and base + enzyme inhibitor are part of the data set
if ( all ( c ( ab_enzyme $ base_ab [i ] , ab_enzyme $ enzyme_ab [i ] ) %in% names ( cols_ab ) , na.rm = TRUE ) ) {
col_base <- unname ( cols_ab [ab_enzyme $ base_ab [i ] ] )
col_enzyme <- unname ( cols_ab [ab_enzyme $ enzyme_ab [i ] ] )
2022-08-28 10:31:50 +02:00
2020-12-16 16:18:53 +01:00
# Set base to R where base + enzyme inhibitor is R ----
2022-08-28 10:31:50 +02:00
rule_current <- paste0 (
ab_enzyme $ base_name [i ] , " ('" , font_bold ( col_base ) , " ') = R if " ,
tolower ( ab_enzyme $ enzyme_name [i ] ) , " ('" , font_bold ( col_enzyme ) , " ') = R"
)
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) ) {
2022-08-28 10:31:50 +02:00
cat ( word_wrap ( rule_current ,
width = getOption ( " width" ) - 30 ,
extra_indent = 6
) )
2020-12-16 16:18:53 +01:00
}
2023-01-21 23:47:20 +01:00
run_changes <- edit_sir (
2022-08-28 10:31:50 +02:00
x = x ,
to = " R" ,
rule = c (
rule_current , " Other rules" , " " ,
paste0 ( " Non-EUCAST: AMR package v" , utils :: packageDescription ( " AMR" ) $ Version )
) ,
2023-01-21 23:47:20 +01:00
rows = which ( as.sir_no_warning ( x [ , col_enzyme , drop = TRUE ] ) == " R" ) ,
2022-08-28 10:31:50 +02:00
cols = col_base ,
last_verbose_info = verbose_info ,
original_data = x.bak ,
warned = warned ,
info = info ,
verbose = verbose
)
2020-09-24 00:30:11 +02:00
n_added <- n_added + run_changes $ added
n_changed <- n_changed + run_changes $ changed
verbose_info <- run_changes $ verbose_info
x <- run_changes $ output
2023-01-21 23:47:20 +01:00
warn_lacking_sir_class <- c ( warn_lacking_sir_class , run_changes $ sir_warn )
2020-05-27 16:37:49 +02:00
# Print number of new changes
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) ) {
2020-05-27 16:37:49 +02:00
# print only on last one of rules in this group
2020-09-24 00:30:11 +02:00
txt_ok ( n_added = n_added , n_changed = n_changed , warned = warned )
2020-05-27 16:37:49 +02:00
# and reset counters
2020-09-24 00:30:11 +02:00
n_added <- 0
n_changed <- 0
2020-05-27 16:37:49 +02:00
}
2022-08-28 10:31:50 +02:00
2020-12-16 16:18:53 +01:00
# Set base + enzyme inhibitor to S where base is S ----
2022-08-28 10:31:50 +02:00
rule_current <- paste0 (
ab_enzyme $ enzyme_name [i ] , " ('" , font_bold ( col_enzyme ) , " ') = S if " ,
tolower ( ab_enzyme $ base_name [i ] ) , " ('" , font_bold ( col_base ) , " ') = S"
)
2021-04-12 12:35:13 +02:00
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) ) {
2022-08-28 10:31:50 +02:00
cat ( word_wrap ( rule_current ,
width = getOption ( " width" ) - 30 ,
extra_indent = 6
) )
2020-05-27 16:37:49 +02:00
}
2023-01-21 23:47:20 +01:00
run_changes <- edit_sir (
2022-08-28 10:31:50 +02:00
x = x ,
to = " S" ,
rule = c (
rule_current , " Other rules" , " " ,
paste0 ( " Non-EUCAST: AMR package v" , utils :: packageDescription ( " AMR" ) $ Version )
) ,
2023-01-21 23:47:20 +01:00
rows = which ( as.sir_no_warning ( x [ , col_base , drop = TRUE ] ) == " S" ) ,
2022-08-28 10:31:50 +02:00
cols = col_enzyme ,
last_verbose_info = verbose_info ,
original_data = x.bak ,
warned = warned ,
info = info ,
verbose = verbose
)
2020-09-24 00:30:11 +02:00
n_added <- n_added + run_changes $ added
n_changed <- n_changed + run_changes $ changed
verbose_info <- run_changes $ verbose_info
x <- run_changes $ output
2023-01-21 23:47:20 +01:00
warn_lacking_sir_class <- c ( warn_lacking_sir_class , run_changes $ sir_warn )
2020-05-27 16:37:49 +02:00
# Print number of new changes
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) ) {
2020-05-27 16:37:49 +02:00
# print only on last one of rules in this group
2020-09-24 00:30:11 +02:00
txt_ok ( n_added = n_added , n_changed = n_changed , warned = warned )
2020-05-27 16:37:49 +02:00
# and reset counters
2020-09-24 00:30:11 +02:00
n_added <- 0
n_changed <- 0
2020-05-27 16:37:49 +02:00
}
}
}
} else {
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) ) {
2021-02-04 16:48:16 +01:00
cat ( " \n" )
2021-04-07 08:37:42 +02:00
message_ ( " Skipping inheritance rules defined by this AMR package, such as setting trimethoprim (TMP) = R where trimethoprim/sulfamethoxazole (SXT) = R. Add \"other\" or \"all\" to the `rules` argument to apply those rules." )
}
}
2022-08-28 10:31:50 +02:00
2022-11-14 15:20:39 +01:00
if ( ! any ( c ( " all" , " custom" ) %in% rules ) && ! is.null ( custom_rules ) ) {
if ( isTRUE ( info ) ) {
2021-04-07 08:37:42 +02:00
message_ ( " Skipping custom EUCAST rules, since the `rules` argument does not contain \"custom\"." )
2020-05-27 16:37:49 +02:00
}
2021-04-07 08:37:42 +02:00
custom_rules <- NULL
2020-05-20 12:00:17 +02:00
}
2022-08-28 10:31:50 +02:00
2020-05-27 16:37:49 +02:00
# Official EUCAST rules ---------------------------------------------------
2019-11-15 15:25:03 +01:00
eucast_notification_shown <- FALSE
2020-08-14 13:36:10 +02:00
if ( ! is.null ( list ( ... ) $ eucast_rules_df ) ) {
2021-06-04 21:07:55 +02:00
# this allows: eucast_rules(x, eucast_rules_df = AMR:::EUCAST_RULES_DF %>% filter(is.na(have_these_values)))
2020-08-14 13:36:10 +02:00
eucast_rules_df <- list ( ... ) $ eucast_rules_df
} else {
2024-04-23 10:55:48 +02:00
# otherwise internal data file, created in data-raw/_pre_commit_checks.R
2021-06-04 21:07:55 +02:00
eucast_rules_df <- EUCAST_RULES_DF
2020-08-14 13:36:10 +02:00
}
2022-08-28 10:31:50 +02:00
2020-09-24 00:30:11 +02:00
# filter on user-set guideline versions ----
if ( any ( c ( " all" , " breakpoints" ) %in% rules ) ) {
2022-08-28 10:31:50 +02:00
eucast_rules_df <- subset (
eucast_rules_df ,
reference.rule_group %unlike% " breakpoint" |
( reference.rule_group %like% " breakpoint" & reference.version == version_breakpoints )
)
2020-09-24 00:30:11 +02:00
}
if ( any ( c ( " all" , " expert" ) %in% rules ) ) {
2022-08-28 10:31:50 +02:00
eucast_rules_df <- subset (
eucast_rules_df ,
reference.rule_group %unlike% " expert" |
( reference.rule_group %like% " expert" & reference.version == version_expertrules )
)
2020-09-24 00:30:11 +02:00
}
2020-12-27 14:23:11 +01:00
# filter out AmpC de-repressed cephalosporin-resistant mutants ----
2021-11-28 23:01:26 +01:00
# no need to filter on version number here - the rules contain these version number, so are inherently filtered
2021-03-11 21:42:30 +01:00
# cefotaxime, ceftriaxone, ceftazidime
if ( is.null ( ampc_cephalosporin_resistance ) || isFALSE ( ampc_cephalosporin_resistance ) ) {
2022-08-28 10:31:50 +02:00
eucast_rules_df <- subset (
eucast_rules_df ,
reference.rule %unlike% " ampc"
)
2020-12-27 14:23:11 +01:00
} else {
2021-03-11 21:42:30 +01:00
if ( isTRUE ( ampc_cephalosporin_resistance ) ) {
ampc_cephalosporin_resistance <- " R"
}
2020-12-27 14:23:11 +01:00
eucast_rules_df [which ( eucast_rules_df $ reference.rule %like% " ampc" ) , " to_value" ] <- as.character ( ampc_cephalosporin_resistance )
}
2022-08-28 10:31:50 +02:00
2021-04-07 08:37:42 +02:00
# Go over all rules and apply them ----
2019-10-11 17:21:02 +02:00
for ( i in seq_len ( nrow ( eucast_rules_df ) ) ) {
2020-09-24 00:30:11 +02:00
rule_previous <- eucast_rules_df [max ( 1 , i - 1 ) , " reference.rule" , drop = TRUE ]
rule_current <- eucast_rules_df [i , " reference.rule" , drop = TRUE ]
rule_next <- eucast_rules_df [min ( nrow ( eucast_rules_df ) , i + 1 ) , " reference.rule" , drop = TRUE ]
rule_group_previous <- eucast_rules_df [max ( 1 , i - 1 ) , " reference.rule_group" , drop = TRUE ]
rule_group_current <- eucast_rules_df [i , " reference.rule_group" , drop = TRUE ]
2021-04-12 12:35:13 +02:00
# don't apply rules if user doesn't want to apply them
2022-11-14 15:20:39 +01:00
if ( rule_group_current %like% " breakpoint" && ! any ( c ( " all" , " breakpoints" ) %in% rules ) ) {
2021-04-12 12:35:13 +02:00
next
}
2022-11-14 15:20:39 +01:00
if ( rule_group_current %like% " expert" && ! any ( c ( " all" , " expert" ) %in% rules ) ) {
2021-04-12 12:35:13 +02:00
next
}
2022-08-28 10:31:50 +02:00
2022-11-14 15:20:39 +01:00
if ( isFALSE ( info ) || isFALSE ( verbose ) ) {
2020-09-24 00:30:11 +02:00
rule_text <- " "
2019-04-05 18:47:39 +02:00
} else {
2020-09-24 00:30:11 +02:00
if ( is.na ( eucast_rules_df [i , " and_these_antibiotics" , drop = TRUE ] ) ) {
rule_text <- paste0 ( " always report as '" , eucast_rules_df [i , " to_value" , drop = TRUE ] , " ': " , get_antibiotic_names ( eucast_rules_df [i , " then_change_these_antibiotics" , drop = TRUE ] ) )
} else {
2022-08-28 10:31:50 +02:00
rule_text <- paste0 (
" report as '" , eucast_rules_df [i , " to_value" , drop = TRUE ] , " ' when " ,
format_antibiotic_names (
ab_names = get_antibiotic_names ( eucast_rules_df [i , " and_these_antibiotics" , drop = TRUE ] ) ,
ab_results = eucast_rules_df [i , " have_these_values" , drop = TRUE ]
) , " : " ,
get_antibiotic_names ( eucast_rules_df [i , " then_change_these_antibiotics" , drop = TRUE ] )
)
2020-09-24 00:30:11 +02:00
}
2018-10-18 12:10:10 +02:00
}
2019-04-05 18:47:39 +02:00
if ( i == 1 ) {
rule_previous <- " "
rule_group_previous <- " "
2018-10-18 12:10:10 +02:00
}
2019-04-05 18:47:39 +02:00
if ( i == nrow ( eucast_rules_df ) ) {
rule_next <- " "
2018-10-18 12:10:10 +02:00
}
2022-08-28 10:31:50 +02:00
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) ) {
2020-09-24 00:30:11 +02:00
# Print EUCAST intro ------------------------------------------------------
2022-11-14 15:20:39 +01:00
if ( rule_group_current %unlike% " other" && eucast_notification_shown == FALSE ) {
2020-12-11 12:17:23 +01:00
cat (
2022-08-28 10:31:50 +02:00
paste0 (
2022-12-17 14:31:33 +01:00
" \n" , font_grey ( strrep ( " -" , 0.95 * getOption ( " width" , 100 ) ) ) , " \n" ,
2022-08-28 10:31:50 +02:00
word_wrap ( " Rules by the " , font_bold ( " European Committee on Antimicrobial Susceptibility Testing (EUCAST)" ) ) , " \n" ,
font_blue ( " https://eucast.org/" ) , " \n"
)
)
2020-09-24 00:30:11 +02:00
eucast_notification_shown <- TRUE
}
2022-08-28 10:31:50 +02:00
2019-04-05 18:47:39 +02:00
# Print rule (group) ------------------------------------------------------
if ( rule_group_current != rule_group_previous ) {
# is new rule group, one of Breakpoints, Expert Rules and Other
2020-05-16 13:05:47 +02:00
cat ( font_bold (
ifelse (
rule_group_current %like% " breakpoint" ,
2022-08-28 10:31:50 +02:00
paste0 (
" \n" ,
word_wrap (
breakpoints_info $ title , " (" ,
font_red ( paste0 ( breakpoints_info $ version_txt , " , " , breakpoints_info $ year ) ) , " )\n"
)
) ,
2020-05-16 13:05:47 +02:00
ifelse (
rule_group_current %like% " expert" ,
2022-08-28 10:31:50 +02:00
paste0 (
" \n" ,
word_wrap (
expertrules_info $ title , " (" ,
font_red ( paste0 ( expertrules_info $ version_txt , " , " , expertrules_info $ year ) ) , " )\n"
)
) ,
" "
)
)
) , " \n" )
2019-04-05 18:47:39 +02:00
}
# Print rule -------------------------------------------------------------
if ( rule_current != rule_previous ) {
# is new rule within group, print its name
2023-01-23 15:01:21 +01:00
cat ( italicise_taxonomy (
word_wrap ( rule_current ,
width = getOption ( " width" ) - 30 ,
extra_indent = 6
) ,
type = " ansi"
2022-08-28 10:31:50 +02:00
) )
2019-04-05 18:47:39 +02:00
warned <- FALSE
}
2018-10-18 12:10:10 +02:00
}
2022-08-28 10:31:50 +02:00
2019-04-05 18:47:39 +02:00
# Get rule from file ------------------------------------------------------
2020-09-24 00:30:11 +02:00
if_mo_property <- trimws ( eucast_rules_df [i , " if_mo_property" , drop = TRUE ] )
like_is_one_of <- trimws ( eucast_rules_df [i , " like.is.one_of" , drop = TRUE ] )
mo_value <- trimws ( eucast_rules_df [i , " this_value" , drop = TRUE ] )
2022-08-28 10:31:50 +02:00
2020-11-09 13:07:02 +01:00
# be sure to comprise all coagulase-negative/-positive staphylococci when they are mentioned
2020-09-24 00:30:11 +02:00
if ( mo_value %like% " coagulase" && any ( x $ genus == " Staphylococcus" , na.rm = TRUE ) ) {
if ( mo_value %like% " negative" ) {
2022-08-28 10:31:50 +02:00
eucast_rules_df [i , " this_value" ] <- paste0 (
2023-01-23 15:01:21 +01:00
" ^(" , paste0 (
all_staph [which ( all_staph $ CNS_CPS %like% " negative" ) ,
" fullname" ,
drop = TRUE
] ,
collapse = " |"
2022-08-28 10:31:50 +02:00
) ,
" )$"
)
2019-04-05 18:47:39 +02:00
} else {
2022-08-28 10:31:50 +02:00
eucast_rules_df [i , " this_value" ] <- paste0 (
2023-01-23 15:01:21 +01:00
" ^(" , paste0 (
all_staph [which ( all_staph $ CNS_CPS %like% " positive" ) ,
" fullname" ,
drop = TRUE
] ,
collapse = " |"
2022-08-28 10:31:50 +02:00
) ,
" )$"
)
2019-04-05 18:47:39 +02:00
}
like_is_one_of <- " like"
}
2020-09-24 00:30:11 +02:00
# be sure to comprise all beta-haemolytic Streptococci (Lancefield groups A, B, C and G) when they are mentioned
if ( mo_value %like% " group [ABCG]" && any ( x $ genus == " Streptococcus" , na.rm = TRUE ) ) {
2022-08-28 10:31:50 +02:00
eucast_rules_df [i , " this_value" ] <- paste0 (
2023-01-23 15:01:21 +01:00
" ^(" , paste0 (
all_strep [which ( all_strep $ Lancefield %like% " group [ABCG]" ) ,
" fullname" ,
drop = TRUE
] ,
collapse = " |"
2022-08-28 10:31:50 +02:00
) ,
" )$"
)
2020-09-24 00:30:11 +02:00
like_is_one_of <- " like"
}
2022-08-28 10:31:50 +02:00
2019-04-05 18:47:39 +02:00
if ( like_is_one_of == " is" ) {
2020-05-18 11:09:02 +02:00
# so e.g. 'Enterococcus' will turn into '^Enterococcus$'
2020-09-24 00:30:11 +02:00
mo_value <- paste0 ( " ^" , mo_value , " $" )
2019-04-05 18:47:39 +02:00
} else if ( like_is_one_of == " one_of" ) {
2019-10-11 17:21:02 +02:00
# so 'Clostridium, Actinomyces, ...' will turn into '^(Clostridium|Actinomyces|...)$'
2022-08-28 10:31:50 +02:00
mo_value <- paste0 (
" ^(" ,
paste ( trimws ( unlist ( strsplit ( mo_value , " ," , fixed = TRUE ) ) ) ,
collapse = " |"
) ,
" )$"
)
2020-09-24 00:30:11 +02:00
} else if ( like_is_one_of != " like" ) {
2019-11-15 15:25:03 +01:00
stop ( " invalid value for column 'like.is.one_of'" , call. = FALSE )
2018-10-18 12:10:10 +02:00
}
2022-08-28 10:31:50 +02:00
2020-09-24 00:30:11 +02:00
source_antibiotics <- eucast_rules_df [i , " and_these_antibiotics" , drop = TRUE ]
source_value <- trimws ( unlist ( strsplit ( eucast_rules_df [i , " have_these_values" , drop = TRUE ] , " ," , fixed = TRUE ) ) )
target_antibiotics <- eucast_rules_df [i , " then_change_these_antibiotics" , drop = TRUE ]
target_value <- eucast_rules_df [i , " to_value" , drop = TRUE ]
2021-04-07 08:37:42 +02:00
2019-04-05 18:47:39 +02:00
if ( is.na ( source_antibiotics ) ) {
2021-02-21 20:15:09 +01:00
rows <- tryCatch ( which ( x [ , if_mo_property , drop = TRUE ] %like% mo_value ) ,
2022-08-28 10:31:50 +02:00
error = function ( e ) integer ( 0 )
)
2019-04-05 18:47:39 +02:00
} else {
2021-11-28 23:01:26 +01:00
source_antibiotics <- get_ab_from_namespace ( source_antibiotics , cols_ab )
2022-11-14 15:20:39 +01:00
if ( length ( source_value ) == 1 && length ( source_antibiotics ) > 1 ) {
2019-04-05 18:47:39 +02:00
source_value <- rep ( source_value , length ( source_antibiotics ) )
}
if ( length ( source_antibiotics ) == 0 ) {
rows <- integer ( 0 )
} else if ( length ( source_antibiotics ) == 1 ) {
2023-01-23 15:01:21 +01:00
rows <- tryCatch (
which ( x [ , if_mo_property , drop = TRUE ] %like% mo_value &
as.sir_no_warning ( x [ , source_antibiotics [1L ] ] ) == source_value [1L ] ) ,
error = function ( e ) integer ( 0 )
2022-08-28 10:31:50 +02:00
)
2019-04-05 18:47:39 +02:00
} else if ( length ( source_antibiotics ) == 2 ) {
2023-01-23 15:01:21 +01:00
rows <- tryCatch (
which ( x [ , if_mo_property , drop = TRUE ] %like% mo_value &
as.sir_no_warning ( x [ , source_antibiotics [1L ] ] ) == source_value [1L ] &
as.sir_no_warning ( x [ , source_antibiotics [2L ] ] ) == source_value [2L ] ) ,
error = function ( e ) integer ( 0 )
2022-08-28 10:31:50 +02:00
)
2020-12-13 13:44:04 +01:00
# nolint start
2022-08-28 10:31:50 +02:00
# } else if (length(source_antibiotics) == 3) {
# rows <- tryCatch(which(x[, if_mo_property, drop = TRUE] %like% mo_value
2023-01-21 23:47:20 +01:00
# & as.sir_no_warning(x[, source_antibiotics[1L]]) == source_value[1L]
# & as.sir_no_warning(x[, source_antibiotics[2L]]) == source_value[2L]
# & as.sir_no_warning(x[, source_antibiotics[3L]]) == source_value[3L]),
2022-08-28 10:31:50 +02:00
# error = function(e) integer(0))
2020-12-13 13:44:04 +01:00
# nolint end
2019-04-05 18:47:39 +02:00
} else {
2020-12-13 13:44:04 +01:00
stop_ ( " only 2 antibiotics supported for source_antibiotics" )
2019-04-05 18:47:39 +02:00
}
2018-10-18 12:10:10 +02:00
}
2022-08-28 10:31:50 +02:00
2021-11-28 23:01:26 +01:00
cols <- get_ab_from_namespace ( target_antibiotics , cols_ab )
2022-08-28 10:31:50 +02:00
2019-04-05 18:47:39 +02:00
# Apply rule on data ------------------------------------------------------
# this will return the unique number of changes
2023-01-21 23:47:20 +01:00
run_changes <- edit_sir (
2022-08-28 10:31:50 +02:00
x = x ,
to = target_value ,
rule = c (
rule_text , rule_group_current , rule_current ,
ifelse ( rule_group_current %like% " breakpoint" ,
paste0 ( breakpoints_info $ title , " " , breakpoints_info $ version_txt , " , " , breakpoints_info $ year ) ,
paste0 ( expertrules_info $ title , " " , expertrules_info $ version_txt , " , " , expertrules_info $ year )
)
) ,
rows = rows ,
cols = cols ,
last_verbose_info = verbose_info ,
original_data = x.bak ,
warned = warned ,
info = info ,
verbose = verbose
)
2020-09-24 00:30:11 +02:00
n_added <- n_added + run_changes $ added
n_changed <- n_changed + run_changes $ changed
verbose_info <- run_changes $ verbose_info
x <- run_changes $ output
2023-01-21 23:47:20 +01:00
warn_lacking_sir_class <- c ( warn_lacking_sir_class , run_changes $ sir_warn )
2019-04-05 18:47:39 +02:00
# Print number of new changes ---------------------------------------------
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) && rule_next != rule_current ) {
2019-04-05 18:47:39 +02:00
# print only on last one of rules in this group
2020-09-24 00:30:11 +02:00
txt_ok ( n_added = n_added , n_changed = n_changed , warned = warned )
2019-08-09 14:28:46 +02:00
# and reset counters
2020-09-24 00:30:11 +02:00
n_added <- 0
n_changed <- 0
2018-11-01 17:06:08 +01:00
}
2021-04-07 08:37:42 +02:00
} # end of going over all rules
# Apply custom rules ----
if ( ! is.null ( custom_rules ) ) {
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) ) {
2021-04-07 08:37:42 +02:00
cat ( " \n" )
cat ( font_bold ( " Custom EUCAST rules, set by user" ) , " \n" )
}
for ( i in seq_len ( length ( custom_rules ) ) ) {
rule <- custom_rules [ [i ] ]
rows <- which ( eval ( parse ( text = rule $ query ) , envir = x ) )
cols <- as.character ( rule $ result_group )
2022-08-28 10:31:50 +02:00
cols <- c (
cols [cols %in% colnames ( x ) ] , # direct column names
unname ( cols_ab [names ( cols_ab ) %in% cols ] )
) # based on previous cols_ab finding
2021-04-07 08:37:42 +02:00
cols <- unique ( cols )
target_value <- as.character ( rule $ result_value )
2022-08-28 10:31:50 +02:00
rule_text <- paste0 (
" report as '" , target_value , " ' when " ,
format_custom_query_rule ( rule $ query , colours = FALSE ) , " : " ,
get_antibiotic_names ( cols )
)
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) ) {
2021-04-07 08:37:42 +02:00
# print rule
2023-01-23 15:01:21 +01:00
cat ( italicise_taxonomy (
word_wrap ( format_custom_query_rule ( rule $ query , colours = FALSE ) ,
width = getOption ( " width" ) - 30 ,
extra_indent = 6
) ,
type = " ansi"
2022-08-28 10:31:50 +02:00
) )
2024-05-30 15:50:17 +02:00
cat ( " \n" )
2021-04-07 08:37:42 +02:00
warned <- FALSE
}
2023-01-21 23:47:20 +01:00
run_changes <- edit_sir (
2022-08-28 10:31:50 +02:00
x = x ,
to = target_value ,
rule = c (
rule_text ,
" Custom EUCAST rules" ,
paste0 ( " Custom EUCAST rule " , i ) ,
paste0 (
" Object '" , deparse ( substitute ( custom_rules ) ) ,
" ' consisting of " , length ( custom_rules ) , " custom rules"
)
) ,
rows = rows ,
cols = cols ,
last_verbose_info = verbose_info ,
original_data = x.bak ,
warned = warned ,
info = info ,
verbose = verbose
)
2021-04-07 08:37:42 +02:00
n_added <- n_added + run_changes $ added
n_changed <- n_changed + run_changes $ changed
verbose_info <- run_changes $ verbose_info
x <- run_changes $ output
2023-01-21 23:47:20 +01:00
warn_lacking_sir_class <- c ( warn_lacking_sir_class , run_changes $ sir_warn )
2021-04-07 08:37:42 +02:00
# Print number of new changes ---------------------------------------------
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) && rule_next != rule_current ) {
2021-04-07 08:37:42 +02:00
# print only on last one of rules in this group
txt_ok ( n_added = n_added , n_changed = n_changed , warned = warned )
# and reset counters
n_added <- 0
n_changed <- 0
}
}
2019-04-05 18:47:39 +02:00
}
2022-08-28 10:31:50 +02:00
2019-04-05 18:47:39 +02:00
# Print overview ----------------------------------------------------------
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) || isTRUE ( verbose ) ) {
2023-02-09 13:07:39 +01:00
verbose_info <- x.bak %pm>%
pm_mutate ( row = pm_row_number ( ) ) %pm>%
pm_select ( `.rowid` , row ) %pm>%
pm_right_join ( verbose_info ,
2022-08-28 10:31:50 +02:00
by = c ( " .rowid" = " rowid" )
2023-02-09 13:07:39 +01:00
) %pm>%
pm_select ( - `.rowid` ) %pm>%
pm_select ( row , pm_everything ( ) ) %pm>%
pm_filter ( ! is.na ( new ) | is.na ( new ) & ! is.na ( old ) ) %pm>%
pm_arrange ( row , rule_group , rule_name , col )
2020-09-29 10:40:25 +02:00
rownames ( verbose_info ) <- NULL
2020-10-04 19:26:43 +02:00
}
2022-08-28 10:31:50 +02:00
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) ) {
if ( isTRUE ( verbose ) ) {
2018-10-19 00:57:10 +02:00
wouldve <- " would have "
} else {
wouldve <- " "
}
2022-08-28 10:31:50 +02:00
2022-12-17 14:31:33 +01:00
cat ( paste0 ( " \n" , font_grey ( strrep ( " -" , 0.95 * getOption ( " width" , 100 ) ) ) , " \n" ) )
2022-08-28 10:31:50 +02:00
cat ( word_wrap ( paste0 (
" The rules " , paste0 ( wouldve , " affected " ) ,
font_bold (
2023-02-09 13:07:39 +01:00
formatnr ( pm_n_distinct ( verbose_info $ row ) ) ,
2022-08-28 10:31:50 +02:00
" out of" , formatnr ( nrow ( x.bak ) ) ,
" rows"
) ,
" , making a total of " ,
font_bold ( formatnr ( nrow ( verbose_info ) ) , " edits\n" )
) ) )
2023-02-09 13:07:39 +01:00
total_n_added <- verbose_info %pm>% pm_filter ( is.na ( old ) ) %pm>% nrow ( )
total_n_changed <- verbose_info %pm>% pm_filter ( ! is.na ( old ) ) %pm>% nrow ( )
2022-08-28 10:31:50 +02:00
2020-10-04 19:26:43 +02:00
# print added values
2020-09-24 00:30:11 +02:00
if ( total_n_added == 0 ) {
2019-04-05 18:47:39 +02:00
colour <- cat # is function
} else {
2020-05-16 13:05:47 +02:00
colour <- font_green # is function
2019-02-08 16:06:54 +01:00
}
2022-08-28 10:31:50 +02:00
cat ( colour ( paste0 (
" => " , wouldve , " added " ,
2023-02-09 13:07:39 +01:00
font_bold ( formatnr ( verbose_info %pm>%
pm_filter ( is.na ( old ) ) %pm>%
2022-08-28 10:31:50 +02:00
nrow ( ) ) , " test results" ) ,
" \n"
) ) )
2020-09-24 00:30:11 +02:00
if ( total_n_added > 0 ) {
2023-02-09 13:07:39 +01:00
added_summary <- verbose_info %pm>%
pm_filter ( is.na ( old ) ) %pm>%
pm_count ( new , name = " n" )
2022-08-28 10:31:50 +02:00
cat ( paste ( " -" ,
paste0 (
formatnr ( added_summary $ n ) , " test result" , ifelse ( added_summary $ n > 1 , " s" , " " ) ,
" added as " , paste0 ( ' "' , added_summary $ new , ' "' )
) ,
collapse = " \n"
) )
2019-04-05 18:47:39 +02:00
}
2022-08-28 10:31:50 +02:00
2020-09-24 00:30:11 +02:00
# print changed values
if ( total_n_changed == 0 ) {
2019-04-05 18:47:39 +02:00
colour <- cat # is function
} else {
2020-05-16 13:05:47 +02:00
colour <- font_blue # is function
2019-04-05 18:47:39 +02:00
}
2020-09-24 00:30:11 +02:00
if ( total_n_added + total_n_changed > 0 ) {
2019-05-20 12:00:18 +02:00
cat ( " \n" )
}
2022-08-28 10:31:50 +02:00
cat ( colour ( paste0 (
" => " , wouldve , " changed " ,
2023-02-09 13:07:39 +01:00
font_bold ( formatnr ( verbose_info %pm>%
pm_filter ( ! is.na ( old ) ) %pm>%
2022-08-28 10:31:50 +02:00
nrow ( ) ) , " test results" ) ,
" \n"
) ) )
2020-09-24 00:30:11 +02:00
if ( total_n_changed > 0 ) {
2023-02-09 13:07:39 +01:00
changed_summary <- verbose_info %pm>%
pm_filter ( ! is.na ( old ) ) %pm>%
pm_mutate ( new = ifelse ( is.na ( new ) , " NA" , new ) ) %pm>%
pm_count ( old , new , name = " n" )
2022-08-28 10:31:50 +02:00
cat ( paste ( " -" ,
paste0 (
formatnr ( changed_summary $ n ) , " test result" , ifelse ( changed_summary $ n > 1 , " s" , " " ) , " changed from " ,
paste0 ( ' "' , changed_summary $ old , ' "' ) , " to " , paste0 ( ' "' , changed_summary $ new , ' "' )
) ,
collapse = " \n"
) )
2019-04-05 18:47:39 +02:00
cat ( " \n" )
}
2022-08-28 10:31:50 +02:00
2022-12-17 14:31:33 +01:00
cat ( paste0 ( font_grey ( strrep ( " -" , 0.95 * getOption ( " width" , 100 ) ) ) , " \n" ) )
2022-08-28 10:31:50 +02:00
2022-11-14 15:20:39 +01:00
if ( isFALSE ( verbose ) && total_n_added + total_n_changed > 0 ) {
2021-05-13 15:56:12 +02:00
cat ( " \n" , word_wrap ( " Use " , font_bold ( " eucast_rules(..., verbose = TRUE)" ) , " (on your original data) to get a data.frame with all specified edits instead." ) , " \n\n" , sep = " " )
2022-11-14 15:20:39 +01:00
} else if ( isTRUE ( verbose ) ) {
2021-05-13 15:56:12 +02:00
cat ( " \n" , word_wrap ( " Used 'Verbose mode' (" , font_bold ( " verbose = TRUE" ) , " ), which returns a data.frame with all specified edits.\nUse " , font_bold ( " verbose = FALSE" ) , " to apply the rules on your data." ) , " \n\n" , sep = " " )
2019-03-28 21:33:28 +01:00
}
2018-10-17 17:32:34 +02:00
}
2022-08-28 10:31:50 +02:00
2023-01-21 23:47:20 +01:00
if ( length ( warn_lacking_sir_class ) > 0 ) {
warn_lacking_sir_class <- unique ( warn_lacking_sir_class )
2021-05-13 15:56:12 +02:00
# take order from original data set
2023-01-21 23:47:20 +01:00
warn_lacking_sir_class <- warn_lacking_sir_class [order ( colnames ( x.bak ) ) ]
warn_lacking_sir_class <- warn_lacking_sir_class [ ! is.na ( warn_lacking_sir_class ) ]
2022-08-28 10:31:50 +02:00
warning_ (
2023-01-21 23:47:20 +01:00
" in `eucast_rules()`: not all columns with antimicrobial results are of class 'sir'. Transform them on beforehand, with e.g.:\n" ,
" - " , x_deparsed , " %>% as.sir(" , ifelse ( length ( warn_lacking_sir_class ) == 1 ,
warn_lacking_sir_class ,
paste0 ( warn_lacking_sir_class [1 ] , " :" , warn_lacking_sir_class [length ( warn_lacking_sir_class ) ] )
2022-08-28 10:31:50 +02:00
) , " )\n" ,
2023-01-21 23:47:20 +01:00
" - " , x_deparsed , " %>% mutate_if(is_sir_eligible, as.sir)\n" ,
" - " , x_deparsed , " %>% mutate(across(where(is_sir_eligible), as.sir))"
2022-08-28 10:31:50 +02:00
)
2020-05-27 16:37:49 +02:00
}
2022-08-28 10:31:50 +02:00
2019-04-05 18:47:39 +02:00
# Return data set ---------------------------------------------------------
2022-11-14 15:20:39 +01:00
if ( isTRUE ( verbose ) ) {
2023-01-05 14:43:18 +01:00
as_original_data_class ( verbose_info , old_attributes $ class ) # will remove tibble groups
2019-04-05 18:47:39 +02:00
} else {
2020-09-24 00:30:11 +02:00
# x was analysed with only unique rows, so join everything together again
x <- x [ , c ( cols_ab , " .rowid" ) , drop = FALSE ]
x.bak <- x.bak [ , setdiff ( colnames ( x.bak ) , cols_ab ) , drop = FALSE ]
2023-02-09 13:07:39 +01:00
x.bak <- x.bak %pm>%
pm_left_join ( x , by = " .rowid" )
2020-09-24 00:30:11 +02:00
x.bak <- x.bak [ , old_cols , drop = FALSE ]
2023-01-05 14:43:18 +01:00
# reset original attributes
2020-09-24 00:30:11 +02:00
attributes ( x.bak ) <- old_attributes
2023-01-05 14:43:18 +01:00
x.bak <- as_original_data_class ( x.bak , old_class = class ( x.bak ) ) # will remove tibble groups
2020-09-24 00:30:11 +02:00
x.bak
}
}
2021-05-13 15:56:12 +02:00
# helper function for editing the table ----
2023-01-21 23:47:20 +01:00
edit_sir <- function ( x ,
2021-04-23 09:59:36 +02:00
to ,
rule ,
2020-09-24 00:30:11 +02:00
rows ,
cols ,
2021-04-23 09:59:36 +02:00
last_verbose_info ,
2020-09-24 00:30:11 +02:00
original_data ,
warned ,
2021-04-07 08:37:42 +02:00
info ,
verbose ) {
2020-09-24 00:30:11 +02:00
cols <- unique ( cols [ ! is.na ( cols ) & ! is.null ( cols ) ] )
2022-08-28 10:31:50 +02:00
2020-09-24 00:30:11 +02:00
# for Verbose Mode, keep track of all changes and return them
2022-08-28 10:31:50 +02:00
track_changes <- list (
added = 0 ,
changed = 0 ,
output = x ,
verbose_info = last_verbose_info ,
2023-01-21 23:47:20 +01:00
sir_warn = character ( 0 )
2022-08-28 10:31:50 +02:00
)
2020-09-24 00:30:11 +02:00
txt_error <- function ( ) {
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) ) cat ( " " , font_red_bg ( " ERROR " ) , " \n\n" )
2020-09-24 00:30:11 +02:00
}
txt_warning <- function ( ) {
if ( warned == FALSE ) {
2022-11-14 15:20:39 +01:00
if ( isTRUE ( info ) ) cat ( " " , font_orange_bg ( " WARNING " ) , sep = " " )
2020-09-24 00:30:11 +02:00
}
2022-08-28 10:31:50 +02:00
warned <<- TRUE
2020-09-24 00:30:11 +02:00
}
2022-08-28 10:31:50 +02:00
2022-11-14 15:20:39 +01:00
if ( length ( rows ) > 0 && length ( cols ) > 0 ) {
2020-09-24 00:30:11 +02:00
new_edits <- x
2023-01-21 23:47:20 +01:00
if ( any ( ! vapply ( FUN.VALUE = logical ( 1 ) , x [ , cols , drop = FALSE ] , is.sir ) , na.rm = TRUE ) ) {
track_changes $ sir_warn <- cols [ ! vapply ( FUN.VALUE = logical ( 1 ) , x [ , cols , drop = FALSE ] , is.sir ) ]
2020-09-24 00:30:11 +02:00
}
tryCatch (
# insert into original table
new_edits [rows , cols ] <- to ,
warning = function ( w ) {
2021-05-13 15:56:12 +02:00
if ( w $ message %like% " invalid factor level" ) {
2020-12-28 22:24:33 +01:00
xyz <- vapply ( FUN.VALUE = logical ( 1 ) , cols , function ( col ) {
2022-08-28 10:31:50 +02:00
new_edits [ , col ] <<- factor (
2023-02-09 13:07:39 +01:00
x = as.character ( pm_pull ( new_edits , col ) ) ,
levels = unique ( c ( to , levels ( pm_pull ( new_edits , col ) ) ) )
2022-08-28 10:31:50 +02:00
)
2020-12-28 22:24:33 +01:00
TRUE
2020-09-24 00:30:11 +02:00
} )
2020-11-10 16:35:56 +01:00
suppressWarnings ( new_edits [rows , cols ] <<- to )
2022-08-28 10:31:50 +02:00
warning_ (
" in `eucast_rules()`: value \"" , to , " \" added to the factor levels of column" ,
ifelse ( length ( cols ) == 1 , " " , " s" ) ,
" " , vector_and ( cols , quotes = " `" , sort = FALSE ) ,
" because this value was not an existing factor level."
)
2020-09-24 00:30:11 +02:00
txt_warning ( )
warned <- FALSE
} else {
2022-03-02 15:38:55 +01:00
warning_ ( " in `eucast_rules()`: " , w $ message )
2020-09-24 00:30:11 +02:00
txt_warning ( )
}
} ,
error = function ( e ) {
txt_error ( )
2023-01-23 15:01:21 +01:00
stop (
paste0 (
" In row(s) " , paste ( rows [seq_len ( min ( length ( rows ) , 10 ) ) ] , collapse = " ," ) ,
ifelse ( length ( rows ) > 10 , " ..." , " " ) ,
" while writing value '" , to ,
" ' to column(s) `" , paste ( cols , collapse = " `, `" ) ,
" `:\n" , e $ message
) ,
call. = FALSE
2022-08-28 10:31:50 +02:00
)
2020-09-24 00:30:11 +02:00
}
)
2022-08-28 10:31:50 +02:00
2020-09-24 00:30:11 +02:00
track_changes $ output <- new_edits
2022-11-14 15:20:39 +01:00
if ( ( isTRUE ( info ) || isTRUE ( verbose ) ) && ! isTRUE ( all.equal ( x , track_changes $ output ) ) ) {
2020-09-24 00:30:11 +02:00
get_original_rows <- function ( rowids ) {
as.integer ( rownames ( original_data [which ( original_data $ .rowid %in% rowids ) , , drop = FALSE ] ) )
}
for ( i in seq_len ( length ( cols ) ) ) {
2022-08-28 10:31:50 +02:00
verbose_new <- data.frame (
rowid = new_edits [rows , " .rowid" , drop = TRUE ] ,
col = cols [i ] ,
mo_fullname = new_edits [rows , " fullname" , drop = TRUE ] ,
old = x [rows , cols [i ] , drop = TRUE ] ,
new = to ,
rule = font_stripstyle ( rule [1 ] ) ,
rule_group = font_stripstyle ( rule [2 ] ) ,
rule_name = font_stripstyle ( rule [3 ] ) ,
rule_source = font_stripstyle ( rule [4 ] ) ,
stringsAsFactors = FALSE
)
colnames ( verbose_new ) <- c (
" rowid" , " col" , " mo_fullname" , " old" , " new" ,
" rule" , " rule_group" , " rule_name" , " rule_source"
)
2023-02-09 13:07:39 +01:00
verbose_new <- verbose_new %pm>% pm_filter ( old != new | is.na ( old ) | is.na ( new ) & ! is.na ( old ) )
2020-09-24 00:30:11 +02:00
# save changes to data set 'verbose_info'
2023-03-12 13:02:37 +01:00
track_changes $ verbose_info <- rbind_AMR (
2023-02-12 17:10:48 +01:00
track_changes $ verbose_info ,
verbose_new
)
2020-09-24 00:30:11 +02:00
# count adds and changes
2023-02-09 13:07:39 +01:00
track_changes $ added <- track_changes $ added + verbose_new %pm>%
pm_filter ( is.na ( old ) ) %pm>%
pm_pull ( rowid ) %pm>%
get_original_rows ( ) %pm>%
2020-09-24 00:30:11 +02:00
length ( )
2023-02-09 13:07:39 +01:00
track_changes $ changed <- track_changes $ changed + verbose_new %pm>%
pm_filter ( ! is.na ( old ) ) %pm>%
pm_pull ( rowid ) %pm>%
get_original_rows ( ) %pm>%
2020-09-24 00:30:11 +02:00
length ( )
}
}
2018-02-21 11:52:31 +01:00
}
2020-09-24 00:30:11 +02:00
return ( track_changes )
2018-02-21 11:52:31 +01:00
}
2021-01-12 22:08:04 +01:00
#' @rdname eucast_rules
#' @export
2023-07-08 17:30:05 +02:00
eucast_dosage <- function ( ab , administration = " iv" , version_breakpoints = 12.0 ) {
2021-01-14 14:41:44 +01:00
meet_criteria ( ab , allow_class = c ( " character" , " numeric" , " integer" , " factor" ) )
2021-01-25 21:58:00 +01:00
meet_criteria ( administration , allow_class = " character" , is_in = dosage $ administration [ ! is.na ( dosage $ administration ) ] , has_length = 1 )
2021-01-14 14:41:44 +01:00
meet_criteria ( version_breakpoints , allow_class = c ( " numeric" , " integer" ) , has_length = 1 , is_in = as.double ( names ( EUCAST_VERSION_BREAKPOINTS ) ) )
2022-08-28 10:31:50 +02:00
2022-10-05 09:12:22 +02:00
# show used version_breakpoints number once per session (AMR_env will reload every session)
2022-08-28 10:31:50 +02:00
if ( message_not_thrown_before ( " eucast_dosage" , " v" , gsub ( " [^0-9]" , " " , version_breakpoints ) , entire_session = TRUE ) ) {
message_ (
" Dosages for antimicrobial drugs, as meant for " ,
format_eucast_version_nr ( version_breakpoints , markdown = FALSE ) , " . " ,
font_red ( " This note will be shown once per session." )
)
2021-01-12 22:08:04 +01:00
}
2022-08-28 10:31:50 +02:00
2021-01-12 22:08:04 +01:00
ab <- as.ab ( ab )
2021-01-25 21:58:00 +01:00
lst <- vector ( " list" , length = length ( ab ) )
for ( i in seq_len ( length ( ab ) ) ) {
df <- AMR :: dosage [which ( AMR :: dosage $ ab == ab [i ] & AMR :: dosage $ administration == administration ) , , drop = FALSE ]
2022-08-28 10:31:50 +02:00
lst [ [i ] ] <- list (
ab = " " ,
name = " " ,
standard_dosage = ifelse ( " standard_dosage" %in% df $ type ,
df [which ( df $ type == " standard_dosage" ) , " original_txt" , drop = TRUE ] ,
NA_character_
) ,
high_dosage = ifelse ( " high_dosage" %in% df $ type ,
df [which ( df $ type == " high_dosage" ) , " original_txt" , drop = TRUE ] ,
NA_character_
)
)
2021-01-25 21:58:00 +01:00
}
2023-03-12 13:02:37 +01:00
out <- do.call ( rbind_AMR , lapply ( lst , as.data.frame , stringsAsFactors = FALSE ) )
2021-01-25 21:58:00 +01:00
rownames ( out ) <- NULL
out $ ab <- ab
out $ name <- ab_name ( ab , language = NULL )
2023-02-18 14:56:06 +01:00
if ( pkg_is_available ( " tibble" ) ) {
2022-08-27 20:49:37 +02:00
import_fn ( " as_tibble" , " tibble" ) ( out )
} else {
out
}
2021-01-12 22:08:04 +01:00
}