diff --git a/DESCRIPTION b/DESCRIPTION index a5e6c939..861b1342 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: AMR -Version: 1.6.0.9009 -Date: 2021-04-23 +Version: 1.6.0.9010 +Date: 2021-04-26 Title: Antimicrobial Resistance Data Analysis Authors@R: c( person(role = c("aut", "cre"), diff --git a/NAMESPACE b/NAMESPACE index c8a9ade3..abf29a54 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -161,8 +161,10 @@ export(ab_tradenames) export(ab_url) export(age) export(age_groups) +export(all_antimicrobials) export(aminoglycosides) export(anti_join_microorganisms) +export(antimicrobials_equal) export(as.ab) export(as.disk) export(as.mic) @@ -237,6 +239,7 @@ export(is.rsi.eligible) export(is_new_episode) export(key_antibiotics) export(key_antibiotics_equal) +export(key_antimicrobials) export(kurtosis) export(labels_rsi_count) export(left_join_microorganisms) diff --git a/NEWS.md b/NEWS.md index 70127e9f..ae862ea5 100755 --- a/NEWS.md +++ b/NEWS.md @@ -1,8 +1,16 @@ -# AMR 1.6.0.9009 -## Last updated: 23 April 2021 +# AMR 1.6.0.9010 +## Last updated: 26 April 2021 ### New * Function `custom_eucast_rules()` that brings support for custom AMR rules in `eucast_rules()` +* Support for all four methods to determine first isolates as summarised by Hindler *et al* (doi: [10.1086/511864](https://doi.org/10.1086/511864)): isolate-based, patient-based, episode-based and phenotype-based. The last method is now the default. + * Since fungal isolates can also be selected, new functions `key_antimicrobials()` and `all_antimicrobials()` have replaced the now deprecated function `key_antibiotics()` + * Using `key_antimicrobials()` still only selects six preferred antibiotics for Gram-negatives, six for Gram-positives, and six universal antibiotics. It has a new `antifungal` argument to set antifungal agents (antimycotics). + * The `first_isolate()` function gained the argument `method` that has to be "phenotype-based", "episode-based", "patient-based", or "isolate-based". The old behaviour is equal to "episode-based", while the new default is "phenotype-based". + * Using `type == "points"` in the `first_isolate()` function for phenotype-based selection will now consider all antimicrobial drugs in the data set, using the new `all_antimicrobials()` + * The `first_isolate()` function can now take a vector of values for `col_keyantibiotics` and can have an episode length of `Inf` + * The `filter_first_isolate()` function has not changed, as it uses the episode-based method. The `filter_first_weighted_isolate()` may now include more isolates as uses the phenotype-based method. + * The documentation of the `first_isolate()` and `key_antimicrobials()` functions has been completely rewritten. ### Changed * Custom MDRO guidelines (`mdro()`, `custom_mdro_guideline()`): @@ -12,7 +20,6 @@ * The `example_isolates` data set now contains some (fictitious) zero-year old patients * Fix for minor translation errors * Printing of microbial codes in a `data.frame` or `tibble` now gives a warning if the data contains old microbial codes (from a previous AMR package version) -* `first_isolate()` can now take a vector of values for `col_keyantibiotics` and can have an episode length of `Inf` * Extended the `like()` functions: * Now checks if `pattern` is a *valid* regular expression * Added `%unlike%` and `%unlike_case%` (as negations of the existing `%like%` and `%like_case%`). This greatly improves readability: @@ -25,9 +32,6 @@ * Fixed an installation error on R-3.0 * Added `info` argument to `as.mo()` to turn on/off the progress bar * Fixed a bug that `col_mo` for some functions (esp. `eucast_rules()` and `mdro()`) could not be column names of the `microorganisms` data set as it would throw an error -* Using `first_isolate()` with key antibiotics: - * Fixed a bug in the algorithm when using `type == "points"`, that now leads to inclusion of slightly more isolates - * Big speed improvement for `key_antibiotics_equal()` when using `type == "points"` # AMR 1.6.0 diff --git a/R/aa_helper_functions.R b/R/aa_helper_functions.R index b59b36fd..ee8fac86 100755 --- a/R/aa_helper_functions.R +++ b/R/aa_helper_functions.R @@ -190,8 +190,8 @@ search_type_in_df <- function(x, type, info = TRUE) { } # -- key antibiotics if (type == "keyantibiotics") { - if (any(colnames(x) %like% "^key.*(ab|antibiotics)")) { - found <- sort(colnames(x)[colnames(x) %like% "^key.*(ab|antibiotics)"])[1] + if (any(colnames(x) %like% "^key.*(ab|antibiotics|antimicrobials)")) { + found <- sort(colnames(x)[colnames(x) %like% "^key.*(ab|antibiotics|antimicrobials)"])[1] } } # -- date @@ -318,7 +318,9 @@ word_wrap <- function(..., msg <- paste0(c(...), collapse = "") if (isTRUE(as_note)) { - msg <- paste0("NOTE: ", gsub("^note:? ?", "", msg, ignore.case = TRUE)) + # \u2139 is a symbol officially named 'information source' + # \ufe0f can add the blue square around it: \u2139\ufe0f + msg <- paste0("\u2139 ", gsub("^note:? ?", "", msg, ignore.case = TRUE)) } if (msg %like% "\n") { @@ -352,8 +354,8 @@ word_wrap <- function(..., msg <- paste0(msg, collapse = " ") msg <- gsub("\n ", "\n", msg, fixed = TRUE) - if (msg_stripped %like% "^NOTE: ") { - indentation <- 6 + extra_indent + if (msg_stripped %like% "\u2139 ") { + indentation <- 2 + extra_indent } else if (msg_stripped %like% "^=> ") { indentation <- 3 + extra_indent } else { diff --git a/R/deprecated.R b/R/deprecated.R index b4b1cbeb..0ac98c67 100755 --- a/R/deprecated.R +++ b/R/deprecated.R @@ -45,3 +45,71 @@ p_symbol <- function(p, emptychar = " ") { s } + +#' @name AMR-deprecated +#' @export +key_antibiotics <- function(x = NULL, + col_mo = NULL, + universal_1 = guess_ab_col(x, "amoxicillin"), + universal_2 = guess_ab_col(x, "amoxicillin/clavulanic acid"), + universal_3 = guess_ab_col(x, "cefuroxime"), + universal_4 = guess_ab_col(x, "piperacillin/tazobactam"), + universal_5 = guess_ab_col(x, "ciprofloxacin"), + universal_6 = guess_ab_col(x, "trimethoprim/sulfamethoxazole"), + GramPos_1 = guess_ab_col(x, "vancomycin"), + GramPos_2 = guess_ab_col(x, "teicoplanin"), + GramPos_3 = guess_ab_col(x, "tetracycline"), + GramPos_4 = guess_ab_col(x, "erythromycin"), + GramPos_5 = guess_ab_col(x, "oxacillin"), + GramPos_6 = guess_ab_col(x, "rifampin"), + GramNeg_1 = guess_ab_col(x, "gentamicin"), + GramNeg_2 = guess_ab_col(x, "tobramycin"), + GramNeg_3 = guess_ab_col(x, "colistin"), + GramNeg_4 = guess_ab_col(x, "cefotaxime"), + GramNeg_5 = guess_ab_col(x, "ceftazidime"), + GramNeg_6 = guess_ab_col(x, "meropenem"), + warnings = TRUE, + ...) { + + .Deprecated(old = "key_antibiotics()", + new = "key_antimicrobials()", + package = "AMR") + + if (is_null_or_grouped_tbl(x)) { + # when `x` is left blank, auto determine it (get_current_data() also contains dplyr::cur_data_all()) + # is also fix for using a grouped df as input (a dot as first argument) + x <- tryCatch(get_current_data(arg_name = "x", call = -2), error = function(e) x) + } + + key_antimicrobials(x = x, + col_mo = col_mo, + universal = c(universal_1, universal_2, universal_3, universal_4, universal_5, universal_6), + gram_negative = c(GramNeg_1, GramNeg_2, GramNeg_3, GramNeg_4, GramNeg_5, GramNeg_6), + gram_positive = c(GramPos_1, GramPos_2, GramPos_3, GramPos_4, GramPos_5, GramPos_6), + antifungal = NULL, + only_rsi_columns = FALSE, + ...) +} + +#' @name AMR-deprecated +#' @export +key_antibiotics_equal <- function(y, + z, + type = "keyantimicrobials", + ignore_I = TRUE, + points_threshold = 2, + info = FALSE, + na.rm = TRUE, + ...) { + + .Deprecated(old = "key_antibiotics_equal()", + new = "antimicrobials_equal()", + package = "AMR") + + antimicrobials_equal(y = y, + z = z, + type = type, + ignore_I = ignore_I, + points_threshold = points_threshold, + info = info) +} diff --git a/R/first_isolate.R b/R/first_isolate.R index 3a2c8b5c..fe5c2648 100755 --- a/R/first_isolate.R +++ b/R/first_isolate.R @@ -34,62 +34,91 @@ #' @param col_testcode column name of the test codes. Use `col_testcode = NULL` to **not** exclude certain test codes (such as test codes for screening). In that case `testcodes_exclude` will be ignored. #' @param col_specimen column name of the specimen type or group #' @param col_icu column name of the logicals (`TRUE`/`FALSE`) whether a ward or department is an Intensive Care Unit (ICU) -#' @param col_keyantibiotics column name of the key antibiotics to determine first (weighted) isolates, see [key_antibiotics()]. Defaults to the first column that starts with 'key' followed by 'ab' or 'antibiotics' (case insensitive). Use `col_keyantibiotics = FALSE` to prevent this. Can also be the output of [key_antibiotics()]. +#' @param col_keyantimicrobials (only useful when `method = "phenotype-based"`) column name of the key antimicrobials to determine first (weighted) isolates, see [key_antimicrobials()]. Defaults to the first column that starts with 'key' followed by 'ab' or 'antibiotics' or 'antimicrobials' (case insensitive). Use `col_keyantimicrobials = FALSE` to prevent this. Can also be the output of [key_antimicrobials()]. #' @param episode_days episode in days after which a genus/species combination will be determined as 'first isolate' again. The default of 365 days is based on the guideline by CLSI, see *Source*. #' @param testcodes_exclude character vector with test codes that should be excluded (case-insensitive) #' @param icu_exclude logical to indicate whether ICU isolates should be excluded (rows with value `TRUE` in the column set with `col_icu`) #' @param specimen_group value in the column set with `col_specimen` to filter on -#' @param type type to determine weighed isolates; can be `"keyantibiotics"` or `"points"`, see *Details* -#' @param ignore_I logical to indicate whether antibiotic interpretations with `"I"` will be ignored when `type = "keyantibiotics"`, see *Details* -#' @param points_threshold points until the comparison of key antibiotics will lead to inclusion of an isolate when `type = "points"`, see *Details* +#' @param type type to determine weighed isolates; can be `"keyantimicrobials"` or `"points"`, see *Details* +#' @param method the algorithm to apply, either `"phenotype-based"`, `"episode-based"`, `"patient-based"` or `"isolate-based"` (can be abbreviated), see *Details* +#' @param ignore_I logical to indicate whether antibiotic interpretations with `"I"` will be ignored when `type = "keyantimicrobials"`, see *Details* +#' @param points_threshold minimum number of points to require before differences in the antibiogram will lead to inclusion of an isolate when `type = "points"`, see *Details* #' @param info a [logical] to indicate info should be printed, defaults to `TRUE` only in interactive mode #' @param include_unknown logical to indicate whether 'unknown' microorganisms should be included too, i.e. microbial code `"UNKNOWN"`, which defaults to `FALSE`. For WHONET users, this means that all records with organism code `"con"` (*contamination*) will be excluded at default. Isolates with a microbial ID of `NA` will always be excluded as first isolate. #' @param include_untested_rsi logical to indicate whether also rows without antibiotic results are still eligible for becoming a first isolate. Use `include_untested_rsi = FALSE` to always return `FALSE` for such rows. This checks the data set for columns of class `` and consequently requires transforming columns with antibiotic results using [as.rsi()] first. -#' @param ... arguments passed on to [first_isolate()] when using [filter_first_isolate()], or arguments passed on to [key_antibiotics()] when using [filter_first_weighted_isolate()] +#' @param ... arguments passed on to [first_isolate()] when using [filter_first_isolate()], or arguments passed on to [key_antimicrobials()] otherwise (such as `universal`, `gram_negative`, `gram_positive`) #' @details +#' To conduct epidemiological analyses on antimicrobial resistance data, only so-called first isolates should be included to prevent overestimation and underestimation of antimicrobial resistance. Different algorithms can be used to do so, see below. +#' #' These functions are context-aware. This means that then the `x` argument can be left blank, see *Examples*. #' #' The [first_isolate()] function is a wrapper around the [is_new_episode()] function, but more efficient for data sets containing microorganism codes or names. #' #' All isolates with a microbial ID of `NA` will be excluded as first isolate. #' -#' ## Why this is so Important -#' To conduct an analysis of antimicrobial resistance, you should only include the first isolate of every patient per episode [(Hindler *et al.* 2007)](https://pubmed.ncbi.nlm.nih.gov/17304462/). If you would not do this, you could easily get an overestimate or underestimate of the resistance of an antibiotic. Imagine that a patient was admitted with an MRSA and that it was found in 5 different blood cultures the following week. The resistance percentage of oxacillin of all *S. aureus* isolates would be overestimated, because you included this MRSA more than once. It would be [selection bias](https://en.wikipedia.org/wiki/Selection_bias). -#' -#' ## `filter_*()` Shortcuts -#' -#' The functions [filter_first_isolate()] and [filter_first_weighted_isolate()] are helper functions to quickly filter on first isolates. +#' ## Different algorithms #' -#' The function [filter_first_isolate()] is essentially equal to either: +#' According to Hindler *et al.* (2007, \doi{10.1086/511864}), there are different algorithms to select first isolates with increasing reliability: isolate-based, patient-based, episode-based and phenotype-based. All algorithms select on a combination of the taxonomic genus and species (not subspecies). #' -#' ``` -#' x[first_isolate(x, ...), ] -#' -#' x %>% filter(first_isolate(...)) -#' ``` +#' All mentioned algorithms are covered in the [first_isolate()] function: #' -#' The function [filter_first_weighted_isolate()] is essentially equal to: #' -#' ``` -#' x %>% -#' mutate(keyab = key_antibiotics(.)) %>% -#' mutate(only_weighted_firsts = first_isolate(x, -#' col_keyantibiotics = "keyab", ...)) %>% -#' filter(only_weighted_firsts == TRUE) %>% -#' select(-only_weighted_firsts, -keyab) -#' ``` -#' @section Key Antibiotics: -#' There are two ways to determine whether isolates can be included as first weighted isolates which will give generally the same results: -#' -#' 1. Using `type = "keyantibiotics"` and argument `ignore_I` +#' | **Algorithm** | **Function to apply** | +#' |--------------------------------------------------|-------------------------------------------------------| +#' | Isolate-based | `first_isolate(x, method = "isolate-based")` | +#' | *(= all isolates)* | | +#' | | | +#' | | | +#' | Patient-based | `first_isolate(x, method = "patient-based")` | +#' | *(= first isolate per patient)* | | +#' | | | +#' | | | +#' | Episode-based | `first_isolate(x, method = "episode-based")`, or: | +#' | *(= first isolate per episode)* | | +#' | - 7-Day interval from initial isolate | - `first_isolate(x, method = "e", episode_days = 7)` | +#' | - 30-Day interval from initial isolate | - `first_isolate(x, method = "e", episode_days = 30)` | +#' | | | +#' | | | +#' | Phenotype-based | `first_isolate(x, method = "phenotype-based")`, or: | +#' | *(= first isolate per phenotype)* | | +#' | - Major difference in any antimicrobial result | - `first_isolate(x, type = "points")` | +#' | - Any difference in key antimicrobial results | - `first_isolate(x, type = "keyantimicrobials")` | #' -#' Any difference from S to R (or vice versa) will (re)select an isolate as a first weighted isolate. With `ignore_I = FALSE`, also differences from I to S|R (or vice versa) will lead to this. This is a reliable method and 30-35 times faster than method 2. Read more about this in the [key_antibiotics()] function. +#' ### Isolate-based +#' +#' This algorithm does not require any selection, as all isolates should be included. It does, however, respect all arguments set in the [first_isolate()] function. For example, the default setting for `include_unknown` (`FALSE`) will omit selection of rows without a microbial ID. +#' +#' ### Patient-based +#' +#' To include every genus-species combination per patient once, set the `episode_days` to `Inf`. Although often inappropriate, this algorithm makes sure that no duplicate isolates are selected from the same patient. +#' +#' ### Episode-based +#' +#' To include every genus-species combination per patient episode once, set the `episode_days` to a sensible number of days. Depending on the type of analysis, this could be 14, 30, 60 or 365. Short episodes are common for analysing specific hospital or ward data, long episodes are common for analysing regional and national data. +#' +#' This is the most common algorithm to correct for duplicate isolates. Patients are categorised into episodes based on their ID and dates (e.g., the date of specimen receipt or laboratory result). While this is a common algorithm, it does not take into account antimicrobial test results. This means that e.g. a methicillin-resistant *Staphylococcus aureus* (MRSA) isolate cannot be differentiated from a wildtype *Staphylococcus aureus* isolate. +#' +#' ### Phenotype-based +#' +#' This is a more reliable algorithm, since it also *weighs* the antibiogram (antimicrobial test results) yielding so-called 'first weighted isolates'. There are two different methods to weigh the antibiogram: +#' +#' 1. Using `type = "points"` and argument `points_threshold` +#' +#' This method weighs *all* antimicrobial agents available in the data set. Any difference from I to S or R (or vice versa) counts as 0.5 points, a difference from S to R (or vice versa) counts as 1 point. When the sum of points exceeds `points_threshold`, which defaults to `2`, an isolate will be selected as a first weighted isolate. #' -#' 2. Using `type = "points"` and argument `points_threshold` +#' All antimicrobials are internally selected using the [all_antimicrobials()] function. The output of this function does not need to be passed to the [first_isolate()] function. #' -#' A difference from I to S|R (or vice versa) means 0.5 points, a difference from S to R (or vice versa) means 1 point. When the sum of points exceeds `points_threshold`, which defaults to `2`, an isolate will be (re)selected as a first weighted isolate. +#' +#' 2. Using `type = "keyantimicrobials"` and argument `ignore_I` +#' +#' This method only weighs specific antimicrobial agents, called *key antimicrobials*. Any difference from S to R (or vice versa) in these key antimicrobials will select an isolate as a first weighted isolate. With `ignore_I = FALSE`, also differences from I to S or R (or vice versa) will lead to this. +#' +#' Key antimicrobials are internally selected using the [key_antimicrobials()] function, but can also be added manually as a variable to the data and set in the `col_keyantimicrobials` argument. Another option is to pass the output of the [key_antimicrobials()] function directly to the `col_keyantimicrobials` argument. +#' +#' +#' The default algorithm is phenotype-based (using `type = "points"`) and episode-based (using `episode_days = 365`). This makes sure that every genus-species combination is selected per patient once per year, while taking into account all antimicrobial test results. #' @rdname first_isolate -#' @seealso [key_antibiotics()] +#' @seealso [key_antimicrobials()] #' @export #' @return A [`logical`] vector #' @source Methodology of this function is strictly based on: @@ -101,7 +130,6 @@ #' # See ?example_isolates. #' #' example_isolates[first_isolate(example_isolates), ] -#' #' \donttest{ #' # faster way, only works in R 3.2 and later: #' example_isolates[first_isolate(), ] @@ -139,7 +167,7 @@ #' #' # Have a look at A and B. #' # B is more reliable because every isolate is counted only once. -#' # Gentamicin resistance in hospital D appears to be 3.7% higher than +#' # Gentamicin resistance in hospital D appears to be 4.2% higher than #' # when you (erroneously) would have used all isolates for analysis. #' } #' } @@ -150,18 +178,32 @@ first_isolate <- function(x = NULL, col_testcode = NULL, col_specimen = NULL, col_icu = NULL, - col_keyantibiotics = NULL, + col_keyantimicrobials = NULL, episode_days = 365, testcodes_exclude = NULL, icu_exclude = FALSE, specimen_group = NULL, - type = "keyantibiotics", + type = "points", + method = c("phenotype-based", "episode-based", "patient-based", "isolate-based"), ignore_I = TRUE, points_threshold = 2, info = interactive(), include_unknown = FALSE, include_untested_rsi = TRUE, ...) { + + dots <- unlist(list(...)) + if (length(dots) != 0) { + # backwards compatibility with old arguments + dots.names <- names(dots) + if ("filter_specimen" %in% dots.names) { + specimen_group <- dots[which(dots.names == "filter_specimen")] + } + if ("col_keyantibiotics" %in% dots.names) { + col_keyantimicrobials <- dots[which(dots.names == "col_keyantibiotics")] + } + } + if (is_null_or_grouped_tbl(x)) { # when `x` is left blank, auto determine it (get_current_data() also contains dplyr::cur_data_all()) # is also fix for using a grouped df as input (a dot as first argument) @@ -177,15 +219,25 @@ first_isolate <- function(x = NULL, } meet_criteria(col_specimen, allow_class = "character", has_length = 1, allow_NULL = TRUE, is_in = colnames(x)) meet_criteria(col_icu, allow_class = "character", has_length = 1, allow_NULL = TRUE, is_in = colnames(x)) - if (length(col_keyantibiotics) > 1) { - meet_criteria(col_keyantibiotics, allow_class = "character", has_length = nrow(x)) - x$keyabcol <- col_keyantibiotics - col_keyantibiotics <- "keyabcol" + # method + if (missing(method)) { + method <- method[1L] + } + meet_criteria(method, allow_class = "character", has_length = 1, is_in = c("phenotype-based", "episode-based", "patient-based", "isolate-based", "p", "e", "i")) + # key antimicrobials + if (length(col_keyantimicrobials) > 1) { + meet_criteria(col_keyantimicrobials, allow_class = "character", has_length = nrow(x)) + x$keyabcol <- col_keyantimicrobials + col_keyantimicrobials <- "keyabcol" } else { - if (isFALSE(col_keyantibiotics)) { - col_keyantibiotics <- NULL + if (isFALSE(col_keyantimicrobials)) { + col_keyantimicrobials <- NULL + # method cannot be phenotype-based anymore + if (method %in% c("phenotype-based", "p")) { + method <- "episode-based" + } } - meet_criteria(col_keyantibiotics, allow_class = "character", has_length = 1, allow_NULL = TRUE, is_in = colnames(x)) + meet_criteria(col_keyantimicrobials, allow_class = "character", has_length = 1, allow_NULL = TRUE, is_in = colnames(x)) } meet_criteria(episode_days, allow_class = c("numeric", "integer"), has_length = 1, is_positive = TRUE, is_finite = FALSE) meet_criteria(testcodes_exclude, allow_class = "character", allow_NULL = TRUE) @@ -198,21 +250,23 @@ first_isolate <- function(x = NULL, meet_criteria(include_unknown, allow_class = "logical", has_length = 1) meet_criteria(include_untested_rsi, allow_class = "logical", has_length = 1) + method[method == "p"] <- "phenotype-based" + method[method == "e"] <- "episode-based" + method[method == "i"] <- "isolate-based" + if (info == TRUE) { + message_(paste0("Determining first isolates using the '", font_bold(method), "' method", + ifelse(method %in% c("episode-based", "phenotype-based"), + ifelse(is.infinite(episode_days), + " without a specified episode length", + paste(" and an episode length of", episode_days, "days")), + "")), + as_note = FALSE, + add_fn = font_black) + } + # remove data.table, grouping from tibbles, etc. x <- as.data.frame(x, stringsAsFactors = FALSE) - dots <- unlist(list(...)) - if (length(dots) != 0) { - # backwards compatibility with old arguments - dots.names <- names(dots) - if ("filter_specimen" %in% dots.names) { - specimen_group <- dots[which(dots.names == "filter_specimen")] - } - if ("tbl" %in% dots.names) { - x <- dots[which(dots.names == "tbl")] - } - } - # try to find columns based on type # -- mo if (is.null(col_mo)) { @@ -220,6 +274,37 @@ first_isolate <- function(x = NULL, stop_if(is.null(col_mo), "`col_mo` must be set") } + # methods ---- + if (method == "isolate-based") { + episode_days <- Inf + col_keyantimicrobials <- NULL + x$dummy_dates <- Sys.Date() + col_date <- "dummy_dates" + x$dummy_patients <- paste("dummy", seq_len(nrow(x))) # all 'patients' must be unique + col_patient_id <- "dummy_patients" + } else if (method == "patient-based") { + episode_days <- Inf + col_keyantimicrobials <- NULL + } else if (method == "episode-based") { + col_keyantimicrobials <- NULL + } else if (method == "phenotype-based") { + if (missing(type) & !is.null(col_keyantimicrobials)) { + # type = "points" is default, but not set explicitly, while col_keyantimicrobials is + type <- "keyantimicrobials" + } + if (type == "points") { + x$keyantimicrobials <- all_antimicrobials(x, only_rsi_columns = FALSE) + col_keyantimicrobials <- "keyantimicrobials" + } else if (type == "keyantimicrobials" & is.null(col_keyantimicrobials)) { + col_keyantimicrobials <- search_type_in_df(x = x, type = "keyantibiotics") + if (is.null(col_keyantimicrobials)) { + # still not found as a column, create it ourselves + x$keyantimicrobials <- key_antimicrobials(x, only_rsi_columns = FALSE, col_mo = col_mo, ...) + col_keyantimicrobials <- "keyantimicrobials" + } + } + } + # -- date if (is.null(col_date)) { col_date <- search_type_in_df(x = x, type = "date") @@ -238,11 +323,6 @@ first_isolate <- function(x = NULL, } stop_if(is.null(col_patient_id), "`col_patient_id` must be set") } - - # -- key antibiotics - if (is.null(col_keyantibiotics)) { - col_keyantibiotics <- search_type_in_df(x = x, type = "keyantibiotics") - } # -- specimen if (is.null(col_specimen) & !is.null(specimen_group)) { @@ -262,7 +342,7 @@ first_isolate <- function(x = NULL, check_columns_existance(col_mo) check_columns_existance(col_testcode) check_columns_existance(col_icu) - check_columns_existance(col_keyantibiotics) + check_columns_existance(col_keyantimicrobials) # convert dates to Date dates <- as.Date(x[, col_date, drop = TRUE]) @@ -281,7 +361,7 @@ first_isolate <- function(x = NULL, } # remove testcodes if (!is.null(testcodes_exclude) & info == TRUE) { - message_("[Criterion] Exclude test codes: ", toString(paste0("'", testcodes_exclude, "'")), + message_("Excluding test codes: ", toString(paste0("'", testcodes_exclude, "'")), add_fn = font_black, as_note = FALSE) } @@ -294,13 +374,13 @@ first_isolate <- function(x = NULL, if (!is.null(specimen_group)) { check_columns_existance(col_specimen, x) if (info == TRUE) { - message_("[Criterion] Exclude other than specimen group '", specimen_group, "'", + message_("Excluding other than specimen group '", specimen_group, "'", add_fn = font_black, as_note = FALSE) } } - if (!is.null(col_keyantibiotics)) { - x$newvar_key_ab <- x[, col_keyantibiotics, drop = TRUE] + if (!is.null(col_keyantimicrobials)) { + x$newvar_key_ab <- x[, col_keyantimicrobials, drop = TRUE] } if (is.null(testcodes_exclude)) { @@ -341,7 +421,7 @@ first_isolate <- function(x = NULL, } if (row.start == row.end) { if (info == TRUE) { - message_("=> Found ", font_bold("1 isolate"), ", as the data only contained 1 row", + message_("=> Found ", font_bold("1 first isolate"), ", as the data only contained 1 row", add_fn = font_black, as_note = FALSE) } @@ -349,7 +429,7 @@ first_isolate <- function(x = NULL, } if (length(c(row.start:row.end)) == pm_n_distinct(x[c(row.start:row.end), col_mo, drop = TRUE])) { if (info == TRUE) { - message_("=> Found ", font_bold(paste(length(c(row.start:row.end)), "isolates")), + message_("=> Found ", font_bold(paste(length(c(row.start:row.end)), "first isolates")), ", as all isolates were different microorganisms", add_fn = font_black, as_note = FALSE) @@ -378,18 +458,18 @@ first_isolate <- function(x = NULL, })) weighted.notice <- "" - if (!is.null(col_keyantibiotics)) { + if (!is.null(col_keyantimicrobials)) { weighted.notice <- "weighted " if (info == TRUE) { - if (type == "keyantibiotics") { - message_("[Criterion] Base inclusion on key antibiotics, ", + if (type == "keyantimicrobials") { + message_("Basing inclusion on key antimicrobials, ", ifelse(ignore_I == FALSE, "not ", ""), "ignoring I", add_fn = font_black, as_note = FALSE) } if (type == "points") { - message_("[Criterion] Base inclusion on key antibiotics, using points threshold of " + message_("Basing inclusion on all antimicrobial results, using a points threshold of " , points_threshold, add_fn = font_black, as_note = FALSE) @@ -397,12 +477,12 @@ first_isolate <- function(x = NULL, } type_param <- type - x$other_key_ab <- !key_antibiotics_equal(y = x$newvar_key_ab, - z = pm_lag(x$newvar_key_ab), - type = type_param, - ignore_I = ignore_I, - points_threshold = points_threshold, - na.rm = TRUE) + x$other_key_ab <- !antimicrobials_equal(y = x$newvar_key_ab, + z = pm_lag(x$newvar_key_ab), + type = type_param, + ignore_I = ignore_I, + points_threshold = points_threshold, + na.rm = TRUE) # with key antibiotics x$newvar_first_isolate <- pm_if_else(x$newvar_row_index_sorted >= row.start & x$newvar_row_index_sorted <= row.end & @@ -429,12 +509,12 @@ first_isolate <- function(x = NULL, } if (!is.null(col_icu)) { if (icu_exclude == TRUE) { - message_("[Criterion] Exclude isolates from ICU.", + message_("Excluding isolates from ICU.", add_fn = font_black, as_note = FALSE) x[which(as.logical(x[, col_icu, drop = TRUE])), "newvar_first_isolate"] <- FALSE } else { - message_("[Criterion] Include isolates from ICU.", + message_("Including isolates from ICU.", add_fn = font_black, as_note = FALSE) } @@ -459,7 +539,8 @@ first_isolate <- function(x = NULL, paste0('"', x, '"') } }) - cat("\nGroup: ", paste0(names(group), " = ", group, collapse = ", "), "\n", sep = "") + message_("\nGroup: ", paste0(names(group), " = ", group, collapse = ", "), "\n", + as_note = FALSE) } } } @@ -508,11 +589,11 @@ first_isolate <- function(x = NULL, if (p_found_total != p_found_scope) { msg_txt <- paste0("=> Found ", font_bold(paste0(n_found, " first ", weighted.notice, "isolates")), - " (", p_found_scope, " within scope and ", p_found_total, " of total where a microbial ID was available)") + " (", method, ", ", p_found_scope, " within scope and ", p_found_total, " of total where a microbial ID was available)") } else { msg_txt <- paste0("=> Found ", font_bold(paste0(n_found, " first ", weighted.notice, "isolates")), - " (", p_found_total, " of total where a microbial ID was available)") + " (", method, ", ", p_found_total, " of total where a microbial ID was available)") } message_(msg_txt, add_fn = font_black, as_note = FALSE) } @@ -527,6 +608,7 @@ filter_first_isolate <- function(x = NULL, col_date = NULL, col_patient_id = NULL, col_mo = NULL, + method = "episode-based", ...) { if (is_null_or_grouped_tbl(x)) { # when `x` is left blank, auto determine it (get_current_data() also contains dplyr::cur_data_all()) @@ -537,11 +619,13 @@ filter_first_isolate <- function(x = NULL, meet_criteria(col_date, allow_class = "character", has_length = 1, allow_NULL = TRUE, is_in = colnames(x)) meet_criteria(col_patient_id, allow_class = "character", has_length = 1, allow_NULL = TRUE, is_in = colnames(x)) meet_criteria(col_mo, allow_class = "character", has_length = 1, allow_NULL = TRUE, is_in = colnames(x)) - + meet_criteria(method, allow_class = "character", has_length = 1, is_in = c("phenotype-based", "episode-based", "patient-based", "isolate-based")) + subset(x, first_isolate(x = x, col_date = col_date, col_patient_id = col_patient_id, col_mo = col_mo, + method = method, ...)) } @@ -551,7 +635,7 @@ filter_first_weighted_isolate <- function(x = NULL, col_date = NULL, col_patient_id = NULL, col_mo = NULL, - col_keyantibiotics = NULL, + method = "phenotype-based", ...) { if (is_null_or_grouped_tbl(x)) { # when `x` is left blank, auto determine it (get_current_data() also contains dplyr::cur_data_all()) @@ -562,22 +646,12 @@ filter_first_weighted_isolate <- function(x = NULL, meet_criteria(col_date, allow_class = "character", has_length = 1, allow_NULL = TRUE, is_in = colnames(x)) meet_criteria(col_patient_id, allow_class = "character", has_length = 1, allow_NULL = TRUE, is_in = colnames(x)) meet_criteria(col_mo, allow_class = "character", has_length = 1, allow_NULL = TRUE, is_in = colnames(x)) - meet_criteria(col_keyantibiotics, allow_class = "character", has_length = 1, allow_NULL = TRUE, is_in = colnames(x)) - - y <- x - if (is.null(col_keyantibiotics)) { - # first try to look for it - col_keyantibiotics <- search_type_in_df(x = x, type = "keyantibiotics") - # still NULL? Then create it since we are calling filter_first_WEIGHTED_isolate() - if (is.null(col_keyantibiotics)) { - y$keyab <- suppressMessages(key_antibiotics(x, - col_mo = col_mo, - ...)) - col_keyantibiotics <- "keyab" - } - } + meet_criteria(method, allow_class = "character", has_length = 1, is_in = c("phenotype-based", "episode-based", "patient-based", "isolate-based")) - subset(x, first_isolate(x = y, + subset(x, first_isolate(x = x, col_date = col_date, - col_patient_id = col_patient_id)) + col_patient_id = col_patient_id, + col_mo = col_mo, + method = method, + ...)) } diff --git a/R/guess_ab_col.R b/R/guess_ab_col.R index 77571f35..c062c577 100755 --- a/R/guess_ab_col.R +++ b/R/guess_ab_col.R @@ -102,6 +102,7 @@ get_column_abx <- function(x, verbose = FALSE, info = TRUE, only_rsi_columns = FALSE, + sort = TRUE, ...) { meet_criteria(x, allow_class = "data.frame") meet_criteria(soft_dependencies, allow_class = "character", allow_NULL = TRUE) @@ -109,6 +110,7 @@ get_column_abx <- function(x, meet_criteria(verbose, allow_class = "logical", has_length = 1) meet_criteria(info, allow_class = "logical", has_length = 1) meet_criteria(only_rsi_columns, allow_class = "logical", has_length = 1) + meet_criteria(sort, allow_class = "logical", has_length = 1) if (info == TRUE) { message_("Auto-guessing columns suitable for analysis", appendLF = FALSE, as_note = FALSE) @@ -186,11 +188,15 @@ get_column_abx <- function(x, } # sort on name - x <- x[order(names(x), x)] + if (sort == TRUE) { + x <- x[order(names(x), x)] + } duplicates <- c(x[duplicated(x)], x[duplicated(names(x))]) duplicates <- duplicates[unique(names(duplicates))] x <- c(x[!names(x) %in% names(duplicates)], duplicates) - x <- x[order(names(x), x)] + if (sort == TRUE) { + x <- x[order(names(x), x)] + } # succeeded with auto-guessing if (info == TRUE) { diff --git a/R/key_antibiotics.R b/R/key_antibiotics.R deleted file mode 100755 index ac8bce5a..00000000 --- a/R/key_antibiotics.R +++ /dev/null @@ -1,360 +0,0 @@ -# ==================================================================== # -# TITLE # -# Antimicrobial Resistance (AMR) Data Analysis for R # -# # -# SOURCE # -# https://github.com/msberends/AMR # -# # -# LICENCE # -# (c) 2018-2021 Berends MS, Luz CF et al. # -# Developed at the University of Groningen, the Netherlands, in # -# collaboration with non-profit organisations Certe Medical # -# Diagnostics & Advice, and University Medical Center Groningen. # -# # -# This R package is free software; you can freely use and distribute # -# it for both personal and commercial purposes under the terms of the # -# GNU General Public License version 2.0 (GNU GPL-2), as published by # -# the Free Software Foundation. # -# We created this package for both routine data analysis and academic # -# research and it was publicly released in the hope that it will be # -# useful, but it comes WITHOUT ANY WARRANTY OR LIABILITY. # -# # -# Visit our website for the full manual and a complete tutorial about # -# how to conduct AMR data analysis: https://msberends.github.io/AMR/ # -# ==================================================================== # - -#' Key Antibiotics for First (Weighted) Isolates -#' -#' These function can be used to determine first isolates (see [first_isolate()]). Using key antibiotics to determine first isolates is more reliable than without key antibiotics. These selected isolates can then be called first 'weighted' isolates. -#' @inheritSection lifecycle Stable Lifecycle -#' @param x a [data.frame] with antibiotics columns, like `AMX` or `amox`. Can be left blank to determine automatically -#' @param y,z character vectors to compare -#' @inheritParams first_isolate -#' @param universal_1,universal_2,universal_3,universal_4,universal_5,universal_6 column names of **broad-spectrum** antibiotics, case-insensitive. See details for which antibiotics will be used at default (which are guessed with [guess_ab_col()]). -#' @param GramPos_1,GramPos_2,GramPos_3,GramPos_4,GramPos_5,GramPos_6 column names of antibiotics for **Gram-positives**, case-insensitive. See details for which antibiotics will be used at default (which are guessed with [guess_ab_col()]). -#' @param GramNeg_1,GramNeg_2,GramNeg_3,GramNeg_4,GramNeg_5,GramNeg_6 column names of antibiotics for **Gram-negatives**, case-insensitive. See details for which antibiotics will be used at default (which are guessed with [guess_ab_col()]). -#' @param warnings give a warning about missing antibiotic columns (they will be ignored) -#' @param ... other arguments passed on to functions -#' @details -#' The [key_antibiotics()] function is context-aware. This means that then the `x` argument can be left blank, see *Examples*. -#' -#' The function [key_antibiotics()] returns a character vector with 12 antibiotic results for every isolate. These isolates can then be compared using [key_antibiotics_equal()], to check if two isolates have generally the same antibiogram. Missing and invalid values are replaced with a dot (`"."`) by [key_antibiotics()] and ignored by [key_antibiotics_equal()]. -#' -#' The [first_isolate()] function only uses this function on the same microbial species from the same patient. Using this, e.g. an MRSA will be included after a susceptible *S. aureus* (MSSA) is found within the same patient episode. Without key antibiotic comparison it would not. See [first_isolate()] for more info. -#' -#' At default, the antibiotics that are used for **Gram-positive bacteria** are: -#' - Amoxicillin -#' - Amoxicillin/clavulanic acid -#' - Cefuroxime -#' - Piperacillin/tazobactam -#' - Ciprofloxacin -#' - Trimethoprim/sulfamethoxazole -#' - Vancomycin -#' - Teicoplanin -#' - Tetracycline -#' - Erythromycin -#' - Oxacillin -#' - Rifampin -#' -#' At default the antibiotics that are used for **Gram-negative bacteria** are: -#' - Amoxicillin -#' - Amoxicillin/clavulanic acid -#' - Cefuroxime -#' - Piperacillin/tazobactam -#' - Ciprofloxacin -#' - Trimethoprim/sulfamethoxazole -#' - Gentamicin -#' - Tobramycin -#' - Colistin -#' - Cefotaxime -#' - Ceftazidime -#' - Meropenem -#' -#' The function [key_antibiotics_equal()] checks the characters returned by [key_antibiotics()] for equality, and returns a [`logical`] vector. -#' @inheritSection first_isolate Key Antibiotics -#' @rdname key_antibiotics -#' @export -#' @seealso [first_isolate()] -#' @inheritSection AMR Read more on Our Website! -#' @examples -#' # `example_isolates` is a data set available in the AMR package. -#' # See ?example_isolates. -#' -#' # output of the `key_antibiotics()` function could be like this: -#' strainA <- "SSSRR.S.R..S" -#' strainB <- "SSSIRSSSRSSS" -#' -#' # those strings can be compared with: -#' key_antibiotics_equal(strainA, strainB) -#' # TRUE, because I is ignored (as well as missing values) -#' -#' key_antibiotics_equal(strainA, strainB, ignore_I = FALSE) -#' # FALSE, because I is not ignored and so the 4th character differs -#' -#' \donttest{ -#' if (require("dplyr")) { -#' # set key antibiotics to a new variable -#' my_patients <- example_isolates %>% -#' mutate(keyab = key_antibiotics()) %>% # no need to define `x` -#' mutate( -#' # now calculate first isolates -#' first_regular = first_isolate(col_keyantibiotics = FALSE), -#' # and first WEIGHTED isolates -#' first_weighted = first_isolate(col_keyantibiotics = "keyab") -#' ) -#' -#' # Check the difference, in this data set it results in a lot more isolates: -#' sum(my_patients$first_regular, na.rm = TRUE) -#' sum(my_patients$first_weighted, na.rm = TRUE) -#' } -#' } -key_antibiotics <- function(x = NULL, - col_mo = NULL, - universal_1 = guess_ab_col(x, "amoxicillin"), - universal_2 = guess_ab_col(x, "amoxicillin/clavulanic acid"), - universal_3 = guess_ab_col(x, "cefuroxime"), - universal_4 = guess_ab_col(x, "piperacillin/tazobactam"), - universal_5 = guess_ab_col(x, "ciprofloxacin"), - universal_6 = guess_ab_col(x, "trimethoprim/sulfamethoxazole"), - GramPos_1 = guess_ab_col(x, "vancomycin"), - GramPos_2 = guess_ab_col(x, "teicoplanin"), - GramPos_3 = guess_ab_col(x, "tetracycline"), - GramPos_4 = guess_ab_col(x, "erythromycin"), - GramPos_5 = guess_ab_col(x, "oxacillin"), - GramPos_6 = guess_ab_col(x, "rifampin"), - GramNeg_1 = guess_ab_col(x, "gentamicin"), - GramNeg_2 = guess_ab_col(x, "tobramycin"), - GramNeg_3 = guess_ab_col(x, "colistin"), - GramNeg_4 = guess_ab_col(x, "cefotaxime"), - GramNeg_5 = guess_ab_col(x, "ceftazidime"), - GramNeg_6 = guess_ab_col(x, "meropenem"), - warnings = TRUE, - ...) { - if (is_null_or_grouped_tbl(x)) { - # when `x` is left blank, auto determine it (get_current_data() also contains dplyr::cur_data_all()) - # is also fix for using a grouped df as input (a dot as first argument) - x <- tryCatch(get_current_data(arg_name = "x", call = -2), error = function(e) x) - } - meet_criteria(x, allow_class = "data.frame") # also checks dimensions to be >0 - meet_criteria(col_mo, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE, is_in = colnames(x)) - meet_criteria(universal_1, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(universal_2, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(universal_3, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(universal_4, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(universal_5, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(universal_6, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(GramPos_1, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(GramPos_2, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(GramPos_3, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(GramPos_4, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(GramPos_5, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(GramPos_6, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(GramNeg_1, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(GramNeg_2, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(GramNeg_3, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(GramNeg_4, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(GramNeg_5, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(GramNeg_6, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE) - meet_criteria(warnings, allow_class = "logical", has_length = 1) - - # force regular data.frame, not a tibble or data.table - x <- as.data.frame(x, stringsAsFactors = FALSE) - - dots <- unlist(list(...)) - if (length(dots) != 0) { - # backwards compatibility with old arguments - dots.names <- names(dots) - if ("info" %in% dots.names) { - warnings <- dots[which(dots.names == "info")] - } - } - - # try to find columns based on type - # -- mo - if (is.null(col_mo)) { - col_mo <- search_type_in_df(x = x, type = "mo") - } - if (is.null(col_mo)) { - warning_("No column found for `col_mo`, ignoring antimicrobial agents set for Gram-negative and Gram-positive bacteria", call = FALSE) - x$gramstain <- NA_character_ - } else { - x$gramstain <- mo_gramstain(as.mo(x[, col_mo, drop = TRUE]), language = NULL) - } - x$key_ab <- NA_character_ - - # check columns - col.list <- c(universal_1, universal_2, universal_3, universal_4, universal_5, universal_6, - GramPos_1, GramPos_2, GramPos_3, GramPos_4, GramPos_5, GramPos_6, - GramNeg_1, GramNeg_2, GramNeg_3, GramNeg_4, GramNeg_5, GramNeg_6) - check_available_columns <- function(x, col.list, warnings = TRUE) { - # check columns - col.list <- col.list[!is.na(col.list) & !is.null(col.list)] - names(col.list) <- col.list - col.list.bak <- col.list - # are they available as upper case or lower case then? - for (i in seq_len(length(col.list))) { - if (is.null(col.list[i]) | isTRUE(is.na(col.list[i]))) { - col.list[i] <- NA - } else if (toupper(col.list[i]) %in% colnames(x)) { - col.list[i] <- toupper(col.list[i]) - } else if (tolower(col.list[i]) %in% colnames(x)) { - col.list[i] <- tolower(col.list[i]) - } else if (!col.list[i] %in% colnames(x)) { - col.list[i] <- NA - } - } - if (!all(col.list %in% colnames(x))) { - if (warnings == TRUE) { - warning_("Some columns do not exist and will be ignored: ", - col.list.bak[!(col.list %in% colnames(x))] %pm>% toString(), - ".\nTHIS MAY STRONGLY INFLUENCE THE OUTCOME.", - immediate = TRUE, - call = FALSE) - } - } - col.list - } - - col.list <- check_available_columns(x = x, col.list = col.list, warnings = warnings) - universal_1 <- col.list[universal_1] - universal_2 <- col.list[universal_2] - universal_3 <- col.list[universal_3] - universal_4 <- col.list[universal_4] - universal_5 <- col.list[universal_5] - universal_6 <- col.list[universal_6] - GramPos_1 <- col.list[GramPos_1] - GramPos_2 <- col.list[GramPos_2] - GramPos_3 <- col.list[GramPos_3] - GramPos_4 <- col.list[GramPos_4] - GramPos_5 <- col.list[GramPos_5] - GramPos_6 <- col.list[GramPos_6] - GramNeg_1 <- col.list[GramNeg_1] - GramNeg_2 <- col.list[GramNeg_2] - GramNeg_3 <- col.list[GramNeg_3] - GramNeg_4 <- col.list[GramNeg_4] - GramNeg_5 <- col.list[GramNeg_5] - GramNeg_6 <- col.list[GramNeg_6] - - universal <- c(universal_1, universal_2, universal_3, - universal_4, universal_5, universal_6) - - gram_positive <- c(universal, - GramPos_1, GramPos_2, GramPos_3, - GramPos_4, GramPos_5, GramPos_6) - gram_positive <- gram_positive[!is.null(gram_positive)] - gram_positive <- gram_positive[!is.na(gram_positive)] - if (length(gram_positive) < 12 & !all(is.na(x$gramstain)) & message_not_thrown_before("key_antibiotics.grampos")) { - warning_("Only using ", length(gram_positive), " different antibiotics as key antibiotics for Gram-positives. See ?key_antibiotics.", call = FALSE) - remember_thrown_message("key_antibiotics.grampos") - } - - gram_negative <- c(universal, - GramNeg_1, GramNeg_2, GramNeg_3, - GramNeg_4, GramNeg_5, GramNeg_6) - gram_negative <- gram_negative[!is.null(gram_negative)] - gram_negative <- gram_negative[!is.na(gram_negative)] - if (length(gram_negative) < 12 & !all(is.na(x$gramstain)) & message_not_thrown_before("key_antibiotics.gramneg")) { - warning_("Only using ", length(gram_negative), " different antibiotics as key antibiotics for Gram-negatives. See ?key_antibiotics.", call = FALSE) - remember_thrown_message("key_antibiotics.gramneg") - } - - # Gram + - x$key_ab <- pm_if_else(x$gramstain == "Gram-positive", - tryCatch(apply(X = x[, gram_positive], - MARGIN = 1, - FUN = function(x) paste(x, collapse = "")), - error = function(e) paste0(rep(".", 12), collapse = "")), - as.character(x$key_ab)) - - # Gram - - x$key_ab <- pm_if_else(x$gramstain == "Gram-negative", - tryCatch(apply(X = x[, gram_negative], - MARGIN = 1, - FUN = function(x) paste(x, collapse = "")), - error = function(e) paste0(rep(".", 12), collapse = "")), - as.character(x$key_ab)) - - # format - key_abs <- toupper(gsub("(NA|NULL|[^RSIrsi])", ".", x$key_ab)) - - if (pm_n_distinct(key_abs) == 1) { - warning_("No distinct key antibiotics determined.", call = FALSE) - } - - key_abs - -} - -#' @rdname key_antibiotics -#' @param info unused - previously used to indicate whether a progress bar should print -#' @param na.rm a [logical] to indicate whether comparison with `NA` should return `FALSE` (defaults to `TRUE` for backwards compatibility) -#' @export -key_antibiotics_equal <- function(y, - z, - type = c("keyantibiotics", "points"), - ignore_I = TRUE, - points_threshold = 2, - info = FALSE, - na.rm = TRUE, - ...) { - meet_criteria(y, allow_class = "character") - meet_criteria(z, allow_class = "character") - if (length(type) == 2) { - type <- type[1L] - } - meet_criteria(type, allow_class = "character", has_length = 1, is_in = c("keyantibiotics", "points")) - meet_criteria(ignore_I, allow_class = "logical", has_length = 1) - meet_criteria(points_threshold, allow_class = c("numeric", "integer"), has_length = 1, is_positive = TRUE, is_finite = TRUE) - meet_criteria(info, allow_class = "logical", has_length = 1) - meet_criteria(na.rm, allow_class = "logical", has_length = 1) - stop_ifnot(length(y) == length(z), "length of `y` and `z` must be equal") - - key2rsi <- function(val) { - as.double(as.rsi(gsub(".", NA_character_, unlist(strsplit(val, "")), fixed = TRUE))) - } - y <- lapply(y, key2rsi) - z <- lapply(z, key2rsi) - - determine_equality <- function(a, b, type, points_threshold, ignore_I) { - if (length(a) != length(b)) { - # incomparable, so not equal - return(FALSE) - } - # ignore NAs on both sides - NA_ind <- which(is.na(a) | is.na(b)) - a[NA_ind] <- NA_real_ - b[NA_ind] <- NA_real_ - - if (type == "points") { - # count points for every single character: - # - no change is 0 points - # - I <-> S|R is 0.5 point - # - S|R <-> R|S is 1 point - # use the levels of as.rsi (S = 1, I = 2, R = 3) - (sum(abs(a - b), na.rm = TRUE) / 2) < points_threshold - } else { - if (ignore_I == TRUE) { - ind <- which(a == 2 | b == 2) # since as.double(as.rsi("I")) == 2 - a[ind] <- NA_real_ - b[ind] <- NA_real_ - } - all(a == b, na.rm = TRUE) - } - } - out <- unlist(mapply(FUN = determine_equality, - y, - z, - MoreArgs = list(type = type, - points_threshold = points_threshold, - ignore_I = ignore_I), - SIMPLIFY = FALSE, - USE.NAMES = FALSE)) - if (na.rm == FALSE) { - out[is.na(y) | is.na(z)] <- NA - } else { - # NA means not equal if `na.rm == TRUE`, as per the manual - out[is.na(y) | is.na(z)] <- FALSE - } - - out -} diff --git a/R/key_antimicrobials.R b/R/key_antimicrobials.R new file mode 100755 index 00000000..e9c63c38 --- /dev/null +++ b/R/key_antimicrobials.R @@ -0,0 +1,327 @@ +# ==================================================================== # +# TITLE # +# Antimicrobial Resistance (AMR) Data Analysis for R # +# # +# SOURCE # +# https://github.com/msberends/AMR # +# # +# LICENCE # +# (c) 2018-2021 Berends MS, Luz CF et al. # +# Developed at the University of Groningen, the Netherlands, in # +# collaboration with non-profit organisations Certe Medical # +# Diagnostics & Advice, and University Medical Center Groningen. # +# # +# This R package is free software; you can freely use and distribute # +# it for both personal and commercial purposes under the terms of the # +# GNU General Public License version 2.0 (GNU GPL-2), as published by # +# the Free Software Foundation. # +# We created this package for both routine data analysis and academic # +# research and it was publicly released in the hope that it will be # +# useful, but it comes WITHOUT ANY WARRANTY OR LIABILITY. # +# # +# Visit our website for the full manual and a complete tutorial about # +# how to conduct AMR data analysis: https://msberends.github.io/AMR/ # +# ==================================================================== # + +#' (Key) Antimicrobials for First Weighted Isolates +#' +#' These functions can be used to determine first weighted isolates by considering the phenotype for isolate selection (see [first_isolate()]). Using a phenotype-based method to determine first isolates is more reliable than methods that disregard phenotypes. +#' @inheritSection lifecycle Stable Lifecycle +#' @param x a [data.frame] with antibiotics columns, like `AMX` or `amox`. Can be left blank to determine automatically +#' @param y,z character vectors to compare +#' @inheritParams first_isolate +#' @param universal names of **broad-spectrum** antimicrobial agents, case-insensitive. Set to `NULL` to ignore. See *Details* for the default agents. +#' @param gram_negative names of antibiotic agents for **Gram-positives**, case-insensitive. Set to `NULL` to ignore. See *Details* for the default agents. +#' @param gram_positive names of antibiotic agents for **Gram-negatives**, case-insensitive. Set to `NULL` to ignore. See *Details* for the default agents. +#' @param antifungal names of antifungal agents for **fungi**, case-insensitive. Set to `NULL` to ignore. See *Details* for the default agents. +#' @param ... ignored, allows for future extensions +#' @details +#' The [key_antimicrobials()] and [all_antimicrobials()] functions are context-aware. This means that then the `x` argument can be left blank, see *Examples*. +#' +#' The function [key_antimicrobials()] returns a character vector with 12 antimicrobial results for every isolate. The function [all_antimicrobials()] returns a character vector with all antimicrobial results for every isolate. These vectors can then be compared using [antimicrobials_equal()], to check if two isolates have generally the same antibiogram. Missing and invalid values are replaced with a dot (`"."`) by [key_antimicrobials()] and ignored by [antimicrobials_equal()]. +#' +#' Please see the [first_isolate()] function how these important functions enable the 'phenotype-based' method for determination of first isolates. +#' +#' The default antimicrobial agents used for **all rows** (set in `universal`) are: +#' +#' - Ampicillin +#' - Amoxicillin/clavulanic acid +#' - Cefuroxime +#' - Ciprofloxacin +#' - Piperacillin/tazobactam +#' - Trimethoprim/sulfamethoxazole +#' +#' The default antimicrobial agents used for **Gram-negative bacteria** (set in `gram_negative`) are: +#' +#' - Cefotaxime +#' - Ceftazidime +#' - Colistin +#' - Gentamicin +#' - Meropenem +#' - Tobramycin +#' +#' The default antimicrobial agents used for **Gram-positive bacteria** (set in `gram_positive`) are: +#' +#' - Erythromycin +#' - Oxacillin +#' - Rifampin +#' - Teicoplanin +#' - Tetracycline +#' - Vancomycin +#' +#' +#' The default antimicrobial agents used for **fungi** (set in `antifungal`) are: +#' +#' - Anidulafungin +#' - Caspofungin +#' - Fluconazole +#' - Miconazole +#' - Nystatin +#' - Voriconazole +#' @rdname key_antimicrobials +#' @export +#' @seealso [first_isolate()] +#' @inheritSection AMR Read more on Our Website! +#' @examples +#' # `example_isolates` is a data set available in the AMR package. +#' # See ?example_isolates. +#' +#' # output of the `key_antimicrobials()` function could be like this: +#' strainA <- "SSSRR.S.R..S" +#' strainB <- "SSSIRSSSRSSS" +#' +#' # those strings can be compared with: +#' antimicrobials_equal(strainA, strainB, type = "keyantimicrobials") +#' # TRUE, because I is ignored (as well as missing values) +#' +#' antimicrobials_equal(strainA, strainB, type = "keyantimicrobials", ignore_I = FALSE) +#' # FALSE, because I is not ignored and so the 4th character differs +#' +#' \donttest{ +#' if (require("dplyr")) { +#' # set key antibiotics to a new variable +#' my_patients <- example_isolates %>% +#' mutate(keyab = key_antimicrobials(antifungal = NULL)) %>% # no need to define `x` +#' mutate( +#' # now calculate first isolates +#' first_regular = first_isolate(col_keyantimicrobials = FALSE), +#' # and first WEIGHTED isolates +#' first_weighted = first_isolate(col_keyantimicrobials = "keyab") +#' ) +#' +#' # Check the difference, in this data set it results in more isolates: +#' sum(my_patients$first_regular, na.rm = TRUE) +#' sum(my_patients$first_weighted, na.rm = TRUE) +#' } +#' } +key_antimicrobials <- function(x = NULL, + col_mo = NULL, + universal = c("ampicillin", "amoxicillin/clavulanic acid", "cefuroxime", + "piperacillin/tazobactam", "ciprofloxacin", "trimethoprim/sulfamethoxazole"), + gram_negative = c("gentamicin", "tobramycin", "colistin", + "cefotaxime", "ceftazidime", "meropenem"), + gram_positive = c("vancomycin", "teicoplanin", "tetracycline", + "erythromycin", "oxacillin", "rifampin"), + antifungal = c("anidulafungin", "caspofungin", "fluconazole", + "miconazole", "nystatin", "voriconazole"), + only_rsi_columns = FALSE, + ...) { + if (is_null_or_grouped_tbl(x)) { + # when `x` is left blank, auto determine it (get_current_data() also contains dplyr::cur_data_all()) + # is also fix for using a grouped df as input (a dot as first argument) + x <- tryCatch(get_current_data(arg_name = "x", call = -2), error = function(e) x) + } + meet_criteria(x, allow_class = "data.frame") # also checks dimensions to be >0 + meet_criteria(col_mo, allow_class = "character", has_length = 1, allow_NULL = TRUE, allow_NA = TRUE, is_in = colnames(x)) + meet_criteria(universal, allow_class = "character", allow_NULL = TRUE) + meet_criteria(gram_negative, allow_class = "character", allow_NULL = TRUE) + meet_criteria(gram_positive, allow_class = "character", allow_NULL = TRUE) + meet_criteria(antifungal, allow_class = "character", allow_NULL = TRUE) + meet_criteria(only_rsi_columns, allow_class = "logical", has_length = 1) + + # force regular data.frame, not a tibble or data.table + x <- as.data.frame(x, stringsAsFactors = FALSE) + cols <- get_column_abx(x, info = FALSE, only_rsi_columns = only_rsi_columns) + + # try to find columns based on type + # -- mo + if (is.null(col_mo)) { + col_mo <- search_type_in_df(x = x, type = "mo", info = FALSE) + } + if (is.null(col_mo)) { + warning_("No column found for `col_mo`, ignoring antibiotics set in `gram_negative` and `gram_positive`, and antimycotics set in `antifungal`", call = FALSE) + gramstain <- NA_character_ + kingdom <- NA_character_ + } else { + x.mo <- as.mo(x[, col_mo, drop = TRUE]) + gramstain <- mo_gramstain(x.mo, language = NULL) + kingdom <- mo_kingdom(x.mo, language = NULL) + } + + AMR_string <- function(x, values, name, filter, cols = cols) { + if (is.null(values)) { + return(rep(NA_character_, length(which(filter)))) + } + + values_old_length <- length(values) + values <- as.ab(values, flag_multiple_results = FALSE, info = FALSE) + values <- cols[names(cols) %in% values] + values_new_length <- length(values) + + if (values_new_length < values_old_length & + any(filter, na.rm = TRUE) & + message_not_thrown_before(paste0("key_antimicrobials.", name))) { + warning_(ifelse(values_new_length == 0, + "No columns available ", + paste0("Only using ", values_new_length, " out of ", values_old_length, " defined columns ")), + "as key antimicrobials for ", name, "s. See ?key_antimicrobials.", + call = FALSE) + remember_thrown_message(paste0("key_antimicrobials.", name)) + } + + generate_antimcrobials_string(x[which(filter), c(universal, values), drop = FALSE]) + } + + if (is.null(universal)) { + universal <- character(0) + } else { + universal <- as.ab(universal, flag_multiple_results = FALSE, info = FALSE) + universal <- cols[names(cols) %in% universal] + } + + key_ab <- rep(NA_character_, nrow(x)) + + key_ab[which(gramstain == "Gram-negative")] <- AMR_string(x = x, + values = gram_negative, + name = "Gram-negative", + filter = gramstain == "Gram-negative", + cols = cols) + + key_ab[which(gramstain == "Gram-positive")] <- AMR_string(x = x, + values = gram_positive, + name = "Gram-positive", + filter = gramstain == "Gram-positive", + cols = cols) + + key_ab[which(kingdom == "Fungi")] <- AMR_string(x = x, + values = antifungal, + name = "antifungal", + filter = kingdom == "Fungi", + cols = cols) + + # back-up - only use `universal` + key_ab[which(is.na(key_ab))] <- AMR_string(x = x, + values = character(0), + name = "", + filter = is.na(key_ab), + cols = cols) + + if (length(unique(key_ab)) == 1) { + warning_("No distinct key antibiotics determined.", call = FALSE) + } + + key_ab +} + +#' @rdname key_antimicrobials +#' @export +all_antimicrobials <- function(x = NULL, + only_rsi_columns = FALSE, + ...) { + if (is_null_or_grouped_tbl(x)) { + # when `x` is left blank, auto determine it (get_current_data() also contains dplyr::cur_data_all()) + # is also fix for using a grouped df as input (a dot as first argument) + x <- tryCatch(get_current_data(arg_name = "x", call = -2), error = function(e) x) + } + meet_criteria(x, allow_class = "data.frame") # also checks dimensions to be >0 + meet_criteria(only_rsi_columns, allow_class = "logical", has_length = 1) + + # force regular data.frame, not a tibble or data.table + x <- as.data.frame(x, stringsAsFactors = FALSE) + cols <- get_column_abx(x, only_rsi_columns = only_rsi_columns, info = FALSE, sort = FALSE) + + generate_antimcrobials_string(x[ , cols, drop = FALSE]) +} + +generate_antimcrobials_string <- function(df) { + if (NCOL(df) == 0) { + return(rep("", NROW(df))) + } + if (NROW(df) == 0) { + return(character(0)) + } + out <- tryCatch( + do.call(paste0, + lapply(as.list(df), + function(x) { + x <- toupper(as.character(x)) + x[!x %in% c("R", "S", "I")] <- "." + paste(x) + })), + error = function(e) rep(strrep(".", NCOL(df)), NROW(df))) + out +} + +#' @rdname key_antimicrobials +#' @param info unused - previously used to indicate whether a progress bar should print +#' @export +antimicrobials_equal <- function(y, + z, + type = c("points", "keyantimicrobials"), + ignore_I = TRUE, + points_threshold = 2, + info = FALSE, + ...) { + meet_criteria(y, allow_class = "character") + meet_criteria(z, allow_class = "character") + stop_if(missing(type), "argument \"type\" is missing, with no default") + meet_criteria(type, allow_class = "character", has_length = 1, is_in = c("points", "keyantimicrobials")) + meet_criteria(ignore_I, allow_class = "logical", has_length = 1) + meet_criteria(points_threshold, allow_class = c("numeric", "integer"), has_length = 1, is_positive = TRUE, is_finite = TRUE) + meet_criteria(info, allow_class = "logical", has_length = 1) + stop_ifnot(length(y) == length(z), "length of `y` and `z` must be equal") + + key2rsi <- function(val) { + as.double(as.rsi(gsub(".", NA_character_, unlist(strsplit(val, "")), fixed = TRUE))) + } + y <- lapply(y, key2rsi) + z <- lapply(z, key2rsi) + + determine_equality <- function(a, b, type, points_threshold, ignore_I) { + if (length(a) != length(b)) { + # incomparable, so not equal + return(FALSE) + } + # ignore NAs on both sides + NA_ind <- which(is.na(a) | is.na(b)) + a[NA_ind] <- NA_real_ + b[NA_ind] <- NA_real_ + + if (type == "points") { + # count points for every single character: + # - no change is 0 points + # - I <-> S|R is 0.5 point + # - S|R <-> R|S is 1 point + # use the levels of as.rsi (S = 1, I = 2, R = 3) + # and divide by 2 (S = 0.5, I = 1, R = 1.5) + (sum(abs(a - b), na.rm = TRUE) / 2) < points_threshold + } else { + if (ignore_I == TRUE) { + ind <- which(a == 2 | b == 2) # since as.double(as.rsi("I")) == 2 + a[ind] <- NA_real_ + b[ind] <- NA_real_ + } + all(a == b, na.rm = TRUE) + } + } + out <- unlist(mapply(FUN = determine_equality, + y, + z, + MoreArgs = list(type = type, + points_threshold = points_threshold, + ignore_I = ignore_I), + SIMPLIFY = FALSE, + USE.NAMES = FALSE)) + out[is.na(y) | is.na(z)] <- NA + out +} diff --git a/R/rsi.R b/R/rsi.R index 2373e831..e4eba008 100755 --- a/R/rsi.R +++ b/R/rsi.R @@ -1027,10 +1027,8 @@ summary.rsi <- function(object, ...) { #' @method c rsi #' @export #' @noRd -c.rsi <- function(x, ...) { - y <- unlist(lapply(list(...), as.character)) - x <- as.character(x) - as.rsi(c(x, y)) +c.rsi <- function(...) { + as.rsi(unlist(lapply(list(...), as.character))) } #' @method unique rsi diff --git a/_pkgdown.yml b/_pkgdown.yml index c6b55461..f97ef686 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -155,7 +155,7 @@ reference: - "`count`" - "`is_new_episode`" - "`first_isolate`" - - "`key_antibiotics`" + - "`key_antimicrobials`" - "`mdro`" - "`count`" - "`plot`" diff --git a/data-raw/AMR_latest.tar.gz b/data-raw/AMR_latest.tar.gz index 876345a4..deb60da7 100644 Binary files a/data-raw/AMR_latest.tar.gz and b/data-raw/AMR_latest.tar.gz differ diff --git a/docs/404.html b/docs/404.html index 1a586efc..fa72c17c 100644 --- a/docs/404.html +++ b/docs/404.html @@ -81,7 +81,7 @@ AMR (for R) - 1.6.0.9009 + 1.6.0.9010 diff --git a/docs/LICENSE-text.html b/docs/LICENSE-text.html index bcd73164..f5ccbf8e 100644 --- a/docs/LICENSE-text.html +++ b/docs/LICENSE-text.html @@ -81,7 +81,7 @@ AMR (for R) - 1.6.0.9009 + 1.6.0.9010 diff --git a/docs/articles/index.html b/docs/articles/index.html index d33f8462..be382c4e 100644 --- a/docs/articles/index.html +++ b/docs/articles/index.html @@ -81,7 +81,7 @@ AMR (for R) - 1.6.0.9009 + 1.6.0.9010 diff --git a/docs/authors.html b/docs/authors.html index a8b7cb75..4ee11444 100644 --- a/docs/authors.html +++ b/docs/authors.html @@ -81,7 +81,7 @@ AMR (for R) - 1.6.0.9009 + 1.6.0.9010 diff --git a/docs/index.html b/docs/index.html index 5a1b7688..3b3a0f08 100644 --- a/docs/index.html +++ b/docs/index.html @@ -42,7 +42,7 @@ AMR (for R) - 1.6.0.9009 + 1.6.0.9010 diff --git a/docs/news/index.html b/docs/news/index.html index 5451e266..88718de9 100644 --- a/docs/news/index.html +++ b/docs/news/index.html @@ -81,7 +81,7 @@ AMR (for R) - 1.6.0.9009 + 1.6.0.9010 @@ -236,13 +236,13 @@ Source: NEWS.md -
-

-AMR 1.6.0.9009 Unreleased +
+

+AMR 1.6.0.9010 Unreleased

-
+

-Last updated: 23 April 2021 +Last updated: 26 April 2021

@@ -250,6 +250,20 @@
  • Function custom_eucast_rules() that brings support for custom AMR rules in eucast_rules()
  • +
  • Support for all four methods to determine first isolates as summarised by Hindler et al (doi: 10.1086/511864): isolate-based, patient-based, episode-based and phenotype-based. The last method is now the default. +
      +
    • Since fungal isolates can also be selected, new functions key_antimicrobials() and all_antimicrobials() have replaced the now deprecated function key_antibiotics() +
    • +
    • Using key_antimicrobials() still only selects six preferred antibiotics for Gram-negatives, six for Gram-positives, and six universal antibiotics. It has a new antifungal argument to set antifungal agents (antimycotics).
    • +
    • The first_isolate() function gained the argument method that has to be “phenotype-based”, “episode-based”, “patient-based”, or “isolate-based”. The old behaviour is equal to “episode-based”, while the new default is “phenotype-based”.
    • +
    • Using type == "points" in the first_isolate() function for phenotype-based selection will now consider all antimicrobial drugs in the data set, using the new all_antimicrobials() +
    • +
    • The first_isolate() function can now take a vector of values for col_keyantibiotics and can have an episode length of Inf +
    • +
    • The filter_first_isolate() function has not changed, as it uses the episode-based method. The filter_first_weighted_isolate() may now include more isolates as uses the phenotype-based method.
    • +
    • The documentation of the first_isolate() and key_antimicrobials() functions has been completely rewritten.
    • +
    +

@@ -267,9 +281,6 @@
  • The example_isolates data set now contains some (fictitious) zero-year old patients
  • Fix for minor translation errors
  • Printing of microbial codes in a data.frame or tibble now gives a warning if the data contains old microbial codes (from a previous AMR package version)
  • -
  • -first_isolate() can now take a vector of values for col_keyantibiotics and can have an episode length of Inf -
  • Extended the like() functions:
    • Now checks if pattern is a valid regular expression

    • @@ -287,13 +298,6 @@
    • Fixed an installation error on R-3.0
    • Added info argument to as.mo() to turn on/off the progress bar
    • Fixed a bug that col_mo for some functions (esp. eucast_rules() and mdro()) could not be column names of the microorganisms data set as it would throw an error
    • -
    • Using first_isolate() with key antibiotics: -
        -
      • Fixed a bug in the algorithm when using type == "points", that now leads to inclusion of slightly more isolates
      • -
      • Big speed improvement for key_antibiotics_equal() when using type == "points" -
      • -
      -
  • @@ -487,7 +491,7 @@
  • first_isolate(),
  • -key_antibiotics(),
  • +key_antibiotics(),
  • mdro(),
  • @@ -1205,7 +1209,7 @@ This works for all drug combinations, such as ampicillin/sulbactam, ceftazidime/
  • Renamed function p.symbol() to p_symbol() (the former is now deprecated and will be removed in a future version)
  • Using negative values for x in age_groups() will now introduce NAs and not return an error anymore
  • Fix for determining the system’s language
  • -
  • Fix for key_antibiotics() on foreign systems
  • +
  • Fix for key_antibiotics() on foreign systems
  • Added 80 new LIS codes for microorganisms
  • Relabeled the factor levels of mdr_tb()
  • @@ -1654,7 +1658,7 @@ This works for all drug combinations, such as ampicillin/sulbactam, ceftazidime/
  • Function scale_y_percent() now contains the limits argument
  • -
  • Automatic argument filling for mdro(), key_antibiotics() and eucast_rules() +
  • Automatic argument filling for mdro(), key_antibiotics() and eucast_rules()
  • Updated examples for resistance prediction (resistance_predict() function)
  • Fix for as.mic() to support more values ending in (several) zeroes
  • diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index 8e168e5c..6096a690 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -12,7 +12,7 @@ articles: datasets: datasets.html resistance_predict: resistance_predict.html welcome_to_AMR: welcome_to_AMR.html -last_built: 2021-04-23T14:13Z +last_built: 2021-04-26T21:56Z urls: reference: https://msberends.github.io/AMR//reference article: https://msberends.github.io/AMR//articles diff --git a/docs/reference/AMR-deprecated.html b/docs/reference/AMR-deprecated.html index 374f569e..a53b28b8 100644 --- a/docs/reference/AMR-deprecated.html +++ b/docs/reference/AMR-deprecated.html @@ -82,7 +82,7 @@ AMR (for R) - 1.5.0.9016 + 1.6.0.9010
    @@ -242,7 +242,43 @@

    These functions are so-called 'Deprecated'. They will be removed in a future release. Using the functions will give a warning with the name of the function it has been replaced by (if there is one).

    -
    p_symbol(p, emptychar = " ")
    +
    p_symbol(p, emptychar = " ")
    +
    +key_antibiotics(
    +  x = NULL,
    +  col_mo = NULL,
    +  universal_1 = guess_ab_col(x, "amoxicillin"),
    +  universal_2 = guess_ab_col(x, "amoxicillin/clavulanic acid"),
    +  universal_3 = guess_ab_col(x, "cefuroxime"),
    +  universal_4 = guess_ab_col(x, "piperacillin/tazobactam"),
    +  universal_5 = guess_ab_col(x, "ciprofloxacin"),
    +  universal_6 = guess_ab_col(x, "trimethoprim/sulfamethoxazole"),
    +  GramPos_1 = guess_ab_col(x, "vancomycin"),
    +  GramPos_2 = guess_ab_col(x, "teicoplanin"),
    +  GramPos_3 = guess_ab_col(x, "tetracycline"),
    +  GramPos_4 = guess_ab_col(x, "erythromycin"),
    +  GramPos_5 = guess_ab_col(x, "oxacillin"),
    +  GramPos_6 = guess_ab_col(x, "rifampin"),
    +  GramNeg_1 = guess_ab_col(x, "gentamicin"),
    +  GramNeg_2 = guess_ab_col(x, "tobramycin"),
    +  GramNeg_3 = guess_ab_col(x, "colistin"),
    +  GramNeg_4 = guess_ab_col(x, "cefotaxime"),
    +  GramNeg_5 = guess_ab_col(x, "ceftazidime"),
    +  GramNeg_6 = guess_ab_col(x, "meropenem"),
    +  warnings = TRUE,
    +  ...
    +)
    +
    +key_antibiotics_equal(
    +  y,
    +  z,
    +  type = "keyantimicrobials",
    +  ignore_I = TRUE,
    +  points_threshold = 2,
    +  info = FALSE,
    +  na.rm = TRUE,
    +  ...
    +)

    Retired Lifecycle

    diff --git a/docs/reference/ab_from_text.html b/docs/reference/ab_from_text.html index 16981d6d..94b3f743 100644 --- a/docs/reference/ab_from_text.html +++ b/docs/reference/ab_from_text.html @@ -82,7 +82,7 @@ AMR (for R) - 1.6.0.9007 + 1.6.0.9010
    diff --git a/docs/reference/as.ab.html b/docs/reference/as.ab.html index 8453ad58..93712ece 100644 --- a/docs/reference/as.ab.html +++ b/docs/reference/as.ab.html @@ -82,7 +82,7 @@ AMR (for R) - 1.6.0.9007 + 1.6.0.9010 diff --git a/docs/reference/as.mo.html b/docs/reference/as.mo.html index ff6a9eb8..811414e9 100644 --- a/docs/reference/as.mo.html +++ b/docs/reference/as.mo.html @@ -82,7 +82,7 @@ AMR (for R) - 1.6.0.9007 + 1.6.0.9010 diff --git a/docs/reference/custom_eucast_rules.html b/docs/reference/custom_eucast_rules.html index 90709a21..c947ae55 100644 --- a/docs/reference/custom_eucast_rules.html +++ b/docs/reference/custom_eucast_rules.html @@ -82,7 +82,7 @@ AMR (for R) - 1.6.0.9007 + 1.6.0.9010 diff --git a/docs/reference/first_isolate.html b/docs/reference/first_isolate.html index 9aa7102b..9a8c3dbb 100644 --- a/docs/reference/first_isolate.html +++ b/docs/reference/first_isolate.html @@ -82,7 +82,7 @@ AMR (for R) - 1.6.0.9009 + 1.6.0.9010 @@ -250,12 +250,13 @@ col_testcode = NULL, col_specimen = NULL, col_icu = NULL, - col_keyantibiotics = NULL, + col_keyantimicrobials = NULL, episode_days = 365, testcodes_exclude = NULL, icu_exclude = FALSE, specimen_group = NULL, - type = "keyantibiotics", + type = "points", + method = c("phenotype-based", "episode-based", "patient-based", "isolate-based"), ignore_I = TRUE, points_threshold = 2, info = interactive(), @@ -269,6 +270,7 @@ col_date = NULL, col_patient_id = NULL, col_mo = NULL, + method = "episode-based", ... ) @@ -277,7 +279,7 @@ col_date = NULL, col_patient_id = NULL, col_mo = NULL, - col_keyantibiotics = NULL, + method = "phenotype-based", ... ) @@ -313,8 +315,8 @@

    column name of the logicals (TRUE/FALSE) whether a ward or department is an Intensive Care Unit (ICU)

    - col_keyantibiotics -

    column name of the key antibiotics to determine first (weighted) isolates, see key_antibiotics(). Defaults to the first column that starts with 'key' followed by 'ab' or 'antibiotics' (case insensitive). Use col_keyantibiotics = FALSE to prevent this. Can also be the output of key_antibiotics().

    + col_keyantimicrobials +

    (only useful when method = "phenotype-based") column name of the key antimicrobials to determine first (weighted) isolates, see key_antimicrobials(). Defaults to the first column that starts with 'key' followed by 'ab' or 'antibiotics' or 'antimicrobials' (case insensitive). Use col_keyantimicrobials = FALSE to prevent this. Can also be the output of key_antimicrobials().

    episode_days @@ -334,15 +336,19 @@ type -

    type to determine weighed isolates; can be "keyantibiotics" or "points", see Details

    +

    type to determine weighed isolates; can be "keyantimicrobials" or "points", see Details

    + + + method +

    the algorithm to apply, either "phenotype-based", "episode-based", "patient-based" or "isolate-based" (can be abbreviated), see Details

    ignore_I -

    logical to indicate whether antibiotic interpretations with "I" will be ignored when type = "keyantibiotics", see Details

    +

    logical to indicate whether antibiotic interpretations with "I" will be ignored when type = "keyantimicrobials", see Details

    points_threshold -

    points until the comparison of key antibiotics will lead to inclusion of an isolate when type = "points", see Details

    +

    minimum number of points to require before differences in the antibiogram will lead to inclusion of an isolate when type = "points", see Details

    info @@ -358,7 +364,7 @@ ... -

    arguments passed on to first_isolate() when using filter_first_isolate(), or arguments passed on to key_antibiotics() when using filter_first_weighted_isolate()

    +

    arguments passed on to first_isolate() when using filter_first_isolate(), or arguments passed on to key_antimicrobials() otherwise (such as universal, gram_negative, gram_positive)

    @@ -371,42 +377,69 @@

    A logical vector

    Details

    -

    These functions are context-aware. This means that then the x argument can be left blank, see Examples.

    +

    To conduct epidemiological analyses on antimicrobial resistance data, only so-called first isolates should be included to prevent overestimation and underestimation of antimicrobial resistance. Different algorithms can be used to do so, see below.

    +

    These functions are context-aware. This means that then the x argument can be left blank, see Examples.

    The first_isolate() function is a wrapper around the is_new_episode() function, but more efficient for data sets containing microorganism codes or names.

    -

    All isolates with a microbial ID of NA will be excluded as first isolate.

    Why this is so Important

    +

    All isolates with a microbial ID of NA will be excluded as first isolate.

    Different algorithms

    -

    To conduct an analysis of antimicrobial resistance, you should only include the first isolate of every patient per episode (Hindler et al. 2007). If you would not do this, you could easily get an overestimate or underestimate of the resistance of an antibiotic. Imagine that a patient was admitted with an MRSA and that it was found in 5 different blood cultures the following week. The resistance percentage of oxacillin of all S. aureus isolates would be overestimated, because you included this MRSA more than once. It would be selection bias.

    - -

    filter_*() Shortcuts

    +

    According to Hindler et al. (2007, doi: 10.1086/511864 +), there are different algorithms to select first isolates with increasing reliability: isolate-based, patient-based, episode-based and phenotype-based. All algorithms select on a combination of the taxonomic genus and species (not subspecies).

    +

    All mentioned algorithms are covered in the first_isolate() function:

    + + + + + + + + + + + + + + + + + + + +
    AlgorithmFunction to apply
    Isolate-basedfirst_isolate(x, method = "isolate-based")
    (= all isolates)
    Patient-basedfirst_isolate(x, method = "patient-based")
    (= first isolate per patient)
    Episode-basedfirst_isolate(x, method = "episode-based"), or:
    (= first isolate per episode)
    - 7-Day interval from initial isolate- first_isolate(x, method = "e", episode_days = 7)
    - 30-Day interval from initial isolate- first_isolate(x, method = "e", episode_days = 30)
    Phenotype-basedfirst_isolate(x, method = "phenotype-based"), or:
    (= first isolate per phenotype)
    - Major difference in any antimicrobial result- first_isolate(x, type = "points")
    - Any difference in key antimicrobial results- first_isolate(x, type = "keyantimicrobials")
    -

    The functions filter_first_isolate() and filter_first_weighted_isolate() are helper functions to quickly filter on first isolates.

    -

    The function filter_first_isolate() is essentially equal to either:

      x[first_isolate(x, ...), ]
    -  
    -  x %>% filter(first_isolate(...))
    -
    - -

    The function filter_first_weighted_isolate() is essentially equal to:

      x %>%
    -    mutate(keyab = key_antibiotics(.)) %>%
    -    mutate(only_weighted_firsts = first_isolate(x,
    -                                                col_keyantibiotics = "keyab", ...)) %>%
    -    filter(only_weighted_firsts == TRUE) %>%
    -    select(-only_weighted_firsts, -keyab)
    -
    +

    Isolate-based

    -

    Key Antibiotics

    +

    This algorithm does not require any selection, as all isolates should be included. It does, however, respect all arguments set in the first_isolate() function. For example, the default setting for include_unknown (FALSE) will omit selection of rows without a microbial ID.

    - +

    Patient-based

    -

    There are two ways to determine whether isolates can be included as first weighted isolates which will give generally the same results:

      -
    1. Using type = "keyantibiotics" and argument ignore_I

      -

      Any difference from S to R (or vice versa) will (re)select an isolate as a first weighted isolate. With ignore_I = FALSE, also differences from I to S|R (or vice versa) will lead to this. This is a reliable method and 30-35 times faster than method 2. Read more about this in the key_antibiotics() function.

    2. + +

      To include every genus-species combination per patient once, set the episode_days to Inf. Although often inappropriate, this algorithm makes sure that no duplicate isolates are selected from the same patient.

      + +

      Episode-based

      + + +

      To include every genus-species combination per patient episode once, set the episode_days to a sensible number of days. Depending on the type of analysis, this could be 14, 30, 60 or 365. Short episodes are common for analysing specific hospital or ward data, long episodes are common for analysing regional and national data.

      +

      This is the most common algorithm to correct for duplicate isolates. Patients are categorised into episodes based on their ID and dates (e.g., the date of specimen receipt or laboratory result). While this is a common algorithm, it does not take into account antimicrobial test results. This means that e.g. a methicillin-resistant Staphylococcus aureus (MRSA) isolate cannot be differentiated from a wildtype Staphylococcus aureus isolate.

      + +

      Phenotype-based

      + + +

      This is a more reliable algorithm, since it also weighs the antibiogram (antimicrobial test results) yielding so-called 'first weighted isolates'. There are two different methods to weigh the antibiogram:

      1. Using type = "points" and argument points_threshold

        -

        A difference from I to S|R (or vice versa) means 0.5 points, a difference from S to R (or vice versa) means 1 point. When the sum of points exceeds points_threshold, which defaults to 2, an isolate will be (re)selected as a first weighted isolate.

      2. +

        This method weighs all antimicrobial agents available in the data set. Any difference from I to S or R (or vice versa) counts as 0.5 points, a difference from S to R (or vice versa) counts as 1 point. When the sum of points exceeds points_threshold, which defaults to 2, an isolate will be selected as a first weighted isolate.

        +

        All antimicrobials are internally selected using the all_antimicrobials() function. The output of this function does not need to be passed to the first_isolate() function.

        +
      3. Using type = "keyantimicrobials" and argument ignore_I

        +

        This method only weighs specific antimicrobial agents, called key antimicrobials. Any difference from S to R (or vice versa) in these key antimicrobials will select an isolate as a first weighted isolate. With ignore_I = FALSE, also differences from I to S or R (or vice versa) will lead to this.

        +

        Key antimicrobials are internally selected using the key_antimicrobials() function, but can also be added manually as a variable to the data and set in the col_keyantimicrobials argument. Another option is to pass the output of the key_antimicrobials() function directly to the col_keyantimicrobials argument.

      +

      The default algorithm is phenotype-based (using type = "points") and episode-based (using episode_days = 365). This makes sure that every genus-species combination is selected per patient once per year, while taking into account all antimicrobial test results.

      + + +

      Stable Lifecycle

      @@ -421,14 +454,13 @@ The lifecycle of this function is stableOn our website https://msberends.github.io/AMR/ you can find a comprehensive tutorial about how to conduct AMR data analysis, the complete documentation of all functions and an example analysis using WHONET data. As we would like to better understand the backgrounds and needs of our users, please participate in our survey!

      See also

      - +

      Examples

      # `example_isolates` is a data set available in the AMR package.
       # See ?example_isolates.
       
       example_isolates[first_isolate(example_isolates), ]
      -
       # \donttest{
       # faster way, only works in R 3.2 and later:
       example_isolates[first_isolate(), ]
      @@ -466,7 +498,7 @@ The lifecycle of this function is stable# Have a look at A and B.
         # B is more reliable because every isolate is counted only once.
      -  # Gentamicin resistance in hospital D appears to be 3.7% higher than
      +  # Gentamicin resistance in hospital D appears to be 4.2% higher than
         # when you (erroneously) would have used all isolates for analysis.
       }
       # }
      diff --git a/docs/reference/index.html b/docs/reference/index.html
      index f8a17ecc..b310497e 100644
      --- a/docs/reference/index.html
      +++ b/docs/reference/index.html
      @@ -81,7 +81,7 @@
             
             
               AMR (for R)
      -        1.6.0.9009
      +        1.6.0.9010
             
           
       
      @@ -496,9 +496,9 @@
             
               
               
      -          

      key_antibiotics() key_antibiotics_equal()

      +

      p_symbol() key_antibiotics() key_antibiotics_equal()

      -

      Key Antibiotics for First (Weighted) Isolates

      +

      Deprecated Functions

      @@ -667,7 +667,7 @@ -

      p_symbol()

      +

      p_symbol() key_antibiotics() key_antibiotics_equal()

      Deprecated Functions

      diff --git a/docs/reference/key_antibiotics.html b/docs/reference/key_antimicrobials.html similarity index 59% rename from docs/reference/key_antibiotics.html rename to docs/reference/key_antimicrobials.html index 23646c27..770324ec 100644 --- a/docs/reference/key_antibiotics.html +++ b/docs/reference/key_antimicrobials.html @@ -6,7 +6,7 @@ -Key Antibiotics for First (Weighted) Isolates — key_antibiotics • AMR (for R) +(Key) Antimicrobials for First Weighted Isolates — key_antimicrobials • AMR (for R) @@ -48,8 +48,8 @@ - - + + @@ -82,7 +82,7 @@ AMR (for R) - 1.6.0.9009 + 1.6.0.9010 @@ -233,48 +233,39 @@
      -

      These function can be used to determine first isolates (see first_isolate()). Using key antibiotics to determine first isolates is more reliable than without key antibiotics. These selected isolates can then be called first 'weighted' isolates.

      +

      These functions can be used to determine first weighted isolates by considering the phenotype for isolate selection (see first_isolate()). Using a phenotype-based method to determine first isolates is more reliable than methods that disregard phenotypes.

      -
      key_antibiotics(
      +    
      key_antimicrobials(
         x = NULL,
         col_mo = NULL,
      -  universal_1 = guess_ab_col(x, "amoxicillin"),
      -  universal_2 = guess_ab_col(x, "amoxicillin/clavulanic acid"),
      -  universal_3 = guess_ab_col(x, "cefuroxime"),
      -  universal_4 = guess_ab_col(x, "piperacillin/tazobactam"),
      -  universal_5 = guess_ab_col(x, "ciprofloxacin"),
      -  universal_6 = guess_ab_col(x, "trimethoprim/sulfamethoxazole"),
      -  GramPos_1 = guess_ab_col(x, "vancomycin"),
      -  GramPos_2 = guess_ab_col(x, "teicoplanin"),
      -  GramPos_3 = guess_ab_col(x, "tetracycline"),
      -  GramPos_4 = guess_ab_col(x, "erythromycin"),
      -  GramPos_5 = guess_ab_col(x, "oxacillin"),
      -  GramPos_6 = guess_ab_col(x, "rifampin"),
      -  GramNeg_1 = guess_ab_col(x, "gentamicin"),
      -  GramNeg_2 = guess_ab_col(x, "tobramycin"),
      -  GramNeg_3 = guess_ab_col(x, "colistin"),
      -  GramNeg_4 = guess_ab_col(x, "cefotaxime"),
      -  GramNeg_5 = guess_ab_col(x, "ceftazidime"),
      -  GramNeg_6 = guess_ab_col(x, "meropenem"),
      -  warnings = TRUE,
      +  universal = c("ampicillin", "amoxicillin/clavulanic acid", "cefuroxime",
      +    "piperacillin/tazobactam", "ciprofloxacin", "trimethoprim/sulfamethoxazole"),
      +  gram_negative = c("gentamicin", "tobramycin", "colistin", "cefotaxime",
      +    "ceftazidime", "meropenem"),
      +  gram_positive = c("vancomycin", "teicoplanin", "tetracycline", "erythromycin",
      +    "oxacillin", "rifampin"),
      +  antifungal = c("anidulafungin", "caspofungin", "fluconazole", "miconazole",
      +    "nystatin", "voriconazole"),
      +  only_rsi_columns = FALSE,
         ...
       )
       
      -key_antibiotics_equal(
      +all_antimicrobials(x = NULL, only_rsi_columns = FALSE, ...)
      +
      +antimicrobials_equal(
         y,
         z,
      -  type = c("keyantibiotics", "points"),
      +  type = c("points", "keyantimicrobials"),
         ignore_I = TRUE,
         points_threshold = 2,
         info = FALSE,
      -  na.rm = TRUE,
         ...
       )
      @@ -290,24 +281,24 @@

      column name of the IDs of the microorganisms (see as.mo()), defaults to the first column of class mo. Values will be coerced using as.mo().

      - universal_1, universal_2, universal_3, universal_4, universal_5, universal_6 -

      column names of broad-spectrum antibiotics, case-insensitive. See details for which antibiotics will be used at default (which are guessed with guess_ab_col()).

      + universal +

      names of broad-spectrum antimicrobial agents, case-insensitive. Set to NULL to ignore. See Details for the default agents.

      - GramPos_1, GramPos_2, GramPos_3, GramPos_4, GramPos_5, GramPos_6 -

      column names of antibiotics for Gram-positives, case-insensitive. See details for which antibiotics will be used at default (which are guessed with guess_ab_col()).

      + gram_negative +

      names of antibiotic agents for Gram-positives, case-insensitive. Set to NULL to ignore. See Details for the default agents.

      - GramNeg_1, GramNeg_2, GramNeg_3, GramNeg_4, GramNeg_5, GramNeg_6 -

      column names of antibiotics for Gram-negatives, case-insensitive. See details for which antibiotics will be used at default (which are guessed with guess_ab_col()).

      + gram_positive +

      names of antibiotic agents for Gram-negatives, case-insensitive. Set to NULL to ignore. See Details for the default agents.

      - warnings -

      give a warning about missing antibiotic columns (they will be ignored)

      + antifungal +

      names of antifungal agents for fungi, case-insensitive. Set to NULL to ignore. See Details for the default agents.

      ... -

      other arguments passed on to functions

      +

      ignored, allows for future extensions

      y, z @@ -315,62 +306,63 @@ type -

      type to determine weighed isolates; can be "keyantibiotics" or "points", see Details

      +

      type to determine weighed isolates; can be "keyantimicrobials" or "points", see Details

      ignore_I -

      logical to indicate whether antibiotic interpretations with "I" will be ignored when type = "keyantibiotics", see Details

      +

      logical to indicate whether antibiotic interpretations with "I" will be ignored when type = "keyantimicrobials", see Details

      points_threshold -

      points until the comparison of key antibiotics will lead to inclusion of an isolate when type = "points", see Details

      +

      minimum number of points to require before differences in the antibiogram will lead to inclusion of an isolate when type = "points", see Details

      info

      unused - previously used to indicate whether a progress bar should print

      - - na.rm -

      a logical to indicate whether comparison with NA should return FALSE (defaults to TRUE for backwards compatibility)

      -

      Details

      -

      The key_antibiotics() function is context-aware. This means that then the x argument can be left blank, see Examples.

      -

      The function key_antibiotics() returns a character vector with 12 antibiotic results for every isolate. These isolates can then be compared using key_antibiotics_equal(), to check if two isolates have generally the same antibiogram. Missing and invalid values are replaced with a dot (".") by key_antibiotics() and ignored by key_antibiotics_equal().

      -

      The first_isolate() function only uses this function on the same microbial species from the same patient. Using this, e.g. an MRSA will be included after a susceptible S. aureus (MSSA) is found within the same patient episode. Without key antibiotic comparison it would not. See first_isolate() for more info.

      -

      At default, the antibiotics that are used for Gram-positive bacteria are:

        -
      • Amoxicillin

      • +

        The key_antimicrobials() and all_antimicrobials() functions are context-aware. This means that then the x argument can be left blank, see Examples.

        +

        The function key_antimicrobials() returns a character vector with 12 antimicrobial results for every isolate. The function all_antimicrobials() returns a character vector with all antimicrobial results for every isolate. These vectors can then be compared using antimicrobials_equal(), to check if two isolates have generally the same antibiogram. Missing and invalid values are replaced with a dot (".") by key_antimicrobials() and ignored by antimicrobials_equal().

        +

        Please see the first_isolate() function how these important functions enable the 'phenotype-based' method for determination of first isolates.

        +

        The default antimicrobial agents used for all rows (set in universal) are:

          +
        • Ampicillin

        • Amoxicillin/clavulanic acid

        • Cefuroxime

        • -
        • Piperacillin/tazobactam

        • Ciprofloxacin

        • +
        • Piperacillin/tazobactam

        • Trimethoprim/sulfamethoxazole

        • -
        • Vancomycin

        • -
        • Teicoplanin

        • -
        • Tetracycline

        • +
        + +

        The default antimicrobial agents used for Gram-negative bacteria (set in gram_negative) are:

          +
        • Cefotaxime

        • +
        • Ceftazidime

        • +
        • Colistin

        • +
        • Gentamicin

        • +
        • Meropenem

        • +
        • Tobramycin

        • +
        + +

        The default antimicrobial agents used for Gram-positive bacteria (set in gram_positive) are:

        • Erythromycin

        • Oxacillin

        • Rifampin

        • +
        • Teicoplanin

        • +
        • Tetracycline

        • +
        • Vancomycin

        -

        At default the antibiotics that are used for Gram-negative bacteria are:

          -
        • Amoxicillin

        • -
        • Amoxicillin/clavulanic acid

        • -
        • Cefuroxime

        • -
        • Piperacillin/tazobactam

        • -
        • Ciprofloxacin

        • -
        • Trimethoprim/sulfamethoxazole

        • -
        • Gentamicin

        • -
        • Tobramycin

        • -
        • Colistin

        • -
        • Cefotaxime

        • -
        • Ceftazidime

        • -
        • Meropenem

        • +

          The default antimicrobial agents used for fungi (set in antifungal) are:

            +
          • Anidulafungin

          • +
          • Caspofungin

          • +
          • Fluconazole

          • +
          • Miconazole

          • +
          • Nystatin

          • +
          • Voriconazole

          -

          The function key_antibiotics_equal() checks the characters returned by key_antibiotics() for equality, and returns a logical vector.

          Stable Lifecycle

          @@ -378,17 +370,6 @@


          The lifecycle of this function is stable. In a stable function, major changes are unlikely. This means that the unlying code will generally evolve by adding new arguments; removing arguments or changing the meaning of existing arguments will be avoided.

          If the unlying code needs breaking changes, they will occur gradually. For example, a argument will be deprecated and first continue to work, but will emit an message informing you of the change. Next, typically after at least one newly released version on CRAN, the message will be transformed to an error.

          -

          Key Antibiotics

          - - - -

          There are two ways to determine whether isolates can be included as first weighted isolates which will give generally the same results:

            -
          1. Using type = "keyantibiotics" and argument ignore_I

            -

            Any difference from S to R (or vice versa) will (re)select an isolate as a first weighted isolate. With ignore_I = FALSE, also differences from I to S|R (or vice versa) will lead to this. This is a reliable method and 30-35 times faster than method 2. Read more about this in the key_antibiotics() function.

          2. -
          3. Using type = "points" and argument points_threshold

            -

            A difference from I to S|R (or vice versa) means 0.5 points, a difference from S to R (or vice versa) means 1 point. When the sum of points exceeds points_threshold, which defaults to 2, an isolate will be (re)selected as a first weighted isolate.

          4. -
          -

          Read more on Our Website!

          @@ -402,30 +383,30 @@ The lifecycle of this function is stable# `example_isolates` is a data set available in the AMR package. # See ?example_isolates. -# output of the `key_antibiotics()` function could be like this: +# output of the `key_antimicrobials()` function could be like this: strainA <- "SSSRR.S.R..S" strainB <- "SSSIRSSSRSSS" # those strings can be compared with: -key_antibiotics_equal(strainA, strainB) +antimicrobials_equal(strainA, strainB, type = "keyantimicrobials") # TRUE, because I is ignored (as well as missing values) -key_antibiotics_equal(strainA, strainB, ignore_I = FALSE) +antimicrobials_equal(strainA, strainB, type = "keyantimicrobials", ignore_I = FALSE) # FALSE, because I is not ignored and so the 4th character differs # \donttest{ if (require("dplyr")) { # set key antibiotics to a new variable my_patients <- example_isolates %>% - mutate(keyab = key_antibiotics()) %>% # no need to define `x` + mutate(keyab = key_antimicrobials(antifungal = NULL)) %>% # no need to define `x` mutate( # now calculate first isolates - first_regular = first_isolate(col_keyantibiotics = FALSE), + first_regular = first_isolate(col_keyantimicrobials = FALSE), # and first WEIGHTED isolates - first_weighted = first_isolate(col_keyantibiotics = "keyab") + first_weighted = first_isolate(col_keyantimicrobials = "keyab") ) - # Check the difference, in this data set it results in a lot more isolates: + # Check the difference, in this data set it results in more isolates: sum(my_patients$first_regular, na.rm = TRUE) sum(my_patients$first_weighted, na.rm = TRUE) } diff --git a/docs/reference/like.html b/docs/reference/like.html index f978776c..f8998870 100644 --- a/docs/reference/like.html +++ b/docs/reference/like.html @@ -82,7 +82,7 @@ AMR (for R) - 1.6.0.9008 + 1.6.0.9010
      diff --git a/docs/reference/mdro.html b/docs/reference/mdro.html index e53c5a0a..0acd9013 100644 --- a/docs/reference/mdro.html +++ b/docs/reference/mdro.html @@ -82,7 +82,7 @@ AMR (for R) - 1.6.0.9007 + 1.6.0.9010
      diff --git a/docs/reference/microorganisms.html b/docs/reference/microorganisms.html index 327fa58f..bdf1e9f9 100644 --- a/docs/reference/microorganisms.html +++ b/docs/reference/microorganisms.html @@ -82,7 +82,7 @@ AMR (for R) - 1.6.0.9007 + 1.6.0.9010 diff --git a/docs/sitemap.xml b/docs/sitemap.xml index f0c0e8de..90bec2ab 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -109,7 +109,7 @@ https://msberends.github.io/AMR//reference/join.html - https://msberends.github.io/AMR//reference/key_antibiotics.html + https://msberends.github.io/AMR//reference/key_antimicrobials.html https://msberends.github.io/AMR//reference/kurtosis.html diff --git a/docs/survey.html b/docs/survey.html index e98c581a..1da3feb9 100644 --- a/docs/survey.html +++ b/docs/survey.html @@ -81,7 +81,7 @@ AMR (for R) - 1.6.0.9009 + 1.6.0.9010 diff --git a/man/AMR-deprecated.Rd b/man/AMR-deprecated.Rd index fa717930..7145536b 100644 --- a/man/AMR-deprecated.Rd +++ b/man/AMR-deprecated.Rd @@ -3,9 +3,47 @@ \name{AMR-deprecated} \alias{AMR-deprecated} \alias{p_symbol} +\alias{key_antibiotics} +\alias{key_antibiotics_equal} \title{Deprecated Functions} \usage{ p_symbol(p, emptychar = " ") + +key_antibiotics( + x = NULL, + col_mo = NULL, + universal_1 = guess_ab_col(x, "amoxicillin"), + universal_2 = guess_ab_col(x, "amoxicillin/clavulanic acid"), + universal_3 = guess_ab_col(x, "cefuroxime"), + universal_4 = guess_ab_col(x, "piperacillin/tazobactam"), + universal_5 = guess_ab_col(x, "ciprofloxacin"), + universal_6 = guess_ab_col(x, "trimethoprim/sulfamethoxazole"), + GramPos_1 = guess_ab_col(x, "vancomycin"), + GramPos_2 = guess_ab_col(x, "teicoplanin"), + GramPos_3 = guess_ab_col(x, "tetracycline"), + GramPos_4 = guess_ab_col(x, "erythromycin"), + GramPos_5 = guess_ab_col(x, "oxacillin"), + GramPos_6 = guess_ab_col(x, "rifampin"), + GramNeg_1 = guess_ab_col(x, "gentamicin"), + GramNeg_2 = guess_ab_col(x, "tobramycin"), + GramNeg_3 = guess_ab_col(x, "colistin"), + GramNeg_4 = guess_ab_col(x, "cefotaxime"), + GramNeg_5 = guess_ab_col(x, "ceftazidime"), + GramNeg_6 = guess_ab_col(x, "meropenem"), + warnings = TRUE, + ... +) + +key_antibiotics_equal( + y, + z, + type = "keyantimicrobials", + ignore_I = TRUE, + points_threshold = 2, + info = FALSE, + na.rm = TRUE, + ... +) } \description{ These functions are so-called '\link{Deprecated}'. They will be removed in a future release. Using the functions will give a warning with the name of the function it has been replaced by (if there is one). diff --git a/man/first_isolate.Rd b/man/first_isolate.Rd index 048e47c2..db51fb10 100755 --- a/man/first_isolate.Rd +++ b/man/first_isolate.Rd @@ -19,12 +19,13 @@ first_isolate( col_testcode = NULL, col_specimen = NULL, col_icu = NULL, - col_keyantibiotics = NULL, + col_keyantimicrobials = NULL, episode_days = 365, testcodes_exclude = NULL, icu_exclude = FALSE, specimen_group = NULL, - type = "keyantibiotics", + type = "points", + method = c("phenotype-based", "episode-based", "patient-based", "isolate-based"), ignore_I = TRUE, points_threshold = 2, info = interactive(), @@ -38,6 +39,7 @@ filter_first_isolate( col_date = NULL, col_patient_id = NULL, col_mo = NULL, + method = "episode-based", ... ) @@ -46,7 +48,7 @@ filter_first_weighted_isolate( col_date = NULL, col_patient_id = NULL, col_mo = NULL, - col_keyantibiotics = NULL, + method = "phenotype-based", ... ) } @@ -65,7 +67,7 @@ filter_first_weighted_isolate( \item{col_icu}{column name of the logicals (\code{TRUE}/\code{FALSE}) whether a ward or department is an Intensive Care Unit (ICU)} -\item{col_keyantibiotics}{column name of the key antibiotics to determine first (weighted) isolates, see \code{\link[=key_antibiotics]{key_antibiotics()}}. Defaults to the first column that starts with 'key' followed by 'ab' or 'antibiotics' (case insensitive). Use \code{col_keyantibiotics = FALSE} to prevent this. Can also be the output of \code{\link[=key_antibiotics]{key_antibiotics()}}.} +\item{col_keyantimicrobials}{(only useful when \code{method = "phenotype-based"}) column name of the key antimicrobials to determine first (weighted) isolates, see \code{\link[=key_antimicrobials]{key_antimicrobials()}}. Defaults to the first column that starts with 'key' followed by 'ab' or 'antibiotics' or 'antimicrobials' (case insensitive). Use \code{col_keyantimicrobials = FALSE} to prevent this. Can also be the output of \code{\link[=key_antimicrobials]{key_antimicrobials()}}.} \item{episode_days}{episode in days after which a genus/species combination will be determined as 'first isolate' again. The default of 365 days is based on the guideline by CLSI, see \emph{Source}.} @@ -75,11 +77,13 @@ filter_first_weighted_isolate( \item{specimen_group}{value in the column set with \code{col_specimen} to filter on} -\item{type}{type to determine weighed isolates; can be \code{"keyantibiotics"} or \code{"points"}, see \emph{Details}} +\item{type}{type to determine weighed isolates; can be \code{"keyantimicrobials"} or \code{"points"}, see \emph{Details}} -\item{ignore_I}{logical to indicate whether antibiotic interpretations with \code{"I"} will be ignored when \code{type = "keyantibiotics"}, see \emph{Details}} +\item{method}{the algorithm to apply, either \code{"phenotype-based"}, \code{"episode-based"}, \code{"patient-based"} or \code{"isolate-based"} (can be abbreviated), see \emph{Details}} -\item{points_threshold}{points until the comparison of key antibiotics will lead to inclusion of an isolate when \code{type = "points"}, see \emph{Details}} +\item{ignore_I}{logical to indicate whether antibiotic interpretations with \code{"I"} will be ignored when \code{type = "keyantimicrobials"}, see \emph{Details}} + +\item{points_threshold}{minimum number of points to require before differences in the antibiogram will lead to inclusion of an isolate when \code{type = "points"}, see \emph{Details}} \item{info}{a \link{logical} to indicate info should be printed, defaults to \code{TRUE} only in interactive mode} @@ -87,7 +91,7 @@ filter_first_weighted_isolate( \item{include_untested_rsi}{logical to indicate whether also rows without antibiotic results are still eligible for becoming a first isolate. Use \code{include_untested_rsi = FALSE} to always return \code{FALSE} for such rows. This checks the data set for columns of class \verb{} and consequently requires transforming columns with antibiotic results using \code{\link[=as.rsi]{as.rsi()}} first.} -\item{...}{arguments passed on to \code{\link[=first_isolate]{first_isolate()}} when using \code{\link[=filter_first_isolate]{filter_first_isolate()}}, or arguments passed on to \code{\link[=key_antibiotics]{key_antibiotics()}} when using \code{\link[=filter_first_weighted_isolate]{filter_first_weighted_isolate()}}} +\item{...}{arguments passed on to \code{\link[=first_isolate]{first_isolate()}} when using \code{\link[=filter_first_isolate]{filter_first_isolate()}}, or arguments passed on to \code{\link[=key_antimicrobials]{key_antimicrobials()}} otherwise (such as \code{universal}, \code{gram_negative}, \code{gram_positive})} } \value{ A \code{\link{logical}} vector @@ -96,47 +100,77 @@ A \code{\link{logical}} vector Determine first (weighted) isolates of all microorganisms of every patient per episode and (if needed) per specimen type. To determine patient episodes not necessarily based on microorganisms, use \code{\link[=is_new_episode]{is_new_episode()}} that also supports grouping with the \code{dplyr} package. } \details{ +To conduct epidemiological analyses on antimicrobial resistance data, only so-called first isolates should be included to prevent overestimation and underestimation of antimicrobial resistance. Different algorithms can be used to do so, see below. + These functions are context-aware. This means that then the \code{x} argument can be left blank, see \emph{Examples}. The \code{\link[=first_isolate]{first_isolate()}} function is a wrapper around the \code{\link[=is_new_episode]{is_new_episode()}} function, but more efficient for data sets containing microorganism codes or names. All isolates with a microbial ID of \code{NA} will be excluded as first isolate. -\subsection{Why this is so Important}{ +\subsection{Different algorithms}{ -To conduct an analysis of antimicrobial resistance, you should only include the first isolate of every patient per episode \href{https://pubmed.ncbi.nlm.nih.gov/17304462/}{(Hindler \emph{et al.} 2007)}. If you would not do this, you could easily get an overestimate or underestimate of the resistance of an antibiotic. Imagine that a patient was admitted with an MRSA and that it was found in 5 different blood cultures the following week. The resistance percentage of oxacillin of all \emph{S. aureus} isolates would be overestimated, because you included this MRSA more than once. It would be \href{https://en.wikipedia.org/wiki/Selection_bias}{selection bias}. +According to Hindler \emph{et al.} (2007, \doi{10.1086/511864}), there are different algorithms to select first isolates with increasing reliability: isolate-based, patient-based, episode-based and phenotype-based. All algorithms select on a combination of the taxonomic genus and species (not subspecies). + +All mentioned algorithms are covered in the \code{\link[=first_isolate]{first_isolate()}} function:\tabular{ll}{ + \strong{Algorithm} \tab \strong{Function to apply} \cr + Isolate-based \tab \code{first_isolate(x, method = "isolate-based")} \cr + \emph{(= all isolates)} \tab \cr + \tab \cr + \tab \cr + Patient-based \tab \code{first_isolate(x, method = "patient-based")} \cr + \emph{(= first isolate per patient)} \tab \cr + \tab \cr + \tab \cr + Episode-based \tab \code{first_isolate(x, method = "episode-based")}, or: \cr + \emph{(= first isolate per episode)} \tab \cr + - 7-Day interval from initial isolate \tab - \code{first_isolate(x, method = "e", episode_days = 7)} \cr + - 30-Day interval from initial isolate \tab - \code{first_isolate(x, method = "e", episode_days = 30)} \cr + \tab \cr + \tab \cr + Phenotype-based \tab \code{first_isolate(x, method = "phenotype-based")}, or: \cr + \emph{(= first isolate per phenotype)} \tab \cr + - Major difference in any antimicrobial result \tab - \code{first_isolate(x, type = "points")} \cr + - Any difference in key antimicrobial results \tab - \code{first_isolate(x, type = "keyantimicrobials")} \cr } -\subsection{\verb{filter_*()} Shortcuts}{ +\subsection{Isolate-based}{ -The functions \code{\link[=filter_first_isolate]{filter_first_isolate()}} and \code{\link[=filter_first_weighted_isolate]{filter_first_weighted_isolate()}} are helper functions to quickly filter on first isolates. - -The function \code{\link[=filter_first_isolate]{filter_first_isolate()}} is essentially equal to either:\preformatted{ x[first_isolate(x, ...), ] - - x \%>\% filter(first_isolate(...)) +This algorithm does not require any selection, as all isolates should be included. It does, however, respect all arguments set in the \code{\link[=first_isolate]{first_isolate()}} function. For example, the default setting for \code{include_unknown} (\code{FALSE}) will omit selection of rows without a microbial ID. } -The function \code{\link[=filter_first_weighted_isolate]{filter_first_weighted_isolate()}} is essentially equal to:\preformatted{ x \%>\% - mutate(keyab = key_antibiotics(.)) \%>\% - mutate(only_weighted_firsts = first_isolate(x, - col_keyantibiotics = "keyab", ...)) \%>\% - filter(only_weighted_firsts == TRUE) \%>\% - select(-only_weighted_firsts, -keyab) -} -} -} -\section{Key Antibiotics}{ +\subsection{Patient-based}{ -There are two ways to determine whether isolates can be included as first weighted isolates which will give generally the same results: +To include every genus-species combination per patient once, set the \code{episode_days} to \code{Inf}. Although often inappropriate, this algorithm makes sure that no duplicate isolates are selected from the same patient. +} + +\subsection{Episode-based}{ + +To include every genus-species combination per patient episode once, set the \code{episode_days} to a sensible number of days. Depending on the type of analysis, this could be 14, 30, 60 or 365. Short episodes are common for analysing specific hospital or ward data, long episodes are common for analysing regional and national data. + +This is the most common algorithm to correct for duplicate isolates. Patients are categorised into episodes based on their ID and dates (e.g., the date of specimen receipt or laboratory result). While this is a common algorithm, it does not take into account antimicrobial test results. This means that e.g. a methicillin-resistant \emph{Staphylococcus aureus} (MRSA) isolate cannot be differentiated from a wildtype \emph{Staphylococcus aureus} isolate. +} + +\subsection{Phenotype-based}{ + +This is a more reliable algorithm, since it also \emph{weighs} the antibiogram (antimicrobial test results) yielding so-called 'first weighted isolates'. There are two different methods to weigh the antibiogram: \enumerate{ -\item Using \code{type = "keyantibiotics"} and argument \code{ignore_I} - -Any difference from S to R (or vice versa) will (re)select an isolate as a first weighted isolate. With \code{ignore_I = FALSE}, also differences from I to S|R (or vice versa) will lead to this. This is a reliable method and 30-35 times faster than method 2. Read more about this in the \code{\link[=key_antibiotics]{key_antibiotics()}} function. \item Using \code{type = "points"} and argument \code{points_threshold} -A difference from I to S|R (or vice versa) means 0.5 points, a difference from S to R (or vice versa) means 1 point. When the sum of points exceeds \code{points_threshold}, which defaults to \code{2}, an isolate will be (re)selected as a first weighted isolate. -} +This method weighs \emph{all} antimicrobial agents available in the data set. Any difference from I to S or R (or vice versa) counts as 0.5 points, a difference from S to R (or vice versa) counts as 1 point. When the sum of points exceeds \code{points_threshold}, which defaults to \code{2}, an isolate will be selected as a first weighted isolate. + +All antimicrobials are internally selected using the \code{\link[=all_antimicrobials]{all_antimicrobials()}} function. The output of this function does not need to be passed to the \code{\link[=first_isolate]{first_isolate()}} function. +\item Using \code{type = "keyantimicrobials"} and argument \code{ignore_I} + +This method only weighs specific antimicrobial agents, called \emph{key antimicrobials}. Any difference from S to R (or vice versa) in these key antimicrobials will select an isolate as a first weighted isolate. With \code{ignore_I = FALSE}, also differences from I to S or R (or vice versa) will lead to this. + +Key antimicrobials are internally selected using the \code{\link[=key_antimicrobials]{key_antimicrobials()}} function, but can also be added manually as a variable to the data and set in the \code{col_keyantimicrobials} argument. Another option is to pass the output of the \code{\link[=key_antimicrobials]{key_antimicrobials()}} function directly to the \code{col_keyantimicrobials} argument. } +The default algorithm is phenotype-based (using \code{type = "points"}) and episode-based (using \code{episode_days = 365}). This makes sure that every genus-species combination is selected per patient once per year, while taking into account all antimicrobial test results. +} + +} +} \section{Stable Lifecycle}{ \if{html}{\figure{lifecycle_stable.svg}{options: style=margin-bottom:5px} \cr} @@ -155,7 +189,6 @@ On our website \url{https://msberends.github.io/AMR/} you can find \href{https:/ # See ?example_isolates. example_isolates[first_isolate(example_isolates), ] - \donttest{ # faster way, only works in R 3.2 and later: example_isolates[first_isolate(), ] @@ -193,11 +226,11 @@ if (require("dplyr")) { # Have a look at A and B. # B is more reliable because every isolate is counted only once. - # Gentamicin resistance in hospital D appears to be 3.7\% higher than + # Gentamicin resistance in hospital D appears to be 4.2\% higher than # when you (erroneously) would have used all isolates for analysis. } } } \seealso{ -\code{\link[=key_antibiotics]{key_antibiotics()}} +\code{\link[=key_antimicrobials]{key_antimicrobials()}} } diff --git a/man/key_antibiotics.Rd b/man/key_antibiotics.Rd deleted file mode 100755 index 13c3ac52..00000000 --- a/man/key_antibiotics.Rd +++ /dev/null @@ -1,176 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/key_antibiotics.R -\name{key_antibiotics} -\alias{key_antibiotics} -\alias{key_antibiotics_equal} -\title{Key Antibiotics for First (Weighted) Isolates} -\usage{ -key_antibiotics( - x = NULL, - col_mo = NULL, - universal_1 = guess_ab_col(x, "amoxicillin"), - universal_2 = guess_ab_col(x, "amoxicillin/clavulanic acid"), - universal_3 = guess_ab_col(x, "cefuroxime"), - universal_4 = guess_ab_col(x, "piperacillin/tazobactam"), - universal_5 = guess_ab_col(x, "ciprofloxacin"), - universal_6 = guess_ab_col(x, "trimethoprim/sulfamethoxazole"), - GramPos_1 = guess_ab_col(x, "vancomycin"), - GramPos_2 = guess_ab_col(x, "teicoplanin"), - GramPos_3 = guess_ab_col(x, "tetracycline"), - GramPos_4 = guess_ab_col(x, "erythromycin"), - GramPos_5 = guess_ab_col(x, "oxacillin"), - GramPos_6 = guess_ab_col(x, "rifampin"), - GramNeg_1 = guess_ab_col(x, "gentamicin"), - GramNeg_2 = guess_ab_col(x, "tobramycin"), - GramNeg_3 = guess_ab_col(x, "colistin"), - GramNeg_4 = guess_ab_col(x, "cefotaxime"), - GramNeg_5 = guess_ab_col(x, "ceftazidime"), - GramNeg_6 = guess_ab_col(x, "meropenem"), - warnings = TRUE, - ... -) - -key_antibiotics_equal( - y, - z, - type = c("keyantibiotics", "points"), - ignore_I = TRUE, - points_threshold = 2, - info = FALSE, - na.rm = TRUE, - ... -) -} -\arguments{ -\item{x}{a \link{data.frame} with antibiotics columns, like \code{AMX} or \code{amox}. Can be left blank to determine automatically} - -\item{col_mo}{column name of the IDs of the microorganisms (see \code{\link[=as.mo]{as.mo()}}), defaults to the first column of class \code{\link{mo}}. Values will be coerced using \code{\link[=as.mo]{as.mo()}}.} - -\item{universal_1, universal_2, universal_3, universal_4, universal_5, universal_6}{column names of \strong{broad-spectrum} antibiotics, case-insensitive. See details for which antibiotics will be used at default (which are guessed with \code{\link[=guess_ab_col]{guess_ab_col()}}).} - -\item{GramPos_1, GramPos_2, GramPos_3, GramPos_4, GramPos_5, GramPos_6}{column names of antibiotics for \strong{Gram-positives}, case-insensitive. See details for which antibiotics will be used at default (which are guessed with \code{\link[=guess_ab_col]{guess_ab_col()}}).} - -\item{GramNeg_1, GramNeg_2, GramNeg_3, GramNeg_4, GramNeg_5, GramNeg_6}{column names of antibiotics for \strong{Gram-negatives}, case-insensitive. See details for which antibiotics will be used at default (which are guessed with \code{\link[=guess_ab_col]{guess_ab_col()}}).} - -\item{warnings}{give a warning about missing antibiotic columns (they will be ignored)} - -\item{...}{other arguments passed on to functions} - -\item{y, z}{character vectors to compare} - -\item{type}{type to determine weighed isolates; can be \code{"keyantibiotics"} or \code{"points"}, see \emph{Details}} - -\item{ignore_I}{logical to indicate whether antibiotic interpretations with \code{"I"} will be ignored when \code{type = "keyantibiotics"}, see \emph{Details}} - -\item{points_threshold}{points until the comparison of key antibiotics will lead to inclusion of an isolate when \code{type = "points"}, see \emph{Details}} - -\item{info}{unused - previously used to indicate whether a progress bar should print} - -\item{na.rm}{a \link{logical} to indicate whether comparison with \code{NA} should return \code{FALSE} (defaults to \code{TRUE} for backwards compatibility)} -} -\description{ -These function can be used to determine first isolates (see \code{\link[=first_isolate]{first_isolate()}}). Using key antibiotics to determine first isolates is more reliable than without key antibiotics. These selected isolates can then be called first 'weighted' isolates. -} -\details{ -The \code{\link[=key_antibiotics]{key_antibiotics()}} function is context-aware. This means that then the \code{x} argument can be left blank, see \emph{Examples}. - -The function \code{\link[=key_antibiotics]{key_antibiotics()}} returns a character vector with 12 antibiotic results for every isolate. These isolates can then be compared using \code{\link[=key_antibiotics_equal]{key_antibiotics_equal()}}, to check if two isolates have generally the same antibiogram. Missing and invalid values are replaced with a dot (\code{"."}) by \code{\link[=key_antibiotics]{key_antibiotics()}} and ignored by \code{\link[=key_antibiotics_equal]{key_antibiotics_equal()}}. - -The \code{\link[=first_isolate]{first_isolate()}} function only uses this function on the same microbial species from the same patient. Using this, e.g. an MRSA will be included after a susceptible \emph{S. aureus} (MSSA) is found within the same patient episode. Without key antibiotic comparison it would not. See \code{\link[=first_isolate]{first_isolate()}} for more info. - -At default, the antibiotics that are used for \strong{Gram-positive bacteria} are: -\itemize{ -\item Amoxicillin -\item Amoxicillin/clavulanic acid -\item Cefuroxime -\item Piperacillin/tazobactam -\item Ciprofloxacin -\item Trimethoprim/sulfamethoxazole -\item Vancomycin -\item Teicoplanin -\item Tetracycline -\item Erythromycin -\item Oxacillin -\item Rifampin -} - -At default the antibiotics that are used for \strong{Gram-negative bacteria} are: -\itemize{ -\item Amoxicillin -\item Amoxicillin/clavulanic acid -\item Cefuroxime -\item Piperacillin/tazobactam -\item Ciprofloxacin -\item Trimethoprim/sulfamethoxazole -\item Gentamicin -\item Tobramycin -\item Colistin -\item Cefotaxime -\item Ceftazidime -\item Meropenem -} - -The function \code{\link[=key_antibiotics_equal]{key_antibiotics_equal()}} checks the characters returned by \code{\link[=key_antibiotics]{key_antibiotics()}} for equality, and returns a \code{\link{logical}} vector. -} -\section{Stable Lifecycle}{ - -\if{html}{\figure{lifecycle_stable.svg}{options: style=margin-bottom:5px} \cr} -The \link[=lifecycle]{lifecycle} of this function is \strong{stable}. In a stable function, major changes are unlikely. This means that the unlying code will generally evolve by adding new arguments; removing arguments or changing the meaning of existing arguments will be avoided. - -If the unlying code needs breaking changes, they will occur gradually. For example, a argument will be deprecated and first continue to work, but will emit an message informing you of the change. Next, typically after at least one newly released version on CRAN, the message will be transformed to an error. -} - -\section{Key Antibiotics}{ - -There are two ways to determine whether isolates can be included as first weighted isolates which will give generally the same results: -\enumerate{ -\item Using \code{type = "keyantibiotics"} and argument \code{ignore_I} - -Any difference from S to R (or vice versa) will (re)select an isolate as a first weighted isolate. With \code{ignore_I = FALSE}, also differences from I to S|R (or vice versa) will lead to this. This is a reliable method and 30-35 times faster than method 2. Read more about this in the \code{\link[=key_antibiotics]{key_antibiotics()}} function. -\item Using \code{type = "points"} and argument \code{points_threshold} - -A difference from I to S|R (or vice versa) means 0.5 points, a difference from S to R (or vice versa) means 1 point. When the sum of points exceeds \code{points_threshold}, which defaults to \code{2}, an isolate will be (re)selected as a first weighted isolate. -} -} - -\section{Read more on Our Website!}{ - -On our website \url{https://msberends.github.io/AMR/} you can find \href{https://msberends.github.io/AMR/articles/AMR.html}{a comprehensive tutorial} about how to conduct AMR data analysis, the \href{https://msberends.github.io/AMR/reference/}{complete documentation of all functions} and \href{https://msberends.github.io/AMR/articles/WHONET.html}{an example analysis using WHONET data}. As we would like to better understand the backgrounds and needs of our users, please \href{https://msberends.github.io/AMR/survey.html}{participate in our survey}! -} - -\examples{ -# `example_isolates` is a data set available in the AMR package. -# See ?example_isolates. - -# output of the `key_antibiotics()` function could be like this: -strainA <- "SSSRR.S.R..S" -strainB <- "SSSIRSSSRSSS" - -# those strings can be compared with: -key_antibiotics_equal(strainA, strainB) -# TRUE, because I is ignored (as well as missing values) - -key_antibiotics_equal(strainA, strainB, ignore_I = FALSE) -# FALSE, because I is not ignored and so the 4th character differs - -\donttest{ -if (require("dplyr")) { - # set key antibiotics to a new variable - my_patients <- example_isolates \%>\% - mutate(keyab = key_antibiotics()) \%>\% # no need to define `x` - mutate( - # now calculate first isolates - first_regular = first_isolate(col_keyantibiotics = FALSE), - # and first WEIGHTED isolates - first_weighted = first_isolate(col_keyantibiotics = "keyab") - ) - - # Check the difference, in this data set it results in a lot more isolates: - sum(my_patients$first_regular, na.rm = TRUE) - sum(my_patients$first_weighted, na.rm = TRUE) -} -} -} -\seealso{ -\code{\link[=first_isolate]{first_isolate()}} -} diff --git a/man/key_antimicrobials.Rd b/man/key_antimicrobials.Rd new file mode 100644 index 00000000..ba992c9a --- /dev/null +++ b/man/key_antimicrobials.Rd @@ -0,0 +1,159 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/key_antimicrobials.R +\name{key_antimicrobials} +\alias{key_antimicrobials} +\alias{all_antimicrobials} +\alias{antimicrobials_equal} +\title{(Key) Antimicrobials for First Weighted Isolates} +\usage{ +key_antimicrobials( + x = NULL, + col_mo = NULL, + universal = c("ampicillin", "amoxicillin/clavulanic acid", "cefuroxime", + "piperacillin/tazobactam", "ciprofloxacin", "trimethoprim/sulfamethoxazole"), + gram_negative = c("gentamicin", "tobramycin", "colistin", "cefotaxime", + "ceftazidime", "meropenem"), + gram_positive = c("vancomycin", "teicoplanin", "tetracycline", "erythromycin", + "oxacillin", "rifampin"), + antifungal = c("anidulafungin", "caspofungin", "fluconazole", "miconazole", + "nystatin", "voriconazole"), + only_rsi_columns = FALSE, + ... +) + +all_antimicrobials(x = NULL, only_rsi_columns = FALSE, ...) + +antimicrobials_equal( + y, + z, + type = c("points", "keyantimicrobials"), + ignore_I = TRUE, + points_threshold = 2, + info = FALSE, + ... +) +} +\arguments{ +\item{x}{a \link{data.frame} with antibiotics columns, like \code{AMX} or \code{amox}. Can be left blank to determine automatically} + +\item{col_mo}{column name of the IDs of the microorganisms (see \code{\link[=as.mo]{as.mo()}}), defaults to the first column of class \code{\link{mo}}. Values will be coerced using \code{\link[=as.mo]{as.mo()}}.} + +\item{universal}{names of \strong{broad-spectrum} antimicrobial agents, case-insensitive. Set to \code{NULL} to ignore. See \emph{Details} for the default agents.} + +\item{gram_negative}{names of antibiotic agents for \strong{Gram-positives}, case-insensitive. Set to \code{NULL} to ignore. See \emph{Details} for the default agents.} + +\item{gram_positive}{names of antibiotic agents for \strong{Gram-negatives}, case-insensitive. Set to \code{NULL} to ignore. See \emph{Details} for the default agents.} + +\item{antifungal}{names of antifungal agents for \strong{fungi}, case-insensitive. Set to \code{NULL} to ignore. See \emph{Details} for the default agents.} + +\item{...}{ignored, allows for future extensions} + +\item{y, z}{character vectors to compare} + +\item{type}{type to determine weighed isolates; can be \code{"keyantimicrobials"} or \code{"points"}, see \emph{Details}} + +\item{ignore_I}{logical to indicate whether antibiotic interpretations with \code{"I"} will be ignored when \code{type = "keyantimicrobials"}, see \emph{Details}} + +\item{points_threshold}{minimum number of points to require before differences in the antibiogram will lead to inclusion of an isolate when \code{type = "points"}, see \emph{Details}} + +\item{info}{unused - previously used to indicate whether a progress bar should print} +} +\description{ +These functions can be used to determine first weighted isolates by considering the phenotype for isolate selection (see \code{\link[=first_isolate]{first_isolate()}}). Using a phenotype-based method to determine first isolates is more reliable than methods that disregard phenotypes. +} +\details{ +The \code{\link[=key_antimicrobials]{key_antimicrobials()}} and \code{\link[=all_antimicrobials]{all_antimicrobials()}} functions are context-aware. This means that then the \code{x} argument can be left blank, see \emph{Examples}. + +The function \code{\link[=key_antimicrobials]{key_antimicrobials()}} returns a character vector with 12 antimicrobial results for every isolate. The function \code{\link[=all_antimicrobials]{all_antimicrobials()}} returns a character vector with all antimicrobial results for every isolate. These vectors can then be compared using \code{\link[=antimicrobials_equal]{antimicrobials_equal()}}, to check if two isolates have generally the same antibiogram. Missing and invalid values are replaced with a dot (\code{"."}) by \code{\link[=key_antimicrobials]{key_antimicrobials()}} and ignored by \code{\link[=antimicrobials_equal]{antimicrobials_equal()}}. + +Please see the \code{\link[=first_isolate]{first_isolate()}} function how these important functions enable the 'phenotype-based' method for determination of first isolates. + +The default antimicrobial agents used for \strong{all rows} (set in \code{universal}) are: +\itemize{ +\item Ampicillin +\item Amoxicillin/clavulanic acid +\item Cefuroxime +\item Ciprofloxacin +\item Piperacillin/tazobactam +\item Trimethoprim/sulfamethoxazole +} + +The default antimicrobial agents used for \strong{Gram-negative bacteria} (set in \code{gram_negative}) are: +\itemize{ +\item Cefotaxime +\item Ceftazidime +\item Colistin +\item Gentamicin +\item Meropenem +\item Tobramycin +} + +The default antimicrobial agents used for \strong{Gram-positive bacteria} (set in \code{gram_positive}) are: +\itemize{ +\item Erythromycin +\item Oxacillin +\item Rifampin +\item Teicoplanin +\item Tetracycline +\item Vancomycin +} + +The default antimicrobial agents used for \strong{fungi} (set in \code{antifungal}) are: +\itemize{ +\item Anidulafungin +\item Caspofungin +\item Fluconazole +\item Miconazole +\item Nystatin +\item Voriconazole +} +} +\section{Stable Lifecycle}{ + +\if{html}{\figure{lifecycle_stable.svg}{options: style=margin-bottom:5px} \cr} +The \link[=lifecycle]{lifecycle} of this function is \strong{stable}. In a stable function, major changes are unlikely. This means that the unlying code will generally evolve by adding new arguments; removing arguments or changing the meaning of existing arguments will be avoided. + +If the unlying code needs breaking changes, they will occur gradually. For example, a argument will be deprecated and first continue to work, but will emit an message informing you of the change. Next, typically after at least one newly released version on CRAN, the message will be transformed to an error. +} + +\section{Read more on Our Website!}{ + +On our website \url{https://msberends.github.io/AMR/} you can find \href{https://msberends.github.io/AMR/articles/AMR.html}{a comprehensive tutorial} about how to conduct AMR data analysis, the \href{https://msberends.github.io/AMR/reference/}{complete documentation of all functions} and \href{https://msberends.github.io/AMR/articles/WHONET.html}{an example analysis using WHONET data}. As we would like to better understand the backgrounds and needs of our users, please \href{https://msberends.github.io/AMR/survey.html}{participate in our survey}! +} + +\examples{ +# `example_isolates` is a data set available in the AMR package. +# See ?example_isolates. + +# output of the `key_antimicrobials()` function could be like this: +strainA <- "SSSRR.S.R..S" +strainB <- "SSSIRSSSRSSS" + +# those strings can be compared with: +antimicrobials_equal(strainA, strainB, type = "keyantimicrobials") +# TRUE, because I is ignored (as well as missing values) + +antimicrobials_equal(strainA, strainB, type = "keyantimicrobials", ignore_I = FALSE) +# FALSE, because I is not ignored and so the 4th character differs + +\donttest{ +if (require("dplyr")) { + # set key antibiotics to a new variable + my_patients <- example_isolates \%>\% + mutate(keyab = key_antimicrobials(antifungal = NULL)) \%>\% # no need to define `x` + mutate( + # now calculate first isolates + first_regular = first_isolate(col_keyantimicrobials = FALSE), + # and first WEIGHTED isolates + first_weighted = first_isolate(col_keyantimicrobials = "keyab") + ) + + # Check the difference, in this data set it results in more isolates: + sum(my_patients$first_regular, na.rm = TRUE) + sum(my_patients$first_weighted, na.rm = TRUE) +} +} +} +\seealso{ +\code{\link[=first_isolate]{first_isolate()}} +} diff --git a/tests/testthat/test-first_isolate.R b/tests/testthat/test-first_isolate.R index b2e7b548..2f68e193 100755 --- a/tests/testthat/test-first_isolate.R +++ b/tests/testthat/test-first_isolate.R @@ -28,58 +28,29 @@ context("first_isolate.R") test_that("first isolates work", { skip_on_cran() - # first isolates - expect_equal( - sum( - first_isolate(x = example_isolates, - col_date = "date", - col_patient_id = "patient_id", - col_mo = "mo", - info = TRUE), - na.rm = TRUE), - 1300) - - # first weighted isolates - ex_iso_with_keyab <- example_isolates - ex_iso_with_keyab$keyab <- key_antibiotics(example_isolates, warnings = FALSE) - expect_equal( - suppressWarnings( - sum( - first_isolate(x = ex_iso_with_keyab, - # let syntax determine arguments automatically - type = "keyantibiotics", - info = TRUE), - na.rm = TRUE)), - 1398) - - # when not ignoring I - expect_equal( - suppressWarnings( - sum( - first_isolate(x = ex_iso_with_keyab, - col_date = "date", - col_patient_id = "patient_id", - col_mo = "mo", - col_keyantibiotics = "keyab", - ignore_I = FALSE, - type = "keyantibiotics", - info = TRUE), - na.rm = TRUE)), - 1421) - # when using points - expect_equal( - suppressWarnings( - sum( - first_isolate(x = ex_iso_with_keyab, - col_date = "date", - col_patient_id = "patient_id", - col_mo = "mo", - col_keyantibiotics = "keyab", - type = "points", - info = TRUE), - na.rm = TRUE)), - 1348) - + # all four methods + expect_equal(sum(first_isolate(x = example_isolates, method = "isolate-based", info = TRUE), na.rm = TRUE), + 1984) + expect_equal(sum(first_isolate(x = example_isolates, method = "patient-based", info = TRUE), na.rm = TRUE), + 1265) + expect_equal(sum(first_isolate(x = example_isolates, method = "episode-based", info = TRUE), na.rm = TRUE), + 1300) + expect_equal(sum(first_isolate(x = example_isolates, method = "phenotype-based", info = TRUE), na.rm = TRUE), + 1379) + + # Phenotype-based, using key antimicrobials + expect_equal(sum(first_isolate(x = example_isolates, + method = "phenotype-based", + type = "keyantimicrobials", + antifungal = NULL, info = TRUE), na.rm = TRUE), + 1395) + expect_equal(sum(first_isolate(x = example_isolates, + method = "phenotype-based", + type = "keyantimicrobials", + antifungal = NULL, info = TRUE, ignore_I = FALSE), na.rm = TRUE), + 1418) + + # first non-ICU isolates expect_equal( sum( @@ -91,7 +62,7 @@ test_that("first isolates work", { info = TRUE, icu_exclude = TRUE), na.rm = TRUE), - 881) + 941) # set 1500 random observations to be of specimen type 'Urine' random_rows <- sample(x = 1:2000, size = 1500, replace = FALSE) @@ -157,11 +128,13 @@ test_that("first isolates work", { mutate(mo = as.character(mo)) %>% first_isolate(col_date = "date", col_mo = "mo", - col_patient_id = "patient_id"), + col_patient_id = "patient_id", + info = FALSE), example_isolates %>% first_isolate(col_date = "date", col_mo = "mo", - col_patient_id = "patient_id")) + col_patient_id = "patient_id", + info = FALSE)) # support for WHONET expect_message(example_isolates %>% @@ -182,31 +155,29 @@ test_that("first isolates work", { col_mo = "mo", info = TRUE), na.rm = TRUE), - 1305) + 1382) # unknown MOs test_unknown <- example_isolates test_unknown$mo <- ifelse(test_unknown$mo == "B_ESCHR_COLI", "UNKNOWN", test_unknown$mo) expect_equal(sum(first_isolate(test_unknown, include_unknown = FALSE)), - 1045) + 1108) expect_equal(sum(first_isolate(test_unknown, include_unknown = TRUE)), - 1528) + 1591) test_unknown$mo <- ifelse(test_unknown$mo == "UNKNOWN", NA, test_unknown$mo) expect_equal(sum(first_isolate(test_unknown)), - 1045) + 1108) # empty rsi results expect_equal(sum(first_isolate(example_isolates, include_untested_rsi = FALSE)), - 1287) + 1366) # shortcuts expect_identical(filter_first_isolate(example_isolates), - subset(example_isolates, first_isolate(example_isolates))) - ex <- example_isolates - ex$keyab <- key_antibiotics(ex) + subset(example_isolates, first_isolate(example_isolates, method = "episode-based"))) expect_identical(filter_first_weighted_isolate(example_isolates), - subset(example_isolates, first_isolate(ex))) + subset(example_isolates, first_isolate(example_isolates, method = "phenotype-based"))) # notice that all mo's are distinct, so all are TRUE expect_true(all(example_isolates %pm>% diff --git a/tests/testthat/test-key_antibiotics.R b/tests/testthat/test-key_antimicrobials.R similarity index 66% rename from tests/testthat/test-key_antibiotics.R rename to tests/testthat/test-key_antimicrobials.R index 1f493c06..c97788b8 100644 --- a/tests/testthat/test-key_antibiotics.R +++ b/tests/testthat/test-key_antimicrobials.R @@ -23,19 +23,20 @@ # how to conduct AMR data analysis: https://msberends.github.io/AMR/ # # ==================================================================== # -context("key_antibiotics.R") +context("key_antimcrobials.R") -test_that("keyantibiotics work", { +test_that("key_antimcrobials work", { skip_on_cran() - expect_equal(length(key_antibiotics(example_isolates, warnings = FALSE)), nrow(example_isolates)) - expect_false(all(is.na(key_antibiotics(example_isolates)))) - expect_true(key_antibiotics_equal("SSS", "SSS")) - expect_false(key_antibiotics_equal("SSS", "SRS")) - expect_true(key_antibiotics_equal("SSS", "SIS", ignore_I = TRUE)) - expect_false(key_antibiotics_equal("SSS", "SIS", ignore_I = FALSE)) - expect_true(key_antibiotics_equal(".SS", "SI.", ignore_I = TRUE)) - expect_false(key_antibiotics_equal(".SS", "SI.", ignore_I = FALSE)) + expect_equal(length(key_antimicrobials(example_isolates, antifungal = NULL)), nrow(example_isolates)) + expect_false(all(is.na(key_antimicrobials(example_isolates, antifungal = NULL)))) + expect_true(antimicrobials_equal("SSS", "SSS")) + expect_false(antimicrobials_equal("SSS", "SRS", type = "keyantimicrobials")) + expect_true(antimicrobials_equal("SSS", "SRS", type = "points")) + expect_true(antimicrobials_equal("SSS", "SIS", ignore_I = TRUE, type = "keyantimicrobials")) + expect_false(antimicrobials_equal("SSS", "SIS", ignore_I = FALSE, type = "keyantimicrobials")) + expect_true(antimicrobials_equal(".SS", "SI.", ignore_I = TRUE, type = "keyantimicrobials")) + expect_false(antimicrobials_equal(".SS", "SI.", ignore_I = FALSE, type = "keyantimicrobials")) library(dplyr, warn.conflicts = FALSE) - expect_warning(key_antibiotics(example_isolates %>% slice(rep(1, 10)))) + expect_warning(key_antimicrobials(example_isolates %>% slice(rep(1, 10)))) })