From 37f6db5ccd818b3f90efb6b13bb0cab492d6011f Mon Sep 17 00:00:00 2001 From: "Matthijs S. Berends" Date: Tue, 4 Oct 2022 14:41:02 +0200 Subject: [PATCH] fix unit tests --- .github/prehooks/pre-commit | 29 ++++ DESCRIPTION | 2 +- NEWS.md | 2 +- R/aa_helper_functions.R | 34 ++-- R/eucast_rules.R | 2 +- R/guess_ab_col.R | 24 +-- R/mdro.R | 4 +- R/mo.R | 318 +++++++++++++++++++--------------- R/mo_matching_score.R | 5 +- R/mo_property.R | 8 +- R/mo_source.R | 18 +- R/rsi.R | 10 +- R/zzz.R | 19 +- data-raw/read_EUCAST.R | 2 +- data/microorganisms.codes.rda | Bin 22968 -> 22356 bytes inst/tinytest/test-mo.R | 38 ++-- man/as.mo.Rd | 6 +- man/microorganisms.codes.Rd | 4 +- man/mo_property.Rd | 6 +- 19 files changed, 297 insertions(+), 234 deletions(-) diff --git a/.github/prehooks/pre-commit b/.github/prehooks/pre-commit index d35cdff5..ddbce453 100755 --- a/.github/prehooks/pre-commit +++ b/.github/prehooks/pre-commit @@ -1,5 +1,34 @@ #!/bin/sh +# ==================================================================== # +# TITLE # +# AMR: An R Package for Working with Antimicrobial Resistance Data # +# # +# SOURCE # +# https://github.com/msberends/AMR # +# # +# CITE AS # +# Berends MS, Luz CF, Friedrich AW, Sinha BNM, Albers CJ, Glasner C # +# (2022). AMR: An R Package for Working with Antimicrobial Resistance # +# Data. Journal of Statistical Software, 104(3), 1-31. # +# doi:10.18637/jss.v104.i03 # +# # +# 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/ # +# ==================================================================== # + echo "Running pre-commit hook..." # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/DESCRIPTION b/DESCRIPTION index beeeb61b..e653f002 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: AMR -Version: 1.8.2.9026 +Version: 1.8.2.9027 Date: 2022-10-04 Title: Antimicrobial Resistance Data Analysis Description: Functions to simplify and standardise antimicrobial resistance (AMR) diff --git a/NEWS.md b/NEWS.md index 6f4994e5..0cc3e329 100755 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# AMR 1.8.2.9026 +# AMR 1.8.2.9027 This version will eventually become v2.0! We're happy to reach a new major milestone soon! diff --git a/R/aa_helper_functions.R b/R/aa_helper_functions.R index c6856b8f..a3abbb24 100755 --- a/R/aa_helper_functions.R +++ b/R/aa_helper_functions.R @@ -409,7 +409,7 @@ word_wrap <- function(..., msg <- paste0(c(...), collapse = "") if (isTRUE(as_note)) { - msg <- paste0(pkg_env$info_icon, " ", gsub("^note:? ?", "", msg, ignore.case = TRUE)) + msg <- paste0(AMR_env$info_icon, " ", gsub("^note:? ?", "", msg, ignore.case = TRUE)) } if (msg %like% "\n") { @@ -742,14 +742,14 @@ meet_criteria <- function(object, # if object is missing, or another error: tryCatch(invisible(object), - error = function(e) pkg_env$meet_criteria_error_txt <- e$message + error = function(e) AMR_env$meet_criteria_error_txt <- e$message ) - if (!is.null(pkg_env$meet_criteria_error_txt)) { - error_txt <- pkg_env$meet_criteria_error_txt - pkg_env$meet_criteria_error_txt <- NULL + if (!is.null(AMR_env$meet_criteria_error_txt)) { + error_txt <- AMR_env$meet_criteria_error_txt + AMR_env$meet_criteria_error_txt <- NULL stop(error_txt, call. = FALSE) # don't use stop_() here, our pkg may not be loaded yet } - pkg_env$meet_criteria_error_txt <- NULL + AMR_env$meet_criteria_error_txt <- NULL if (is.null(object)) { stop_if(allow_NULL == FALSE, "argument `", obj_name, "` must not be NULL", call = call_depth) @@ -990,9 +990,9 @@ message_not_thrown_before <- function(fn, ..., entire_session = FALSE) { # this is to prevent that messages/notes will be printed for every dplyr group or more than once per session # e.g. this would show a msg 4 times: example_isolates %>% group_by(ward) %>% filter(mo_is_gram_negative()) salt <- gsub("[^a-zA-Z0-9|_-]", "?", substr(paste(c(...), sep = "|", collapse = "|"), 1, 512), perl = TRUE) - not_thrown_before <- is.null(pkg_env[[paste0("thrown_msg.", fn, ".", salt)]]) || + not_thrown_before <- is.null(AMR_env[[paste0("thrown_msg.", fn, ".", salt)]]) || !identical( - pkg_env[[paste0("thrown_msg.", fn, ".", salt)]], + AMR_env[[paste0("thrown_msg.", fn, ".", salt)]], unique_call_id( entire_session = entire_session, match_fn = fn @@ -1003,7 +1003,7 @@ message_not_thrown_before <- function(fn, ..., entire_session = FALSE) { assign( x = paste0("thrown_msg.", fn, ".", salt), value = unique_call_id(entire_session = entire_session, match_fn = fn), - envir = pkg_env + envir = AMR_env ) } not_thrown_before @@ -1354,11 +1354,11 @@ percentage <- function(x, digits = NULL, ...) { } time_start_tracking <- function() { - pkg_env$time_start <- round(as.double(Sys.time()) * 1000) + AMR_env$time_start <- round(as.double(Sys.time()) * 1000) } time_track <- function(name = NULL) { - paste("(until now:", trimws(round(as.double(Sys.time()) * 1000) - pkg_env$time_start), "ms)") + paste("(until now:", trimws(round(as.double(Sys.time()) * 1000) - AMR_env$time_start), "ms)") } trimws2 <- function(..., whitespace = "[\u0009\u000A\u000B\u000C\u000D\u0020\u0085\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u200B\u200C\u200D\u2028\u2029\u202F\u205F\u2060\u3000\uFEFF]") { @@ -1370,19 +1370,19 @@ trimws2 <- function(..., whitespace = "[\u0009\u000A\u000B\u000C\u000D\u0020\u00 # Faster data.table implementations ---- match <- function(x, ...) { - if (isTRUE(pkg_env$has_data.table) && is.character(x)) { + if (isTRUE(AMR_env$has_data.table) && is.character(x)) { # data.table::chmatch() is 35% faster than base::match() for character getExportedValue(name = "chmatch", ns = asNamespace("data.table"))(x, ...) } else { base::match(x, ...) } } -`%in%` <- function(x, ...) { - if (isTRUE(pkg_env$has_data.table) && is.character(x)) { - # data.table::`%chin%`() is 20% faster than base::`%in%`() for character - getExportedValue(name = "%chin%", ns = asNamespace("data.table"))(x, ...) +`%in%` <- function(x, table) { + if (isTRUE(AMR_env$has_data.table) && is.character(x) && is.character(table)) { + # data.table::`%chin%`() is 20-50% faster than base::`%in%`() for character + getExportedValue(name = "%chin%", ns = asNamespace("data.table"))(x, table) } else { - base::`%in%`(x, ...) + base::`%in%`(x, table) } } diff --git a/R/eucast_rules.R b/R/eucast_rules.R index 93d01b91..6b25a997 100755 --- a/R/eucast_rules.R +++ b/R/eucast_rules.R @@ -1178,7 +1178,7 @@ eucast_dosage <- function(ab, administration = "iv", version_breakpoints = 11.0) meet_criteria(administration, allow_class = "character", is_in = dosage$administration[!is.na(dosage$administration)], has_length = 1) meet_criteria(version_breakpoints, allow_class = c("numeric", "integer"), has_length = 1, is_in = as.double(names(EUCAST_VERSION_BREAKPOINTS))) - # show used version_breakpoints number once per session (pkg_env will reload every session) + # show used version_breakpoints number once per session (AMR_env will reload every session) if (message_not_thrown_before("eucast_dosage", "v", gsub("[^0-9]", "", version_breakpoints), entire_session = TRUE)) { message_( "Dosages for antimicrobial drugs, as meant for ", diff --git a/R/guess_ab_col.R b/R/guess_ab_col.R index 9185496a..a7890bfd 100755 --- a/R/guess_ab_col.R +++ b/R/guess_ab_col.R @@ -112,17 +112,17 @@ get_column_abx <- function(x, entire_session = FALSE, match_fn = fn ), - pkg_env$get_column_abx.call + AMR_env$get_column_abx.call )) { # so within the same call, within the same environment, we got here again. # but we could've come from another function within the same call, so now only check the columns that changed # first remove the columns that are not existing anymore - previous <- pkg_env$get_column_abx.out + previous <- AMR_env$get_column_abx.out current <- previous[previous %in% colnames(x)] # then compare columns in current call with columns in original call - new_cols <- colnames(x)[!colnames(x) %in% pkg_env$get_column_abx.checked_cols] + new_cols <- colnames(x)[!colnames(x) %in% AMR_env$get_column_abx.checked_cols] if (length(new_cols) > 0) { # these columns did not exist in the last call, so add them new_cols_rsi <- get_column_abx(x[, new_cols, drop = FALSE], reuse_previous_result = FALSE, info = FALSE, sort = FALSE) @@ -132,11 +132,11 @@ get_column_abx <- function(x, } # update pkg environment to improve speed on next run - pkg_env$get_column_abx.out <- current - pkg_env$get_column_abx.checked_cols <- colnames(x) + AMR_env$get_column_abx.out <- current + AMR_env$get_column_abx.checked_cols <- colnames(x) # and return right values - return(pkg_env$get_column_abx.out) + return(AMR_env$get_column_abx.out) } meet_criteria(x, allow_class = "data.frame") @@ -243,9 +243,9 @@ get_column_abx <- function(x, if (info == TRUE && all_okay == TRUE) { message_("No columns found.") } - pkg_env$get_column_abx.call <- unique_call_id(entire_session = FALSE, match_fn = fn) - pkg_env$get_column_abx.checked_cols <- colnames(x.bak) - pkg_env$get_column_abx.out <- out + AMR_env$get_column_abx.call <- unique_call_id(entire_session = FALSE, match_fn = fn) + AMR_env$get_column_abx.checked_cols <- colnames(x.bak) + AMR_env$get_column_abx.out <- out return(out) } @@ -320,9 +320,9 @@ get_column_abx <- function(x, } } - pkg_env$get_column_abx.call <- unique_call_id(entire_session = FALSE, match_fn = fn) - pkg_env$get_column_abx.checked_cols <- colnames(x.bak) - pkg_env$get_column_abx.out <- out + AMR_env$get_column_abx.call <- unique_call_id(entire_session = FALSE, match_fn = fn) + AMR_env$get_column_abx.checked_cols <- colnames(x.bak) + AMR_env$get_column_abx.out <- out out } diff --git a/R/mdro.R b/R/mdro.R index 96e630b9..47dffbce 100755 --- a/R/mdro.R +++ b/R/mdro.R @@ -1955,14 +1955,14 @@ run_custom_mdro_guideline <- function(df, guideline, info) { for (i in seq_len(n_dots)) { qry <- tryCatch(eval(parse(text = guideline[[i]]$query), envir = df, enclos = parent.frame()), error = function(e) { - pkg_env$err_msg <- e$message + AMR_env$err_msg <- e$message return("error") } ) if (identical(qry, "error")) { warning_("in `custom_mdro_guideline()`: rule ", i, " (`", as.character(guideline[[i]]$query), "`) was ignored because of this error message: ", - pkg_env$err_msg, + AMR_env$err_msg, call = FALSE, add_fn = font_red ) diff --git a/R/mo.R b/R/mo.R index f85d948a..3afd8440 100755 --- a/R/mo.R +++ b/R/mo.R @@ -179,12 +179,14 @@ as.mo <- function(x, x <- replace_old_mo_codes(x, property = "mo") # ignore cases that match the ignore pattern x <- replace_ignore_pattern(x, ignore_pattern) - + + x_lower <- tolower(x) + # WHONET: xxx = no growth - x[tolower(x) %in% c("", "xxx", "na", "nan")] <- NA_character_ + x[x_lower %in% c("", "xxx", "na", "nan")] <- NA_character_ out <- rep(NA_character_, length(x)) - + # below we use base R's match(), known for powering '%in%', and incredibly fast! # From reference_df ---- @@ -195,9 +197,11 @@ as.mo <- function(x, # From MO code ---- out[is.na(out) & x %in% MO_lookup$mo] <- x[is.na(out) & x %in% MO_lookup$mo] # From full name ---- - out[is.na(out) & x %in% MO_lookup$fullname] <- MO_lookup$mo[match(x[is.na(out) & x %in% MO_lookup$fullname], MO_lookup$fullname)] + out[is.na(out) & x_lower %in% MO_lookup$fullname_lower] <- MO_lookup$mo[match(x_lower[is.na(out) & x_lower %in% MO_lookup$fullname_lower], MO_lookup$fullname_lower)] + # one exception: "Fungi" matches the kingdom, but instead it should return the 'unknown' code for fungi + out[out == "F_[KNG]_FUNGI"] <- "F_FUNGUS" # From known codes ---- - out[is.na(out) & x %in% AMR::microorganisms.codes$code] <- AMR::microorganisms.codes$mo[match(x[is.na(out) & x %in% AMR::microorganisms.codes$code], AMR::microorganisms.codes$code)] + out[is.na(out) & toupper(x) %in% AMR::microorganisms.codes$code] <- AMR::microorganisms.codes$mo[match(toupper(x)[is.na(out) & toupper(x) %in% AMR::microorganisms.codes$code], AMR::microorganisms.codes$code)] # From SNOMED ---- if (any(is.na(out) & !is.na(x)) && any(is.na(out) & x %in% unlist(microorganisms$snomed), na.rm = TRUE)) { # found this extremely fast gem here: https://stackoverflow.com/a/11002456/4575331 @@ -208,7 +212,7 @@ as.mo <- function(x, out[is.na(out)] <- convert_colloquial_input(x[is.na(out)]) # From previous hits in this session ---- old <- out - out[is.na(out) & paste(x, minimum_matching_score) %in% pkg_env$mo_previously_coerced$x] <- pkg_env$mo_previously_coerced$mo[match(paste(x, minimum_matching_score)[is.na(out) & paste(x, minimum_matching_score) %in% pkg_env$mo_previously_coerced$x], pkg_env$mo_previously_coerced$x)] + out[is.na(out) & paste(x, minimum_matching_score) %in% AMR_env$mo_previously_coerced$x] <- AMR_env$mo_previously_coerced$mo[match(paste(x, minimum_matching_score)[is.na(out) & paste(x, minimum_matching_score) %in% AMR_env$mo_previously_coerced$x], AMR_env$mo_previously_coerced$x)] new <- out if (isTRUE(info) && message_not_thrown_before("as.mo", old, new, entire_session = TRUE) && any(is.na(old) & !is.na(new), na.rm = TRUE)) { message_( @@ -220,8 +224,8 @@ as.mo <- function(x, # For all other input ---- if (any(is.na(out) & !is.na(x))) { # reset uncertainties - pkg_env$mo_uncertainties <- pkg_env$mo_uncertainties[0, ] - pkg_env$mo_failures <- NULL + AMR_env$mo_uncertainties <- AMR_env$mo_uncertainties[0, ] + AMR_env$mo_failures <- NULL # Laboratory systems: remove (translated) entries like "no growth", "not E. coli", etc. x[trimws2(x) %like% translate_into_language("no .*growth", language = language)] <- NA_character_ @@ -238,8 +242,6 @@ as.mo <- function(x, x_coerced <- vapply(FUN.VALUE = character(1), x_unique, function(x_search) { progress$tick() - print(x_search) - # some required cleaning steps x_out <- trimws2(x_search) # this applies the `remove_from_input` argument, which defaults to mo_cleaning_regex() @@ -248,7 +250,11 @@ as.mo <- function(x, x_search_cleaned <- x_out x_out <- tolower(x_out) - print(x_out) + # input must not be too short + if (nchar(x_out) < 3) { + return("UNKNOWN") + } + # take out the parts, split by space x_parts <- strsplit(gsub("-", " ", x_out, fixed = TRUE), " ", fixed = TRUE)[[1]] @@ -282,7 +288,7 @@ as.mo <- function(x, } else { mo_to_search <- MO_lookup$fullname[filtr] } - pkg_env$mo_to_search <- mo_to_search + AMR_env$mo_to_search <- mo_to_search # determine the matching score on the original search value m <- mo_matching_score(x = x_search_cleaned, n = mo_to_search) if (is.null(minimum_matching_score)) { @@ -302,20 +308,21 @@ as.mo <- function(x, result_mo <- NA_character_ } else { result_mo <- MO_lookup$mo[match(top_hits[1], MO_lookup$fullname)] - pkg_env$mo_uncertainties <- rbind(pkg_env$mo_uncertainties, + AMR_env$mo_uncertainties <- rbind(AMR_env$mo_uncertainties, data.frame( - minimum_matching_score = ifelse(is.null(minimum_matching_score), "NULL", minimum_matching_score), original_input = x_search, input = x_search_cleaned, fullname = top_hits[1], mo = result_mo, candidates = ifelse(length(top_hits) > 1, paste(top_hits[2:min(26, length(top_hits))], collapse = ", "), ""), + minimum_matching_score = ifelse(is.null(minimum_matching_score), "NULL", minimum_matching_score), + keep_synonyms = keep_synonyms, stringsAsFactors = FALSE ), stringsAsFactors = FALSE ) # save to package env to save time for next time - pkg_env$mo_previously_coerced <- unique(rbind(pkg_env$mo_previously_coerced, + AMR_env$mo_previously_coerced <- unique(rbind(AMR_env$mo_previously_coerced, data.frame( x = paste(x_search, minimum_matching_score), mo = result_mo, @@ -334,21 +341,21 @@ as.mo <- function(x, out[is.na(out)] <- x_coerced[match(x[is.na(out)], x_unique)] # Throw note about uncertainties ---- - if (isTRUE(info) && NROW(pkg_env$mo_uncertainties) > 0) { - if (message_not_thrown_before("as.mo", "uncertainties", pkg_env$mo_uncertainties$original_input)) { + if (isTRUE(info) && NROW(AMR_env$mo_uncertainties) > 0) { + if (message_not_thrown_before("as.mo", "uncertainties", AMR_env$mo_uncertainties$original_input)) { plural <- c("", "this") - if (length(pkg_env$mo_uncertainties$original_input) > 1) { + if (length(AMR_env$mo_uncertainties$original_input) > 1) { plural <- c("s", "these uncertainties") } - if (length(pkg_env$mo_uncertainties$original_input) <= 3) { + if (length(AMR_env$mo_uncertainties$original_input) <= 3) { examples <- vector_and(paste0( - '"', pkg_env$mo_uncertainties$original_input, - '" (assumed ', font_italic(pkg_env$mo_uncertainties$fullname, collapse = NULL), ")" + '"', AMR_env$mo_uncertainties$original_input, + '" (assumed ', font_italic(AMR_env$mo_uncertainties$fullname, collapse = NULL), ")" ), quotes = FALSE ) } else { - examples <- paste0(nr2char(length(pkg_env$mo_uncertainties$original_input)), " microorganism", plural[1]) + examples <- paste0(nr2char(length(AMR_env$mo_uncertainties$original_input)), " microorganism", plural[1]) } msg <- paste0( "Microorganism translation was uncertain for ", examples, @@ -364,18 +371,18 @@ as.mo <- function(x, gbif_matches[!gbif_matches %in% AMR::microorganisms$gbif] <- NA lpsn_matches <- AMR::microorganisms$lpsn_renamed_to[match(out, AMR::microorganisms$mo)] lpsn_matches[!lpsn_matches %in% AMR::microorganisms$lpsn] <- NA - pkg_env$mo_renamed <- list(old = out[!is.na(gbif_matches) | !is.na(lpsn_matches)], + AMR_env$mo_renamed <- list(old = out[!is.na(gbif_matches) | !is.na(lpsn_matches)], gbif_matches = gbif_matches[!is.na(gbif_matches) | !is.na(lpsn_matches)], lpsn_matches = lpsn_matches[!is.na(gbif_matches) | !is.na(lpsn_matches)]) if (isFALSE(keep_synonyms)) { out[which(!is.na(gbif_matches))] <- AMR::microorganisms$mo[match(gbif_matches[which(!is.na(gbif_matches))], AMR::microorganisms$gbif)] out[which(!is.na(lpsn_matches))] <- AMR::microorganisms$mo[match(lpsn_matches[which(!is.na(lpsn_matches))], AMR::microorganisms$lpsn)] - if (isTRUE(info) && length(pkg_env$mo_renamed$old) > 0) { + if (isTRUE(info) && length(AMR_env$mo_renamed$old) > 0) { print(mo_renamed(), extra_txt = " (use `keep_synonyms = TRUE` to leave uncorrected)") } - } else if (is.null(getOption("AMR_keep_synonyms")) && length(pkg_env$mo_renamed$old) > 0 && message_not_thrown_before("as.mo", "keep_synonyms_warning", entire_session = TRUE)) { + } else if (is.null(getOption("AMR_keep_synonyms")) && length(AMR_env$mo_renamed$old) > 0 && message_not_thrown_before("as.mo", "keep_synonyms_warning", entire_session = TRUE)) { # keep synonyms is TRUE, so check if any do have synonyms - warning_("Function `as.mo()` returned some old taxonomic names. Use `as.mo(..., keep_synonyms = FALSE)` to clean the input to currently accepted taxonomic names, or set the R option `AMR_keep_synonyms` to `FALSE`. This warning will be shown once per session.") + warning_("Function `as.mo()` returned ", nr2char(length(unique(AMR_env$mo_renamed$old))), " old taxonomic name", ifelse(length(unique(AMR_env$mo_renamed$old)) > 1, "s", ""), ". Use `as.mo(..., keep_synonyms = FALSE)` to clean the input to currently accepted taxonomic names, or set the R option `AMR_keep_synonyms` to `FALSE`. This warning will be shown once per session.") } # Apply Becker ---- @@ -432,7 +439,10 @@ as.mo <- function(x, # All unknowns ---- out[is.na(out) & !is.na(x)] <- "UNKNOWN" - pkg_env$mo_failures <- unique(x[out == "UNKNOWN" & x != "UNKNOWN" & !is.na(x)]) + AMR_env$mo_failures <- unique(x[out == "UNKNOWN" & x != "UNKNOWN" & !is.na(x)]) + if (length(AMR_env$mo_failures) > 0) { + warning_("The following input could not be coerced and was returned as \"UNKNOWN\": ", vector_and(AMR_env$mo_failures, quotes = TRUE), ".\nYou can retrieve this list with `mo_failures()`.") + } # Return class ---- set_clean_class(out, @@ -440,12 +450,73 @@ as.mo <- function(x, ) } +# OTHER DOCUMENTED FUNCTIONS ---------------------------------------------- + #' @rdname as.mo #' @export is.mo <- function(x) { inherits(x, "mo") } +#' @rdname as.mo +#' @export +mo_uncertainties <- function() { + set_clean_class(AMR_env$mo_uncertainties, new_class = c("mo_uncertainties", "data.frame")) +} + +#' @rdname as.mo +#' @export +mo_renamed <- function() { + x <- AMR_env$mo_renamed + + x$new <- synonym_mo_to_accepted_mo(x$old) + mo_old <- AMR::microorganisms$fullname[match(x$old, AMR::microorganisms$mo)] + mo_new <- AMR::microorganisms$fullname[match(x$new, AMR::microorganisms$mo)] + ref_old <- AMR::microorganisms$ref[match(x$old, AMR::microorganisms$mo)] + ref_new <- AMR::microorganisms$ref[match(x$new, AMR::microorganisms$mo)] + + df_renamed <- data.frame(old = mo_old, + new = mo_new, + ref_old = ref_old, + ref_new = ref_new, + stringsAsFactors = FALSE) + df_renamed <- unique(df_renamed) + df_renamed <- df_renamed[order(df_renamed$old), , drop = FALSE] + set_clean_class(df_renamed, new_class = c("mo_renamed", "data.frame")) +} + +#' @rdname as.mo +#' @export +mo_failures <- function() { + AMR_env$mo_failures +} + +#' @rdname as.mo +#' @export +mo_reset_session <- function() { + if (NROW(AMR_env$mo_previously_coerced) > 0) { + message_("Reset ", nr2char(NROW(AMR_env$mo_previously_coerced)), " previously matched input value", ifelse(NROW(AMR_env$mo_previously_coerced) > 1, "s", ""), ".") + AMR_env$mo_previously_coerced <- AMR_env$mo_previously_coerced[0, , drop = FALSE] + AMR_env$mo_uncertainties <- AMR_env$mo_uncertainties[0, , drop = FALSE] + } else { + message_("No previously matched input values to reset.") + } +} + +#' @rdname as.mo +#' @export +mo_cleaning_regex <- function() { + paste0( + "(", + "[^A-Za-z- \\(\\)\\[\\]{}]+", + "|", + "([({]|\\[).+([})]|\\])", + "|", + "(^| )(e?spp|e?ssp|e?ss|e?sp|e?subsp|sube?species|biovar|biotype|serovar|e?species)[.]*( |$))") +} + +# UNDOCUMENTED METHODS ---------------------------------------------------- + # will be exported using s3_register() in R/zzz.R pillar_shaft.mo <- function(x, ...) { out <- format(x) @@ -675,18 +746,6 @@ rep.mo <- function(x, ...) { y } -#' @rdname as.mo -#' @export -mo_failures <- function() { - pkg_env$mo_failures -} - -#' @rdname as.mo -#' @export -mo_uncertainties <- function() { - set_clean_class(pkg_env$mo_uncertainties, new_class = c("mo_uncertainties", "data.frame")) -} - #' @method print mo_uncertainties #' @export #' @noRd @@ -768,7 +827,13 @@ print.mo_uncertainties <- function(x, ...) { ), collapse = "\n" ), + # Add "Based on {input}" text if it differs from the original input ifelse(x[i, ]$original_input != x[i, ]$input, paste0(strrep(" ", nchar(x[i, ]$original_input) + 6), "Based on input \"", x[i, ]$input, "\""), ""), + # Add note if result was coerced to accepted taxonomic name + ifelse(x[i, ]$keep_synonyms == FALSE & x[i, ]$mo %in% AMR::microorganisms$mo[which(AMR::microorganisms$status == "synonym")], + paste0(strrep(" ", nchar(x[i, ]$original_input) + 6), + font_red(paste0("This old taxonomic name was converted to ", font_italic(AMR::microorganisms$fullname[match(synonym_mo_to_accepted_mo(x[i, ]$mo), AMR::microorganisms$mo)], collapse = NULL), " (", synonym_mo_to_accepted_mo(x[i, ]$mo), ")."), collapse = NULL)), + ""), candidates, sep = "\n" ) @@ -777,30 +842,6 @@ print.mo_uncertainties <- function(x, ...) { cat(txt) } - -#' @rdname as.mo -#' @export -mo_renamed <- function() { - x <- pkg_env$mo_renamed - - x$new <- ifelse(is.na(x$lpsn_matches), - AMR::microorganisms$mo[match(x$gbif_matches, AMR::microorganisms$gbif)], - AMR::microorganisms$mo[match(x$lpsn_matches, AMR::microorganisms$lpsn)]) - mo_old <- AMR::microorganisms$fullname[match(x$old, AMR::microorganisms$mo)] - mo_new <- AMR::microorganisms$fullname[match(x$new, AMR::microorganisms$mo)] - ref_old <- AMR::microorganisms$ref[match(x$old, AMR::microorganisms$mo)] - ref_new <- AMR::microorganisms$ref[match(x$new, AMR::microorganisms$mo)] - - df_renamed <- data.frame(old = mo_old, - new = mo_new, - ref_old = ref_old, - ref_new = ref_new, - stringsAsFactors = FALSE) - df_renamed <- unique(df_renamed) - df_renamed <- df_renamed[order(df_renamed$old), , drop = FALSE] - set_clean_class(df_renamed, new_class = c("mo_renamed", "data.frame")) -} - #' @method print mo_renamed #' @export #' @noRd @@ -812,6 +853,8 @@ print.mo_renamed <- function(x, extra_txt = "", n = 25, ...) { x$ref_old[!is.na(x$ref_old)] <- paste0(" (", gsub("et al.", font_italic("et al."), x$ref_old[!is.na(x$ref_old)], fixed = TRUE), ")") x$ref_new[!is.na(x$ref_new)] <- paste0(" (", gsub("et al.", font_italic("et al."), x$ref_new[!is.na(x$ref_new)], fixed = TRUE), ")") + x$ref_old[is.na(x$ref_old)] <- " (author unknown)" + x$ref_new[is.na(x$ref_new)] <- " (author unknown)" rows <- seq_len(min(NROW(x), n)) @@ -825,28 +868,57 @@ print.mo_renamed <- function(x, extra_txt = "", n = 25, ...) { ) } -#' @rdname as.mo -#' @export -mo_reset_session <- function() { - if (NROW(pkg_env$mo_previously_coerced) > 0) { - message_("Reset ", NROW(pkg_env$mo_previously_coerced), " previously matched input values.") - pkg_env$mo_previously_coerced <- pkg_env$mo_previously_coerced[0, , drop = FALSE] - pkg_env$mo_uncertainties <- pkg_env$mo_uncertainties[0, , drop = FALSE] - } else { - message_("No previously matched input values to reset.") - } -} +# UNDOCUMENTED HELPER FUNCTIONS ------------------------------------------- -#' @rdname as.mo -#' @export -mo_cleaning_regex <- function() { - paste0( - "(", - "[^A-Za-z- \\(\\)\\[\\]{}]+", - "|", - "([({]|\\[).+([})]|\\])", - "|", - "(^| )(e?spp|e?ssp|e?ss|e?sp|e?subsp|sube?species|biovar|biotype|serovar|e?species)( |$))") +convert_colloquial_input <- function(x) { + x.bak <- trimws2(x) + x <- trimws2(tolower(x)) + out <- rep(NA_character_, length(x)) + + # Streptococci, like GBS = Group B Streptococci (B_STRPT_GRPB) + out[x %like_case% "^g[abcdfghkl]s$"] <- gsub("g([abcdfghkl])s", + "B_STRPT_GRP\\U\\1", + x[x %like_case% "^g[abcdfghkl]s$"], + perl = TRUE) + # Streptococci in different languages, like "estreptococos grupo B" + out[x %like_case% "strepto[ck]o[ck].* [abcdfghkl]$"] <- gsub(".*e?strepto[ck]o[ck].* ([abcdfghkl])$", + "B_STRPT_GRP\\U\\1", + x[x %like_case% "strepto[ck]o[ck].* [abcdfghkl]$"], + perl = TRUE) + out[x %like_case% "group [abcdfghkl] strepto[ck]o[ck]"] <- gsub(".*group ([abcdfghkl]) strepto[ck]o[ck].*", + "B_STRPT_GRP\\U\\1", + x[x %like_case% "group [abcdfghkl] strepto[ck]o[ck]"], + perl = TRUE) + out[x %like_case% "ha?emoly.*strep"] <- "B_STRPT_HAEM" + out[x %like_case% "(strepto.* mil+er+i|^mgs[^a-z]*$)"] <- "B_STRPT_MILL" + out[x %like_case% "mil+er+i gr"] <- "B_STRPT_MILL" + out[x %like_case% "((strepto|^s).* viridans|^vgs[^a-z]*$)"] <- "B_STRPT_VIRI" + + # CoNS/CoPS in different languages (support for German, Dutch, Spanish, Portuguese) + out[x %like_case% "([ck]oagulas[ea].negatie?[vf]|^[ck]o?ns[^a-z]*$)"] <- "B_STPHY_CONS" + out[x %like_case% "([ck]oagulas[ea].positie?[vf]|^[ck]o?ps[^a-z]*$)"] <- "B_STPHY_COPS" + + # Gram stains + out[x %like_case% "gram[ -]?neg.*|negatie?[vf]"] <- "B_GRAMN" + out[x %like_case% "gram[ -]?pos.*|positie?[vf]"] <- "B_GRAMP" + + # yeasts and fungi + out[x %like_case% "^yeast?"] <- "F_YEAST" + out[x %like_case% "^fung(us|i)"] <- "F_FUNGUS" + + # Salmonella city names, starting with capital species name - they are all S. enterica + out[x.bak %like_case% "[sS]almonella [A-Z][a-z]+ ?.*" & x %unlike% "typhi"] <- "B_SLMNL_ENTR" + out[x %like_case% "salmonella group"] <- "B_SLMNL" + + # trivial names known to the field + out[x %like_case% "meningo[ck]o[ck]"] <- "B_NESSR_MNNG" + out[x %like_case% "gono[ck]o[ck]"] <- "B_NESSR_GNRR" + out[x %like_case% "pneumo[ck]o[ck]"] <- "B_STRPT_PNMN" + + # unexisting names (xxx and con are WHONET codes) + out[x %in% c("con", "other", "none", "unknown") | x %like_case% "virus"] <- "UNKNOWN" + + out } nr2char <- function(x) { @@ -861,17 +933,6 @@ nr2char <- function(x) { } } -get_mo_uncertainties <- function() { - remember <- list(uncertainties = pkg_env$mo_uncertainties) - # empty them, otherwise e.g. mo_shortname("Chlamydophila psittaci") will give 3 notes - pkg_env$mo_uncertainties <- NULL - remember -} - -load_mo_uncertainties <- function(metadata) { - pkg_env$mo_uncertainties <- metadata$uncertainties -} - parse_and_convert <- function(x) { if (tryCatch(is.character(x) && all(Encoding(x) == "unknown", na.rm = TRUE), error = function(e) FALSE)) { return(trimws2(x)) @@ -1008,51 +1069,24 @@ repair_reference_df <- function(reference_df) { reference_df } -convert_colloquial_input <- function(x) { - x.bak <- trimws2(x) - x <- trimws2(tolower(x)) - out <- rep(NA_character_, length(x)) - - # Streptococci, like GBS = Group B Streptococci (B_STRPT_GRPB) - out[x %like_case% "^g[abcdfghkl]s$"] <- gsub("g([abcdfghkl])s", - "B_STRPT_GRP\\U\\1", - x[x %like_case% "^g[abcdfghkl]s$"], - perl = TRUE) - # Streptococci in different languages, like "estreptococos grupo B" - out[x %like_case% "strepto[ck]o[ck].* [abcdfghkl]$"] <- gsub(".*e?strepto[ck]o[ck].* ([abcdfghkl])$", - "B_STRPT_GRP\\U\\1", - x[x %like_case% "strepto[ck]o[ck].* [abcdfghkl]$"], - perl = TRUE) - out[x %like_case% "group [abcdfghkl] strepto[ck]o[ck]"] <- gsub(".*group ([abcdfghkl]) strepto[ck]o[ck].*", - "B_STRPT_GRP\\U\\1", - x[x %like_case% "group [abcdfghkl] strepto[ck]o[ck]"], - perl = TRUE) - out[x %like_case% "ha?emoly.*strep"] <- "B_STRPT_HAEM" - out[x %like_case% "(strepto.* mil+er+i|^mgs[^a-z]*$)"] <- "B_STRPT_MILL" - out[x %like_case% "((strepto|^s).* viridans|^vgs[^a-z]*$)"] <- "B_STRPT_VIRI" - - # CoNS/CoPS in different languages (support for German, Dutch, Spanish, Portuguese) - out[x %like_case% "([ck]oagulas[ea].negatie?[vf]|^[ck]o?ns[^a-z]*$)"] <- "B_STPHY_CONS" - out[x %like_case% "([ck]oagulas[ea].positie?[vf]|^[ck]o?ps[^a-z]*$)"] <- "B_STPHY_COPS" - - # Gram stains - out[x %like_case% "gram[ -]?neg.*|negatie?[vf]"] <- "B_GRAMN" - out[x %like_case% "gram[ -]?pos.*|positie?[vf]"] <- "B_GRAMP" - - # yeasts and fungi - out[x %like_case% "^yeast?"] <- "F_YEAST" - out[x %like_case% "^fung(us|i)"] <- "F_FUNGUS" - - # Salmonella city names, starting with capital species name - they are all S. enterica - out[x.bak %like_case% "[sS]almonella [A-Z][a-z]+ ?.*" & x %unlike% "typhi"] <- "B_SLMNL_ENTR" - - # trivial names known to the field - out[x %like_case% "meningo[ck]o[ck]"] <- "B_NESSR_MNNG" - out[x %like_case% "gono[ck]o[ck]"] <- "B_NESSR_GNRR" - out[x %like_case% "pneumo[ck]o[ck]"] <- "B_STRPT_PNMN" - - # unexisting names (xxx and con are WHONET codes) - out[x %in% c("con", "other", "none", "unknown") | x %like_case% "virus"] <- "UNKNOWN" - - out +get_mo_uncertainties <- function() { + remember <- list(uncertainties = AMR_env$mo_uncertainties) + # empty them, otherwise e.g. mo_shortname("Chlamydophila psittaci") will give 3 notes + AMR_env$mo_uncertainties <- NULL + remember +} + +load_mo_uncertainties <- function(metadata) { + AMR_env$mo_uncertainties <- metadata$uncertainties +} + +synonym_mo_to_accepted_mo <- function(x) { + x_gbif <- AMR::microorganisms$gbif_renamed_to[match(x, AMR::microorganisms$mo)] + x_lpsn <- AMR::microorganisms$lpsn_renamed_to[match(x, AMR::microorganisms$mo)] + x_gbif[!x_gbif %in% AMR::microorganisms$gbif] <- NA + x_lpsn[!x_lpsn %in% AMR::microorganisms$lpsn] <- NA + + ifelse(is.na(x_lpsn), + AMR::microorganisms$mo[match(x_gbif, AMR::microorganisms$gbif)], + AMR::microorganisms$mo[match(x_lpsn, AMR::microorganisms$lpsn)]) } diff --git a/R/mo_matching_score.R b/R/mo_matching_score.R index ba339c1e..f0b8671d 100755 --- a/R/mo_matching_score.R +++ b/R/mo_matching_score.R @@ -79,7 +79,10 @@ mo_matching_score <- function(x, n) { # only keep one space x <- gsub(" +", " ", x) - + + # start with a capital letter + substr(x, 1, 1) <- toupper(substr(x, 1, 1)) + # n is always a taxonomically valid full name if (length(n) == 1) { n <- rep(n, length(x)) diff --git a/R/mo_property.R b/R/mo_property.R index f57371a6..4346b91b 100755 --- a/R/mo_property.R +++ b/R/mo_property.R @@ -33,13 +33,13 @@ #' @param x any [character] (vector) that can be coerced to a valid microorganism code with [as.mo()]. Can be left blank for auto-guessing the column containing microorganism codes if used in a data set, see *Examples*. #' @param property one of the column names of the [microorganisms] data set: `r vector_or(colnames(microorganisms), sort = FALSE, quotes = TRUE)`, or must be `"shortname"` #' @inheritParams as.mo -#' @param ... other arguments passed on to [as.mo()], such as 'allow_uncertain' and 'ignore_pattern' +#' @param ... other arguments passed on to [as.mo()], such as 'minimum_matching_score', 'ignore_pattern', and 'remove_from_input' #' @param ab any (vector of) text that can be coerced to a valid antibiotic code with [as.ab()] #' @param open browse the URL using [`browseURL()`][utils::browseURL()] #' @details All functions will, at default, keep old taxonomic properties. Please refer to this example, knowing that *Escherichia blattae* was renamed to *Shimwellia blattae* in 2010: #' - `mo_name("Escherichia blattae")` will return `"Shimwellia blattae"` (with a message about the renaming) -#' - `mo_ref("Escherichia blattae")` will return `"Burgess et al., 1973"` (with a message about the renaming) -#' - `mo_ref("Shimwellia blattae")` will return `"Priest et al., 2010"` (without a message) +#' - `mo_ref("Escherichia blattae", keep_synonyms = TRUE)` will return `"Burgess et al., 1973"` (with a warning about the renaming) +#' - `mo_ref("Shimwellia blattae", keep_synonyms = FALSE)` will return `"Priest et al., 2010"` (without a message) #' #' The short name - [mo_shortname()] - almost always returns the first character of the genus and the full species, like `"E. coli"`. Exceptions are abbreviations of staphylococci (such as *"CoNS"*, Coagulase-Negative Staphylococci) and beta-haemolytic streptococci (such as *"GBS"*, Group B Streptococci). Please bear in mind that e.g. *E. coli* could mean *Escherichia coli* (kingdom of Bacteria) as well as *Entamoeba coli* (kingdom of Protozoa). Returning to the full name will be done using [as.mo()] internally, giving priority to bacteria and human pathogens, i.e. `"E. coli"` will be considered *Escherichia coli*. In other words, `mo_fullname(mo_shortname("Entamoeba coli"))` returns `"Escherichia coli"`. #' @@ -504,7 +504,7 @@ mo_is_intrinsic_resistant <- function(x, ab, language = get_AMR_locale(), keep_s stop_("length of `x` and `ab` must be equal, or one of them must be of length 1.") } - # show used version number once per session (pkg_env will reload every session) + # show used version number once per session (AMR_env will reload every session) if (message_not_thrown_before("mo_is_intrinsic_resistant", "version.mo", entire_session = TRUE)) { message_( "Determining intrinsic resistance based on ", diff --git a/R/mo_source.R b/R/mo_source.R index 1b223ee1..609e9731 100644 --- a/R/mo_source.R +++ b/R/mo_source.R @@ -134,7 +134,7 @@ set_mo_source <- function(path, destination = getOption("AMR_mo_source", "~/mo_s stop_ifnot(interactive(), "this function can only be used in interactive mode, since it must ask for the user's permission to write a file to their file system.") if (is.null(path) || path %in% c(FALSE, "")) { - pkg_env$mo_source <- NULL + AMR_env$mo_source <- NULL if (file.exists(mo_source_destination)) { unlink(mo_source_destination) message_("Removed mo_source file '", font_bold(mo_source_destination), "'", @@ -227,7 +227,7 @@ set_mo_source <- function(path, destination = getOption("AMR_mo_source", "~/mo_s attr(df, "mo_source_destination") <- mo_source_destination attr(df, "mo_source_timestamp") <- file.mtime(path) saveRDS(df, mo_source_destination) - pkg_env$mo_source <- df + AMR_env$mo_source <- df message_( action, " mo_source file '", font_bold(mo_source_destination), "' (", formatted_filesize(mo_source_destination), @@ -247,24 +247,24 @@ get_mo_source <- function(destination = getOption("AMR_mo_source", "~/mo_source. } return(NULL) } - if (is.null(pkg_env$mo_source)) { - pkg_env$mo_source <- readRDS(path.expand(destination)) + if (is.null(AMR_env$mo_source)) { + AMR_env$mo_source <- readRDS(path.expand(destination)) } - old_time <- attributes(pkg_env$mo_source)$mo_source_timestamp - new_time <- file.mtime(attributes(pkg_env$mo_source)$mo_source_location) + old_time <- attributes(AMR_env$mo_source)$mo_source_timestamp + new_time <- file.mtime(attributes(AMR_env$mo_source)$mo_source_location) if (interactive() && !identical(old_time, new_time)) { # source file was updated, also update reference - set_mo_source(attributes(pkg_env$mo_source)$mo_source_location) + set_mo_source(attributes(AMR_env$mo_source)$mo_source_location) } - pkg_env$mo_source + AMR_env$mo_source } check_validity_mo_source <- function(x, refer_to_name = "`reference_df`", stop_on_error = TRUE) { if (paste(deparse(substitute(x)), collapse = "") == "get_mo_source()") { return(TRUE) } - if (is.null(pkg_env$mo_source) && (identical(x, get_mo_source()))) { + if (is.null(AMR_env$mo_source) && (identical(x, get_mo_source()))) { return(TRUE) } if (is.null(x)) { diff --git a/R/rsi.R b/R/rsi.R index bb60ea9a..07467133 100755 --- a/R/rsi.R +++ b/R/rsi.R @@ -906,8 +906,8 @@ as_rsi_method <- function(method_short, } # write to verbose output - pkg_env$rsi_interpretation_history <- rbind( - pkg_env$rsi_interpretation_history, + AMR_env$rsi_interpretation_history <- rbind( + AMR_env$rsi_interpretation_history, data.frame( datetime = Sys.time(), index = i, @@ -964,7 +964,7 @@ as_rsi_method <- function(method_short, rsi_interpretation_history <- function(clean = FALSE) { meet_criteria(clean, allow_class = "logical", has_length = 1) - out.bak <- pkg_env$rsi_interpretation_history + out.bak <- AMR_env$rsi_interpretation_history out <- out.bak if (NROW(out) == 0) { message_("No results to return. Run `as.rsi()` on MIC values or disk diffusion zones first to see a 'logbook' data set here.") @@ -975,9 +975,9 @@ rsi_interpretation_history <- function(clean = FALSE) { out$interpretation <- as.rsi(out$interpretation) # keep stored for next use if (isTRUE(clean)) { - pkg_env$rsi_interpretation_history <- pkg_env$rsi_interpretation_history[0, , drop = FALSE] + AMR_env$rsi_interpretation_history <- AMR_env$rsi_interpretation_history[0, , drop = FALSE] } else { - pkg_env$rsi_interpretation_history <- out.bak + AMR_env$rsi_interpretation_history <- out.bak } if (pkg_is_available("tibble", also_load = FALSE)) { diff --git a/R/zzz.R b/R/zzz.R index 6014c6ca..d821db8e 100755 --- a/R/zzz.R +++ b/R/zzz.R @@ -28,23 +28,24 @@ # ==================================================================== # # set up package environment, used by numerous AMR functions -pkg_env <- new.env(hash = FALSE) -pkg_env$mo_uncertainties <- data.frame( - uncertainty = integer(0), +AMR_env <- new.env(hash = FALSE) +AMR_env$mo_uncertainties <- data.frame( original_input = character(0), input = character(0), fullname = character(0), mo = character(0), candidates = character(0), + minimum_matching_score = integer(0), + keep_synonyms = logical(0), stringsAsFactors = FALSE ) -pkg_env$mo_renamed <- list() -pkg_env$mo_previously_coerced <- data.frame( +AMR_env$mo_renamed <- list() +AMR_env$mo_previously_coerced <- data.frame( x = character(0), mo = character(0), stringsAsFactors = FALSE ) -pkg_env$rsi_interpretation_history <- data.frame( +AMR_env$rsi_interpretation_history <- data.frame( datetime = Sys.time()[0], index = integer(0), ab_input = character(0), @@ -60,7 +61,7 @@ pkg_env$rsi_interpretation_history <- data.frame( interpretation = character(0), stringsAsFactors = FALSE ) -pkg_env$has_data.table <- pkg_is_available("data.table", also_load = FALSE) +AMR_env$has_data.table <- pkg_is_available("data.table", also_load = FALSE) # determine info icon for messages utf8_supported <- isTRUE(base::l10n_info()$`UTF-8`) @@ -69,9 +70,9 @@ is_latex <- tryCatch(import_fn("is_latex_output", "knitr", error_on_fail = FALSE ) if (utf8_supported && !is_latex) { # \u2139 is a symbol officially named 'information source' - pkg_env$info_icon <- "\u2139" + AMR_env$info_icon <- "\u2139" } else { - pkg_env$info_icon <- "i" + AMR_env$info_icon <- "i" } .onLoad <- function(lib, pkg) { diff --git a/data-raw/read_EUCAST.R b/data-raw/read_EUCAST.R index 70dee5f3..1ec66726 100644 --- a/data-raw/read_EUCAST.R +++ b/data-raw/read_EUCAST.R @@ -109,7 +109,7 @@ read_EUCAST <- function(sheet, file, guideline_name) { for (i in seq_len(length(x))) { y <- trimws2(unlist(strsplit(x[i], "(,|and)"))) y <- trimws2(gsub("[(].*[)]", "", y)) - y <- suppressWarnings(suppressMessages(as.mo(y, allow_uncertain = FALSE))) + y <- suppressWarnings(suppressMessages(as.mo(y))) if (!is.null(mo_uncertainties())) uncertainties <<- add_uncertainties(uncertainties, mo_uncertainties()) y <- y[!is.na(y) & y != "UNKNOWN"] x[i] <- paste(y, collapse = "|") diff --git a/data/microorganisms.codes.rda b/data/microorganisms.codes.rda index e8435c6538e977800e4aba9b3138ec3c81c922d6..4164a79acc6c792b9591cf02f3a0c84612b0ba9a 100644 GIT binary patch literal 22356 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 L0ssI200CKAivQ&2UJ%gRpOV=m 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 diff --git a/inst/tinytest/test-mo.R b/inst/tinytest/test-mo.R index 7106ada4..7cb766f8 100644 --- a/inst/tinytest/test-mo.R +++ b/inst/tinytest/test-mo.R @@ -40,7 +40,7 @@ expect_equal(as.character(as.mo("Escherichia coli")), "B_ESCHR_COLI") expect_equal(as.character(as.mo(112283007)), "B_ESCHR_COLI") expect_equal(as.character(as.mo("Escherichia species")), "B_ESCHR") expect_equal(as.character(as.mo("Escherichia")), "B_ESCHR") -expect_equal(as.character(as.mo("Esch spp.")), "B_ESCHR") +expect_equal(as.character(as.mo("Eschr spp.")), "B_ESCHR") expect_equal(as.character(as.mo(" B_ESCHR_COLI ")), "B_ESCHR_COLI") expect_equal(as.character(as.mo("e coli")), "B_ESCHR_COLI") # not Campylobacter expect_equal(as.character(as.mo("klpn")), "B_KLBSL_PNMN") @@ -53,9 +53,8 @@ expect_equal(as.character(as.mo("Strepto")), "B_STRPT") expect_equal(as.character(as.mo("Streptococcus")), "B_STRPT") # not Peptostreptoccus expect_equal(as.character(as.mo("Estreptococos grupo B")), "B_STRPT_GRPB") expect_equal(as.character(as.mo("Group B Streptococci")), "B_STRPT_GRPB") -expect_equal(as.character(as.mo(c("mycobacterie", "mycobakterium"))), c("B_MYCBC", "B_MYCBC")) -expect_equal(as.character(as.mo(c("GAS", "GBS", "a MGS", "haemoly strep"))), c("B_STRPT_GRPA", "B_STRPT_GRPB", "B_STRPT_MILL", "B_STRPT_HAEM")) +expect_equal(as.character(as.mo(c("GAS", "GBS", "haemoly strep"))), c("B_STRPT_GRPA", "B_STRPT_GRPB", "B_STRPT_HAEM")) expect_equal(as.character(as.mo("S. pyo")), "B_STRPT_PYGN") # not Actinomyces pyogenes @@ -90,14 +89,13 @@ expect_identical( "staaur", "S. aureus", "S aureus", - "Sthafilokkockus aureeuzz", + "Sthafilokkockus aureus", "Staphylococcus aureus", "MRSA", - "VISA", - "meth.-resis. S. aureus (MRSA)" - )) + "VISA" + ), minimum_matching_score = 0) )), - rep("B_STPHY_AURS", 10) + rep("B_STPHY_AURS", 9) ) expect_identical( as.character( @@ -148,8 +146,8 @@ expect_identical(as.character(as.mo("STCPYO", Lancefield = TRUE)), "B_STRPT_GRPA expect_identical(as.character(as.mo("S. agalactiae", Lancefield = FALSE)), "B_STRPT_AGLC") expect_identical(as.character(as.mo("S. agalactiae", Lancefield = TRUE)), "B_STRPT_GRPB") # group B expect_identical(as.character(suppressWarnings(as.mo("estreptococos grupo B"))), "B_STRPT_GRPB") -expect_identical(as.character(as.mo("S. equisimilis", Lancefield = FALSE)), "B_STRPT_DYSG_EQSM") -expect_identical(as.character(as.mo("S. equisimilis", Lancefield = TRUE)), "B_STRPT_GRPC") # group C +expect_identical(as.character(as.mo("S. equi", Lancefield = FALSE)), "B_STRPT_EQUI") +expect_identical(as.character(as.mo("S. equi", Lancefield = TRUE)), "B_STRPT_GRPC") # group C # Enterococci must only be influenced if Lancefield = "all" expect_identical(as.character(as.mo("E. faecium", Lancefield = FALSE)), "B_ENTRC_FACM") expect_identical(as.character(as.mo("E. faecium", Lancefield = TRUE)), "B_ENTRC_FACM") @@ -213,19 +211,17 @@ expect_equal( # check empty values expect_equal( - as.character(suppressWarnings(as.mo(""))), + as.character(as.mo("")), NA_character_ ) # check less prevalent MOs -expect_equal(as.character(as.mo("Gomphosphaeria aponina delicatula")), "B_GMPHS_APNN_DLCT") -expect_equal(as.character(as.mo("Gomphosphaeria apo del")), "B_GMPHS_APNN_DLCT") -expect_equal(as.character(as.mo("G apo deli")), "B_GMPHS_APNN_DLCT") -expect_equal(as.character(as.mo("Gomphosphaeria aponina")), "B_GMPHS_APNN") -expect_equal(as.character(as.mo("Gomphosphaeria species")), "B_GMPHS") -expect_equal(as.character(as.mo("Gomphosphaeria")), "B_GMPHS") -expect_equal(as.character(as.mo(" B_GMPHS_APNN ")), "B_GMPHS_APNN") -expect_equal(as.character(as.mo("g aponina")), "B_GMPHS_APNN") +expect_equal(as.character(as.mo("Actinosynnema pretiosum auranticum")), "B_ANNMA_PRTS_ARNT") +expect_equal(as.character(as.mo("Actinosynnema preti aura")), "B_ANNMA_PRTS_ARNT") +expect_equal(as.character(as.mo("A pre aur")), "B_ANNMA_PRTS_ARNT") +expect_equal(as.character(as.mo("Actinosynnema pretiosum")), "B_ANNMA_PRTS") +expect_equal(as.character(as.mo("Actinosynnema")), "B_ANNMA") +expect_equal(as.character(as.mo(" B_ANNMA_PRTS ")), "B_ANNMA_PRTS") # check old names expect_equal(suppressMessages(as.character(as.mo("Escherichia blattae"))), "B_SHMWL_BLTT") @@ -250,7 +246,7 @@ expect_error(as.mo("E. coli", reference_df = data.frame(mycol = "TestingOwnID")) # combination of existing mo and other code expect_identical( - as.character(as.mo(c("B_ESCHR_COL", "ESCCOL"))), + suppressWarnings(as.character(as.mo(c("B_ESCHR_COL", "ESCCOL")))), c("B_ESCHR_COLI", "B_ESCHR_COLI") ) @@ -274,7 +270,7 @@ expect_equal( c("B_MCRBC_PRXY", "B_STRPT_SUIS", "B_RLTLL_TRRG") ) expect_stdout(print(mo_uncertainties())) -x <- as.mo("S. aur") +x <- as.mo("Sta. aur") # many hits expect_stdout(print(mo_uncertainties())) diff --git a/man/as.mo.Rd b/man/as.mo.Rd index 4e8f4b1e..2c9d6536 100644 --- a/man/as.mo.Rd +++ b/man/as.mo.Rd @@ -4,9 +4,9 @@ \alias{as.mo} \alias{mo} \alias{is.mo} -\alias{mo_failures} \alias{mo_uncertainties} \alias{mo_renamed} +\alias{mo_failures} \alias{mo_reset_session} \alias{mo_cleaning_regex} \title{Transform Input to a Microorganism Code} @@ -27,12 +27,12 @@ as.mo( is.mo(x) -mo_failures() - mo_uncertainties() mo_renamed() +mo_failures() + mo_reset_session() mo_cleaning_regex() diff --git a/man/microorganisms.codes.Rd b/man/microorganisms.codes.Rd index 5643c9b3..c9c460a7 100644 --- a/man/microorganisms.codes.Rd +++ b/man/microorganisms.codes.Rd @@ -3,9 +3,9 @@ \docType{data} \name{microorganisms.codes} \alias{microorganisms.codes} -\title{Data Set with 5,508 Common Microorganism Codes} +\title{Data Set with 5,411 Common Microorganism Codes} \format{ -A \link[tibble:tibble]{tibble} with 5,508 observations and 2 variables: +A \link[tibble:tibble]{tibble} with 5,411 observations and 2 variables: \itemize{ \item \code{code}\cr Commonly used code of a microorganism \item \code{mo}\cr ID of the microorganism in the \link{microorganisms} data set diff --git a/man/mo_property.Rd b/man/mo_property.Rd index 8adba27b..0e107686 100644 --- a/man/mo_property.Rd +++ b/man/mo_property.Rd @@ -261,7 +261,7 @@ mo_property( \item{keep_synonyms}{a \link{logical} to indicate if old, previously valid taxonomic names must be preserved and not be corrected to currently accepted names. The default is \code{FALSE}, which will return a note if old taxonomic names were processed. The default can be set with \code{options(AMR_keep_synonyms = TRUE)} or \code{options(AMR_keep_synonyms = FALSE)}.} -\item{...}{other arguments passed on to \code{\link[=as.mo]{as.mo()}}, such as 'allow_uncertain' and 'ignore_pattern'} +\item{...}{other arguments passed on to \code{\link[=as.mo]{as.mo()}}, such as 'minimum_matching_score', 'ignore_pattern', and 'remove_from_input'} \item{ab}{any (vector of) text that can be coerced to a valid antibiotic code with \code{\link[=as.ab]{as.ab()}}} @@ -285,8 +285,8 @@ Use these functions to return a specific property of a microorganism based on th All functions will, at default, keep old taxonomic properties. Please refer to this example, knowing that \emph{Escherichia blattae} was renamed to \emph{Shimwellia blattae} in 2010: \itemize{ \item \code{mo_name("Escherichia blattae")} will return \code{"Shimwellia blattae"} (with a message about the renaming) -\item \code{mo_ref("Escherichia blattae")} will return \code{"Burgess et al., 1973"} (with a message about the renaming) -\item \code{mo_ref("Shimwellia blattae")} will return \code{"Priest et al., 2010"} (without a message) +\item \code{mo_ref("Escherichia blattae", keep_synonyms = TRUE)} will return \code{"Burgess et al., 1973"} (with a warning about the renaming) +\item \code{mo_ref("Shimwellia blattae", keep_synonyms = FALSE)} will return \code{"Priest et al., 2010"} (without a message) } The short name - \code{\link[=mo_shortname]{mo_shortname()}} - almost always returns the first character of the genus and the full species, like \code{"E. coli"}. Exceptions are abbreviations of staphylococci (such as \emph{"CoNS"}, Coagulase-Negative Staphylococci) and beta-haemolytic streptococci (such as \emph{"GBS"}, Group B Streptococci). Please bear in mind that e.g. \emph{E. coli} could mean \emph{Escherichia coli} (kingdom of Bacteria) as well as \emph{Entamoeba coli} (kingdom of Protozoa). Returning to the full name will be done using \code{\link[=as.mo]{as.mo()}} internally, giving priority to bacteria and human pathogens, i.e. \code{"E. coli"} will be considered \emph{Escherichia coli}. In other words, \code{mo_fullname(mo_shortname("Entamoeba coli"))} returns \code{"Escherichia coli"}.