R/ab_property.R

Defines functions ab_validate set_ab_names ab_property ab_url ab_info ab_ddd_units ab_ddd ab_loinc ab_atc_group2 ab_atc_group1 ab_atc ab_group ab_tradenames ab_synonyms ab_cid ab_name

Documented in ab_atc ab_atc_group1 ab_atc_group2 ab_cid ab_ddd ab_ddd_units ab_group ab_info ab_loinc ab_name ab_property ab_synonyms ab_tradenames ab_url set_ab_names

# ==================================================================== #
# TITLE:                                                               #
# AMR: An R Package for Working with Antimicrobial Resistance Data     #
#                                                                      #
# SOURCE CODE:                                                         #
# https://github.com/msberends/AMR                                     #
#                                                                      #
# PLEASE CITE THIS SOFTWARE 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.                 #
# https://doi.org/10.18637/jss.v104.i03                                #
#                                                                      #
# 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.                   #
#                                                                      #
# 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/   #
# ==================================================================== #

#' Get Properties of an Antibiotic
#'
#' Use these functions to return a specific property of an antibiotic from the [antibiotics] data set. All input values will be evaluated internally with [as.ab()].
#' @param x any (vector of) text that can be coerced to a valid antibiotic drug code with [as.ab()]
#' @param tolower a [logical] to indicate whether the first [character] of every output should be transformed to a lower case [character]. This will lead to e.g. "polymyxin B" and not "polymyxin b".
#' @param property one of the column names of one of the [antibiotics] data set: `vector_or(colnames(antibiotics), sort = FALSE)`.
#' @param language language of the returned text - the default is the current system language (see [get_AMR_locale()]) and can also be set with the [package option][AMR-options] [`AMR_locale`][AMR-options]. Use `language = NULL` or `language = ""` to prevent translation.
#' @param administration way of administration, either `"oral"` or `"iv"`
#' @param open browse the URL using [utils::browseURL()]
#' @param ... in case of [set_ab_names()] and `data` is a [data.frame]: columns to select (supports tidy selection such as `column1:column4`), otherwise other arguments passed on to [as.ab()]
#' @param data a [data.frame] of which the columns need to be renamed, or a [character] vector of column names
#' @param snake_case a [logical] to indicate whether the names should be in so-called [snake case](https://en.wikipedia.org/wiki/Snake_case): in lower case and all spaces/slashes replaced with an underscore (`_`)
#' @param only_first a [logical] to indicate whether only the first ATC code must be returned, with giving preference to J0-codes (i.e., the antimicrobial drug group)
#' @details All output [will be translated][translate] where possible.
#'
#' The function [ab_url()] will return the direct URL to the official WHO website. A warning will be returned if the required ATC code is not available.
#'
#' The function [set_ab_names()] is a special column renaming function for [data.frame]s. It renames columns names that resemble antimicrobial drugs. It always makes sure that the new column names are unique. If `property = "atc"` is set, preference is given to ATC codes from the J-group.
#' @inheritSection as.ab Source
#' @rdname ab_property
#' @name ab_property
#' @return
#' - An [integer] in case of [ab_cid()]
#' - A named [list] in case of [ab_info()] and multiple [ab_atc()]/[ab_synonyms()]/[ab_tradenames()]
#' - A [double] in case of [ab_ddd()]
#' - A [data.frame] in case of [set_ab_names()]
#' - A [character] in all other cases
#' @export
#' @seealso [antibiotics]
#' @inheritSection AMR Reference Data Publicly Available
#' @examples
#' # all properties:
#' ab_name("AMX")
#' ab_atc("AMX")
#' ab_cid("AMX")
#' ab_synonyms("AMX")
#' ab_tradenames("AMX")
#' ab_group("AMX")
#' ab_atc_group1("AMX")
#' ab_atc_group2("AMX")
#' ab_url("AMX")
#'
#' # smart lowercase transformation
#' ab_name(x = c("AMC", "PLB"))
#' ab_name(x = c("AMC", "PLB"), tolower = TRUE)
#'
#' # defined daily doses (DDD)
#' ab_ddd("AMX", "oral")
#' ab_ddd_units("AMX", "oral")
#' ab_ddd("AMX", "iv")
#' ab_ddd_units("AMX", "iv")
#'
#' ab_info("AMX") # all properties as a list
#'
#' # all ab_* functions use as.ab() internally, so you can go from 'any' to 'any':
#' ab_atc("AMP")
#' ab_group("J01CA01")
#' ab_loinc("ampicillin")
#' ab_name("21066-6")
#' ab_name(6249)
#' ab_name("J01CA01")
#'
#' # spelling from different languages and dyslexia are no problem
#' ab_atc("ceftriaxon")
#' ab_atc("cephtriaxone")
#' ab_atc("cephthriaxone")
#' ab_atc("seephthriaaksone")
#'
#' # use set_ab_names() for renaming columns
#' colnames(example_isolates)
#' colnames(set_ab_names(example_isolates))
#' colnames(set_ab_names(example_isolates, NIT:VAN))
#' \donttest{
#' if (require("dplyr")) {
#'   example_isolates %>%
#'     set_ab_names()
#'
#'   # this does the same:
#'   example_isolates %>%
#'     rename_with(set_ab_names)
#'
#'   # set_ab_names() works with any AB property:
#'   example_isolates %>%
#'     set_ab_names(property = "atc")
#'
#'   example_isolates %>%
#'     set_ab_names(where(is.sir)) %>%
#'     colnames()
#'
#'   example_isolates %>%
#'     set_ab_names(NIT:VAN) %>%
#'     colnames()
#' }
#' }
ab_name <- function(x, language = get_AMR_locale(), tolower = FALSE, ...) {
  meet_criteria(x, allow_NA = TRUE)
  language <- validate_language(language)
  meet_criteria(tolower, allow_class = "logical", has_length = 1)

  x <- translate_into_language(ab_validate(x = x, property = "name", ...), language = language, only_affect_ab_names = TRUE)
  if (tolower == TRUE) {
    # use perl to only transform the first character
    # as we want "polymyxin B", not "polymyxin b"
    x <- gsub("^([A-Z])", "\\L\\1", x, perl = TRUE)
  }
  x
}

#' @rdname ab_property
#' @export
ab_cid <- function(x, ...) {
  meet_criteria(x, allow_NA = TRUE)
  ab_validate(x = x, property = "cid", ...)
}

#' @rdname ab_property
#' @export
ab_synonyms <- function(x, ...) {
  meet_criteria(x, allow_NA = TRUE)
  syns <- ab_validate(x = x, property = "synonyms", ...)
  names(syns) <- x
  if (length(syns) == 1) {
    unname(unlist(syns))
  } else {
    syns
  }
}

#' @rdname ab_property
#' @export
ab_tradenames <- function(x, ...) {
  meet_criteria(x, allow_NA = TRUE)
  ab_synonyms(x, ...)
}

#' @rdname ab_property
#' @export
ab_group <- function(x, language = get_AMR_locale(), ...) {
  meet_criteria(x, allow_NA = TRUE)
  language <- validate_language(language)
  translate_into_language(ab_validate(x = x, property = "group", ...), language = language, only_affect_ab_names = TRUE)
}

#' @rdname ab_property
#' @aliases ATC
#' @export
ab_atc <- function(x, only_first = FALSE, ...) {
  meet_criteria(x, allow_NA = TRUE)
  meet_criteria(only_first, allow_class = "logical", has_length = 1)

  atcs <- ab_validate(x = x, property = "atc", ...)

  if (only_first == TRUE) {
    atcs <- vapply(
      FUN.VALUE = character(1),
      # get only the first ATC code
      atcs,
      function(x) {
        # try to get the J-group
        if (any(x %like% "^J")) {
          x[x %like% "^J"][1L]
        } else {
          as.character(x[1L])
        }
      }
    )
  } else if (length(atcs) == 1) {
    atcs <- unname(unlist(atcs))
  } else {
    names(atcs) <- x
  }

  atcs
}

#' @rdname ab_property
#' @export
ab_atc_group1 <- function(x, language = get_AMR_locale(), ...) {
  meet_criteria(x, allow_NA = TRUE)
  language <- validate_language(language)
  translate_into_language(ab_validate(x = x, property = "atc_group1", ...), language = language, only_affect_ab_names = TRUE)
}

#' @rdname ab_property
#' @export
ab_atc_group2 <- function(x, language = get_AMR_locale(), ...) {
  meet_criteria(x, allow_NA = TRUE)
  language <- validate_language(language)
  translate_into_language(ab_validate(x = x, property = "atc_group2", ...), language = language, only_affect_ab_names = TRUE)
}

#' @rdname ab_property
#' @export
ab_loinc <- function(x, ...) {
  meet_criteria(x, allow_NA = TRUE)
  loincs <- ab_validate(x = x, property = "loinc", ...)
  names(loincs) <- x
  if (length(loincs) == 1) {
    unname(unlist(loincs))
  } else {
    loincs
  }
}

#' @rdname ab_property
#' @export
ab_ddd <- function(x, administration = "oral", ...) {
  meet_criteria(x, allow_NA = TRUE)
  meet_criteria(administration, is_in = c("oral", "iv"), has_length = 1)

  x <- as.ab(x, ...)
  ddd_prop <- paste0(administration, "_ddd")
  out <- ab_validate(x = x, property = ddd_prop)

  if (any(ab_name(x, language = NULL) %like% "/" & is.na(out))) {
    warning_(
      "in `ab_ddd()`: DDDs of some combined products are available for different dose combinations and not (yet) part of the AMR package.",
      "Please refer to the WHOCC website:\n",
      "www.whocc.no/ddd/list_of_ddds_combined_products/"
    )
  }
  out
}

#' @rdname ab_property
#' @export
ab_ddd_units <- function(x, administration = "oral", ...) {
  meet_criteria(x, allow_NA = TRUE)
  meet_criteria(administration, is_in = c("oral", "iv"), has_length = 1)

  x <- as.ab(x, ...)
  ddd_prop <- paste0(administration, "_units")
  out <- ab_validate(x = x, property = ddd_prop)

  if (any(ab_name(x, language = NULL) %like% "/" & is.na(out))) {
    warning_(
      "in `ab_ddd_units()`: DDDs of some combined products are available for different dose combinations and not (yet) part of the AMR package.",
      "Please refer to the WHOCC website:\n",
      "www.whocc.no/ddd/list_of_ddds_combined_products/"
    )
  }
  out
}

#' @rdname ab_property
#' @export
ab_info <- function(x, language = get_AMR_locale(), ...) {
  meet_criteria(x, allow_NA = TRUE)
  language <- validate_language(language)

  x <- as.ab(x, ...)
  list(
    ab = as.character(x),
    cid = ab_cid(x),
    name = ab_name(x, language = language),
    group = ab_group(x, language = language),
    atc = ab_atc(x),
    atc_group1 = ab_atc_group1(x, language = language),
    atc_group2 = ab_atc_group2(x, language = language),
    tradenames = ab_tradenames(x),
    loinc = ab_loinc(x),
    ddd = list(
      oral = list(
        amount = ab_ddd(x, administration = "oral"),
        units = ab_ddd_units(x, administration = "oral")
      ),
      iv = list(
        amount = ab_ddd(x, administration = "iv"),
        units = ab_ddd_units(x, administration = "iv")
      )
    )
  )
}


#' @rdname ab_property
#' @export
ab_url <- function(x, open = FALSE, ...) {
  meet_criteria(x, allow_NA = TRUE)
  meet_criteria(open, allow_class = "logical", has_length = 1)

  ab <- as.ab(x = x, ...)
  atcs <- ab_atc(ab, only_first = TRUE)
  u <- paste0("https://www.whocc.no/atc_ddd_index/?code=", atcs, "&showdescription=no")
  u[is.na(atcs)] <- NA_character_
  names(u) <- ab_name(ab)

  NAs <- ab_name(ab, tolower = TRUE, language = NULL)[!is.na(ab) & is.na(atcs)]
  if (length(NAs) > 0) {
    warning_("in `ab_url()`: no ATC code available for ", vector_and(NAs, quotes = FALSE), ".")
  }

  if (open == TRUE) {
    if (length(u) > 1 && !is.na(u[1L])) {
      warning_("in `ab_url()`: only the first URL will be opened, as `browseURL()` only suports one string.")
    }
    if (!is.na(u[1L])) {
      utils::browseURL(u[1L])
    }
  }
  u
}

#' @rdname ab_property
#' @export
ab_property <- function(x, property = "name", language = get_AMR_locale(), ...) {
  meet_criteria(x, allow_NA = TRUE)
  meet_criteria(property, is_in = colnames(AMR::antibiotics), has_length = 1)
  language <- validate_language(language)
  translate_into_language(ab_validate(x = x, property = property, ...), language = language)
}

#' @rdname ab_property
#' @aliases ATC
#' @export
set_ab_names <- function(data, ..., property = "name", language = get_AMR_locale(), snake_case = NULL) {
  meet_criteria(data, allow_class = c("data.frame", "character"))
  meet_criteria(property, is_in = colnames(AMR::antibiotics), has_length = 1, ignore.case = TRUE)
  language <- validate_language(language)
  meet_criteria(snake_case, allow_class = "logical", has_length = 1, allow_NULL = TRUE)

  x_deparsed <- deparse(substitute(data))
  if (length(x_deparsed) > 1 || any(x_deparsed %unlike% "[a-z]+")) {
    x_deparsed <- "your_data"
  }

  property <- tolower(property)
  if (is.null(snake_case)) {
    snake_case <- property == "name"
  }

  if (is.data.frame(data)) {
    if (tryCatch(length(c(...)) > 1, error = function(e) TRUE)) {
      df <- tryCatch(suppressWarnings(pm_select(data, ...)),
        error = function(e) {
          data[, c(...), drop = FALSE]
        }
      )
    } else if (tryCatch(is.character(c(...)), error = function(e) FALSE)) {
      df <- data[, c(...), drop = FALSE]
    } else {
      df <- data
    }
    vars <- get_column_abx(df, info = FALSE, only_sir_columns = FALSE, sort = FALSE, fn = "set_ab_names")
    if (length(vars) == 0) {
      message_("No columns with antibiotic results found for `set_ab_names()`, leaving names unchanged.")
      return(data)
    }
  } else {
    # quickly get antibiotic drug codes
    vars_ab <- as.ab(data, fast_mode = TRUE)
    vars <- data[!is.na(vars_ab)]
  }
  x <- vapply(
    FUN.VALUE = character(1),
    ab_property(vars, property = property, language = language),
    function(x) {
      if (property == "atc") {
        # try to get the J-group
        if (any(x %like% "^J")) {
          x[x %like% "^J"][1L]
        } else {
          as.character(x[1L])
        }
      } else {
        as.character(x[1L])
      }
    },
    USE.NAMES = FALSE
  )
  if (any(x %in% c("", NA))) {
    warning_(
      "in `set_ab_names()`: no ", property, " found for column(s): ",
      vector_and(vars[x %in% c("", NA)], sort = FALSE)
    )
    x[x %in% c("", NA)] <- vars[x %in% c("", NA)]
  }

  if (snake_case == TRUE) {
    x <- tolower(gsub("[^a-zA-Z0-9]+", "_", x))
  }

  if (anyDuplicated(x)) {
    # very hacky way of adding the index to each duplicate
    # so      "Amoxicillin", "Amoxicillin",   "Amoxicillin"
    # will be "Amoxicillin", "Amoxicillin_2", "Amoxicillin_3"
    invisible(lapply(
      unique(x),
      function(u) {
        dups <- which(x == u)
        if (length(dups) > 1) {
          # there are duplicates
          dup_add_int <- dups[2:length(dups)]
          x[dup_add_int] <<- paste0(x[dup_add_int], "_", 2:length(dups))
        }
      }
    ))
  }
  if (is.data.frame(data)) {
    colnames(data)[colnames(data) %in% vars] <- x
    data
  } else {
    data[which(!is.na(vars_ab))] <- x
    data
  }
}

ab_validate <- function(x, property, ...) {
  if (tryCatch(all(x[!is.na(x)] %in% AMR_env$AB_lookup$ab), error = function(e) FALSE)) {
    # special case for ab_* functions where class is already 'ab'
    x <- AMR_env$AB_lookup[match(x, AMR_env$AB_lookup$ab), property, drop = TRUE]
  } else {
    # try to catch an error when inputting an invalid argument
    # so the 'call.' can be set to FALSE
    tryCatch(x[1L] %in% AMR_env$AB_lookup[1, property, drop = TRUE],
      error = function(e) stop(e$message, call. = FALSE)
    )

    if (!all(x %in% AMR_env$AB_lookup[, property, drop = TRUE])) {
      x <- as.ab(x, ...)
      if (all(is.na(x)) && is.list(AMR_env$AB_lookup[, property, drop = TRUE])) {
        x <- rep(NA_character_, length(x))
      } else {
        x <- AMR_env$AB_lookup[match(x, AMR_env$AB_lookup$ab), property, drop = TRUE]
      }
    }
  }

  if (property == "ab") {
    return(set_clean_class(x, new_class = c("ab", "character")))
  } else if (property == "cid") {
    return(as.integer(x))
  } else if (property %like% "ddd") {
    return(as.double(x))
  } else {
    x[is.na(x)] <- NA
    return(x)
  }
}

Try the AMR package in your browser

Any scripts or data that you put into this service are public.

AMR documentation built on Oct. 22, 2023, 1:08 a.m.