AMR/R/classes.R

431 lines
15 KiB
R
Raw Normal View History

2018-02-21 11:52:31 +01:00
# ==================================================================== #
# TITLE #
# Antimicrobial Resistance (AMR) Analysis #
# #
# AUTHORS #
# Berends MS (m.s.berends@umcg.nl), Luz CF (c.f.luz@umcg.nl) #
# #
# LICENCE #
# This program is free software; you can redistribute it and/or modify #
# it under the terms of the GNU General Public License version 2.0, #
# as published by the Free Software Foundation. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# ==================================================================== #
#' Class 'rsi'
#'
#' This transforms a vector to a new class \code{rsi}, which is an ordered factor with levels \code{S < I < R}. Invalid antimicrobial interpretations will be translated as \code{NA} with a warning.
#' @rdname as.rsi
#' @param x vector
#' @return Ordered factor with new class \code{rsi} and new attribute \code{package}
2018-07-13 17:23:46 +02:00
#' @keywords rsi
2018-02-21 11:52:31 +01:00
#' @export
#' @importFrom dplyr %>%
#' @seealso \code{\link{as.mic}}
2018-02-21 11:52:31 +01:00
#' @examples
#' rsi_data <- as.rsi(c(rep("S", 474), rep("I", 36), rep("R", 370)))
#' rsi_data <- as.rsi(c(rep("S", 474), rep("I", 36), rep("R", 370), "A", "B", "C"))
2018-02-22 20:48:48 +01:00
#' is.rsi(rsi_data)
2018-04-02 16:05:09 +02:00
#'
2018-06-19 10:05:38 +02:00
#' # this can also coerce combined MIC/RSI values:
#' as.rsi("<= 0.002; S") # will return S
2018-06-19 10:05:38 +02:00
#'
2018-03-13 15:40:10 +01:00
#' plot(rsi_data) # for percentages
#' barplot(rsi_data) # for frequencies
2018-02-21 11:52:31 +01:00
as.rsi <- function(x) {
if (is.rsi(x)) {
x
} else {
2018-04-02 16:05:09 +02:00
2018-02-21 11:52:31 +01:00
x <- x %>% unlist()
x.bak <- x
2018-04-02 16:05:09 +02:00
2018-02-21 11:52:31 +01:00
na_before <- x[is.na(x) | x == ''] %>% length()
2018-04-02 16:05:09 +02:00
# remove all spaces
2018-05-31 09:02:49 +02:00
x <- gsub(' +', '', x)
# remove all MIC-like values: numbers, operators and periods
2018-05-31 14:19:25 +02:00
x <- gsub('[0-9.,;:<=>]+', '', x)
2018-05-31 09:02:49 +02:00
# disallow more than 3 characters
x[nchar(x) > 3] <- NA
# set to capitals
x <- toupper(x)
2018-04-02 16:05:09 +02:00
# remove all invalid characters
2018-05-31 09:02:49 +02:00
x <- gsub('[^RSI]+', '', x)
# in cases of "S;S" keep S, but in case of "S;I" make it NA
2018-02-21 11:52:31 +01:00
x <- gsub('^S+$', 'S', x)
x <- gsub('^I+$', 'I', x)
x <- gsub('^R+$', 'R', x)
x[!x %in% c('S', 'I', 'R')] <- NA
na_after <- x[is.na(x) | x == ''] %>% length()
2018-04-02 16:05:09 +02:00
2018-02-21 11:52:31 +01:00
if (na_before != na_after) {
list_missing <- x.bak[is.na(x) & !is.na(x.bak) & x.bak != ''] %>%
unique() %>%
sort()
list_missing <- paste0('"', list_missing , '"', collapse = ", ")
warning(na_after - na_before, ' results truncated (',
round(((na_after - na_before) / length(x)) * 100),
2018-02-21 11:52:31 +01:00
'%) that were invalid antimicrobial interpretations: ',
list_missing, call. = FALSE)
}
2018-04-02 16:05:09 +02:00
2018-05-31 09:02:49 +02:00
x <- x %>% factor(levels = c("S", "I", "R"), ordered = TRUE)
2018-02-21 11:52:31 +01:00
class(x) <- c('rsi', 'ordered', 'factor')
attr(x, 'package') <- 'AMR'
2018-02-21 11:52:31 +01:00
x
}
}
#' @rdname as.rsi
#' @export
#' @importFrom dplyr %>%
is.rsi <- function(x) {
class(x) %>% identical(c('rsi', 'ordered', 'factor'))
}
#' @exportMethod print.rsi
#' @export
#' @importFrom dplyr %>%
#' @noRd
print.rsi <- function(x, ...) {
n_total <- x %>% length()
x <- x[!is.na(x)]
n <- x %>% length()
S <- x[x == 'S'] %>% length()
I <- x[x == 'I'] %>% length()
R <- x[x == 'R'] %>% length()
IR <- x[x %in% c('I', 'R')] %>% length()
2018-04-25 15:33:58 +02:00
cat("Class 'rsi'\n")
2018-05-02 14:56:25 +02:00
cat(n, " results (missing: ", n_total - n, ' = ', percent((n_total - n) / n_total, force_zero = TRUE), ')\n', sep = "")
if (n > 0) {
cat('\n')
cat('Sum of S: ', S, ' (', percent(S / n, force_zero = TRUE), ')\n', sep = "")
cat('Sum of IR: ', IR, ' (', percent(IR / n, force_zero = TRUE), ')\n', sep = "")
cat('- Sum of R: ', R, ' (', percent(R / n, force_zero = TRUE), ')\n', sep = "")
cat('- Sum of I: ', I, ' (', percent(I / n, force_zero = TRUE), ')\n', sep = "")
}
2018-02-21 11:52:31 +01:00
}
#' @exportMethod summary.rsi
#' @export
#' @importFrom dplyr %>%
#' @noRd
summary.rsi <- function(object, ...) {
x <- object
n_total <- x %>% length()
x <- x[!is.na(x)]
n <- x %>% length()
S <- x[x == 'S'] %>% length()
I <- x[x == 'I'] %>% length()
R <- x[x == 'R'] %>% length()
IR <- x[x %in% c('I', 'R')] %>% length()
lst <- c('rsi', n_total - n, S, IR, R, I)
names(lst) <- c("Mode", "<NA>", "Sum S", "Sum IR", "Sum R", "Sum I")
lst
}
#' @exportMethod plot.rsi
#' @export
2018-03-13 14:34:10 +01:00
#' @importFrom dplyr %>% group_by summarise filter mutate if_else n_distinct
2018-02-21 11:52:31 +01:00
#' @importFrom graphics plot text
#' @noRd
plot.rsi <- function(x, ...) {
x_name <- deparse(substitute(x))
2018-04-02 16:05:09 +02:00
2018-02-21 11:52:31 +01:00
data <- data.frame(x = x,
y = 1,
stringsAsFactors = TRUE) %>%
group_by(x) %>%
summarise(n = sum(y)) %>%
filter(!is.na(x)) %>%
mutate(s = round((n / sum(n)) * 100, 1))
data$x <- factor(data$x, levels = c('S', 'I', 'R'), ordered = TRUE)
2018-04-02 16:05:09 +02:00
2018-02-21 11:52:31 +01:00
ymax <- if_else(max(data$s) > 95, 105, 100)
plot(x = data$x,
y = data$s,
lwd = 2,
col = c('green', 'orange', 'red'),
ylim = c(0, ymax),
ylab = 'Percentage',
xlab = 'Antimicrobial Interpretation',
2018-05-31 09:02:49 +02:00
main = paste('Susceptibility Analysis of', x_name),
2018-03-13 14:34:10 +01:00
axes = FALSE,
2018-02-21 11:52:31 +01:00
...)
2018-03-13 14:34:10 +01:00
# x axis
axis(side = 1, at = 1:n_distinct(data$x), labels = levels(data$x), lwd = 0)
# y axis, 0-100%
axis(side = 2, at = seq(0, 100, 5))
2018-04-02 16:05:09 +02:00
2018-02-21 11:52:31 +01:00
text(x = data$x,
2018-03-13 14:34:10 +01:00
y = data$s + 4,
2018-02-21 11:52:31 +01:00
labels = paste0(data$s, '% (n = ', data$n, ')'))
}
2018-03-13 14:34:10 +01:00
#' @exportMethod barplot.rsi
#' @export
#' @importFrom dplyr %>% group_by summarise filter mutate if_else n_distinct
2018-03-13 15:40:10 +01:00
#' @importFrom graphics barplot axis
2018-03-13 14:34:10 +01:00
#' @noRd
2018-03-13 15:40:10 +01:00
barplot.rsi <- function(height, ...) {
x <- height
x_name <- deparse(substitute(height))
2018-04-02 16:05:09 +02:00
2018-03-13 14:34:10 +01:00
data <- data.frame(rsi = x, cnt = 1) %>%
group_by(rsi) %>%
summarise(cnt = sum(cnt)) %>%
droplevels()
2018-03-13 15:40:10 +01:00
barplot(table(x),
2018-03-13 14:34:10 +01:00
col = c('green3', 'orange2', 'red3'),
xlab = 'Antimicrobial Interpretation',
2018-05-31 09:02:49 +02:00
main = paste('Susceptibility Analysis of', x_name),
2018-03-13 14:34:10 +01:00
ylab = 'Frequency',
axes = FALSE,
...)
# y axis, 0-100%
axis(side = 2, at = seq(0, max(data$cnt) + max(data$cnt) * 1.1, by = 25))
}
2018-02-21 11:52:31 +01:00
#' Class 'mic'
#'
#' This transforms a vector to a new class \code{mic}, which is an ordered factor with valid MIC values as levels. Invalid MIC values will be translated as \code{NA} with a warning.
2018-02-21 11:52:31 +01:00
#' @rdname as.mic
#' @param x vector
#' @param na.rm a logical indicating whether missing values should be removed
#' @return Ordered factor with new class \code{mic} and new attribute \code{package}
2018-07-13 17:23:46 +02:00
#' @keywords mic
2018-02-21 11:52:31 +01:00
#' @export
#' @importFrom dplyr %>%
#' @seealso \code{\link{as.rsi}}
2018-02-22 20:48:48 +01:00
#' @examples
#' mic_data <- as.mic(c(">=32", "1.0", "1", "1.00", 8, "<=0.128", "8", "16", "16"))
#' is.mic(mic_data)
2018-04-02 16:05:09 +02:00
#'
2018-06-19 10:05:38 +02:00
#' # this can also coerce combined MIC/RSI values:
#' as.mic("<=0.002; S") # will return <=0.002
2018-06-19 10:05:38 +02:00
#'
2018-02-22 20:48:48 +01:00
#' plot(mic_data)
2018-03-13 14:34:10 +01:00
#' barplot(mic_data)
2018-02-21 11:52:31 +01:00
as.mic <- function(x, na.rm = FALSE) {
if (is.mic(x)) {
x
} else {
x <- x %>% unlist()
if (na.rm == TRUE) {
x <- x[!is.na(x)]
}
x.bak <- x
2018-04-02 16:05:09 +02:00
2018-06-19 10:05:38 +02:00
# comma to period
2018-02-21 11:52:31 +01:00
x <- gsub(',', '.', x, fixed = TRUE)
2018-06-19 10:05:38 +02:00
# remove space between operator and number ("<= 0.002" -> "<=0.002")
x <- gsub('(<|=|>) +', '\\1', x)
2018-02-21 11:52:31 +01:00
# starting dots must start with 0
2018-05-31 09:02:49 +02:00
x <- gsub('^[.]+', '0.', x)
2018-02-21 11:52:31 +01:00
# <=0.2560.512 should be 0.512
x <- gsub('.*[.].*[.]', '0.', x)
# remove ending .0
2018-05-31 09:02:49 +02:00
x <- gsub('[.]+0$', '', x)
2018-02-21 11:52:31 +01:00
# remove all after last digit
2018-05-31 09:02:49 +02:00
x <- gsub('[^0-9]+$', '', x)
2018-02-21 11:52:31 +01:00
# remove last zeroes
x <- gsub('[.]?0+$', '', x)
2018-06-19 10:05:38 +02:00
# force to be character
x <- as.character(x)
2018-04-02 16:05:09 +02:00
2018-06-19 10:05:38 +02:00
# these are alllowed MIC values and will become factor levels
2018-02-21 11:52:31 +01:00
lvls <- c("<0.002", "<=0.002", "0.002", ">=0.002", ">0.002",
"<0.003", "<=0.003", "0.003", ">=0.003", ">0.003",
"<0.004", "<=0.004", "0.004", ">=0.004", ">0.004",
"<0.006", "<=0.006", "0.006", ">=0.006", ">0.006",
"<0.008", "<=0.008", "0.008", ">=0.008", ">0.008",
"<0.012", "<=0.012", "0.012", ">=0.012", ">0.012",
2018-07-28 10:48:27 +02:00
"<0.0125", "<=0.0125", "0.0125", ">=0.0125", ">0.0125",
2018-02-21 11:52:31 +01:00
"<0.016", "<=0.016", "0.016", ">=0.016", ">0.016",
"<0.023", "<=0.023", "0.023", ">=0.023", ">0.023",
2018-03-13 11:57:30 +01:00
"<0.025", "<=0.025", "0.025", ">=0.025", ">0.025",
2018-02-21 11:52:31 +01:00
"<0.03", "<=0.03", "0.03", ">=0.03", ">0.03",
"<0.032", "<=0.032", "0.032", ">=0.032", ">0.032",
"<0.047", "<=0.047", "0.047", ">=0.047", ">0.047",
"<0.05", "<=0.05", "0.05", ">=0.05", ">0.05",
2018-07-28 10:48:27 +02:00
"<0.054", "<=0.054", "0.054", ">=0.054", ">0.054",
2018-02-21 11:52:31 +01:00
"<0.06", "<=0.06", "0.06", ">=0.06", ">0.06",
"<0.0625", "<=0.0625", "0.0625", ">=0.0625", ">0.0625",
2018-03-13 11:57:30 +01:00
"<0.063", "<=0.063", "0.063", ">=0.063", ">0.063",
2018-02-21 11:52:31 +01:00
"<0.064", "<=0.064", "0.064", ">=0.064", ">0.064",
"<0.09", "<=0.09", "0.09", ">=0.09", ">0.09",
"<0.094", "<=0.094", "0.094", ">=0.094", ">0.094",
"<0.12", "<=0.12", "0.12", ">=0.12", ">0.12",
"<0.125", "<=0.125", "0.125", ">=0.125", ">0.125",
"<0.128", "<=0.128", "0.128", ">=0.128", ">0.128",
2018-03-13 11:57:30 +01:00
"<0.16", "<=0.16", "0.16", ">=0.16", ">0.16",
2018-02-21 11:52:31 +01:00
"<0.19", "<=0.19", "0.19", ">=0.19", ">0.19",
2018-07-28 10:48:27 +02:00
"<0.23", "<=0.23", "0.23", ">=0.23", ">0.23",
2018-02-21 11:52:31 +01:00
"<0.25", "<=0.25", "0.25", ">=0.25", ">0.25",
"<0.256", "<=0.256", "0.256", ">=0.256", ">0.256",
2018-07-28 10:48:27 +02:00
"<0.28", "<=0.28", "0.28", ">=0.28", ">0.28",
"<0.30", "<=0.30", "0.30", ">=0.30", ">0.30",
2018-03-13 11:57:30 +01:00
"<0.32", "<=0.32", "0.32", ">=0.32", ">0.32",
2018-07-28 10:48:27 +02:00
"<0.36", "<=0.36", "0.36", ">=0.36", ">0.36",
2018-02-21 11:52:31 +01:00
"<0.38", "<=0.38", "0.38", ">=0.38", ">0.38",
"<0.5", "<=0.5", "0.5", ">=0.5", ">0.5",
"<0.512", "<=0.512", "0.512", ">=0.512", ">0.512",
2018-03-13 11:57:30 +01:00
"<0.64", "<=0.64", "0.64", ">=0.64", ">0.64",
2018-02-21 11:52:31 +01:00
"<0.75", "<=0.75", "0.75", ">=0.75", ">0.75",
"<1", "<=1", "1", ">=1", ">1",
"<1.5", "<=1.5", "1.5", ">=1.5", ">1.5",
"<2", "<=2", "2", ">=2", ">2",
"<3", "<=3", "3", ">=3", ">3",
"<4", "<=4", "4", ">=4", ">4",
2018-07-28 10:48:27 +02:00
"<5", "<=5", "5", ">=5", ">5",
2018-02-21 11:52:31 +01:00
"<6", "<=6", "6", ">=6", ">6",
2018-07-28 10:48:27 +02:00
"<7", "<=7", "7", ">=7", ">7",
2018-02-21 11:52:31 +01:00
"<8", "<=8", "8", ">=8", ">8",
"<10", "<=10", "10", ">=10", ">10",
"<12", "<=12", "12", ">=12", ">12",
"<16", "<=16", "16", ">=16", ">16",
"<20", "<=20", "20", ">=20", ">20",
"<24", "<=24", "24", ">=24", ">24",
"<32", "<=32", "32", ">=32", ">32",
"<40", "<=40", "40", ">=40", ">40",
"<48", "<=48", "48", ">=48", ">48",
"<64", "<=64", "64", ">=64", ">64",
"<80", "<=80", "80", ">=80", ">80",
"<96", "<=96", "96", ">=96", ">96",
"<128", "<=128", "128", ">=128", ">128",
"<160", "<=160", "160", ">=160", ">160",
"<256", "<=256", "256", ">=256", ">256",
"<320", "<=320", "320", ">=320", ">320",
"<512", "<=512", "512", ">=512", ">512",
"<1024", "<=1024", "1024", ">=1024", ">1024")
2018-04-02 16:05:09 +02:00
2018-02-21 11:52:31 +01:00
na_before <- x[is.na(x) | x == ''] %>% length()
x[!x %in% lvls] <- NA
na_after <- x[is.na(x) | x == ''] %>% length()
2018-04-02 16:05:09 +02:00
2018-02-21 11:52:31 +01:00
if (na_before != na_after) {
list_missing <- x.bak[is.na(x) & !is.na(x.bak) & x.bak != ''] %>%
unique() %>%
sort()
list_missing <- paste0('"', list_missing , '"', collapse = ", ")
warning(na_after - na_before, ' results truncated (',
round(((na_after - na_before) / length(x)) * 100),
2018-02-21 11:52:31 +01:00
'%) that were invalid MICs: ',
list_missing, call. = FALSE)
}
2018-04-02 16:05:09 +02:00
2018-02-21 11:52:31 +01:00
x <- factor(x = x,
levels = lvls,
ordered = TRUE)
class(x) <- c('mic', 'ordered', 'factor')
attr(x, 'package') <- 'AMR'
2018-02-21 11:52:31 +01:00
x
}
}
#' @rdname as.mic
#' @export
#' @importFrom dplyr %>%
is.mic <- function(x) {
class(x) %>% identical(c('mic', 'ordered', 'factor'))
}
#' @exportMethod as.double.mic
#' @export
#' @noRd
as.double.mic <- function(x, ...) {
2018-03-13 11:57:30 +01:00
as.double(gsub('(<|=|>)+', '', as.character(x)))
2018-02-21 11:52:31 +01:00
}
#' @exportMethod as.integer.mic
#' @export
#' @noRd
as.integer.mic <- function(x, ...) {
2018-03-13 11:57:30 +01:00
as.integer(gsub('(<|=|>)+', '', as.character(x)))
2018-02-21 11:52:31 +01:00
}
#' @exportMethod as.numeric.mic
#' @export
#' @noRd
as.numeric.mic <- function(x, ...) {
2018-03-13 11:57:30 +01:00
as.numeric(gsub('(<|=|>)+', '', as.character(x)))
2018-02-21 11:52:31 +01:00
}
#' @exportMethod print.mic
#' @export
#' @importFrom dplyr %>% tibble group_by summarise pull
#' @noRd
print.mic <- function(x, ...) {
n_total <- x %>% length()
x <- x[!is.na(x)]
n <- x %>% length()
2018-07-15 22:56:41 +02:00
cat("Class 'mic'\n")
cat(n, " results (missing: ", n_total - n, ' = ', percent((n_total - n) / n_total, force_zero = TRUE), ')\n', sep = "")
if (n > 0) {
cat('\n')
tibble(MIC = x, y = 1) %>%
group_by(MIC) %>%
summarise(n = sum(y)) %>%
base::print.data.frame(row.names = FALSE)
}
2018-02-21 11:52:31 +01:00
}
#' @exportMethod summary.mic
#' @export
2018-04-03 16:07:32 +02:00
#' @importFrom dplyr %>%
2018-02-21 11:52:31 +01:00
#' @noRd
summary.mic <- function(object, ...) {
x <- object
n_total <- x %>% length()
x <- x[!is.na(x)]
n <- x %>% length()
2018-04-03 16:07:32 +02:00
lst <- c('mic',
n_total - n,
sort(x)[1] %>% as.character(),
sort(x)[n] %>% as.character())
names(lst) <- c("Mode", "<NA>", "Min.", "Max.")
lst
2018-02-21 11:52:31 +01:00
}
#' @exportMethod plot.mic
#' @export
#' @importFrom dplyr %>% group_by summarise
#' @importFrom graphics plot text
#' @noRd
plot.mic <- function(x, ...) {
x_name <- deparse(substitute(x))
2018-03-13 14:34:10 +01:00
create_barplot_mic(x, x_name, ...)
}
#' @exportMethod barplot.mic
#' @export
#' @importFrom dplyr %>% group_by summarise
2018-03-13 15:40:10 +01:00
#' @importFrom graphics barplot axis
2018-03-13 14:34:10 +01:00
#' @noRd
2018-03-13 15:40:10 +01:00
barplot.mic <- function(height, ...) {
x_name <- deparse(substitute(height))
create_barplot_mic(height, x_name, ...)
2018-03-13 14:34:10 +01:00
}
2018-03-13 15:40:10 +01:00
#' @importFrom graphics barplot axis
2018-03-13 14:34:10 +01:00
create_barplot_mic <- function(x, x_name, ...) {
2018-02-21 11:52:31 +01:00
data <- data.frame(mic = x, cnt = 1) %>%
group_by(mic) %>%
summarise(cnt = sum(cnt)) %>%
droplevels()
2018-03-13 14:34:10 +01:00
barplot(table(droplevels(x)),
ylab = 'Frequency',
xlab = 'MIC value',
2018-04-02 16:05:09 +02:00
main = paste('MIC values of', x_name),
2018-03-13 14:34:10 +01:00
axes = FALSE,
...)
axis(2, seq(0, max(data$cnt)))
2018-02-21 11:52:31 +01:00
}