1
0
mirror of https://github.com/msberends/AMR.git synced 2024-12-27 08:46:12 +01:00
AMR/R/key_antibiotics.R

305 lines
13 KiB
R
Raw Normal View History

2018-07-17 13:02:05 +02:00
# ==================================================================== #
# TITLE #
# Antimicrobial Resistance (AMR) Analysis #
# #
2019-01-02 23:24:07 +01:00
# SOURCE #
# https://gitlab.com/msberends/AMR #
2018-07-17 13:02:05 +02:00
# #
# LICENCE #
2019-01-02 23:24:07 +01:00
# (c) 2019 Berends MS (m.s.berends@umcg.nl), Luz CF (c.f.luz@umcg.nl) #
2018-07-17 13:02:05 +02:00
# #
2019-01-02 23:24:07 +01:00
# This R package is free software; you can freely use and distribute #
# it for both personal and commercial purposes under the terms of the #
# GNU General Public License version 2.0 (GNU GPL-2), as published by #
# the Free Software Foundation. #
# #
# This R package was created for academic research and was publicly #
# released in the hope that it will be useful, but it comes WITHOUT #
# ANY WARRANTY OR LIABILITY. #
2019-04-05 18:47:39 +02:00
# Visit our website for more info: https://msberends.gitlab.io/AMR. #
2018-07-17 13:02:05 +02:00
# ==================================================================== #
#' Key antibiotics for first \emph{weighted} isolates
#'
#' These function can be used to determine first isolates (see \code{\link{first_isolate}}). Using key antibiotics to determine first isolates is more reliable than without key antibiotics. These selected isolates will then be called first \emph{weighted} isolates.
#' @param tbl table with antibiotics coloms, like \code{amox} and \code{amcl}.
2018-07-17 14:48:11 +02:00
#' @param x,y characters to compare
2018-07-17 13:02:05 +02:00
#' @inheritParams first_isolate
2019-01-11 20:37:23 +01:00
#' @param universal_1,universal_2,universal_3,universal_4,universal_5,universal_6 column names of \strong{broad-spectrum} antibiotics, case-insensitive. At default, the columns containing these antibiotics will be guessed with \code{\link{guess_ab_col}}.
#' @param GramPos_1,GramPos_2,GramPos_3,GramPos_4,GramPos_5,GramPos_6 column names of antibiotics for \strong{Gram positives}, case-insensitive. At default, the columns containing these antibiotics will be guessed with \code{\link{guess_ab_col}}.
#' @param GramNeg_1,GramNeg_2,GramNeg_3,GramNeg_4,GramNeg_5,GramNeg_6 column names of antibiotics for \strong{Gram negatives}, case-insensitive. At default, the columns containing these antibiotics will be guessed with \code{\link{guess_ab_col}}.
2018-07-23 10:35:39 +02:00
#' @param warnings give warning about missing antibiotic columns, they will anyway be ignored
2018-12-22 22:39:34 +01:00
#' @param ... other parameters passed on to function
2019-01-11 20:37:23 +01:00
#' @details The function \code{key_antibiotics} returns a character vector with 12 antibiotic results for every isolate. These isolates can then be compared using \code{key_antibiotics_equal}, to check if two isolates have generally the same antibiogram. Missing and invalid values are replaced with a dot (\code{"."}). The \code{\link{first_isolate}} function only uses this function on the same microbial species from the same patient. Using this, an MRSA will be included after a susceptible \emph{S. aureus} (MSSA) found within the same episode (see \code{episode} parameter of \code{\link{first_isolate}}). Without key antibiotic comparison it would not.
2018-07-17 13:02:05 +02:00
#'
2018-07-19 15:11:23 +02:00
#' At default, the antibiotics that are used for \strong{Gram positive bacteria} are (colum names): \cr
#' \code{"amox"}, \code{"amcl"}, \code{"cfur"}, \code{"pita"}, \code{"cipr"}, \code{"trsu"} (until here is universal), \code{"vanc"}, \code{"teic"}, \code{"tetr"}, \code{"eryt"}, \code{"oxac"}, \code{"rifa"}.
2018-07-17 13:02:05 +02:00
#'
2018-07-19 15:11:23 +02:00
#' At default, the antibiotics that are used for \strong{Gram negative bacteria} are (colum names): \cr
#' \code{"amox"}, \code{"amcl"}, \code{"cfur"}, \code{"pita"}, \code{"cipr"}, \code{"trsu"} (until here is universal), \code{"gent"}, \code{"tobr"}, \code{"coli"}, \code{"cfot"}, \code{"cfta"}, \code{"mero"}.
2018-07-17 13:02:05 +02:00
#'
#'
2018-07-23 10:35:39 +02:00
#' The function \code{key_antibiotics_equal} checks the characters returned by \code{key_antibiotics} for equality, and returns a logical vector.
2018-07-17 13:02:05 +02:00
#' @inheritSection first_isolate Key antibiotics
#' @rdname key_antibiotics
#' @export
2019-02-18 02:33:37 +01:00
#' @importFrom dplyr %>% mutate if_else pull
2018-12-22 22:39:34 +01:00
#' @importFrom crayon blue bold
2018-07-17 13:02:05 +02:00
#' @seealso \code{\link{first_isolate}}
2019-01-02 23:24:07 +01:00
#' @inheritSection AMR Read more on our website!
2018-07-17 13:02:05 +02:00
#' @examples
#' # septic_patients is a dataset available in the AMR package
#' ?septic_patients
2018-12-22 22:39:34 +01:00
#' library(dplyr)
2018-07-17 13:02:05 +02:00
#' # set key antibiotics to a new variable
2018-12-22 22:39:34 +01:00
#' my_patients <- septic_patients %>%
#' mutate(keyab = key_antibiotics(.)) %>%
#' mutate(
#' # now calculate first isolates
2018-12-22 22:39:34 +01:00
#' first_regular = first_isolate(., col_keyantibiotics = FALSE),
#' # and first WEIGHTED isolates
2018-12-22 22:39:34 +01:00
#' first_weighted = first_isolate(., col_keyantibiotics = "keyab")
#' )
2018-07-17 13:02:05 +02:00
#'
#' # Check the difference, in this data set it results in 7% more isolates:
#' sum(my_patients$first_regular, na.rm = TRUE)
#' sum(my_patients$first_weighted, na.rm = TRUE)
2018-07-17 13:02:05 +02:00
#'
2018-07-23 10:35:39 +02:00
#'
#' # output of the `key_antibiotics` function could be like this:
#' strainA <- "SSSRR.S.R..S"
#' strainB <- "SSSIRSSSRSSS"
#'
#' key_antibiotics_equal(strainA, strainB)
2018-12-22 22:39:34 +01:00
#' # TRUE, because I is ignored (as well as missing values)
2018-07-23 10:35:39 +02:00
#'
#' key_antibiotics_equal(strainA, strainB, ignore_I = FALSE)
#' # FALSE, because I is not ignored and so the 4th value differs
2018-07-17 13:02:05 +02:00
key_antibiotics <- function(tbl,
2018-12-22 22:39:34 +01:00
col_mo = NULL,
2019-05-10 16:44:59 +02:00
universal_1 = guess_ab_col(tbl, "AMX"),
universal_2 = guess_ab_col(tbl, "AMC"),
universal_3 = guess_ab_col(tbl, "CXM"),
universal_4 = guess_ab_col(tbl, "TZP"),
universal_5 = guess_ab_col(tbl, "CIP"),
universal_6 = guess_ab_col(tbl, "SXT"),
GramPos_1 = guess_ab_col(tbl, "VAN"),
GramPos_2 = guess_ab_col(tbl, "TEC"),
GramPos_3 = guess_ab_col(tbl, "TCY"),
GramPos_4 = guess_ab_col(tbl, "ERY"),
GramPos_5 = guess_ab_col(tbl, "OXA"),
GramPos_6 = guess_ab_col(tbl, "RIF"),
GramNeg_1 = guess_ab_col(tbl, "GEN"),
GramNeg_2 = guess_ab_col(tbl, "TOB"),
GramNeg_3 = guess_ab_col(tbl, "COL"),
GramNeg_4 = guess_ab_col(tbl, "CTX"),
GramNeg_5 = guess_ab_col(tbl, "CAZ"),
GramNeg_6 = guess_ab_col(tbl, "MEM"),
2018-08-31 13:36:19 +02:00
warnings = TRUE,
2018-12-22 22:39:34 +01:00
...) {
2018-07-17 13:02:05 +02:00
2018-12-22 22:39:34 +01:00
# try to find columns based on type
# -- mo
2019-01-15 12:45:24 +01:00
if (is.null(col_mo)) {
col_mo <- search_type_in_df(tbl = tbl, type = "mo")
2018-08-31 13:36:19 +02:00
}
2018-12-22 22:39:34 +01:00
if (is.null(col_mo)) {
stop("`col_mo` must be set.", call. = FALSE)
2018-07-17 13:02:05 +02:00
}
# check columns
2018-07-19 15:11:23 +02:00
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)
2019-05-10 16:44:59 +02:00
check_available_columns <- function(tbl, col.list, info = 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 1: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(tbl)) {
col.list[i] <- toupper(col.list[i])
} else if (tolower(col.list[i]) %in% colnames(tbl)) {
col.list[i] <- tolower(col.list[i])
} else if (!col.list[i] %in% colnames(tbl)) {
col.list[i] <- NA
}
}
if (!all(col.list %in% colnames(tbl))) {
if (info == TRUE) {
warning('Some columns do not exist and will be ignored: ',
col.list.bak[!(col.list %in% colnames(tbl))] %>% toString(),
'.\nTHIS MAY STRONGLY INFLUENCE THE OUTCOME.',
immediate. = TRUE,
call. = FALSE)
}
}
col.list
}
2018-07-23 10:35:39 +02:00
col.list <- check_available_columns(tbl = tbl, col.list = col.list, info = warnings)
2018-07-19 15:11:23 +02:00
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]
2018-07-17 13:02:05 +02:00
2018-07-19 15:11:23 +02:00
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)
2019-01-03 23:56:19 +01:00
gram_positive <- gram_positive[!is.null(gram_positive)]
2019-05-10 16:44:59 +02:00
gram_positive <- gram_positive[!is.na(gram_positive)]
if (length(gram_positive) < 12) {
warning("only using ", length(gram_positive), " different antibiotics as key antibiotics for Gram positives. See ?key_antibiotics.", call. = FALSE)
}
2018-07-17 13:02:05 +02:00
2018-07-19 15:11:23 +02:00
gram_negative = c(universal,
GramNeg_1, GramNeg_2, GramNeg_3,
GramNeg_4, GramNeg_5, GramNeg_6)
2019-01-03 23:56:19 +01:00
gram_negative <- gram_negative[!is.null(gram_negative)]
2019-05-10 16:44:59 +02:00
gram_negative <- gram_negative[!is.na(gram_negative)]
if (length(gram_negative) < 12) {
warning("only using ", length(gram_negative), " different antibiotics as key antibiotics for Gram negatives. See ?key_antibiotics.", call. = FALSE)
}
2018-07-17 13:02:05 +02:00
2018-12-22 22:39:34 +01:00
# join to microorganisms data set
tbl <- tbl %>%
mutate_at(vars(col_mo), as.mo) %>%
left_join_microorganisms(by = col_mo) %>%
2019-02-18 02:33:37 +01:00
mutate(key_ab = NA_character_,
gramstain = mo_gramstain(pull(., col_mo)))
2018-07-17 13:02:05 +02:00
# Gram +
tbl <- tbl %>% mutate(key_ab =
2018-09-24 23:33:29 +02:00
if_else(gramstain == "Gram positive",
2018-07-17 13:02:05 +02:00
apply(X = tbl[, gram_positive],
MARGIN = 1,
FUN = function(x) paste(x, collapse = "")),
key_ab))
# Gram -
tbl <- tbl %>% mutate(key_ab =
2018-09-24 23:33:29 +02:00
if_else(gramstain == "Gram negative",
2018-07-17 13:02:05 +02:00
apply(X = tbl[, gram_negative],
MARGIN = 1,
FUN = function(x) paste(x, collapse = "")),
key_ab))
# format
key_abs <- tbl %>%
pull(key_ab) %>%
2018-07-17 19:51:09 +02:00
gsub('(NA|NULL)', '.', .) %>%
gsub('[^SIR]', '.', ., ignore.case = TRUE)
2018-07-17 13:02:05 +02:00
key_abs
}
#' @importFrom dplyr progress_estimated %>%
#' @rdname key_antibiotics
#' @export
key_antibiotics_equal <- function(x,
y,
type = c("keyantibiotics", "points"),
ignore_I = TRUE,
points_threshold = 2,
info = FALSE) {
# x is active row, y is lag
2018-07-17 13:02:05 +02:00
type <- type[1]
if (length(x) != length(y)) {
stop('Length of `x` and `y` must be equal.')
}
# only show progress bar on points or when at least 5000 isolates
info_needed <- info == TRUE & (type == "points" | length(x) > 5000)
2018-07-17 13:02:05 +02:00
result <- logical(length(x))
2018-07-23 10:35:39 +02:00
if (info_needed == TRUE) {
p <- dplyr::progress_estimated(length(x))
}
2018-07-17 19:51:09 +02:00
for (i in 1:length(x)) {
2018-07-17 13:02:05 +02:00
if (info_needed == TRUE) {
p$tick()$print()
2018-07-17 19:51:09 +02:00
}
if (is.na(x[i])) {
x[i] <- ''
}
if (is.na(y[i])) {
y[i] <- ''
2018-07-17 13:02:05 +02:00
}
if (x[i] == y[i]) {
2018-07-17 13:02:05 +02:00
result[i] <- TRUE
2018-07-17 13:02:05 +02:00
} else if (nchar(x[i]) != nchar(y[i])) {
2018-07-17 13:02:05 +02:00
result[i] <- FALSE
2018-07-17 13:02:05 +02:00
} else {
2018-07-17 13:02:05 +02:00
x_split <- strsplit(x[i], "")[[1]]
y_split <- strsplit(y[i], "")[[1]]
2018-07-17 13:02:05 +02:00
if (type == 'keyantibiotics') {
2018-07-17 13:02:05 +02:00
if (ignore_I == TRUE) {
x_split[x_split == "I"] <- "."
y_split[y_split == "I"] <- "."
}
2018-07-17 13:02:05 +02:00
y_split[x_split == "."] <- "."
x_split[y_split == "."] <- "."
2018-07-17 13:02:05 +02:00
result[i] <- all(x_split == y_split)
} else if (type == 'points') {
2018-07-17 19:51:09 +02:00
# 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)
2018-07-17 13:02:05 +02:00
suppressWarnings(x_split <- x_split %>% as.rsi() %>% as.double())
suppressWarnings(y_split <- y_split %>% as.rsi() %>% as.double())
points <- (x_split - y_split) %>% abs() %>% sum(na.rm = TRUE) / 2
result[i] <- points >= points_threshold
2018-07-17 13:02:05 +02:00
} else {
stop('`', type, '` is not a valid value for type, must be "points" or "keyantibiotics". See ?first_isolate.')
2018-07-17 13:02:05 +02:00
}
}
}
if (info_needed == TRUE) {
cat('\n')
}
result
2018-07-17 13:02:05 +02:00
}