2018-02-21 11:52:31 +01:00
# ==================================================================== #
# TITLE #
# Antimicrobial Resistance (AMR) Analysis #
# #
2019-01-02 23:24:07 +01:00
# SOURCE #
# https://gitlab.com/msberends/AMR #
2018-02-21 11:52:31 +01:00
# #
# LICENCE #
2020-01-05 17:22:09 +01:00
# (c) 2018-2020 Berends MS, Luz CF et al. #
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. #
2019-04-05 18:47:39 +02:00
# Visit our website for more info: https://msberends.gitlab.io/AMR. #
2018-02-21 11:52:31 +01:00
# ==================================================================== #
2019-04-05 18:47:39 +02:00
# global variables
2020-02-17 14:38:01 +01:00
EUCAST_VERSION_BREAKPOINTS <- " 10.0, 2020"
2019-04-05 18:47:39 +02:00
EUCAST_VERSION_EXPERT_RULES <- " 3.1, 2016"
2020-01-26 20:20:00 +01:00
#' Apply EUCAST rules
2019-11-15 15:25:03 +01:00
#'
#' @description
2020-05-20 12:00:17 +02:00
#' Apply susceptibility rules as defined by the European Committee on Antimicrobial Susceptibility Testing (EUCAST, <http://eucast.org>), see *Source*. This includes (1) expert rules and intrinsic resistance and (2) inferred resistance as defined in their breakpoint tables.
2019-11-15 15:25:03 +01:00
#'
#' To improve the interpretation of the antibiogram before EUCAST rules are applied, some non-EUCAST rules are applied at default, see Details.
2020-01-05 17:22:09 +01:00
#' @inheritSection lifecycle Maturing lifecycle
2019-11-28 22:32:17 +01:00
#' @param x data with antibiotic columns, like e.g. `AMX` and `AMC`
2018-02-21 11:52:31 +01:00
#' @param info print progress
2020-05-20 12:00:17 +02:00
#' @param rules a character vector that specifies which rules should be applied. Must be one or more of `"breakpoints"`, `"expert"`, `"other"`, `"all"`, and defaults to `c("breakpoints", "expert")`. The default value can be set to another value using e.g. `options(AMR.eucast_rules = "all")`.
2019-08-09 14:28:46 +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.
2019-11-28 22:32:17 +01:00
#' @param ... column name of an antibiotic, please see section *Antibiotics* below
2018-11-01 20:50:10 +01:00
#' @inheritParams first_isolate
2019-04-05 18:47:39 +02:00
#' @details
2019-11-28 22:32:17 +01:00
#' **Note:** This function does not translate MIC values to RSI values. Use [as.rsi()] for that. \cr
#' **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.
2019-04-09 14:59:17 +02:00
#'
2020-05-20 12:00:17 +02:00
#' Before further processing, some non-EUCAST rules can be applied to improve the efficacy of the EUCAST rules. These non-EUCAST rules, that are then applied to all isolates, are:
2019-11-28 22:32:17 +01:00
#' - Inherit amoxicillin (AMX) from ampicillin (AMP), where amoxicillin (AMX) is unavailable;
#' - Inherit ampicillin (AMP) from amoxicillin (AMX), where ampicillin (AMP) is unavailable;
#' - Set amoxicillin (AMX) = R where amoxicillin/clavulanic acid (AMC) = R;
#' - Set piperacillin (PIP) = R where piperacillin/tazobactam (TZP) = R;
#' - Set trimethoprim (TMP) = R where trimethoprim/sulfamethoxazole (SXT) = R;
#' - Set amoxicillin/clavulanic acid (AMC) = S where amoxicillin (AMX) = S;
#' - Set piperacillin/tazobactam (TZP) = S where piperacillin (PIP) = S;
#' - Set trimethoprim/sulfamethoxazole (SXT) = S where trimethoprim (TMP) = S.
2020-01-26 20:20:00 +01:00
#'
2020-05-20 12:00:17 +02:00
#' These rules are not applied at default, since they are not approved by EUCAST. To use these rules, please use `eucast_rules(..., rules = "all")`, or set the default behaviour of the `[eucast_rules()]` function with `options(AMR.eucast_rules = "all")` (or any other valid input value(s) to the `rules` parameter).
2019-11-15 15:25:03 +01:00
#'
2019-11-28 22:32:17 +01:00
#' The file containing all EUCAST rules is located here: <https://gitlab.com/msberends/AMR/blob/master/data-raw/eucast_rules.tsv>.
2019-04-05 18:47:39 +02:00
#'
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
#'
2019-11-28 22:32:17 +01:00
#' The following antibiotics are used for the functions [eucast_rules()] and [mdro()]. These are shown below in the format '**antimicrobial ID**: name ([ATC code](https://www.whocc.no/atc/structure_and_principles/))', sorted by name:
2018-07-26 16:30:42 +02:00
#'
2019-11-28 22:32:17 +01:00
#' **AMK**: amikacin ([J01GB06](https://www.whocc.no/atc_ddd_index/?code=J01GB06)),
#' **AMX**: amoxicillin ([J01CA04](https://www.whocc.no/atc_ddd_index/?code=J01CA04)),
#' **AMC**: amoxicillin/clavulanic acid ([J01CR02](https://www.whocc.no/atc_ddd_index/?code=J01CR02)),
#' **AMP**: ampicillin ([J01CA01](https://www.whocc.no/atc_ddd_index/?code=J01CA01)),
#' **SAM**: ampicillin/sulbactam ([J01CR01](https://www.whocc.no/atc_ddd_index/?code=J01CR01)),
#' **AZM**: azithromycin ([J01FA10](https://www.whocc.no/atc_ddd_index/?code=J01FA10)),
#' **AZL**: azlocillin ([J01CA09](https://www.whocc.no/atc_ddd_index/?code=J01CA09)),
#' **ATM**: aztreonam ([J01DF01](https://www.whocc.no/atc_ddd_index/?code=J01DF01)),
#' **CAP**: capreomycin ([J04AB30](https://www.whocc.no/atc_ddd_index/?code=J04AB30)),
#' **RID**: cefaloridine ([J01DB02](https://www.whocc.no/atc_ddd_index/?code=J01DB02)),
#' **CZO**: cefazolin ([J01DB04](https://www.whocc.no/atc_ddd_index/?code=J01DB04)),
#' **FEP**: cefepime ([J01DE01](https://www.whocc.no/atc_ddd_index/?code=J01DE01)),
#' **CTX**: cefotaxime ([J01DD01](https://www.whocc.no/atc_ddd_index/?code=J01DD01)),
#' **CTT**: cefotetan ([J01DC05](https://www.whocc.no/atc_ddd_index/?code=J01DC05)),
#' **FOX**: cefoxitin ([J01DC01](https://www.whocc.no/atc_ddd_index/?code=J01DC01)),
#' **CPT**: ceftaroline ([J01DI02](https://www.whocc.no/atc_ddd_index/?code=J01DI02)),
#' **CAZ**: ceftazidime ([J01DD02](https://www.whocc.no/atc_ddd_index/?code=J01DD02)),
#' **CRO**: ceftriaxone ([J01DD04](https://www.whocc.no/atc_ddd_index/?code=J01DD04)),
#' **CXM**: cefuroxime ([J01DC02](https://www.whocc.no/atc_ddd_index/?code=J01DC02)),
#' **CED**: cephradine ([J01DB09](https://www.whocc.no/atc_ddd_index/?code=J01DB09)),
#' **CHL**: chloramphenicol ([J01BA01](https://www.whocc.no/atc_ddd_index/?code=J01BA01)),
#' **CIP**: ciprofloxacin ([J01MA02](https://www.whocc.no/atc_ddd_index/?code=J01MA02)),
#' **CLR**: clarithromycin ([J01FA09](https://www.whocc.no/atc_ddd_index/?code=J01FA09)),
#' **CLI**: clindamycin ([J01FF01](https://www.whocc.no/atc_ddd_index/?code=J01FF01)),
#' **COL**: colistin ([J01XB01](https://www.whocc.no/atc_ddd_index/?code=J01XB01)),
#' **DAP**: daptomycin ([J01XX09](https://www.whocc.no/atc_ddd_index/?code=J01XX09)),
#' **DOR**: doripenem ([J01DH04](https://www.whocc.no/atc_ddd_index/?code=J01DH04)),
#' **DOX**: doxycycline ([J01AA02](https://www.whocc.no/atc_ddd_index/?code=J01AA02)),
#' **ETP**: ertapenem ([J01DH03](https://www.whocc.no/atc_ddd_index/?code=J01DH03)),
#' **ERY**: erythromycin ([J01FA01](https://www.whocc.no/atc_ddd_index/?code=J01FA01)),
#' **ETH**: ethambutol ([J04AK02](https://www.whocc.no/atc_ddd_index/?code=J04AK02)),
#' **FLC**: flucloxacillin ([J01CF05](https://www.whocc.no/atc_ddd_index/?code=J01CF05)),
#' **FOS**: fosfomycin ([J01XX01](https://www.whocc.no/atc_ddd_index/?code=J01XX01)),
#' **FUS**: fusidic acid ([J01XC01](https://www.whocc.no/atc_ddd_index/?code=J01XC01)),
#' **GAT**: gatifloxacin ([J01MA16](https://www.whocc.no/atc_ddd_index/?code=J01MA16)),
#' **GEN**: gentamicin ([J01GB03](https://www.whocc.no/atc_ddd_index/?code=J01GB03)),
#' **GEH**: gentamicin-high (no ATC code),
#' **IPM**: imipenem ([J01DH51](https://www.whocc.no/atc_ddd_index/?code=J01DH51)),
#' **INH**: isoniazid ([J04AC01](https://www.whocc.no/atc_ddd_index/?code=J04AC01)),
#' **KAN**: kanamycin ([J01GB04](https://www.whocc.no/atc_ddd_index/?code=J01GB04)),
#' **LVX**: levofloxacin ([J01MA12](https://www.whocc.no/atc_ddd_index/?code=J01MA12)),
#' **LIN**: lincomycin ([J01FF02](https://www.whocc.no/atc_ddd_index/?code=J01FF02)),
#' **LNZ**: linezolid ([J01XX08](https://www.whocc.no/atc_ddd_index/?code=J01XX08)),
#' **MEM**: meropenem ([J01DH02](https://www.whocc.no/atc_ddd_index/?code=J01DH02)),
#' **MTR**: metronidazole ([J01XD01](https://www.whocc.no/atc_ddd_index/?code=J01XD01)),
#' **MEZ**: mezlocillin ([J01CA10](https://www.whocc.no/atc_ddd_index/?code=J01CA10)),
#' **MNO**: minocycline ([J01AA08](https://www.whocc.no/atc_ddd_index/?code=J01AA08)),
#' **MFX**: moxifloxacin ([J01MA14](https://www.whocc.no/atc_ddd_index/?code=J01MA14)),
#' **NAL**: nalidixic acid ([J01MB02](https://www.whocc.no/atc_ddd_index/?code=J01MB02)),
#' **NEO**: neomycin ([J01GB05](https://www.whocc.no/atc_ddd_index/?code=J01GB05)),
#' **NET**: netilmicin ([J01GB07](https://www.whocc.no/atc_ddd_index/?code=J01GB07)),
#' **NIT**: nitrofurantoin ([J01XE01](https://www.whocc.no/atc_ddd_index/?code=J01XE01)),
#' **NOR**: norfloxacin ([J01MA06](https://www.whocc.no/atc_ddd_index/?code=J01MA06)),
#' **NOV**: novobiocin ([QJ01XX95](https://www.whocc.no/atc_ddd_index/?code=QJ01XX95)),
#' **OFX**: ofloxacin ([J01MA01](https://www.whocc.no/atc_ddd_index/?code=J01MA01)),
#' **OXA**: oxacillin ([J01CF04](https://www.whocc.no/atc_ddd_index/?code=J01CF04)),
#' **PEN**: penicillin G ([J01CE01](https://www.whocc.no/atc_ddd_index/?code=J01CE01)),
#' **PIP**: piperacillin ([J01CA12](https://www.whocc.no/atc_ddd_index/?code=J01CA12)),
#' **TZP**: piperacillin/tazobactam ([J01CR05](https://www.whocc.no/atc_ddd_index/?code=J01CR05)),
#' **PLB**: polymyxin B ([J01XB02](https://www.whocc.no/atc_ddd_index/?code=J01XB02)),
#' **PRI**: pristinamycin ([J01FG01](https://www.whocc.no/atc_ddd_index/?code=J01FG01)),
#' **PZA**: pyrazinamide ([J04AK01](https://www.whocc.no/atc_ddd_index/?code=J04AK01)),
#' **QDA**: quinupristin/dalfopristin ([J01FG02](https://www.whocc.no/atc_ddd_index/?code=J01FG02)),
#' **RIB**: rifabutin ([J04AB04](https://www.whocc.no/atc_ddd_index/?code=J04AB04)),
#' **RIF**: rifampicin ([J04AB02](https://www.whocc.no/atc_ddd_index/?code=J04AB02)),
#' **RFP**: rifapentine ([J04AB05](https://www.whocc.no/atc_ddd_index/?code=J04AB05)),
#' **RXT**: roxithromycin ([J01FA06](https://www.whocc.no/atc_ddd_index/?code=J01FA06)),
#' **SIS**: sisomicin ([J01GB08](https://www.whocc.no/atc_ddd_index/?code=J01GB08)),
#' **STH**: streptomycin-high (no ATC code),
#' **TEC**: teicoplanin ([J01XA02](https://www.whocc.no/atc_ddd_index/?code=J01XA02)),
#' **TLV**: telavancin ([J01XA03](https://www.whocc.no/atc_ddd_index/?code=J01XA03)),
#' **TCY**: tetracycline ([J01AA07](https://www.whocc.no/atc_ddd_index/?code=J01AA07)),
#' **TIC**: ticarcillin ([J01CA13](https://www.whocc.no/atc_ddd_index/?code=J01CA13)),
#' **TCC**: ticarcillin/clavulanic acid ([J01CR03](https://www.whocc.no/atc_ddd_index/?code=J01CR03)),
#' **TGC**: tigecycline ([J01AA12](https://www.whocc.no/atc_ddd_index/?code=J01AA12)),
#' **TOB**: tobramycin ([J01GB01](https://www.whocc.no/atc_ddd_index/?code=J01GB01)),
#' **TMP**: trimethoprim ([J01EA01](https://www.whocc.no/atc_ddd_index/?code=J01EA01)),
#' **SXT**: trimethoprim/sulfamethoxazole ([J01EE01](https://www.whocc.no/atc_ddd_index/?code=J01EE01)),
#' **VAN**: vancomycin ([J01XA01](https://www.whocc.no/atc_ddd_index/?code=J01XA01)).
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
2019-11-28 22:32:17 +01: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
2019-11-28 22:32:17 +01:00
#' - EUCAST Expert Rules. Version 2.0, 2012. \cr
#' Leclercq et al. **EUCAST expert rules in antimicrobial susceptibility testing.** *Clin Microbiol Infect.* 2013;19(2):141-60. \cr
#' <https://doi.org/10.1111/j.1469-0691.2011.03703.x>
#' - EUCAST Expert Rules, Intrinsic Resistance and Exceptional Phenotypes Tables. Version 3.1, 2016. \cr
#' <http://www.eucast.org/fileadmin/src/media/PDFs/EUCAST_files/Expert_Rules/Expert_rules_intrinsic_exceptional_V3.1.pdf>
#' - EUCAST Breakpoint tables for interpretation of MICs and zone diameters. Version 9.0, 2019. \cr
#' <http://www.eucast.org/fileadmin/src/media/PDFs/EUCAST_files/Breakpoint_tables/v_9.0_Breakpoint_Tables.xlsx>
2019-01-02 23:24:07 +01:00
#' @inheritSection AMR Read more on our website!
2018-02-21 11:52:31 +01:00
#' @examples
2019-11-18 12:10:47 +01:00
#' \donttest{
2018-10-09 13:53:33 +02:00
#' a <- data.frame(mo = c("Staphylococcus aureus",
#' "Enterococcus faecalis",
#' "Escherichia coli",
#' "Klebsiella pneumoniae",
#' "Pseudomonas aeruginosa"),
2019-05-10 16:44:59 +02:00
#' VAN = "-", # Vancomycin
#' AMX = "-", # Amoxicillin
#' COL = "-", # Colistin
#' CAZ = "-", # Ceftazidime
#' CXM = "-", # Cefuroxime
#' PEN = "S", # Penicillin G
#' FOX = "S", # Cefoxitin
2018-02-22 21:37:10 +01:00
#' stringsAsFactors = FALSE)
2018-10-18 12:10:10 +02:00
#'
2018-02-22 21:37:10 +01:00
#' a
2019-05-10 16:44:59 +02:00
#' # mo VAN AMX COL CAZ CXM PEN FOX
2018-10-18 12:10:10 +02:00
#' # 1 Staphylococcus aureus - - - - - S S
#' # 2 Enterococcus faecalis - - - - - S S
#' # 3 Escherichia coli - - - - - S S
#' # 4 Klebsiella pneumoniae - - - - - S S
#' # 5 Pseudomonas aeruginosa - - - - - S S
#'
2019-02-08 16:06:54 +01:00
#'
#' # apply EUCAST rules: 18 results are forced as R or S
#' b <- eucast_rules(a)
2018-04-02 16:05:09 +02:00
#'
2018-02-22 21:37:10 +01:00
#' b
2019-05-10 16:44:59 +02:00
#' # mo VAN AMX COL CAZ CXM PEN FOX
2018-10-18 12:10:10 +02:00
#' # 1 Staphylococcus aureus - S R R S S S
#' # 2 Enterococcus faecalis - - R R R S R
#' # 3 Escherichia coli R - - - - R S
#' # 4 Klebsiella pneumoniae R R - - - R S
#' # 5 Pseudomonas aeruginosa R R - - R R R
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
2019-02-08 16:06:54 +01:00
#' # with 18 rows, containing all details about the transformations:
#' c <- eucast_rules(a, verbose = TRUE)
2019-08-09 14:28:46 +02:00
#' }
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-05-20 12:00:17 +02:00
rules = getOption ( " AMR.eucast_rules" , default = c ( " breakpoints" , " expert" ) ) ,
2018-11-01 20:23:33 +01:00
verbose = FALSE ,
2019-04-05 18:47:39 +02:00
... ) {
2019-08-06 14:39:22 +02:00
2020-02-14 19:54:13 +01:00
check_dataset_integrity ( )
2019-08-06 14:39:22 +02:00
if ( verbose == TRUE & interactive ( ) ) {
2019-08-09 14:28:46 +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." ,
2019-08-06 14:39:22 +02:00
" \n\nThis may overwrite your existing data if you use e.g.:" ,
" \ndata <- eucast_rules(data, verbose = TRUE)\n\nDo you want to continue?" )
2019-10-06 21:44:08 +02:00
if ( " rstudioapi" %in% rownames ( utils :: installed.packages ( ) ) ) {
2020-05-16 21:40:50 +02:00
showQuestion <- get ( " showQuestion" , envir = asNamespace ( " rstudioapi" ) )
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 ) ) {
2019-08-20 11:40:54 +02:00
message ( " Cancelled, returning original data" )
return ( x )
2019-08-06 14:39:22 +02:00
}
}
2019-05-23 16:58:59 +02:00
if ( ! is.data.frame ( x ) ) {
2019-05-10 16:44:59 +02:00
stop ( " `x` must be a data frame." , call. = FALSE )
2018-11-01 20:23:33 +01:00
}
2019-08-09 14:28:46 +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 ) ) {
2019-05-23 16:58:59 +02:00
col_mo <- search_type_in_df ( x = x , type = " mo" )
2018-12-22 22:39:34 +01:00
}
if ( is.null ( col_mo ) ) {
stop ( " `col_mo` must be set." , call. = FALSE )
2018-02-21 11:52:31 +01:00
}
2019-08-09 14:28:46 +02:00
2018-10-18 12:10:10 +02:00
if ( ! all ( rules %in% c ( " breakpoints" , " expert" , " other" , " all" ) ) ) {
2020-05-20 12:00:17 +02:00
stop ( ' `rules` must be one or more of: "breakpoints", "expert", "other", "all".' )
2018-10-18 12:10:10 +02:00
}
2019-08-09 14:28:46 +02:00
2018-11-01 20:23:33 +01:00
if ( is.null ( col_mo ) ) {
2019-04-05 18:47:39 +02:00
stop ( " `col_mo` must be set" )
2018-11-01 20:23:33 +01:00
}
2019-08-09 14:28:46 +02:00
2019-06-07 22:47:37 +02:00
decimal.mark <- getOption ( " OutDec" )
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
}
2019-08-09 14:28:46 +02:00
2018-10-17 17:32:34 +02:00
warned <- FALSE
2020-05-27 16:37:49 +02:00
warn_lacking_rsi_class <- FALSE
2019-08-09 14:28:46 +02:00
2019-10-11 17:21:02 +02:00
txt_error <- function ( ) {
2020-05-16 13:05:47 +02:00
if ( info == TRUE ) cat ( " " , font_red_bg ( font_white ( " ERROR " ) ) , " \n\n" )
2019-10-11 17:21:02 +02:00
}
txt_warning <- function ( ) {
if ( warned == FALSE ) {
2020-05-16 13:05:47 +02:00
if ( info == TRUE ) cat ( " " , font_yellow_bg ( font_black ( " WARNING " ) ) )
2019-10-11 17:21:02 +02:00
}
warned <<- TRUE
}
2019-08-09 14:28:46 +02:00
txt_ok <- function ( no_added , no_changed ) {
2018-10-17 17:32:34 +02:00
if ( warned == FALSE ) {
2019-08-09 14:28:46 +02:00
if ( no_added + no_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
2020-05-16 13:05:47 +02:00
cat ( font_grey ( " (" ) )
2019-08-09 14:28:46 +02:00
# additions
if ( no_added > 0 ) {
if ( no_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-05-16 13:05:47 +02:00
cat ( font_green ( formatnr ( no_added ) , " values added" ) )
2019-08-09 14:28:46 +02:00
}
}
# separator
if ( no_added > 0 & no_changed > 0 ) {
2020-05-16 13:05:47 +02:00
cat ( font_grey ( " , " ) )
2019-08-09 14:28:46 +02:00
}
# changes
if ( no_changed > 0 ) {
if ( no_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-05-16 13:05:47 +02:00
cat ( font_blue ( formatnr ( no_changed ) , " values changed" ) )
2019-08-09 14:28:46 +02:00
}
}
# closing
2020-05-16 13:05:47 +02:00
cat ( font_grey ( " )\n" ) )
2018-10-17 17:32:34 +02:00
}
warned <<- FALSE
}
}
2019-08-09 14:28:46 +02:00
2019-05-20 12:00:18 +02:00
cols_ab <- get_column_abx ( x = x ,
2019-05-10 16:44:59 +02:00
soft_dependencies = c ( " AMC" ,
" AMK" ,
" AMX" ,
" AMP" ,
" AZM" ,
" AZL" ,
" ATM" ,
" RID" ,
" FEP" ,
" CTX" ,
" FOX" ,
" CED" ,
" CAZ" ,
" CRO" ,
" CXM" ,
" CHL" ,
" CIP" ,
" CLR" ,
" CLI" ,
" FLC" ,
" COL" ,
" CZO" ,
" DAP" ,
" DOX" ,
" ETP" ,
" ERY" ,
" FOS" ,
" FUS" ,
" GEN" ,
" IPM" ,
" KAN" ,
" LVX" ,
" LIN" ,
" LNZ" ,
" MEM" ,
" MEZ" ,
" MNO" ,
" MFX" ,
" NAL" ,
" NEO" ,
" NET" ,
" NIT" ,
" NOR" ,
" NOV" ,
" OFX" ,
" OXA" ,
" PEN" ,
" PIP" ,
" TZP" ,
" PLB" ,
" PRI" ,
" QDA" ,
" RIF" ,
" RXT" ,
" SIS" ,
" TEC" ,
" TCY" ,
" TIC" ,
" TGC" ,
" TOB" ,
" TMP" ,
" SXT" ,
" VAN" ) ,
hard_dependencies = NULL ,
2019-05-20 19:12:41 +02:00
verbose = verbose ,
... )
2019-08-09 14:28:46 +02:00
2019-10-11 17:21:02 +02:00
AMC <- cols_ab [ " AMC" ]
AMK <- cols_ab [ " AMK" ]
AMP <- cols_ab [ " AMP" ]
AMX <- cols_ab [ " AMX" ]
ATM <- cols_ab [ " ATM" ]
AZL <- cols_ab [ " AZL" ]
AZM <- cols_ab [ " AZM" ]
CAZ <- cols_ab [ " CAZ" ]
CED <- cols_ab [ " CED" ]
CHL <- cols_ab [ " CHL" ]
CIP <- cols_ab [ " CIP" ]
CLI <- cols_ab [ " CLI" ]
CLR <- cols_ab [ " CLR" ]
COL <- cols_ab [ " COL" ]
CRO <- cols_ab [ " CRO" ]
CTX <- cols_ab [ " CTX" ]
CXM <- cols_ab [ " CXM" ]
CZO <- cols_ab [ " CZO" ]
DAP <- cols_ab [ " DAP" ]
DOX <- cols_ab [ " DOX" ]
ERY <- cols_ab [ " ERY" ]
ETP <- cols_ab [ " ETP" ]
FEP <- cols_ab [ " FEP" ]
FLC <- cols_ab [ " FLC" ]
FOS <- cols_ab [ " FOS" ]
FOX <- cols_ab [ " FOX" ]
FUS <- cols_ab [ " FUS" ]
GEN <- cols_ab [ " GEN" ]
IPM <- cols_ab [ " IPM" ]
KAN <- cols_ab [ " KAN" ]
LIN <- cols_ab [ " LIN" ]
LNZ <- cols_ab [ " LNZ" ]
LVX <- cols_ab [ " LVX" ]
MEM <- cols_ab [ " MEM" ]
MEZ <- cols_ab [ " MEZ" ]
MFX <- cols_ab [ " MFX" ]
MNO <- cols_ab [ " MNO" ]
NAL <- cols_ab [ " NAL" ]
NEO <- cols_ab [ " NEO" ]
NET <- cols_ab [ " NET" ]
NIT <- cols_ab [ " NIT" ]
NOR <- cols_ab [ " NOR" ]
NOV <- cols_ab [ " NOV" ]
OFX <- cols_ab [ " OFX" ]
OXA <- cols_ab [ " OXA" ]
PEN <- cols_ab [ " PEN" ]
PIP <- cols_ab [ " PIP" ]
PLB <- cols_ab [ " PLB" ]
PRI <- cols_ab [ " PRI" ]
QDA <- cols_ab [ " QDA" ]
RID <- cols_ab [ " RID" ]
RIF <- cols_ab [ " RIF" ]
RXT <- cols_ab [ " RXT" ]
2020-05-27 16:37:49 +02:00
SAM <- cols_ab [ " SAM" ]
2019-10-11 17:21:02 +02:00
SIS <- cols_ab [ " SIS" ]
SXT <- cols_ab [ " SXT" ]
TCY <- cols_ab [ " TCY" ]
TEC <- cols_ab [ " TEC" ]
TGC <- cols_ab [ " TGC" ]
TIC <- cols_ab [ " TIC" ]
TMP <- cols_ab [ " TMP" ]
TOB <- cols_ab [ " TOB" ]
TZP <- cols_ab [ " TZP" ]
VAN <- cols_ab [ " VAN" ]
2019-08-09 14:28:46 +02:00
2019-04-05 18:47:39 +02:00
ab_missing <- function ( ab ) {
all ( ab %in% c ( NULL , NA ) )
}
2019-08-09 14:28:46 +02:00
2019-02-08 16:06:54 +01:00
verbose_info <- data.frame ( row = integer ( 0 ) ,
col = character ( 0 ) ,
mo_fullname = character ( 0 ) ,
2019-08-09 14:28:46 +02:00
old = as.rsi ( character ( 0 ) ) ,
new = as.rsi ( character ( 0 ) ) ,
2019-04-05 18:47:39 +02:00
rule = character ( 0 ) ,
2019-02-08 16:06:54 +01:00
rule_group = character ( 0 ) ,
2019-04-05 18:47:39 +02:00
rule_name = character ( 0 ) ,
2018-10-19 00:17:03 +02:00
stringsAsFactors = FALSE )
2019-08-09 14:28:46 +02:00
2018-03-23 14:46:02 +01:00
# helper function for editing the table
2018-10-17 17:32:34 +02:00
edit_rsi <- function ( to , rule , rows , cols ) {
2019-01-03 23:56:19 +01:00
cols <- unique ( cols [ ! is.na ( cols ) & ! is.null ( cols ) ] )
2018-02-21 11:52:31 +01:00
if ( length ( rows ) > 0 & length ( cols ) > 0 ) {
2019-05-23 16:58:59 +02:00
before_df <- x_original
2020-05-27 16:37:49 +02:00
if ( any ( ! sapply ( x [ , cols , drop = FALSE ] , is.rsi ) , na.rm = TRUE ) ) {
warn_lacking_rsi_class <<- TRUE
}
2018-10-17 17:32:34 +02:00
tryCatch (
# insert into original table
2019-05-23 16:58:59 +02:00
x_original [rows , cols ] <<- to ,
2018-10-17 17:32:34 +02:00
warning = function ( w ) {
2019-10-11 17:21:02 +02:00
if ( w $ message %like% " invalid factor level" ) {
2020-05-16 13:05:47 +02:00
xyz <- sapply ( cols , function ( col ) {
x_original [ , col ] <<- factor ( x = as.character ( pull ( x_original , col ) ) , levels = c ( to , levels ( pull ( x_original , col ) ) ) )
x [ , col ] <<- factor ( x = as.character ( pull ( x , col ) ) , levels = c ( to , levels ( pull ( x , col ) ) ) )
invisible ( )
} )
2019-08-07 15:37:39 +02:00
x_original [rows , cols ] <<- to
2019-10-11 17:21:02 +02:00
warning ( ' Value "' , to , ' " added to the factor levels of column(s) `' , paste ( cols , collapse = " `, `" ) , " ` because this value was not an existing factor level.\nA better way is to use as.rsi() on beforehand on antimicrobial columns to guarantee the right structure." , call. = FALSE )
2019-08-07 15:37:39 +02:00
txt_warning ( )
warned <<- FALSE
2018-10-17 17:32:34 +02:00
} else {
warning ( w $ message , call. = FALSE )
2019-08-07 15:37:39 +02:00
txt_warning ( )
cat ( " \n" ) # txt_warning() does not append a "\n" on itself
2018-10-17 17:32:34 +02:00
}
} ,
error = function ( e ) {
txt_error ( )
2019-08-08 15:52:07 +02:00
stop ( paste0 ( " In row(s) " , paste ( rows [1 : min ( length ( rows ) , 10 ) ] , collapse = " ," ) ,
ifelse ( length ( rows ) > 10 , " ..." , " " ) ,
2019-10-11 17:21:02 +02:00
" while writing value '" , to ,
" ' to column(s) `" , paste ( cols , collapse = " `, `" ) ,
2019-08-09 14:28:46 +02:00
" `:\n" , e $ message ) ,
2019-08-08 15:52:07 +02:00
call. = FALSE )
2018-10-17 17:32:34 +02:00
}
)
2019-08-07 15:37:39 +02:00
tryCatch (
x [rows , cols ] <<- x_original [rows , cols ] ,
error = function ( e ) {
2019-08-09 14:28:46 +02:00
stop ( paste0 ( " In row(s) " , paste ( rows [1 : min ( length ( rows ) , 10 ) ] , collapse = " ," ) ,
2019-10-11 17:21:02 +02:00
" ... while writing value '" , to ,
" ' to column(s) `" , paste ( cols , collapse = " `, `" ) ,
2019-08-09 14:28:46 +02:00
" `:\n" , e $ message ) , call. = FALSE )
2019-08-07 15:37:39 +02:00
}
)
2019-08-06 14:39:22 +02:00
# before_df might not be a data.frame, but a tibble or data.table instead
2019-10-11 17:21:02 +02:00
old <- as.data.frame ( before_df , stringsAsFactors = FALSE ) [rows , ]
2019-08-09 14:28:46 +02:00
track_changes <- list ( added = 0 ,
changed = 0 )
2019-10-11 17:21:02 +02:00
for ( i in seq_len ( length ( cols ) ) ) {
2019-04-05 18:47:39 +02:00
verbose_new <- data.frame ( row = rows ,
col = cols [i ] ,
2019-05-23 16:58:59 +02:00
mo_fullname = x [rows , " fullname" ] ,
2019-08-09 14:28:46 +02:00
old = as.rsi ( as.character ( old [ , cols [i ] ] ) , warn = FALSE ) ,
new = as.rsi ( as.character ( x [rows , cols [i ] ] ) ) ,
2020-05-16 13:05:47 +02:00
rule = font_stripstyle ( rule [1 ] ) ,
rule_group = font_stripstyle ( rule [2 ] ) ,
rule_name = font_stripstyle ( rule [3 ] ) ,
2019-04-05 18:47:39 +02:00
stringsAsFactors = FALSE )
colnames ( verbose_new ) <- c ( " row" , " col" , " mo_fullname" , " old" , " new" , " rule" , " rule_group" , " rule_name" )
verbose_new <- verbose_new %>% filter ( old != new | is.na ( old ) )
2019-08-06 14:39:22 +02:00
# save changes to data set 'verbose_info'
2019-04-05 18:47:39 +02:00
verbose_info <<- rbind ( verbose_info , verbose_new )
2019-08-09 14:28:46 +02:00
# count adds and changes
track_changes $ added <- track_changes $ added + verbose_new %>% filter ( is.na ( old ) ) %>% nrow ( )
track_changes $ changed <- track_changes $ changed + verbose_new %>% filter ( ! is.na ( old ) ) %>% nrow ( )
2018-10-17 17:32:34 +02:00
}
2019-08-09 14:28:46 +02:00
# after the applied changes: return list with counts of added and changed
return ( track_changes )
2018-10-17 17:32:34 +02:00
}
2019-08-06 14:39:22 +02:00
# no changes were applied: return number of (new) changes: none.
2019-08-09 14:28:46 +02:00
return ( list ( added = 0 ,
changed = 0 ) )
2018-02-21 11:52:31 +01:00
}
2019-08-09 14:28:46 +02:00
2018-10-18 12:10:10 +02:00
# save original table
2019-05-23 16:58:59 +02:00
x_original <- x
2020-06-02 16:05:56 +02:00
x_original_attr <- attributes ( x )
x_original <- as.data.frame ( x_original , stringsAsFactors = FALSE ) # no tibbles, data.tables, etc.
2019-08-09 14:28:46 +02:00
2018-10-18 12:10:10 +02:00
# join to microorganisms data set
2020-05-16 13:05:47 +02:00
x <- as.data.frame ( x , stringsAsFactors = FALSE )
x [ , col_mo ] <- as.mo ( x [ , col_mo , drop = TRUE ] )
x <- x %>%
left_join_microorganisms ( by = col_mo , suffix = c ( " _oldcols" , " " ) )
2020-05-18 11:09:02 +02:00
x $ gramstain <- mo_gramstain ( x [ , col_mo , drop = TRUE ] , language = NULL )
2020-05-16 13:05:47 +02:00
x $ genus_species <- paste ( x $ genus , x $ species )
2019-11-15 15:25:03 +01:00
if ( ab_missing ( AMP ) & ! ab_missing ( AMX ) ) {
2018-12-31 01:48:53 +01:00
# ampicillin column is missing, but amoxicillin is available
2020-05-16 13:05:47 +02:00
message ( font_blue ( paste0 ( " NOTE: Using column `" , font_bold ( AMX ) , " ` as input for ampicillin (J01CA01) since many EUCAST rules depend on it." ) ) )
2019-05-10 16:44:59 +02:00
AMP <- AMX
2018-10-17 17:32:34 +02:00
}
2019-08-09 14:28:46 +02:00
2019-10-11 17:21:02 +02:00
# nolint start
2018-12-31 01:48:53 +01:00
# antibiotic classes
2019-05-10 16:44:59 +02:00
aminoglycosides <- c ( TOB , GEN , KAN , NEO , NET , SIS )
tetracyclines <- c ( DOX , MNO , TCY ) # since EUCAST v3.1 tigecycline (TGC) is set apart
polymyxins <- c ( PLB , COL )
macrolides <- c ( ERY , AZM , RXT , CLR ) # since EUCAST v3.1 clinda is set apart
glycopeptides <- c ( VAN , TEC )
streptogramins <- c ( QDA , PRI ) # should officially also be quinupristin/dalfopristin
aminopenicillins <- c ( AMP , AMX )
cephalosporins <- c ( FEP , CTX , FOX , CED , CAZ , CRO , CXM , CZO )
2019-11-28 22:32:17 +01:00
cephalosporins_except_CAZ <- cephalosporins [cephalosporins != ifelse ( is.null ( CAZ ) , " " , CAZ ) ]
2019-05-10 16:44:59 +02:00
carbapenems <- c ( ETP , IPM , MEM )
ureidopenicillins <- c ( PIP , TZP , AZL , MEZ )
all_betalactams <- c ( aminopenicillins , cephalosporins , carbapenems , ureidopenicillins , AMC , OXA , FLC , PEN )
fluoroquinolones <- c ( OFX , CIP , NOR , LVX , MFX )
2019-10-11 17:21:02 +02:00
# nolint end
2019-08-09 14:28:46 +02:00
2019-04-05 18:47:39 +02:00
# Help function to get available antibiotic column names ------------------
get_antibiotic_columns <- function ( x , df ) {
x <- trimws ( unlist ( strsplit ( x , " ," , fixed = TRUE ) ) )
y <- character ( 0 )
2019-10-11 17:21:02 +02:00
for ( i in seq_len ( length ( x ) ) ) {
2019-08-08 15:52:07 +02:00
if ( is.function ( get ( x [i ] ) ) ) {
stop ( " Column " , x [i ] , " is also a function. Please create an issue on github.com/msberends/AMR/issues." )
}
2019-04-05 18:47:39 +02:00
y <- c ( y , tryCatch ( get ( x [i ] ) , error = function ( e ) " " ) )
2018-10-18 12:10:10 +02:00
}
2019-04-05 18:47:39 +02:00
y [y != " " & y %in% colnames ( df ) ]
}
2019-07-09 13:36:03 +02:00
get_antibiotic_names <- function ( x ) {
2019-11-28 22:32:17 +01:00
x <- x %>%
2019-07-09 13:36:03 +02:00
strsplit ( " ," ) %>%
unlist ( ) %>%
trimws ( ) %>%
2020-02-14 19:54:13 +01:00
sapply ( function ( x ) if ( x %in% antibiotics $ ab ) ab_name ( x , language = NULL , tolower = TRUE ) else x ) %>%
2019-07-09 13:36:03 +02:00
sort ( ) %>%
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 )
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 ) {
ab_names <- trimws ( unlist ( strsplit ( ab_names , " ," ) ) )
ab_results <- trimws ( unlist ( strsplit ( ab_results , " ," ) ) )
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)
x <- paste ( paste0 ( ab_names , collapse = " and " ) , " are all" )
}
return ( paste0 ( x , " '" , ab_results , " '" ) )
} else {
if ( length ( ab_names ) == 2 ) {
# like PEN,FOX S,R
paste0 ( ab_names [1 ] , " is '" , ab_results [1 ] , " ' and " ,
ab_names [2 ] , " is '" , ab_results [2 ] , " '" )
} else {
# like PEN,FOX,GEN S,R,R (although dependency on > 2 ABx does not exist at the moment)
paste0 ( ab_names [1 ] , " is '" , ab_results [1 ] , " ' and " ,
ab_names [2 ] , " is '" , ab_results [2 ] , " ' and " ,
ab_names [3 ] , " is '" , ab_results [3 ] , " '" )
}
}
}
2020-05-27 16:37:49 +02:00
as.rsi_no_warning <- function ( x ) suppressWarnings ( as.rsi ( x ) )
no_added <- 0
no_changed <- 0
# Other rules: enzyme inhibitors ------------------------------------------
if ( any ( c ( " all" , " other" ) %in% rules ) ) {
if ( info == TRUE ) {
cat ( font_bold ( paste0 ( " \nRules by this AMR package (" ,
font_red ( paste0 ( " v" , utils :: packageVersion ( " AMR" ) , " , " ,
2020-06-05 13:56:05 +02:00
format ( utils :: packageDate ( " AMR" ) , " %Y" ) ) ) , " ), see ?eucast_rules\n" ) ) )
2020-05-27 16:37:49 +02:00
}
ab_enzyme <- subset ( antibiotics , name %like% " /" ) [ , c ( " ab" , " name" ) ]
ab_enzyme $ base_name <- gsub ( " ^([a-zA-Z0-9]+).*" , " \\1" , ab_enzyme $ name )
ab_enzyme $ base_ab <- as.ab ( ab_enzyme $ base_name )
for ( i in seq_len ( nrow ( ab_enzyme ) ) ) {
if ( all ( c ( ab_enzyme [i , ] $ ab , ab_enzyme [i , ] $ base_ab ) %in% names ( cols_ab ) , na.rm = TRUE ) ) {
ab_name_base <- ab_name ( cols_ab [ab_enzyme [i , ] $ base_ab ] , language = NULL , tolower = TRUE )
ab_name_enzyme <- ab_name ( cols_ab [ab_enzyme [i , ] $ ab ] , language = NULL , tolower = TRUE )
# Set base to R where base + enzyme inhibitor is R
rule_current <- paste0 ( " Set " , ab_name_base , " (" , cols_ab [ab_enzyme [i , ] $ base_ab ] , " ) = R where " ,
ab_name_enzyme , " (" , cols_ab [ab_enzyme [i , ] $ ab ] , " ) = R" )
if ( info == TRUE ) {
cat ( rule_current )
}
run_changes <- edit_rsi ( to = " R" ,
rule = c ( rule_current , " Other rules" , " " ) ,
rows = which ( as.rsi_no_warning ( x [ , cols_ab [ab_enzyme [i , ] $ ab ] ] ) == " R" ) ,
cols = cols_ab [ab_enzyme [i , ] $ base_ab ] )
no_added <- no_added + run_changes $ added
no_changed <- no_changed + run_changes $ changed
# Print number of new changes
if ( info == TRUE ) {
# print only on last one of rules in this group
txt_ok ( no_added = no_added , no_changed = no_changed )
# and reset counters
no_added <- 0
no_changed <- 0
}
# Set base + enzyme inhibitor to S where base is S
rule_current <- paste0 ( " Set " , ab_name_enzyme , " (" , cols_ab [ab_enzyme [i , ] $ ab ] , " ) = S where " ,
ab_name_base , " (" , cols_ab [ab_enzyme [i , ] $ base_ab ] , " ) = S" )
if ( info == TRUE ) {
cat ( rule_current )
}
run_changes <- edit_rsi ( to = " S" ,
rule = c ( rule_current , " Other rules" , " " ) ,
rows = which ( as.rsi_no_warning ( x [ , cols_ab [ab_enzyme [i , ] $ base_ab ] ] ) == " S" ) ,
cols = cols_ab [ab_enzyme [i , ] $ ab ] )
no_added <- no_added + run_changes $ added
no_changed <- no_changed + run_changes $ changed
# Print number of new changes
if ( info == TRUE ) {
# print only on last one of rules in this group
txt_ok ( no_added = no_added , no_changed = no_changed )
# and reset counters
no_added <- 0
no_changed <- 0
}
}
}
} else {
if ( info == TRUE ) {
cat ( font_red ( " \nSkipping inheritance rules defined by this package, such as setting trimethoprim (TMP) = R where trimethoprim/sulfamethoxazole (SXT) = R.\nUse eucast_rules(..., rules = \"all\") to also apply those rules.\n" ) )
}
2020-05-20 12:00:17 +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
2019-06-01 20:40:49 +02:00
eucast_rules_df <- eucast_rules_file # internal data file
2019-10-11 17:21:02 +02:00
for ( i in seq_len ( nrow ( eucast_rules_df ) ) ) {
2019-08-09 14:28:46 +02:00
2019-04-05 18:47:39 +02:00
rule_previous <- eucast_rules_df [max ( 1 , i - 1 ) , " reference.rule" ]
rule_current <- eucast_rules_df [i , " reference.rule" ]
rule_next <- eucast_rules_df [min ( nrow ( eucast_rules_df ) , i + 1 ) , " reference.rule" ]
rule_group_previous <- eucast_rules_df [max ( 1 , i - 1 ) , " reference.rule_group" ]
rule_group_current <- eucast_rules_df [i , " reference.rule_group" ]
if ( is.na ( eucast_rules_df [i , 4 ] ) ) {
2019-07-09 13:36:03 +02:00
rule_text <- paste0 ( " always report as '" , eucast_rules_df [i , 7 ] , " ': " , get_antibiotic_names ( eucast_rules_df [i , 6 ] ) )
2019-04-05 18:47:39 +02:00
} else {
2019-07-09 13:36:03 +02:00
rule_text <- paste0 ( " report as '" , eucast_rules_df [i , 7 ] , " ' when " ,
2019-08-09 14:28:46 +02:00
format_antibiotic_names ( ab_names = get_antibiotic_names ( eucast_rules_df [i , 4 ] ) ,
ab_results = eucast_rules_df [i , 5 ] ) , " : " ,
2019-07-09 13:36:03 +02:00
get_antibiotic_names ( eucast_rules_df [i , 6 ] ) )
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
}
2019-08-09 14:28:46 +02:00
2019-04-05 18:47:39 +02:00
# don't apply rules if user doesn't want to apply them
if ( rule_group_current %like% " breakpoint" & ! any ( c ( " all" , " breakpoints" ) %in% rules ) ) {
next
2018-10-18 12:10:10 +02:00
}
2019-04-05 18:47:39 +02:00
if ( rule_group_current %like% " expert" & ! any ( c ( " all" , " expert" ) %in% rules ) ) {
next
2018-10-18 12:10:10 +02:00
}
2019-08-09 14:28:46 +02:00
2019-11-15 15:25:03 +01:00
if ( info == TRUE & ! rule_group_current %like% " other" & eucast_notification_shown == FALSE ) {
2020-05-27 16:37:49 +02:00
cat ( paste0 ( " \n" , font_grey ( strrep ( " -" , options ( ) $ width - 1 ) ) ,
" \nRules by the " , font_bold ( " European Committee on Antimicrobial Susceptibility Testing (EUCAST)" ) ,
2020-05-16 13:05:47 +02:00
" \n" , font_blue ( " http://eucast.org/" ) , " \n" ) )
2019-11-15 15:25:03 +01:00
eucast_notification_shown <- TRUE
}
2018-10-18 12:10:10 +02:00
if ( info == TRUE ) {
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" ,
paste0 ( " \nEUCAST Clinical Breakpoints (" ,
font_red ( paste0 ( " v" , EUCAST_VERSION_BREAKPOINTS ) ) , " )\n" ) ,
ifelse (
rule_group_current %like% " expert" ,
2020-02-17 14:38:01 +01:00
paste0 ( " \nEUCAST Expert Rules, Intrinsic Resistance and Exceptional Phenotypes (" ,
2020-05-16 13:05:47 +02:00
font_red ( paste0 ( " v" , EUCAST_VERSION_EXPERT_RULES ) ) , " )\n" ) ,
2020-05-27 16:37:49 +02:00
" " ) ) ) )
2019-04-05 18:47:39 +02:00
}
# Print rule -------------------------------------------------------------
if ( rule_current != rule_previous ) {
# is new rule within group, print its name
2020-02-14 19:54:13 +01:00
if ( rule_current %in% c ( microorganisms $ family ,
microorganisms $ fullname ) ) {
2020-05-16 13:05:47 +02:00
cat ( font_italic ( rule_current ) )
2019-04-05 18:47:39 +02:00
} else {
cat ( rule_current )
}
warned <- FALSE
}
2018-10-18 12:10:10 +02:00
}
2019-08-09 14:28:46 +02:00
2019-04-05 18:47:39 +02:00
# Get rule from file ------------------------------------------------------
col_mo_property <- eucast_rules_df [i , 1 ]
like_is_one_of <- eucast_rules_df [i , 2 ]
2019-08-09 14:28:46 +02:00
2019-04-05 18:47:39 +02:00
# be sure to comprise all coagulase-negative/-positive Staphylococci when they are mentioned
2020-05-18 11:09:02 +02:00
if ( eucast_rules_df [i , 3 ] %like% " coagulase" ) {
all_staph <- microorganisms [which ( microorganisms $ genus == " Staphylococcus" ) , ]
all_staph $ CNS_CPS <- suppressWarnings ( mo_name ( all_staph $ mo , Becker = " all" , language = NULL ) )
if ( eucast_rules_df [i , 3 ] %like% " coagulase" ) {
eucast_rules_df [i , 3 ] <- paste0 ( " ^(" , paste0 ( all_staph [which ( all_staph $ CNS_CPS %like% " negative" ) ,
" fullname" ,
drop = TRUE ] ,
collapse = " |" ) ,
2019-04-05 18:47:39 +02:00
" )$" )
} else {
2020-05-18 11:09:02 +02:00
eucast_rules_df [i , 3 ] <- paste0 ( " ^(" , paste0 ( all_staph [which ( all_staph $ CNS_CPS %like% " positive" ) ,
" fullname" ,
drop = TRUE ] ,
collapse = " |" ) ,
2019-04-05 18:47:39 +02:00
" )$" )
}
like_is_one_of <- " like"
}
2019-08-09 14:28:46 +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$'
2019-04-05 18:47:39 +02:00
mo_value <- paste0 ( " ^" , eucast_rules_df [i , 3 ] , " $" )
} else if ( like_is_one_of == " one_of" ) {
2019-10-11 17:21:02 +02:00
# so 'Clostridium, Actinomyces, ...' will turn into '^(Clostridium|Actinomyces|...)$'
2019-04-05 18:47:39 +02:00
mo_value <- paste0 ( " ^(" ,
paste ( trimws ( unlist ( strsplit ( eucast_rules_df [i , 3 ] , " ," , fixed = TRUE ) ) ) ,
collapse = " |" ) ,
" )$" )
} else if ( like_is_one_of == " like" ) {
mo_value <- eucast_rules_df [i , 3 ]
} else {
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
}
2019-08-09 14:28:46 +02:00
2019-04-05 18:47:39 +02:00
source_antibiotics <- eucast_rules_df [i , 4 ]
source_value <- trimws ( unlist ( strsplit ( eucast_rules_df [i , 5 ] , " ," , fixed = TRUE ) ) )
target_antibiotics <- eucast_rules_df [i , 6 ]
target_value <- eucast_rules_df [i , 7 ]
2019-08-09 14:28:46 +02:00
2019-04-05 18:47:39 +02:00
if ( is.na ( source_antibiotics ) ) {
2019-05-23 16:58:59 +02:00
rows <- tryCatch ( which ( x [ , col_mo_property ] %like% mo_value ) ,
2019-04-05 18:47:39 +02:00
error = function ( e ) integer ( 0 ) )
} else {
2019-05-23 16:58:59 +02:00
source_antibiotics <- get_antibiotic_columns ( source_antibiotics , x )
2019-04-05 18:47:39 +02:00
if ( length ( source_value ) == 1 & length ( source_antibiotics ) > 1 ) {
source_value <- rep ( source_value , length ( source_antibiotics ) )
}
if ( length ( source_antibiotics ) == 0 ) {
rows <- integer ( 0 )
} else if ( length ( source_antibiotics ) == 1 ) {
2019-05-23 16:58:59 +02:00
rows <- tryCatch ( which ( x [ , col_mo_property ] %like% mo_value
2020-05-27 16:37:49 +02:00
& as.rsi_no_warning ( x [ , source_antibiotics [1L ] ] ) == source_value [1L ] ) ,
2019-04-05 18:47:39 +02:00
error = function ( e ) integer ( 0 ) )
} else if ( length ( source_antibiotics ) == 2 ) {
2019-05-23 16:58:59 +02:00
rows <- tryCatch ( which ( x [ , col_mo_property ] %like% mo_value
2020-05-27 16:37:49 +02:00
& as.rsi_no_warning ( x [ , source_antibiotics [1L ] ] ) == source_value [1L ]
& as.rsi_no_warning ( x [ , source_antibiotics [2L ] ] ) == source_value [2L ] ) ,
2019-04-05 18:47:39 +02:00
error = function ( e ) integer ( 0 ) )
} else if ( length ( source_antibiotics ) == 3 ) {
2019-05-23 16:58:59 +02:00
rows <- tryCatch ( which ( x [ , col_mo_property ] %like% mo_value
2020-05-27 16:37:49 +02:00
& as.rsi_no_warning ( x [ , source_antibiotics [1L ] ] ) == source_value [1L ]
& as.rsi_no_warning ( x [ , source_antibiotics [2L ] ] ) == source_value [2L ]
& as.rsi_no_warning ( x [ , source_antibiotics [3L ] ] ) == source_value [3L ] ) ,
2019-04-05 18:47:39 +02:00
error = function ( e ) integer ( 0 ) )
} else {
stop ( " only 3 antibiotics supported for source_antibiotics " , call. = FALSE )
}
2018-10-18 12:10:10 +02:00
}
2019-08-09 14:28:46 +02:00
2019-05-23 16:58:59 +02:00
cols <- get_antibiotic_columns ( target_antibiotics , x )
2019-08-09 14:28:46 +02:00
2019-04-05 18:47:39 +02:00
# Apply rule on data ------------------------------------------------------
# this will return the unique number of changes
2019-08-09 14:28:46 +02:00
run_changes <- edit_rsi ( to = target_value ,
rule = c ( rule_text , rule_group_current , rule_current ) ,
rows = rows ,
cols = cols )
no_added <- no_added + run_changes $ added
no_changed <- no_changed + run_changes $ changed
2019-04-05 18:47:39 +02:00
# Print number of new changes ---------------------------------------------
if ( info == TRUE & rule_next != rule_current ) {
# print only on last one of rules in this group
2019-08-09 14:28:46 +02:00
txt_ok ( no_added = no_added , no_changed = no_changed )
# and reset counters
no_added <- 0
no_changed <- 0
2018-11-01 17:06:08 +01:00
}
2019-04-05 18:47:39 +02:00
}
2019-08-09 14:28:46 +02:00
2019-04-05 18:47:39 +02:00
# Print overview ----------------------------------------------------------
2018-02-21 11:52:31 +01:00
if ( info == TRUE ) {
2018-10-19 00:57:10 +02:00
if ( verbose == TRUE ) {
wouldve <- " would have "
} else {
wouldve <- " "
}
2019-08-09 14:28:46 +02:00
2019-04-05 18:47:39 +02:00
verbose_info <- verbose_info %>%
arrange ( row , rule_group , rule_name , col )
2019-08-09 14:28:46 +02:00
2020-05-16 13:05:47 +02:00
cat ( paste0 ( " \n" , font_grey ( strrep ( " -" , options ( ) $ width - 1 ) ) , " \n" ) )
2020-05-27 16:37:49 +02:00
cat ( font_bold ( paste ( " The rules" , paste0 ( wouldve , " affected" ) ,
2019-04-05 18:47:39 +02:00
formatnr ( n_distinct ( verbose_info $ row ) ) ,
2019-10-11 17:21:02 +02:00
" out of" , formatnr ( nrow ( x_original ) ) ,
" rows, making a total of" , formatnr ( nrow ( verbose_info ) ) , " edits\n" ) ) )
2019-08-09 14:28:46 +02:00
2019-05-20 12:00:18 +02:00
n_added <- verbose_info %>% filter ( is.na ( old ) ) %>% nrow ( )
n_changed <- verbose_info %>% filter ( ! is.na ( old ) ) %>% nrow ( )
2019-08-09 14:28:46 +02:00
2019-04-05 18:47:39 +02:00
# print added values ----
2019-05-20 12:00:18 +02:00
if ( 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
}
2019-04-05 18:47:39 +02:00
cat ( colour ( paste0 ( " => " , wouldve , " added " ,
2020-05-16 13:05:47 +02:00
font_bold ( formatnr ( verbose_info %>%
2019-04-05 18:47:39 +02:00
filter ( is.na ( old ) ) %>%
nrow ( ) ) , " test results" ) ,
2019-02-08 16:06:54 +01:00
" \n" ) ) )
2019-05-20 12:00:18 +02:00
if ( n_added > 0 ) {
2020-05-16 13:05:47 +02:00
added_summary <- verbose_info %>%
2019-04-05 18:47:39 +02:00
filter ( is.na ( old ) ) %>%
group_by ( new ) %>%
2020-05-16 13:05:47 +02:00
summarise ( n = n ( ) )
cat ( paste ( " -" ,
paste0 ( formatnr ( added_summary $ n ) , " test result" , ifelse ( added_summary $ n > 1 , " s" , " " ) ,
" added as " , added_summary $ new ) , collapse = " \n" ) )
2019-04-05 18:47:39 +02:00
}
2019-08-09 14:28:46 +02:00
2019-04-05 18:47:39 +02:00
# print changed values ----
2019-05-20 12:00:18 +02:00
if ( 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
}
2019-05-20 12:00:18 +02:00
if ( n_added + n_changed > 0 ) {
cat ( " \n" )
}
cat ( colour ( paste0 ( " => " , wouldve , " changed " ,
2020-05-16 13:05:47 +02:00
font_bold ( formatnr ( verbose_info %>%
2019-04-05 18:47:39 +02:00
filter ( ! is.na ( old ) ) %>%
nrow ( ) ) , " test results" ) ,
2019-02-08 16:06:54 +01:00
" \n" ) ) )
2019-05-20 12:00:18 +02:00
if ( n_changed > 0 ) {
2020-05-16 13:05:47 +02:00
changed_summary <- verbose_info %>%
2019-04-05 18:47:39 +02:00
filter ( ! is.na ( old ) ) %>%
group_by ( old , new ) %>%
2020-05-16 13:05:47 +02:00
summarise ( n = n ( ) )
cat ( paste ( " -" ,
paste0 ( formatnr ( changed_summary $ n ) , " test result" , ifelse ( changed_summary $ n > 1 , " s" , " " ) , " changed from " ,
changed_summary $ old , " to " , changed_summary $ new ) , collapse = " \n" ) )
2019-04-05 18:47:39 +02:00
cat ( " \n" )
}
2020-05-16 13:05:47 +02:00
cat ( paste0 ( font_grey ( strrep ( " -" , options ( ) $ width - 1 ) ) , " \n" ) )
2019-08-09 14:28:46 +02:00
2019-04-05 18:47:39 +02:00
if ( verbose == FALSE & nrow ( verbose_info ) > 0 ) {
2020-05-16 13:05:47 +02:00
cat ( paste ( " \nUse" , font_bold ( " eucast_rules(..., verbose = TRUE)" ) , " (on your original data) to get a data.frame with all specified edits instead.\n\n" ) )
2019-07-09 13:36:03 +02:00
} else if ( verbose == TRUE ) {
2020-05-16 13:05:47 +02:00
cat ( paste0 ( " \nUsed '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" ) )
2019-03-28 21:33:28 +01:00
}
2018-10-17 17:32:34 +02:00
}
2019-08-09 14:28:46 +02:00
2020-05-27 16:37:49 +02:00
if ( isTRUE ( warn_lacking_rsi_class ) ) {
warning ( " Not all columns with antimicrobial results are of class <rsi>.\n" ,
" Transform eligible columns to class <rsi> on beforehand: your_data %>% mutate_if(is.rsi.eligible, as.rsi)" ,
call. = FALSE )
}
2019-04-05 18:47:39 +02:00
# Return data set ---------------------------------------------------------
2018-10-19 00:17:03 +02:00
if ( verbose == TRUE ) {
2020-05-16 13:05:47 +02:00
rownames ( verbose_info ) <- NULL
2019-04-05 18:47:39 +02:00
verbose_info
} else {
2020-06-02 16:05:56 +02:00
# reset original attributes
attributes ( x_original ) <- x_original_attr
2019-05-23 16:58:59 +02:00
x_original
2018-02-21 11:52:31 +01:00
}
}