1
0
mirror of https://github.com/msberends/AMR.git synced 2024-12-27 10:06:12 +01:00
AMR/R/ab.R

690 lines
26 KiB
R
Raw Normal View History

2019-05-10 16:44:59 +02:00
# ==================================================================== #
# TITLE: #
2022-10-05 09:12:22 +02:00
# AMR: An R Package for Working with Antimicrobial Resistance Data #
2019-05-10 16:44:59 +02:00
# #
# SOURCE CODE: #
2020-07-08 14:48:06 +02:00
# https://github.com/msberends/AMR #
2019-05-10 16:44:59 +02:00
# #
# PLEASE CITE THIS SOFTWARE AS: #
# Berends MS, Luz CF, Friedrich AW, et al. (2022). #
# AMR: An R Package for Working with Antimicrobial Resistance Data. #
# Journal of Statistical Software, 104(3), 1-31. #
2023-05-27 10:39:22 +02:00
# https://doi.org/10.18637/jss.v104.i03 #
2022-10-05 09:12:22 +02:00
# #
2022-12-27 15:16:15 +01:00
# Developed at the University of Groningen and the University Medical #
# Center Groningen in The Netherlands, in collaboration with many #
# colleagues from around the world, see our website. #
2019-05-10 16:44:59 +02: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. #
# We created this package for both routine data analysis and academic #
# research and it was publicly released in the hope that it will be #
# useful, but it comes WITHOUT ANY WARRANTY OR LIABILITY. #
2020-10-08 11:16:03 +02:00
# #
# Visit our website for the full manual and a complete tutorial about #
# how to conduct AMR data analysis: https://msberends.github.io/AMR/ #
2019-05-10 16:44:59 +02:00
# ==================================================================== #
#' Transform Input to an Antibiotic ID
2019-05-10 16:44:59 +02:00
#'
2022-11-13 13:44:25 +01:00
#' Use this function to determine the antibiotic drug code of one or more antibiotics. The data set [antibiotics] will be searched for abbreviations, official names and synonyms (brand names).
2021-05-12 18:15:03 +02:00
#' @param x a [character] vector to determine to antibiotic ID
2022-11-13 13:44:25 +01:00
#' @param flag_multiple_results a [logical] to indicate whether a note should be printed to the console that probably more than one antibiotic drug code or name can be retrieved from a single input value.
#' @param info a [logical] to indicate whether a progress bar should be printed - the default is `TRUE` only in interactive mode
2019-10-04 15:36:12 +02:00
#' @param ... arguments passed on to internal functions
2019-05-10 16:44:59 +02:00
#' @rdname as.ab
#' @inheritSection WHOCC WHOCC
#' @details All entries in the [antibiotics] data set have three different identifiers: a human readable EARS-Net code (column `ab`, used by ECDC and WHONET), an ATC code (column `atc`, used by WHO), and a CID code (column `cid`, Compound ID, used by PubChem). The data set contains more than 5,000 official brand names from many different countries, as found in PubChem. Not that some drugs contain multiple ATC codes.
2022-08-28 10:31:50 +02:00
#'
2020-07-01 16:21:36 +02:00
#' All these properties will be searched for the user input. The [as.ab()] can correct for different forms of misspelling:
2022-08-28 10:31:50 +02:00
#'
2020-12-17 16:22:25 +01:00
#' * Wrong spelling of drug names (such as "tobramicin" or "gentamycin"), which corrects for most audible similarities such as f/ph, x/ks, c/z/s, t/th, etc.
2020-07-01 16:21:36 +02:00
#' * Too few or too many vowels or consonants
2020-12-17 16:22:25 +01:00
#' * Switching two characters (such as "mreopenem", often the case in clinical data, when doctors typed too fast)
2020-07-01 16:21:36 +02:00
#' * Digitalised paper records, leaving artefacts like 0/o/O (zero and O's), B/8, n/r, etc.
2019-05-13 10:10:16 +02:00
#'
#' Use the [`ab_*`][ab_property()] functions to get properties based on the returned antibiotic ID, see *Examples*.
2022-08-28 10:31:50 +02:00
#'
2022-11-13 13:44:25 +01:00
#' Note: the [as.ab()] and [`ab_*`][ab_property()] functions may use very long regular expression to match brand names of antimicrobial drugs. This may fail on some systems.
2022-10-30 14:31:45 +01:00
#'
2022-10-10 20:20:39 +02:00
#' You can add your own manual codes to be considered by [as.ab()] and all [`ab_*`][ab_property()] functions, see [add_custom_antimicrobials()].
2019-05-10 16:44:59 +02:00
#' @section Source:
2024-03-03 23:24:57 +01:00
#' World Health Organization (WHO) Collaborating Centre for Drug Statistics Methodology: \url{https://atcddd.fhi.no/atc_ddd_index/}
2019-05-10 16:44:59 +02:00
#'
2021-05-24 15:29:17 +02:00
#' European Commission Public Health PHARMACEUTICALS - COMMUNITY REGISTER: \url{https://ec.europa.eu/health/documents/community-register/html/reg_hum_atc.htm}
#' @aliases ab
#' @return A [character] [vector] with additional class [`ab`]
2022-08-28 10:31:50 +02:00
#' @seealso
#' * [antibiotics] for the [data.frame] that is being used to determine ATCs
2020-06-25 17:34:50 +02:00
#' * [ab_from_text()] for a function to retrieve antimicrobial drugs from clinical text (from health care records)
#' @inheritSection AMR Reference Data Publicly Available
#' @export
2019-05-10 16:44:59 +02:00
#' @examples
#' # these examples all return "ERY", the ID of erythromycin:
2019-05-10 16:44:59 +02:00
#' as.ab("J01FA01")
#' as.ab("J 01 FA 01")
#' as.ab("Erythromycin")
#' as.ab("eryt")
#' as.ab(" eryt 123")
#' as.ab("ERYT")
#' as.ab("ERY")
2019-05-16 21:20:00 +02:00
#' as.ab("eritromicine") # spelled wrong, yet works
2022-08-28 10:31:50 +02:00
#' as.ab("Erythrocin") # trade name
#' as.ab("Romycin") # trade name
#'
#' # spelling from different languages and dyslexia are no problem
#' ab_atc("ceftriaxon")
2022-08-28 10:31:50 +02:00
#' ab_atc("cephtriaxone") # small spelling error
#' ab_atc("cephthriaxone") # or a bit more severe
#' ab_atc("seephthriaaksone") # and even this works
2019-05-10 16:44:59 +02:00
#'
#' # use ab_* functions to get a specific properties (see ?ab_property);
2019-05-10 16:44:59 +02:00
#' # they use as.ab() internally:
#' ab_name("J01FA01")
#' ab_name("eryt")
2022-10-30 14:31:45 +01:00
#'
2021-05-24 09:00:11 +02:00
#' \donttest{
#' if (require("dplyr")) {
2023-01-21 23:47:20 +01:00
#' # you can quickly rename 'sir' columns using set_ab_names() with dplyr:
#' example_isolates %>%
2023-01-21 23:47:20 +01:00
#' set_ab_names(where(is.sir), property = "atc")
#' }
2021-05-24 09:00:11 +02:00
#' }
as.ab <- function(x, flag_multiple_results = TRUE, info = interactive(), ...) {
2020-10-19 20:44:45 +02:00
meet_criteria(x, allow_class = c("character", "numeric", "integer", "factor"), allow_NA = TRUE)
meet_criteria(flag_multiple_results, allow_class = "logical", has_length = 1)
meet_criteria(info, allow_class = "logical", has_length = 1)
2022-08-28 10:31:50 +02:00
2019-05-10 16:44:59 +02:00
if (is.ab(x)) {
return(x)
}
2022-10-14 13:02:50 +02:00
if (all(x %in% c(AMR_env$AB_lookup$ab, NA))) {
# all valid AB codes, but not yet right class
return(set_clean_class(x,
2022-08-28 10:31:50 +02:00
new_class = c("ab", "character")
))
}
2022-08-28 10:31:50 +02:00
2024-05-12 16:24:44 +02:00
loop_time <- list(...)$loop_time
if (is.null(loop_time)) {
loop_time <- 1
}
2020-06-25 17:34:50 +02:00
already_regex <- isTRUE(list(...)$already_regex)
fast_mode <- isTRUE(list(...)$fast_mode)
2022-08-28 10:31:50 +02:00
2019-05-10 16:44:59 +02:00
x_bak <- x
2020-06-25 17:34:50 +02:00
x <- toupper(x)
2022-08-28 10:31:50 +02:00
2019-10-04 15:36:12 +02:00
# remove diacritics
x <- iconv(x, from = "UTF-8", to = "ASCII//TRANSLIT")
x <- gsub('"', "", x, fixed = TRUE)
x <- gsub("(specimen|specimen date|specimen_date|spec_date|gender|^dates?$|animal|host($|[a-z]))", "", x, ignore.case = TRUE, perl = TRUE)
# penicillin is a special case: we call it so, but then most often mean benzylpenicillin
x[x %like_case% "^PENICILLIN" & x %unlike_case% "[ /+-]"] <- "benzylpenicillin"
2020-06-25 17:34:50 +02:00
x_bak_clean <- x
if (already_regex == FALSE) {
x_bak_clean <- generalise_antibiotic_name(x_bak_clean)
2020-06-25 17:34:50 +02:00
}
2022-08-28 10:31:50 +02:00
2020-09-19 11:54:01 +02:00
x <- unique(x_bak_clean) # this means that every x is in fact generalise_antibiotic_name(x)
2019-05-10 16:44:59 +02:00
x_new <- rep(NA_character_, length(x))
x_unknown <- character(0)
2022-08-28 21:13:26 +02:00
x_unknown_ATCs <- character(0)
2022-08-28 10:31:50 +02:00
note_if_more_than_one_found <- function(found, index, from_text) {
2024-05-12 16:24:44 +02:00
if (loop_time == 1 && isTRUE(length(from_text) > 1)) {
abnames <- ab_name(from_text, tolower = TRUE, loop_time = loop_time + 1)
if (ab_name(found[1L], language = NULL) %like% "(clavulanic acid|(avi|tazo|mono|vabor)bactam)") {
abnames <- abnames[!abnames %in% c("clavulanic acid", "avibactam", "tazobactam", "vaborbactam", "monobactam")]
2020-09-24 00:30:11 +02:00
}
if (length(abnames) > 1) {
2024-05-20 15:27:04 +02:00
if (toupper(paste(abnames, collapse = " ")) %in% AMR_env$AB_lookup$generalised_name) {
# if the found values combined is a valid AB, return that
found <- AMR_env$AB_lookup$ab[match(toupper(paste(abnames, collapse = " ")), AMR_env$AB_lookup$generalised_name)][1]
} else {
message_(
"More than one result was found for item ", index, ": ",
vector_and(abnames, quotes = FALSE)
)
}
2020-09-24 00:30:11 +02:00
}
}
found[1L]
}
2022-08-28 10:31:50 +02:00
# Fill in names, AB codes, CID codes and ATC codes directly (`x` is already clean and uppercase)
2022-10-14 13:02:50 +02:00
known_names <- x %in% AMR_env$AB_lookup$generalised_name
x_new[known_names] <- AMR_env$AB_lookup$ab[match(x[known_names], AMR_env$AB_lookup$generalised_name)]
known_codes_ab <- x %in% AMR_env$AB_lookup$ab
known_codes_atc <- vapply(FUN.VALUE = logical(1), x, function(x_) x_ %in% unlist(AMR_env$AB_lookup$atc), USE.NAMES = FALSE)
known_codes_cid <- x %in% AMR_env$AB_lookup$cid
x_new[known_codes_ab] <- AMR_env$AB_lookup$ab[match(x[known_codes_ab], AMR_env$AB_lookup$ab)]
x_new[known_codes_atc] <- AMR_env$AB_lookup$ab[vapply(
2022-08-28 10:31:50 +02:00
FUN.VALUE = integer(1),
x[known_codes_atc],
function(x_) {
which(vapply(
FUN.VALUE = logical(1),
2022-10-14 13:02:50 +02:00
AMR_env$AB_lookup$atc,
2022-08-28 10:31:50 +02:00
function(atc) x_ %in% atc
))[1L]
},
USE.NAMES = FALSE
)]
2022-10-14 13:02:50 +02:00
x_new[known_codes_cid] <- AMR_env$AB_lookup$ab[match(x[known_codes_cid], AMR_env$AB_lookup$cid)]
2022-10-10 15:44:59 +02:00
previously_coerced <- x %in% AMR_env$ab_previously_coerced$x
x_new[previously_coerced & is.na(x_new)] <- AMR_env$ab_previously_coerced$ab[match(x[is.na(x_new) & x %in% AMR_env$ab_previously_coerced$x], AMR_env$ab_previously_coerced$x)]
already_known <- known_names | known_codes_ab | known_codes_atc | known_codes_cid | previously_coerced
2022-10-30 14:31:45 +01:00
2022-10-10 15:44:59 +02:00
# fix for NAs
x_new[is.na(x)] <- NA
already_known[is.na(x)] <- FALSE
2022-10-30 14:31:45 +01:00
2024-05-12 16:24:44 +02:00
if (loop_time == 1 && sum(already_known) < length(x)) {
progress <- progress_ticker(n = sum(!already_known), n_min = 25, print = info) # start if n >= 25
on.exit(close(progress))
}
2022-10-30 14:31:45 +01:00
for (i in which(!already_known)) {
2024-05-12 16:24:44 +02:00
if (loop_time == 1) {
progress$tick()
}
2022-08-28 10:31:50 +02:00
2022-10-05 09:12:22 +02:00
if (is.na(x[i]) || is.null(x[i])) {
2019-05-10 16:44:59 +02:00
next
}
2022-10-05 09:12:22 +02:00
if (identical(x[i], "") ||
2022-08-28 10:31:50 +02:00
# prevent "bacteria" from coercing to TMP, since Bacterial is a brand name of it:
identical(tolower(x[i]), "bacteria")) {
x_unknown <- c(x_unknown, x_bak[x[i] == x_bak_clean][1])
next
}
2022-08-28 21:13:26 +02:00
if (x[i] %like_case% "[A-Z][0-9][0-9][A-Z][A-Z][0-9][0-9]") {
# seems an ATC code, but the available ones are in `already_known`, so:
x_unknown <- c(x_unknown, x[i])
x_unknown_ATCs <- c(x_unknown_ATCs, x[i])
x_new[i] <- NA_character_
next
}
2022-08-28 10:31:50 +02:00
if (fast_mode == FALSE && flag_multiple_results == TRUE && x[i] %like% "[ ]") {
2024-05-12 16:24:44 +02:00
from_text <- tryCatch(suppressWarnings(ab_from_text(x[i], loop_time = loop_time + 1, translate_ab = FALSE)[[1]]),
2022-08-28 10:31:50 +02:00
error = function(e) character(0)
)
} else {
from_text <- character(0)
}
2022-08-28 10:31:50 +02:00
# old code for phenoxymethylpenicillin (Peni V)
if (x[i] == "PNV") {
x_new[i] <- "PHN"
next
}
2022-08-28 10:31:50 +02:00
2020-01-26 20:20:00 +01:00
# exact LOINC code
2022-08-28 10:31:50 +02:00
loinc_found <- unlist(lapply(
2022-10-14 13:02:50 +02:00
AMR_env$AB_lookup$generalised_loinc,
2022-08-28 10:31:50 +02:00
function(s) x[i] %in% s
))
2022-10-14 13:02:50 +02:00
found <- AMR_env$AB_lookup$ab[loinc_found == TRUE]
2020-01-26 20:20:00 +01:00
if (length(found) > 0) {
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
2020-01-26 20:20:00 +01:00
next
}
2022-08-28 10:31:50 +02:00
2019-05-10 16:44:59 +02:00
# exact synonym
2022-08-28 10:31:50 +02:00
synonym_found <- unlist(lapply(
2022-10-14 13:02:50 +02:00
AMR_env$AB_lookup$generalised_synonyms,
2022-08-28 10:31:50 +02:00
function(s) x[i] %in% s
))
2022-10-14 13:02:50 +02:00
found <- AMR_env$AB_lookup$ab[synonym_found == TRUE]
2019-05-10 16:44:59 +02:00
if (length(found) > 0) {
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
2019-05-10 16:44:59 +02:00
next
}
2022-08-28 10:31:50 +02:00
2019-05-10 16:44:59 +02:00
# exact abbreviation
2022-08-28 10:31:50 +02:00
abbr_found <- unlist(lapply(
2022-10-14 13:02:50 +02:00
AMR_env$AB_lookup$generalised_abbreviations,
2022-08-28 10:31:50 +02:00
# require at least 2 characters for abbreviations
2022-10-05 09:12:22 +02:00
function(s) x[i] %in% s && nchar(x[i]) >= 2
2022-08-28 10:31:50 +02:00
))
2022-10-14 13:02:50 +02:00
found <- AMR_env$AB_lookup$ab[abbr_found == TRUE]
2019-05-10 16:44:59 +02:00
if (length(found) > 0) {
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
2019-05-10 16:44:59 +02:00
next
}
2023-03-11 14:24:34 +01:00
# length of input is quite long, and Levenshtein distance is only max 2
if (nchar(x[i]) >= 10) {
2022-10-14 13:02:50 +02:00
levenshtein <- as.double(utils::adist(x[i], AMR_env$AB_lookup$generalised_name))
if (any(levenshtein <= 2)) {
2022-10-14 13:02:50 +02:00
found <- AMR_env$AB_lookup$ab[which(levenshtein <= 2)]
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
next
}
}
2022-08-28 10:31:50 +02:00
2019-05-10 16:44:59 +02:00
# allow characters that resemble others, but only continue when having more than 3 characters
if (nchar(x[i]) <= 3) {
x_unknown <- c(x_unknown, x_bak[x[i] == x_bak_clean][1])
next
}
2020-06-25 17:34:50 +02:00
x_spelling <- x[i]
if (already_regex == FALSE) {
x_spelling <- gsub("[IY]+", "[IY]+", x_spelling, perl = TRUE)
x_spelling <- gsub("(C|K|Q|QU|S|Z|X|KS)+", "(C|K|Q|QU|S|Z|X|KS)+", x_spelling, perl = TRUE)
x_spelling <- gsub("(PH|F|V)+", "(PH|F|V)+", x_spelling, perl = TRUE)
x_spelling <- gsub("(TH|T)+", "(TH|T)+", x_spelling, perl = TRUE)
x_spelling <- gsub("A+", "A+", x_spelling, perl = TRUE)
x_spelling <- gsub("E+", "E+", x_spelling, perl = TRUE)
x_spelling <- gsub("O+", "O+", x_spelling, perl = TRUE)
2020-06-25 17:34:50 +02:00
# allow any ending of -in/-ine and -im/-ime
x_spelling <- gsub("(\\[IY\\]\\+(N|M)|\\[IY\\]\\+(N|M)E\\+?)$", "[IY]+(N|M)E*", x_spelling, perl = TRUE)
2020-06-25 17:34:50 +02:00
# allow any ending of -ol/-ole
x_spelling <- gsub("(O\\+L|O\\+LE\\+)$", "O+LE*", x_spelling, perl = TRUE)
2020-06-25 17:34:50 +02:00
# allow any ending of -on/-one
x_spelling <- gsub("(O\\+N|O\\+NE\\+)$", "O+NE*", x_spelling, perl = TRUE)
2020-06-25 17:34:50 +02:00
# replace multiple same characters to single one with '+', like "ll" -> "l+"
x_spelling <- gsub("(.)\\1+", "\\1+", x_spelling, perl = TRUE)
2020-06-25 17:34:50 +02:00
# replace spaces and slashes with a possibility on both
x_spelling <- gsub("[ /]", "( .*|.*/)", x_spelling, perl = TRUE)
2020-06-25 17:34:50 +02:00
# correct for digital reading text (OCR)
x_spelling <- gsub("[NRD8B]", "[NRD8B]", x_spelling, perl = TRUE)
x_spelling <- gsub("(O|0)", "(O|0)+", x_spelling, perl = TRUE)
2020-07-01 16:21:36 +02:00
x_spelling <- gsub("++", "+", x_spelling, fixed = TRUE)
2020-06-25 17:34:50 +02:00
}
2022-08-28 10:31:50 +02:00
2019-05-10 16:44:59 +02:00
# try if name starts with it
2022-10-14 13:02:50 +02:00
found <- AMR_env$AB_lookup[which(AMR_env$AB_lookup$generalised_name %like% paste0("^", x_spelling)), "ab", drop = TRUE]
2019-05-10 16:44:59 +02:00
if (length(found) > 0) {
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
2019-05-10 16:44:59 +02:00
next
}
2020-06-25 17:34:50 +02:00
# try if name ends with it
2022-10-14 13:02:50 +02:00
found <- AMR_env$AB_lookup[which(AMR_env$AB_lookup$generalised_name %like% paste0(x_spelling, "$")), "ab", drop = TRUE]
2022-10-05 09:12:22 +02:00
if (nchar(x[i]) >= 4 && length(found) > 0) {
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
2020-06-25 17:34:50 +02:00
next
}
2022-08-28 10:31:50 +02:00
2019-05-10 16:44:59 +02:00
# and try if any synonym starts with it
2022-08-28 10:31:50 +02:00
synonym_found <- unlist(lapply(
2022-10-14 13:02:50 +02:00
AMR_env$AB_lookup$generalised_synonyms,
2022-08-28 10:31:50 +02:00
function(s) any(s %like% paste0("^", x_spelling))
))
2022-10-14 13:02:50 +02:00
found <- AMR_env$AB_lookup$ab[synonym_found == TRUE]
2019-05-10 16:44:59 +02:00
if (length(found) > 0) {
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
2019-05-10 16:44:59 +02:00
next
}
2022-08-28 10:31:50 +02:00
# INITIAL SEARCH - More uncertain results ----
2024-05-12 16:24:44 +02:00
if (loop_time <= 2 && fast_mode == FALSE) {
# only run on first and second try
2022-08-28 10:31:50 +02:00
2020-06-25 17:34:50 +02:00
# try by removing all spaces
if (x[i] %like% " ") {
found <- suppressWarnings(as.ab(gsub(" +", "", x[i], perl = TRUE), loop_time = loop_time + 2))
2022-10-05 09:12:22 +02:00
if (length(found) > 0 && !is.na(found)) {
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
2020-06-25 17:34:50 +02:00
next
}
2019-06-11 14:18:25 +02:00
}
2022-08-28 10:31:50 +02:00
2020-06-25 17:34:50 +02:00
# try by removing all spaces and numbers
2022-10-05 09:12:22 +02:00
if (x[i] %like% " " || x[i] %like% "[0-9]") {
found <- suppressWarnings(as.ab(gsub("[ 0-9]", "", x[i], perl = TRUE), loop_time = loop_time + 2))
2022-10-05 09:12:22 +02:00
if (length(found) > 0 && !is.na(found)) {
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
2020-06-25 17:34:50 +02:00
next
}
2019-06-11 14:18:25 +02:00
}
2022-08-28 10:31:50 +02:00
2019-10-04 15:36:12 +02:00
# transform back from other languages and try again
2023-01-23 15:01:21 +01:00
x_translated <- paste(
lapply(
strsplit(x[i], "[^A-Z0-9]"),
function(y) {
for (i in seq_len(length(y))) {
for (lang in LANGUAGES_SUPPORTED[LANGUAGES_SUPPORTED != "en"]) {
y[i] <- ifelse(tolower(y[i]) %in% tolower(TRANSLATIONS[, lang, drop = TRUE]),
TRANSLATIONS[which(tolower(TRANSLATIONS[, lang, drop = TRUE]) == tolower(y[i]) &
!isFALSE(TRANSLATIONS$fixed)), "pattern"],
y[i]
)
}
2022-08-28 10:31:50 +02:00
}
2023-01-23 15:01:21 +01:00
generalise_antibiotic_name(y)
2022-08-28 10:31:50 +02:00
}
2023-01-23 15:01:21 +01:00
)[[1]],
collapse = "/"
2022-08-28 10:31:50 +02:00
)
x_translated_guess <- suppressWarnings(as.ab(x_translated, loop_time = loop_time + 2))
2020-06-25 17:34:50 +02:00
if (!is.na(x_translated_guess)) {
x_new[i] <- x_translated_guess
next
}
2022-08-28 10:31:50 +02:00
2020-06-25 17:34:50 +02:00
# now also try to coerce brandname combinations like "Amoxy/clavulanic acid"
2023-01-23 15:01:21 +01:00
x_translated <- paste(
lapply(
strsplit(x_translated, "[^A-Z0-9 ]"),
function(y) {
for (i in seq_len(length(y))) {
y_name <- suppressWarnings(ab_name(y[i], language = NULL, loop_time = loop_time + 2))
2023-01-23 15:01:21 +01:00
y[i] <- ifelse(!is.na(y_name),
y_name,
y[i]
)
}
generalise_antibiotic_name(y)
2022-08-28 10:31:50 +02:00
}
2023-01-23 15:01:21 +01:00
)[[1]],
collapse = "/"
2022-08-28 10:31:50 +02:00
)
x_translated_guess <- suppressWarnings(as.ab(x_translated, loop_time = loop_time + 2))
2019-10-04 15:36:12 +02:00
if (!is.na(x_translated_guess)) {
x_new[i] <- x_translated_guess
next
}
2022-08-28 10:31:50 +02:00
2020-06-25 17:34:50 +02:00
# try by removing all trailing capitals
if (x[i] %like_case% "[a-z]+[A-Z]+$") {
found <- suppressWarnings(as.ab(gsub("[A-Z]+$", "", x[i], perl = TRUE), loop_time = loop_time + 2))
2020-06-25 17:34:50 +02:00
if (!is.na(found)) {
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
2019-10-06 21:07:38 +02:00
next
}
2019-10-04 15:36:12 +02:00
}
2022-08-28 10:31:50 +02:00
2020-06-25 17:34:50 +02:00
# keep only letters
found <- suppressWarnings(as.ab(gsub("[^A-Z]", "", x[i], perl = TRUE), loop_time = loop_time + 2))
2020-06-25 17:34:50 +02:00
if (!is.na(found)) {
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
2020-04-14 14:12:31 +02:00
next
}
2022-08-28 10:31:50 +02:00
2020-06-25 17:34:50 +02:00
# try from a bigger text, like from a health care record, see ?ab_from_text
# already calculated above if flag_multiple_results = TRUE
if (flag_multiple_results == TRUE) {
found <- from_text[1L]
} else {
found <- tryCatch(suppressWarnings(ab_from_text(x[i], loop_time = loop_time + 2, translate_ab = FALSE)[[1]][1L]),
2022-08-28 10:31:50 +02:00
error = function(e) NA_character_
)
}
2020-06-25 17:34:50 +02:00
if (!is.na(found)) {
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
2020-06-25 17:34:50 +02:00
next
}
2022-08-28 10:31:50 +02:00
2020-06-25 17:34:50 +02:00
# first 5 except for cephalosporins, then first 7 (those cephalosporins all start quite the same!)
found <- suppressWarnings(as.ab(substr(x[i], 1, 5), loop_time = loop_time + 2))
2024-05-12 16:24:44 +02:00
if (!is.na(found) && ab_group(found, loop_time = loop_time + 1) %unlike% "cephalosporins") {
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
2020-06-25 17:34:50 +02:00
next
}
found <- suppressWarnings(as.ab(substr(x[i], 1, 7), loop_time = loop_time + 2))
2020-06-25 17:34:50 +02:00
if (!is.na(found)) {
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
2020-06-25 17:34:50 +02:00
next
}
2022-08-28 10:31:50 +02:00
2020-06-25 17:34:50 +02:00
# make all consonants facultative
search_str <- gsub("([BCDFGHJKLMNPQRSTVWXZ])", "\\1*", x[i], perl = TRUE)
found <- suppressWarnings(as.ab(search_str, loop_time = loop_time + 2, already_regex = TRUE))
2020-06-25 17:34:50 +02:00
# keep at least 4 normal characters
if (nchar(gsub(".\\*", "", search_str, perl = TRUE)) < 4) {
2020-06-25 17:34:50 +02:00
found <- NA
}
if (!is.na(found)) {
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
2020-06-25 17:34:50 +02:00
next
}
2022-08-28 10:31:50 +02:00
2020-06-25 17:34:50 +02:00
# make all vowels facultative
search_str <- gsub("([AEIOUY])", "\\1*", x[i], perl = TRUE)
found <- suppressWarnings(as.ab(search_str, loop_time = loop_time + 2, already_regex = TRUE))
2020-06-25 17:34:50 +02:00
# keep at least 5 normal characters
if (nchar(gsub(".\\*", "", search_str, perl = TRUE)) < 5) {
2020-06-25 17:34:50 +02:00
found <- NA
}
if (!is.na(found)) {
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
2020-06-25 17:34:50 +02:00
next
}
2022-08-28 10:31:50 +02:00
2020-06-25 17:34:50 +02:00
# allow misspelling of vowels
x_spelling <- gsub("A+", "[AEIOU]+", x_spelling, fixed = TRUE)
x_spelling <- gsub("E+", "[AEIOU]+", x_spelling, fixed = TRUE)
x_spelling <- gsub("I+", "[AEIOU]+", x_spelling, fixed = TRUE)
x_spelling <- gsub("O+", "[AEIOU]+", x_spelling, fixed = TRUE)
x_spelling <- gsub("U+", "[AEIOU]+", x_spelling, fixed = TRUE)
found <- suppressWarnings(as.ab(x_spelling, loop_time = loop_time + 2, already_regex = TRUE))
2020-06-25 17:34:50 +02:00
if (!is.na(found)) {
x_new[i] <- note_if_more_than_one_found(found, i, from_text)
2020-06-25 17:34:50 +02:00
next
}
2022-08-28 10:31:50 +02:00
2020-07-01 16:21:36 +02:00
# try with switched character, like "mreopenem"
for (j in seq_len(nchar(x[i]))) {
x_switched <- paste0(
# beginning part:
substr(x[i], 1, j - 1),
# here is the switching of 2 characters:
2022-08-28 10:31:50 +02:00
substr(x[i], j + 1, j + 1),
substr(x[i], j, j),
2020-07-01 16:21:36 +02:00
# ending part:
2022-08-28 10:31:50 +02:00
substr(x[i], j + 2, nchar(x[i]))
)
2024-05-12 16:24:44 +02:00
found <- suppressWarnings(as.ab(x_switched, loop_time = loop_time + 1))
2020-07-01 16:21:36 +02:00
if (!is.na(found)) {
break
}
}
if (!is.na(found)) {
x_new[i] <- found[1L]
next
}
2024-05-12 16:24:44 +02:00
} # end of loop_time <= 2
2022-08-28 10:31:50 +02:00
2019-05-10 16:44:59 +02:00
# not found
x_unknown <- c(x_unknown, x_bak[x[i] == x_bak_clean][1])
}
2022-08-28 10:31:50 +02:00
2024-05-12 16:24:44 +02:00
if (loop_time == 1 && sum(already_known) < length(x)) {
close(progress)
}
2022-10-30 14:31:45 +01:00
2022-10-10 15:44:59 +02:00
# save to package env to save time for next time
2024-05-12 16:24:44 +02:00
if (loop_time == 1) {
2022-10-30 21:05:46 +01:00
AMR_env$ab_previously_coerced <- AMR_env$ab_previously_coerced[which(!AMR_env$ab_previously_coerced$x %in% x), , drop = FALSE]
2023-03-12 13:02:37 +01:00
AMR_env$ab_previously_coerced <- unique(rbind_AMR(
2023-02-12 17:10:48 +01:00
AMR_env$ab_previously_coerced,
2022-10-30 21:05:46 +01:00
data.frame(
x = x,
ab = x_new,
2022-10-31 13:25:41 +01:00
x_bak = x_bak[match(x, x_bak_clean)],
2022-10-30 21:05:46 +01:00
stringsAsFactors = FALSE
2023-02-12 17:10:48 +01:00
)
))
2022-10-30 21:05:46 +01:00
}
2022-08-28 10:31:50 +02:00
# take failed ATC codes apart from rest
2022-08-28 21:13:26 +02:00
if (length(x_unknown_ATCs) > 0 && fast_mode == FALSE) {
2022-08-28 10:31:50 +02:00
warning_(
"in `as.ab()`: these ATC codes are not (yet) in the antibiotics data set: ",
vector_and(x_unknown_ATCs), "."
)
}
2022-08-28 21:13:26 +02:00
x_unknown <- x_unknown[!x_unknown %in% x_unknown_ATCs]
2023-01-23 15:01:21 +01:00
x_unknown <- c(
x_unknown,
AMR_env$ab_previously_coerced$x_bak[which(AMR_env$ab_previously_coerced$x %in% x & is.na(AMR_env$ab_previously_coerced$ab))]
)
x_unknown <- x_unknown[!x_unknown %in% c("", NA)]
2022-08-28 21:13:26 +02:00
if (length(x_unknown) > 0 && fast_mode == FALSE) {
2022-08-28 10:31:50 +02:00
warning_(
"in `as.ab()`: these values could not be coerced to a valid antimicrobial ID: ",
vector_and(x_unknown), "."
)
2019-05-10 16:44:59 +02:00
}
2022-08-28 10:31:50 +02:00
x_result <- x_new[match(x_bak_clean, x)]
2019-05-16 21:20:00 +02:00
if (length(x_result) == 0) {
x_result <- NA_character_
}
2022-08-28 10:31:50 +02:00
set_clean_class(x_result,
2022-08-28 10:31:50 +02:00
new_class = c("ab", "character")
)
2019-05-10 16:44:59 +02:00
}
2019-05-16 21:20:00 +02:00
#' @rdname as.ab
2019-05-10 16:44:59 +02:00
#' @export
is.ab <- function(x) {
2020-01-31 23:27:38 +01:00
inherits(x, "ab")
2019-05-10 16:44:59 +02:00
}
# will be exported using s3_register() in R/zzz.R
pillar_shaft.ab <- function(x, ...) {
out <- trimws(format(x))
out[is.na(x)] <- font_na(NA)
2023-07-11 09:50:45 +02:00
# add the names to the drugs as mouse-over!
if (tryCatch(isTRUE(getExportedValue("ansi_has_hyperlink_support", ns = asNamespace("cli"))()), error = function(e) FALSE)) {
2023-07-12 16:27:43 +02:00
out[!is.na(x)] <- font_url(url = paste0(x[!is.na(x)], ": ", ab_name(x[!is.na(x)])),
2023-07-11 09:50:45 +02:00
txt = out[!is.na(x)])
}
create_pillar_column(out, align = "left", min_width = 4)
}
# will be exported using s3_register() in R/zzz.R
type_sum.ab <- function(x, ...) {
"ab"
}
2020-05-28 16:48:55 +02:00
#' @method print ab
2019-05-10 16:44:59 +02:00
#' @export
#' @noRd
print.ab <- function(x, ...) {
2022-10-19 11:47:57 +02:00
cat("Class 'ab'\n")
2019-08-07 15:37:39 +02:00
print(as.character(x), quote = FALSE)
2019-05-10 16:44:59 +02:00
}
2020-05-28 16:48:55 +02:00
#' @method as.data.frame ab
2019-05-10 16:44:59 +02:00
#' @export
#' @noRd
2020-05-19 13:18:01 +02:00
as.data.frame.ab <- function(x, ...) {
2020-05-19 12:08:49 +02:00
nm <- deparse1(substitute(x))
2019-05-10 16:44:59 +02:00
if (!"nm" %in% names(list(...))) {
2020-05-19 12:08:49 +02:00
as.data.frame.vector(as.ab(x), ..., nm = nm)
2019-05-10 16:44:59 +02:00
} else {
2020-05-19 12:08:49 +02:00
as.data.frame.vector(as.ab(x), ...)
2019-05-10 16:44:59 +02:00
}
}
2020-05-28 16:48:55 +02:00
#' @method [ ab
2019-05-10 16:44:59 +02:00
#' @export
#' @noRd
2019-08-14 14:57:06 +02:00
"[.ab" <- function(x, ...) {
y <- NextMethod()
2019-08-14 14:57:06 +02:00
attributes(y) <- attributes(x)
y
}
2020-05-28 16:48:55 +02:00
#' @method [[ ab
2019-08-14 14:57:06 +02:00
#' @export
#' @noRd
"[[.ab" <- function(x, ...) {
2019-08-14 14:57:06 +02:00
y <- NextMethod()
attributes(y) <- attributes(x)
2019-08-14 14:57:06 +02:00
y
}
2020-05-28 16:48:55 +02:00
#' @method [<- ab
2019-08-14 14:57:06 +02:00
#' @export
#' @noRd
"[<-.ab" <- function(i, j, ..., value) {
2019-08-14 14:57:06 +02:00
y <- NextMethod()
attributes(y) <- attributes(i)
2022-11-13 13:44:25 +01:00
return_after_integrity_check(y, "antimicrobial drug code", AMR_env$AB_lookup$ab)
2019-08-14 14:57:06 +02:00
}
2020-05-28 16:48:55 +02:00
#' @method [[<- ab
2019-08-14 14:57:06 +02:00
#' @export
#' @noRd
"[[<-.ab" <- function(i, j, ..., value) {
y <- NextMethod()
attributes(y) <- attributes(i)
2022-11-13 13:44:25 +01:00
return_after_integrity_check(y, "antimicrobial drug code", AMR_env$AB_lookup$ab)
2019-08-14 14:57:06 +02:00
}
2020-05-28 16:48:55 +02:00
#' @method c ab
2019-08-14 14:57:06 +02:00
#' @export
#' @noRd
c.ab <- function(...) {
x <- list(...)[[1L]]
2019-08-14 14:57:06 +02:00
y <- NextMethod()
attributes(y) <- attributes(x)
2022-11-13 13:44:25 +01:00
return_after_integrity_check(y, "antimicrobial drug code", AMR_env$AB_lookup$ab)
2019-05-10 16:44:59 +02:00
}
#' @method unique ab
#' @export
#' @noRd
unique.ab <- function(x, incomparables = FALSE, ...) {
y <- NextMethod()
attributes(y) <- attributes(x)
y
}
2021-07-06 16:35:14 +02:00
#' @method rep ab
#' @export
#' @noRd
rep.ab <- function(x, ...) {
y <- NextMethod()
attributes(y) <- attributes(x)
y
}
generalise_antibiotic_name <- function(x) {
x <- toupper(x)
# remove suffices
2023-01-21 23:47:20 +01:00
x <- gsub("_(MIC|RSI|SIR|DIS[CK])$", "", x, perl = TRUE)
# remove disk concentrations, like LVX_NM -> LVX
x <- gsub("_[A-Z]{2}[0-9_.]{0,3}$", "", x, perl = TRUE)
# keep only max 1 space
x <- trimws2(gsub(" +", " ", x, perl = TRUE))
# non-character, space or number should be a slash
2024-04-24 11:42:43 +02:00
x <- gsub("[^A-Z0-9 -)(]", "/", x, perl = TRUE)
# correct for 'high level' antibiotics
x <- gsub("([^A-Z0-9/ -]+)?(HIGH(.?LE?VE?L)?|[^A-Z0-9/]H[^A-Z0-9]?L)([^A-Z0-9 -]+)?", "-HIGH", x, perl = TRUE)
# remove part between brackets if that's followed by another string
x <- gsub("(.*)+ [(].*[)]", "\\1", x)
# spaces around non-characters must be removed: amox + clav -> amox/clav
x <- gsub("(.*[A-Z0-9]) ([^A-Z0-9].*)", "\\1\\2", x, perl = TRUE)
x <- gsub("(.*[^A-Z0-9]) ([A-Z0-9].*)", "\\1\\2", x, perl = TRUE)
# remove hyphen after a starting "co"
x <- gsub("^CO-", "CO", x, perl = TRUE)
# replace operators with a space
x <- gsub("(/| AND | WITH | W/|[+]|[-])+", " ", x, perl = TRUE)
x
}
get_translate_ab <- function(translate_ab) {
translate_ab <- as.character(translate_ab)[1L]
if (translate_ab %in% c("TRUE", "official")) {
return("name")
} else if (translate_ab %in% c(NA_character_, "FALSE")) {
return(FALSE)
} else {
translate_ab <- tolower(translate_ab)
stop_ifnot(translate_ab %in% colnames(AMR::antibiotics),
2023-01-23 15:01:21 +01:00
"invalid value for 'translate_ab', this must be a column name of the antibiotics data set\n",
"or TRUE (equals 'name') or FALSE to not translate at all.",
call = FALSE
)
translate_ab
}
}