R/misc.R

Defines functions .onLoad .onAttach expect_match2 viridis6 is_verbose is_try_error warn_deprecated use_alias limit_chars trim_wsp rm_wsp wsp_per_line wsp round_largest_remainder usc escape_all escape_dot regex_or grepl_expr get_matches_expr get_matches SW get_arg warning2 stop2 sort_dependencies substitute_name eval_silent eval2 deparse0 deparse_no_string empty_data_frame do_call is_named named_list nlist collapse_lists rename require_package cglue zero_length_transformer glue str_subset str_if collapse_comma collapse lc str2lang str2expression all_vars os_is_windows ufrom_list from_list plapply cblapply rblapply ulapply is_symmetric is_wholenumber subset_keep_attr rm_attr structure_not_null expand has_cols has_rows as_one_variable as_one_character as_one_numeric as_one_integer as_one_logical as_factor is_like_factor is_equal isNA is_atomic_or_null first_not_null rmNULL isNULL first_greater repl move2end move2start array2list subset2 find_rows find_elements match_rows seq_dim seq_cols seq_rows slice_col slice extract p

Documented in do_call

# type-stable indexing of vector and matrix type objects
# @param x an R object typically a vector or matrix
# @param i optional index; if NULL, x is returned unchanged
# @param row indicating if rows or cols should be indexed
#   only relevant if x has two or three dimensions
p <- function(x, i = NULL, row = TRUE) {
  # TODO: replace by "slice"
  if (isTRUE(length(dim(x)) > 3L)) {
    stop2("'p' can only handle objects up to 3 dimensions.")
  }
  if (!length(i)) {
    out <- x
  } else if (length(dim(x)) == 2L) {
    if (row) {
      out <- x[i, , drop = FALSE]
    } else {
      out <- x[, i, drop = FALSE]
    }
  } else if (length(dim(x)) == 3L) {
    if (row) {
      out <- x[i, , , drop = FALSE]
    } else {
      out <- x[, i, , drop = FALSE]
    }
  } else {
    out <- x[i]
  }
  out
}

# extract parts of an object with selective dropping of dimensions
# @param x,...,drop same as in x[..., drop]
# @param drop_dim Optional numeric or logical vector controlling
#   which dimensions to drop. Will overwrite argument 'drop'.
extract <- function(x, ..., drop = FALSE, drop_dim = NULL) {
  if (!length(dim(x))) {
    return(x[...])
  }
  if (length(drop_dim)) {
    drop <- FALSE
  } else {
    drop <- as_one_logical(drop)
  }
  out <- x[..., drop = drop]
  if (drop || !length(drop_dim) || any(dim(out) == 0L)) {
    return(out)
  }
  if (is.numeric(drop_dim)) {
    drop_dim <- seq_along(dim(x)) %in% drop_dim
  }
  if (!is.logical(drop_dim)) {
    stop2("'drop_dim' needs to be logical or numeric.")
  }
  keep <- dim(out) > 1L | !drop_dim
  new_dim <- dim(out)[keep]
  if (length(new_dim) <= 1L) {
    # use vectors instead of 1D arrays
    new_dim <- NULL
  }
  dim(out) <- new_dim
  out
}

# extract slices of one array dimension without dropping other dimensions
# @param x an array
# @param dim dimension from which to take the slice
# @param i slice index
# @param drop Logical (length 1) indicating whether to drop dimension `dim`.
slice <- function(x, dim, i, drop = TRUE) {
  ndim <- length(dim(x))
  commas1 <- collapse(rep(", ", dim - 1))
  commas2 <- collapse(rep(", ", ndim - dim))
  drop_dim <- ifelse(drop, ", drop_dim = dim", "")
  expr <- paste0("extract(x, ", commas1, "i", commas2, drop_dim, ")")
  eval2(expr)
}

# slice out columns without dropping other dimensions
# @param x an array; a vector or 1D array is treated as already sliced
# @param i column index
slice_col <- function(x, i) {
  if (length(dim(x)) < 2L) {
    # a vector or 1D array is treated as already sliced
    return(x)
  }
  slice(x, 2, i)
}

seq_rows <- function(x) {
  seq_len(NROW(x))
}

seq_cols <- function(x) {
  seq_len(NCOL(x))
}

seq_dim <- function(x, dim) {
  dim <- as_one_numeric(dim)
  if (dim == 1) {
    len <- NROW(x)
  } else if (dim == 2) {
    len <- NCOL(x)
  } else {
    len <- dim(x)[dim]
  }
  if (length(len) == 1L && !isNA(len)) {
    out <- seq_len(len)
  } else {
    out <- integer(0)
  }
  out
}

# match rows in x with rows in y
match_rows <- function(x, y, ...) {
  x <- as.data.frame(x)
  y <- as.data.frame(y)
  x <- do.call("paste", c(x, sep = "\r"))
  y <- do.call("paste", c(y, sep = "\r"))
  match(x, y, ...)
}

# find elements of 'x' matching sub-elements passed via 'ls' and '...'
find_elements <- function(x, ..., ls = list(), fun = '%in%') {
  x <- as.list(x)
  if (!length(x)) {
    return(logical(0))
  }
  out <- rep(TRUE, length(x))
  ls <- c(ls, list(...))
  if (!length(ls)) {
    return(out)
  }
  if (is.null(names(ls))) {
    stop("Argument 'ls' must be named.")
  }
  for (name in names(ls)) {
    tmp <- from_list(x, name)
    out <- out & do_call(fun, list(tmp, ls[[name]]))
  }
  out
}

# find rows of 'x' matching columns passed via 'ls' and '...'
# similar to 'find_elements' but for matrix like objects
find_rows <- function(x, ..., ls = list(), fun = '%in%') {
  x <- as.data.frame(x)
  if (!nrow(x)) {
    return(logical(0))
  }
  out <- rep(TRUE, nrow(x))
  ls <- c(ls, list(...))
  if (!length(ls)) {
    return(out)
  }
  if (is.null(names(ls))) {
    stop("Argument 'ls' must be named.")
  }
  for (name in names(ls)) {
    out <- out & do_call(fun, list(x[[name]], ls[[name]]))
  }
  out
}

# subset 'x' using arguments passed via 'ls' and '...'
subset2 <- function(x, ..., ls = list(), fun = '%in%') {
  x[find_rows(x, ..., ls = ls, fun = fun), , drop = FALSE]
}

# convert array to list of elements with reduced dimension
# @param x an arrary of dimension d
# @return a list of arrays of dimension d-1
array2list <- function(x) {
  if (is.null(dim(x))) {
    return(as.list(x))
  }
  ndim <- length(dim(x))
  out <- list(length = dim(x)[ndim])
  ind <- collapse(rep(",", ndim - 1))
  for (i in seq_len(dim(x)[ndim])) {
    out[[i]] <- eval2(paste0("x[", ind, i, "]"))
    if (length(dim(x)) > 2) {
      # avoid accidental dropping of other dimensions
      dim(out[[i]]) <- dim(x)[-ndim]
    }
  }
  names(out) <- dimnames(x)[[ndim]]
  out
}

# move elements to the start of a named object
move2start <- function(x, first) {
  x[c(first, setdiff(names(x), first))]
}

# move elements to the end of a named object
move2end <- function(x, last) {
  x[c(setdiff(names(x), last), last)]
}

# wrapper around replicate but without simplifying
repl <- function(expr, n) {
  replicate(n, expr, simplify = FALSE)
}

# find the first element in A that is greater than target
# @param A a matrix
# @param target a vector of length nrow(A)
# @param i column of A being checked first
# @return a vector of the same length as target containing the
#   column ids where A[,i] was first greater than target
first_greater <- function(A, target, i = 1) {
  ifelse(target <= A[, i] | ncol(A) == i, i, first_greater(A, target, i + 1))
}

# check if an object is NULL
isNULL <- function(x) {
  is.null(x) || ifelse(is.vector(x), all(sapply(x, is.null)), FALSE)
}

# recursively removes NULL entries from an object
rmNULL <- function(x, recursive = TRUE) {
  x <- Filter(Negate(isNULL), x)
  if (recursive) {
    x <- lapply(x, function(x) if (is.list(x)) rmNULL(x) else x)
  }
  x
}

# find the first argument that is not NULL
first_not_null <- function(...) {
  dots <- list(...)
  out <- NULL
  i <- 1L
  while (isNULL(out) && i <= length(dots)) {
    if (!isNULL(dots[[i]])) {
      out <- dots[[i]]
    }
    i <- i + 1L
  }
  out
}

is_atomic_or_null <- function(x) {
  is.atomic(x) || is.null(x)
}

isNA <- function(x) {
  length(x) == 1L && is.na(x)
}

is_equal <- function(x, y, check.attributes = FALSE, ...) {
  isTRUE(all.equal(x, y, check.attributes = check.attributes, ...))
}

# check if 'x' will behave like a factor in design matrices
is_like_factor <- function(x) {
  is.factor(x) || is.character(x) || is.logical(x)
}

# as.factor but allows to pass levels
as_factor <- function(x, levels = NULL) {
  if (is.null(levels)) {
    out <- as.factor(x)
  } else {
    out <- factor(x, levels = levels)
  }
  out
}

# coerce 'x' to a single logical value
as_one_logical <- function(x, allow_na = FALSE) {
  s <- substitute(x)
  x <- as.logical(x)
  if (length(x) != 1L || anyNA(x) && !allow_na) {
    s <- deparse0(s, max_char = 100L)
    stop2("Cannot coerce '", s, "' to a single logical value.")
  }
  x
}

# coerce 'x' to a single integer value
as_one_integer <- function(x, allow_na = FALSE) {
  s <- substitute(x)
  x <- SW(as.integer(x))
  if (length(x) != 1L || anyNA(x) && !allow_na) {
    s <- deparse0(s, max_char = 100L)
    stop2("Cannot coerce '", s, "' to a single integer value.")
  }
  x
}

# coerce 'x' to a single numeric value
as_one_numeric <- function(x, allow_na = FALSE) {
  s <- substitute(x)
  x <- SW(as.numeric(x))
  if (length(x) != 1L || anyNA(x) && !allow_na) {
    s <- deparse0(s, max_char = 100L)
    stop2("Cannot coerce '", s, "' to a single numeric value.")
  }
  x
}

# coerce 'x' to a single character string
as_one_character <- function(x, allow_na = FALSE) {
  s <- substitute(x)
  x <- as.character(x)
  if (length(x) != 1L || anyNA(x) && !allow_na) {
    s <- deparse0(s, max_char = 100L)
    stop2("Cannot coerce '", s, "' to a single character value.")
  }
  x
}

# coerce 'x' to a single character variable name
as_one_variable <- function(x, allow_na = TRUE) {
  x <- as_one_character(x)
  if (x == "NA" && allow_na) {
    return(x)
  }
  if (!nzchar(x) || !is_equal(x, all_vars(x))) {
    stop2("Cannot coerce '", x, "' to a single variable name.")
  }
  x
}

has_rows <- function(x) {
  isTRUE(nrow(x) > 0L)
}

has_cols <- function(x) {
  isTRUE(ncol(x) > 0L)
}

# expand arguments to the same length
# @param ... arguments to expand
# @param length optional expansion length
#   otherwise taken to be the largest supplied length
# @return a data.frame with one variable per element in '...'
expand <- function(..., dots = list(), length = NULL) {
  dots <- c(dots, list(...))
  max_dim <- NULL
  if (is.null(length)) {
    lengths <- lengths(dots)
    length <- max(lengths)
    max_dim <- dim(dots[[match(length, lengths)]])
  }
  out <- as.data.frame(lapply(dots, rep, length.out = length))
  structure(out, max_dim = max_dim)
}

# structure but ignore NULL
structure_not_null <- function(.Data, ...) {
  if (!is.null(.Data)) {
    .Data <- structure(.Data, ...)
  }
  .Data
}

# remove specified attributes
rm_attr <- function(x, attr) {
  attributes(x)[attr] <- NULL
  x
}

# unidimensional subsetting while keeping attributes
subset_keep_attr <- function(x, y) {
  att <- attributes(x)
  x <- x[y]
  att$names <- names(x)
  attributes(x) <- att
  x
}

'%||%' <- function(x, y) {
  if (is.null(x)) x <- y
  x
}

# check if 'x' is a whole number (integer)
is_wholenumber <- function(x, tol = .Machine$double.eps) {
  if (is.numeric(x)) {
    out <- abs(x - round(x)) < tol
  } else {
    out <- rep(FALSE, length(x))
  }
  dim(out) <- dim(x)
  out
}

# helper function to check symmetry of a matrix
is_symmetric <- function(x, tol = sqrt(.Machine$double.eps)) {
  isSymmetric(x, tol = tol, check.attributes = FALSE)
}

# unlist lapply output
ulapply <- function(X, FUN, ..., recursive = TRUE, use.names = TRUE) {
  unlist(lapply(X, FUN, ...), recursive, use.names)
}

# rbind lapply output
rblapply <- function(X, FUN, ...) {
  do.call(rbind, lapply(X, FUN, ...))
}

# cbind lapply output
cblapply <- function(X, FUN, ...) {
  do.call(cbind, lapply(X, FUN, ...))
}

# parallel lapply sensitive to the operating system
plapply <- function(X, FUN, cores = 1, ...) {
  if (cores == 1) {
    out <- lapply(X, FUN, ...)
  } else {
    if (!os_is_windows()) {
      out <- parallel::mclapply(X = X, FUN = FUN, mc.cores = cores, ...)
    } else {
      cl <- parallel::makePSOCKcluster(cores)
      on.exit(parallel::stopCluster(cl))
      out <- parallel::parLapply(cl = cl, X = X, fun = FUN, ...)
    }
  }
  out
}

# extract objects stored in each element of a list
# @param x a list-like object
# @param name name of the object to extract
from_list <- function(x, name, ...) {
  lapply(x, "[[", name, ...)
}

# unlist from_list output
ufrom_list <- function(x, name, ..., recursive = TRUE, use.names = TRUE) {
  unlist(from_list(x, name, ...), recursive, use.names)
}

# check if the operating system is Windows
os_is_windows <- function() {
  isTRUE(Sys.info()[['sysname']] == "Windows")
}

# find variables in a character string or expression
all_vars <- function(expr, ...) {
  if (is.character(expr)) {
    expr <- str2expression(expr)
  }
  all.vars(expr, ...)
}

# reimplemented for older R versions
# see ?parse in R 3.6 or higher
str2expression <- function(x) {
  parse(text = x, keep.source = FALSE)
}

# reimplemented for older R versions
# see ?parse in R 3.6 or higher
str2lang <- function(x) {
  str2expression(x)[[1]]
}

# append list(...) to x
lc <- function(x, ...) {
  dots <- rmNULL(list(...), recursive = FALSE)
  c(x, dots)
}

'c<-' <- function(x, value) {
  c(x, value)
}

'lc<-' <- function(x, value) {
  lc(x, value)
}

collapse <- function(..., sep = "") {
  paste(..., sep = sep, collapse = "")
}

collapse_comma <- function(...) {
  paste0("'", ..., "'", collapse = ", ")
}

# add characters to an existing string
'str_add<-' <- function(x, start = FALSE, value) {
  if (start) paste0(value, x) else paste0(x, value)
}

# add list of characters to an existing list
'str_add_list<-' <- function(x, start = FALSE, value) {
  stopifnot(is.list(x), is.list(value))
  out <- if (start) list(value, x) else list(x, value)
  collapse_lists(ls = out)
}

# type-stable if clause for strings with default else output
str_if <- function(cond, yes, no = "") {
  cond <- as_one_logical(cond)
  if (cond) as.character(yes) else as.character(no)
}

# select elements which match a regex pattern
str_subset <- function(x, pattern, ...) {
  x[grepl(pattern, x, ...)]
}

# similar to glue::glue but specialized for generating Stan code
glue <- function(..., sep = "", collapse = NULL, envir = parent.frame(),
                 open = "{", close = "}", na = "NA") {
  dots <- list(...)
  dots <- dots[lengths(dots) > 0L]
  args <- list(
    .x = NULL, .sep = sep, .envir = envir, .open = open,
    .close = close, .na = na, .trim = FALSE,
    .transformer = zero_length_transformer
  )
  out <- do.call(glue::glue_data, c(dots, args))
  if (!is.null(collapse)) {
    collapse <- as_one_character(collapse)
    out <- paste0(out, collapse = collapse)
  }
  out
}

# used in 'glue' to handle zero-length inputs
zero_length_transformer <- function(text, envir) {
  out <- glue::identity_transformer(text, envir)
  if (!length(out)) {
    out <- ""
  }
  out
}

# collapse strings evaluated with glue
cglue <- function(..., envir = parent.frame()) {
  glue(..., envir = envir, collapse = "")
}

# check if a certain package is installed
# @param package package name
# @param version optional minimal version number to require
require_package <- function(package, version = NULL) {
  if (!requireNamespace(package, quietly = TRUE)) {
    stop2("Please install the '", package, "' package.")
  }
  if (!is.null(version)) {
    version <- as.package_version(version)
    if (utils::packageVersion(package) < version) {
      stop2("Please install package '", package,
            "' version ", version, " or higher.")
    }
  }
  invisible(TRUE)
}

# rename specified patterns in a character vector
# @param x a character vector to be renamed
# @param pattern the regular expressions in x to be replaced
# @param replacement the replacements
# @param fixed same as for 'gsub'
# @param check_dup: logical; check for duplications in x after renaming
# @param ... passed to 'gsub'
# @return renamed character vector of the same length as x
rename <- function(x, pattern = NULL, replacement = NULL,
                   fixed = TRUE, check_dup = FALSE, ...) {
  pattern <- as.character(pattern)
  replacement <- as.character(replacement)
  if (!length(pattern) && !length(replacement)) {
    # default renaming to avoid special characters in coeffcient names
    pattern <- c(
      " ", "(", ")", "[", "]", ",", "\"", "'",
      "?", "+", "-", "*", "/", "^", "="
    )
    replacement <- c(rep("", 9), "P", "M", "MU", "D", "E", "EQ")
  }
  if (length(replacement) == 1L) {
    replacement <- rep(replacement, length(pattern))
  }
  stopifnot(length(pattern) == length(replacement))
  # avoid zero-length pattern error
  has_chars <- nzchar(pattern)
  pattern <- pattern[has_chars]
  replacement <- replacement[has_chars]
  out <- x
  for (i in seq_along(pattern)) {
    out <- gsub(pattern[i], replacement[i], out, fixed = fixed, ...)
  }
  dup <- duplicated(out)
  if (check_dup && any(dup)) {
    dup <- x[out %in% out[dup]]
    stop2("Internal renaming led to duplicated names. \n",
          "Occured for: ", collapse_comma(dup))
  }
  out
}

# collapse strings having the same name in different lists
# @param ... named lists
# @param ls a list of named lists
# @param a named list containing the collapsed strings
collapse_lists <- function(..., ls = list()) {
  ls <- c(list(...), ls)
  elements <- unique(unlist(lapply(ls, names)))
  args <- c(FUN = collapse, lapply(ls, "[", elements), SIMPLIFY = FALSE)
  out <- do.call(mapply, args)
  names(out) <- elements
  out
}

# create a named list using object names
nlist <- function(...) {
  m <- match.call()
  dots <- list(...)
  no_names <- is.null(names(dots))
  has_name <- if (no_names) FALSE else nzchar(names(dots))
  if (all(has_name)) return(dots)
  nms <- as.character(m)[-1]
  if (no_names) {
    names(dots) <- nms
  } else {
    names(dots)[!has_name] <- nms[!has_name]
  }
  dots
}

# initialize a named list
# @param names names of the elements
# @param values optional values of the elements
named_list <- function(names, values = NULL) {
  if (!is.null(values)) {
    if (length(values) <= 1L) {
      values <- replicate(length(names), values)
    }
    values <- as.list(values)
    stopifnot(length(values) == length(names))
  } else {
    values <- vector("list", length(names))
  }
  setNames(values, names)
}

# is an object named?
is_named <- function(x) {
  names <- names(x)
  if (is.null(names)) {
    return(FALSE)
  }
  if (any(!nzchar(names) | is.na(names))) {
    return(FALSE)
  }
  TRUE
}

#' Execute a Function Call
#'
#' Execute a function call similar to \code{\link{do.call}}, but without
#' deparsing function arguments. For large number of arguments (i.e., more
#' than a few thousand) this function currently is somewhat inefficient
#' and should be used with care in this case.
#'
#' @param what Either a function or a non-empty character string naming the
#'   function to be called.
#' @param args A list of arguments to the function call. The names attribute of
#'   \code{args} gives the argument names.
#' @param pkg Optional name of the package in which to search for the
#'   function if \code{what} is a character string.
#' @param envir An environment within which to evaluate the call.
#'
#' @return The result of the (evaluated) function call.
#'
#' @keywords internal
#' @export
do_call <- function(what, args, pkg = NULL, envir = parent.frame()) {
  call <- ""
  if (length(args)) {
    if (!is.list(args)) {
      stop2("'args' must be a list.")
    }
    fun_args <- names(args)
    if (is.null(fun_args)) {
      fun_args <- rep("", length(args))
    } else {
      nzc <- nzchar(fun_args)
      fun_args[nzc] <- paste0("`", fun_args[nzc], "` = ")
    }
    names(args) <- paste0(".x", seq_along(args))
    call <- paste0(fun_args, names(args), collapse = ",")
  } else {
    args <- list()
  }
  if (is.function(what)) {
    args$.fun <- what
    what <- ".fun"
  } else {
    what <- paste0("`", as_one_character(what), "`")
    if (!is.null(pkg)) {
      what <- paste0(as_one_character(pkg), "::", what)
    }
  }
  call <- paste0(what, "(", call, ")")
  eval2(call, envir = args, enclos = envir)
}

# create an empty data frame
empty_data_frame <- function() {
  as.data.frame(matrix(nrow = 0, ncol = 0))
}

# replace elements in x with elements in value
# @param x named list-like object
# @param value another named list-like object
# @param dont_replace names of elements that cannot be replaced
'replace_args<-' <- function(x, dont_replace = NULL, value) {
  value_name <- deparse0(substitute(value), max_char = 100L)
  value <- as.list(value)
  if (length(value) && is.null(names(value))) {
    stop2("Argument '", value_name, "' must be named.")
  }
  invalid <- names(value)[names(value) %in% dont_replace]
  if (length(invalid)) {
    invalid <- collapse_comma(invalid)
    stop2("Argument(s) ", invalid, " cannot be replaced.")
  }
  x[names(value)] <- value
  x
}

# deparse0 'x' if it is no string
deparse_no_string <- function(x) {
  if (!is.character(x)) {
    x <- deparse0(x)
  }
  x
}

# combine deparse lines into one string
# since R 4.0 we also have base::deparse1 for this purpose
deparse0 <- function(x, max_char = NULL, ...) {
  out <- collapse(deparse(x, ...))
  if (isTRUE(max_char > 0)) {
    out <- substr(out, 1L, max_char)
  }
  out
}

# like 'eval' but parses characters before evaluation
eval2 <- function(expr, envir = parent.frame(), ...) {
  if (is.character(expr)) {
    expr <- str2expression(expr)
  }
  eval(expr, envir, ...)
}

# evaluate an expression without printing output or messages
# @param expr expression to be evaluated
# @param type type of output to be suppressed (see ?sink)
# @param try wrap evaluation of expr in 'try' and
#   not suppress outputs if evaluation fails?
# @param silent actually evaluate silently?
eval_silent <- function(expr, type = "output", try = FALSE,
                        silent = TRUE, ...) {
  try <- as_one_logical(try)
  silent <- as_one_logical(silent)
  type <- match.arg(type, c("output", "message"))
  expr <- substitute(expr)
  envir <- parent.frame()
  if (silent) {
    if (try && type == "message") {
      try_out <- try(utils::capture.output(
        out <- eval(expr, envir), type = type, ...
      ))
      if (is_try_error(try_out)) {
        # try again without suppressing error messages
        out <- eval(expr, envir)
      }
    } else {
      utils::capture.output(out <- eval(expr, envir), type = type, ...)
    }
  } else {
    out <- eval(expr, envir)
  }
  out
}

# find the name that 'x' had in a specific environment
substitute_name <- function(x, envir = parent.frame(), nchar = 50) {
  out <- substitute(x)
  out <- eval2(paste0("substitute(", out, ")"), envir = envir)
  if (missing(out)) {
    return(NULL)
  }
  substr(collapse(deparse(out)), 1, nchar)
}

# recursive sorting of dependencies
# @param x named list of dependencies per element
# @param sorted already sorted element names
# @return a vector of sorted element names
sort_dependencies <- function(x, sorted = NULL) {
  if (!length(x)) {
    return(NULL)
  }
  if (length(names(x)) != length(x)) {
    stop2("Argument 'x' must be named.")
  }
  take <- !ulapply(x, function(dep) any(!dep %in% sorted))
  new <- setdiff(names(x)[take], sorted)
  out <- union(sorted, new)
  if (length(new)) {
    out <- union(out, sort_dependencies(x, sorted = out))
  } else if (!all(names(x) %in% out)) {
    stop2("Cannot handle circular dependency structures.")
  }
  out
}

stop2 <- function(...) {
  stop(..., call. = FALSE)
}

warning2 <- function(...) {
  warning(..., call. = FALSE)
}

# get first occurrence of 'x' in '...' objects
# @param x The name of the required element
# @param ... named R objects that may contain 'x'
get_arg <- function(x, ...) {
  dots <- list(...)
  i <- 1
  out <- NULL
  while (i <= length(dots) && is.null(out)) {
    if (!is.null(dots[[i]][[x]])) {
      out <- dots[[i]][[x]]
    } else {
      i <- i + 1
    }
  }
  out
}

SW <- function(expr) {
  base::suppressWarnings(expr)
}

# get pattern matches in text as vector
# @param simplify return an atomic vector of matches?
# @param first only return the first match in each string?
# @return character vector containing matches
get_matches <- function(pattern, text, simplify = TRUE,
                        first = FALSE, ...) {
  x <- regmatches(text, gregexpr(pattern, text, ...))
  if (first) {
    x <- lapply(x, function(t) if (length(t)) t[1] else t)
  }
  if (simplify) {
    if (first) {
      x <- lapply(x, function(t) if (length(t)) t else "")
    }
    x <- unlist(x)
  }
  x
}

# find matches in the parse tree of an expression
# @param pattern pattern to be matched
# @param expr expression to be searched in
# @return character vector containing matches
get_matches_expr <- function(pattern, expr, ...) {
  if (is.character(expr)) {
    expr <- str2expression(expr)
  }
  out <- NULL
  for (i in seq_along(expr)) {
    sexpr <- try(expr[[i]], silent = TRUE)
    if (!is_try_error(sexpr)) {
      sexpr_char <- deparse0(sexpr)
      out <- c(out, get_matches(pattern, sexpr_char, ...))
    }
    if (is.call(sexpr) || is.expression(sexpr)) {
      out <- c(out, get_matches_expr(pattern, sexpr, ...))
    }
  }
  trim_wsp(unique(out))
}

# like 'grepl' but handles (parse trees of) expressions
grepl_expr <- function(pattern, expr, ...) {
  as.logical(ulapply(expr, function(e)
    length(get_matches_expr(pattern, e, ...)) > 0L))
}

# combine character vectors into a joint regular 'or' expression
# @param x a character vector
# @param escape escape all special characters in 'x'?
regex_or <- function(x, escape = FALSE) {
  if (escape) {
    x <- escape_all(x)
  }
  paste0("(", paste0("(", x, ")", collapse = "|"), ")")
}

# escape dots in character strings
escape_dot <- function(x) {
  gsub(".", "\\.", x, fixed = TRUE)
}

# escape all special characters in character strings
escape_all <- function(x) {
  specials <- c(".", "*", "+", "?", "^", "$", "(", ")", "[", "]", "|")
  for (s in specials) {
    x <- gsub(s, paste0("\\", s), x, fixed = TRUE)
  }
  x
}

# add an underscore to non-empty character strings
# @param x a character vector
# @param pos position of the underscore
usc <- function(x, pos = c("prefix", "suffix")) {
  pos <- match.arg(pos)
  x <- as.character(x)
  if (!length(x)) x <- ""
  if (pos == "prefix") {
    x <- ifelse(nzchar(x), paste0("_", x), "")
  } else {
    x <- ifelse(nzchar(x), paste0(x, "_"), "")
  }
  x
}

# round using the largest remainder method
round_largest_remainder <- function(x) {
  x <- as.numeric(x)
  total <- round(sum(x))
  out <- floor(x)
  diff <- x - out
  J <- order(diff, decreasing = TRUE)
  I <- seq_len(total - floor(sum(out)))
  out[J[I]] <- out[J[I]] + 1
  out
}

# add leading and trailing white spaces
# @param x object accepted by paste
# @param nsp number of white spaces to add
wsp <- function(x = "", nsp = 1) {
  sp <- collapse(rep(" ", nsp))
  if (length(x)) {
    out <- ifelse(nzchar(x), paste0(sp, x, sp), sp)
  } else {
    out <- NULL
  }
  out
}

# add white space per line the the strings
# @param x object accepted by paste
# @param nsp number of white spaces to add
wsp_per_line <- function(x, nsp) {
  sp <- collapse(rep(" ", nsp))
  x <- paste0(sp, x)
  x <- gsub("\\n(?=.+)", paste0("\n", sp), x, perl = TRUE)
  x
}

# remove whitespaces in character strings
rm_wsp <- function(x) {
  out <- gsub("[ \t\r\n]+", "", x, perl = TRUE)
  dim(out) <- dim(x)
  out
}

# trim whitespaces in character strings
trim_wsp <- function(x) {
  out <- gsub("[ \t\r\n]+", " ", x, perl = TRUE)
  dim(out) <- dim(x)
  out
}

# limit the number of characters of a vector
# @param x a character vector
# @param chars maximum number of characters to show
# @param lsuffix number of characters to keep at the end of the strings
# @return possible truncated character vector
limit_chars <- function(x, chars = NULL, lsuffix = 4) {
  stopifnot(is.character(x))
  if (!is.null(chars)) {
    chars_x <- nchar(x) - lsuffix
    suffix <- substr(x, chars_x + 1, chars_x + lsuffix)
    x <- substr(x, 1, chars_x)
    x <- ifelse(chars_x <= chars, x, paste0(substr(x, 1, chars - 3), "..."))
    x <- paste0(x, suffix)
  }
  x
}

# ensure that deprecated arguments still work
# @param arg input to the new argument
# @param alias input to the deprecated argument
# @param default the default value of alias
# @param warn should a warning be printed if alias is specified?
use_alias <- function(arg, alias = NULL, default = NULL, warn = TRUE) {
  arg_name <- Reduce(paste, deparse(substitute(arg)))
  alias_name <- Reduce(paste, deparse(substitute(alias)))
  if (!is_equal(alias, default)) {
    arg <- alias
    if (grepl("^dots\\$", alias_name)) {
      alias_name <- gsub("^dots\\$", "", alias_name)
    } else if (grepl("^dots\\[\\[", alias_name)) {
      alias_name <- gsub("^dots\\[\\[\"|\"\\]\\]$", "", alias_name)
    }
    if (warn) {
      warning2("Argument '", alias_name, "' is deprecated. ",
               "Please use argument '", arg_name, "' instead.")
    }
  }
  arg
}

warn_deprecated <- function(new, old = as.character(sys.call(sys.parent()))[1]) {
  msg <- paste0("Function '", old, "' is deprecated.")
  if (!missing(new)) {
    msg <- paste0(msg, " Please use '", new, "' instead.")
  }
  warning2(msg)
  invisible(NULL)
}

# check if x is a try-error resulting from try()
is_try_error <- function(x) {
  inherits(x, "try-error")
}

# check if verbose mode is activated
is_verbose <- function() {
  as_one_logical(getOption("brms.verbose", FALSE))
}

viridis6 <- function() {
  c("#440154", "#414487", "#2A788E", "#22A884", "#7AD151", "#FDE725")
}

expect_match2 <- function(object, regexp, ..., all = TRUE) {
  testthat::expect_match(object, regexp, fixed = TRUE, ..., all = all)
}

# startup messages for brms
.onAttach <- function(libname, pkgname) {
  version <- utils::packageVersion("brms")
  packageStartupMessage(
    "Loading 'brms' package (version ", version, "). Useful instructions\n",
    "can be found by typing help('brms'). A more detailed introduction\n",
    "to the package is available through vignette('brms_overview')."
  )
  invisible(NULL)
}

# code to execute when loading brms
.onLoad <- function(libname, pkgname) {
  # ensure compatibility with older R versions
  backports::import(pkgname)
  # dynamically register the 'recover_data' and 'emm_basis'
  # methods needed by 'emmeans', if that package is installed
  if (requireNamespace("emmeans", quietly = TRUE) &&
      utils::packageVersion("emmeans") >= "1.4.0") {
    emmeans::.emm_register("brmsfit", pkgname)
  }
  invisible(NULL)
}
paul-buerkner/brms documentation built on March 29, 2024, 12:15 a.m.