R/assert.R

Defines functions assert_symmetric assert_square assert_greater assert_lower assert_increasing assert_decreasing assert_constant assert_even assert_odd assert_negative assert_positive assert_count assert_function assert_scalar assert_type assert_unique assert_infinite assert_missing assert_colnames assert_rownames assert_names assert_filled assert_empty assert_ncol assert_nrow assert_dim assert_lengths assert_length assert_package validate

Documented in assert_colnames assert_constant assert_count assert_decreasing assert_dim assert_empty assert_even assert_filled assert_function assert_greater assert_increasing assert_infinite assert_length assert_lengths assert_lower assert_missing assert_names assert_ncol assert_negative assert_nrow assert_odd assert_package assert_positive assert_rownames assert_scalar assert_square assert_symmetric assert_type assert_unique validate

# CHECK DATA INPUT
#' @include predicates.R
NULL

#' Validate a Condition
#'
#' @param expr An object to be evaluated.
#' @return
#'  Returns `NULL` on success, otherwise returns the error as a string.
#' @author N. Frerebeau
#' @family validation methods
#' @name validate
#' @rdname validate
#' @export
validate <- function(expr) {
  cnd <- catch_message(eval(expr))
  if (has_length(cnd)) return(cnd)
  NULL
}

# Packages =====================================================================
#' Check the Availability of a Package
#'
#' @param x A [`character`] vector naming the packages to check.
#' @param ask A [`logical`] scalar: should the user be asked to select packages
#'  before they are downloaded and installed?
#' @details
#'  `assert_package()` is designed for use inside other functions in your own
#'  package to check for the availability of a suggested package.
#'
#'  If the required packages are not available and \R is running interactively,
#'  the user will be asked to install the packages.
#'
#'  `needs()` is an alias for `assert_package()`.
#' @return Invisibly returns `NULL`.
#' @family checking methods
#' @author N. Frerebeau
#' @export
assert_package <- function(x, ask = TRUE) {
  ok <- vapply(X = x, FUN = requireNamespace, FUN.VALUE = logical(1),
               quietly = TRUE)

  miss <- x[!ok]
  n <- length(miss)

  if (n != 0) {
    msg <- ngettext(n, "Package %s is required.", "Packages %s are required.")
    pkg <- paste0(sQuote(miss), collapse = ", ")
    err <- sprintf(msg, pkg)
    install <- "0"
    if (ask && interactive()) {
      cat(
        err,
        ngettext(n, "Do you want to install it?", "Do you want to install them?"),
        "1. Yes",
        "2. No",
        sep = "\n"
      )
      install <- readline(tr_("Choice: "))
    }
    if (install == "1") {
      utils::install.packages(miss)
    } else {
      throw_error("error_missing_package", err)
    }
  }
  invisible(NULL)
}

#' @export
#' @rdname assert_package
needs <- assert_package

# Attributes ===================================================================
#' Check Object Length(s)
#'
#' @param x An object to be checked.
#' @param expected An appropriate expected value.
#' @param allow_empty A [`logical`] scalar: should [empty][is_empty()] object be
#'  ignored?
#' @param allow_null A [`logical`] scalar: should `NULL` object be ignored?
#' @param empty Deprecated.
#' @return
#'  Throws an error, if any, and returns `x` invisibly otherwise.
#' @author N. Frerebeau
#' @family checking methods
#' @export
assert_length <- function(x, expected, allow_empty = empty, allow_null = FALSE, empty = FALSE) {
  if (is.null(x) && isTRUE(allow_null)) return(invisible(NULL))

  arg <- deparse(substitute(x))
  if (!(allow_empty && is_empty(x)) && !has_length(x, n = expected)) {
    txt <- tr_("%s must be of length %d; not %d.")
    msg <- sprintf(txt, sQuote(arg), expected, length(x))
    throw_error("error_bad_length", msg)
  }
  invisible(x)
}

#' @export
#' @rdname assert_length
assert_lengths <- function(x, expected) {
  arg <- deparse(substitute(x))
  n <- lengths(x)
  if (!all(n == expected)) {
    txt <- tr_("Elements of %s must be of lengths %s; not %s.")
    msg <- sprintf(txt, sQuote(arg), paste0(expected, collapse = ", "),
                   paste0(n, collapse = ", "))
    throw_error("error_bad_length", msg)
  }
  invisible(x)
}

#' Check Object Dimensions
#'
#' @param x An object to be checked.
#' @param expected An appropriate expected value.
#' @return
#'  Throws an error, if any, and returns `x` invisibly otherwise.
#' @author N. Frerebeau
#' @family checking methods
#' @export
assert_dim <- function(x, expected) {
  arg <- deparse(substitute(x))
  n <- dim(x)
  if (!all(n == expected)) {
    txt <- tr_("%s must be of dimension %s; not %s.")
    msg <- sprintf(txt, sQuote(arg), paste0(expected, collapse = " x "),
                   paste0(n, collapse = " x "))
    throw_error("error_bad_dimensions", msg)
  }
  invisible(x)
}

#' @export
#' @rdname assert_dim
assert_nrow <- function(x, expected) {
  arg <- deparse(substitute(x))
  n <- nrow(x)
  if (n != expected) {
    txt <- ngettext(expected, "%s must have %s row; not %s.",
                    "%s must have %s rows; not %s.")
    msg <- sprintf(txt, sQuote(arg), expected, n)
    throw_error("error_bad_dimensions", msg)
  }
  invisible(x)
}

#' @export
#' @rdname assert_dim
assert_ncol <- function(x, expected) {
  arg <- deparse(substitute(x))
  n <- ncol(x)
  if (n != expected) {
    txt <- ngettext(expected, "%s must have %s column; not %s.",
                    "%s must have %s columns; not %s.")
    msg <- sprintf(txt, sQuote(arg), expected, n)
    throw_error("error_bad_dimensions", msg)
  }
  invisible(x)
}

#' Check Object Filling
#'
#' Checks if an object is (not) empty.
#' @param x An object to be checked.
#' @return
#'  Throws an error, if any, and returns `x` invisibly otherwise.
#' @author N. Frerebeau
#' @family checking methods
#' @export
assert_empty <- function(x) {
  arg <- deparse(substitute(x))
  if (!is_empty(x)) {
    msg <- sprintf(tr_("%s must be empty."), sQuote(arg))
    throw_error("error_bad_dimensions", msg)
  }
  invisible(x)
}

#' @export
#' @rdname assert_empty
assert_filled <- function(x) {
  arg <- deparse(substitute(x))
  if (is_empty(x)) {
    msg <- sprintf(tr_("%s must not be empty."), sQuote(arg))
    throw_error("error_bad_dimensions", msg)
  }
  invisible(x)
}

# Names ========================================================================
#' Check Object Names
#'
#' @param x An object to be checked.
#' @param expected An appropriate expected value.
#' @return
#'  Throws an error, if any, and returns `x` invisibly otherwise.
#' @author N. Frerebeau
#' @family checking methods
#' @export
assert_names <- function(x, expected = NULL) {
  arg <- deparse(substitute(x))
  if (!has_names(x, names = expected)) {
    if (is.null(expected)) {
      msg <- sprintf(tr_("%s must have names."), sQuote(arg))
    } else {
      msg <- sprintf(tr_("%s must have the following names: %s."),
                     sQuote(arg), paste0(expected, collapse = ", "))
    }
    throw_error("error_bad_names", msg)
  }
  invisible(x)
}

#' @export
#' @rdname assert_names
assert_rownames <- function(x, expected = NULL) {
  arg <- deparse(substitute(x))
  if (!has_rownames(x, names = expected)) {
    if (is.null(expected)) {
      msg <- sprintf(tr_("%s must have row names."), sQuote(arg))
    } else {
      msg <- sprintf(tr_("%s must have the following row names: %s."),
                     sQuote(arg), paste0(expected, collapse = ", "))
    }
    throw_error("error_bad_names", msg)
  }
  invisible(x)
}

#' @export
#' @rdname assert_names
assert_colnames <- function(x, expected = NULL) {
  arg <- deparse(substitute(x))
  if (!has_colnames(x, names = expected)) {
    if (is.null(expected)) {
      msg <- sprintf(tr_("%s must have column names."), sQuote(arg))
    } else {
      msg <- sprintf(tr_("%s must have the following column names: %s."),
                     sQuote(arg), paste0(expected, collapse = ", "))
    }
    throw_error("error_bad_names", msg)
  }
  invisible(x)
}

# NA/NaN/Inf/duplicates ========================================================
#' Check Missing Values
#'
#' Checks if an object contains any missing (`NA`, `NaN`) values.
#' @param x An object to be checked.
#' @return
#'  Throws an error, if any, and returns `x` invisibly otherwise.
#' @author N. Frerebeau
#' @family checking methods
#' @export
assert_missing <- function(x) {
  arg <- deparse(substitute(x))
  n <- sum(is.na(x))
  if (n > 0) {
    txt <- ngettext(n, "%s must not contain missing values (%d detected).",
                    "%s must not contain missing values (%d detected).")
    msg <- sprintf(txt, sQuote(arg), n)
    throw_error("error_data_missing", msg)
  }
  invisible(x)
}

#' Check Infinite Values
#'
#' Checks if an object contains any infinite (`Inf`) values.
#' @param x An object to be checked.
#' @return
#'  Throws an error, if any, and returns `x` invisibly otherwise.
#' @author N. Frerebeau
#' @family checking methods
#' @export
assert_infinite <- function(x) {
  arg <- deparse(substitute(x))
  n <- sum(is.infinite(x))
  if (n > 0) {
    txt <- ngettext(n, "%s must not contain infinite values (%d detected).",
                    "%s must not contain infinite values (%d detected).")
    msg <- sprintf(txt, sQuote(arg), n)
    throw_error("error_data_infinite", msg)
  }
  invisible(x)
}

#' Check Duplicates
#'
#' Checks if an object contains duplicated elements.
#' @param x An object to be checked.
#' @return
#'  Throws an error, if any, and returns `x` invisibly otherwise.
#' @author N. Frerebeau
#' @family checking methods
#' @export
assert_unique <- function(x) {
  arg <- deparse(substitute(x))
  if (has_duplicates(x)) {
    msg <- sprintf(tr_("Elements of %s must be unique."), sQuote(arg))
    throw_error("error_data_duplicates", msg)
  }
  invisible(x)
}

# Types ========================================================================
#' Check Data Types
#'
#' @param x An object to be checked.
#' @param expected A [`character`] string specifying the expected
#'  type. It must be one of "`list`", "`atomic`", "`vector`", "`numeric`",
#'  "`integer`", "`double`", "`character`" or "`logical`".
#' @param allow_empty A [`logical`] scalar: should [empty][is_empty()] object be
#'  allowed?
#' @param allow_null A [`logical`] scalar: should `NULL` object be ignored?
#' @return
#'  Throws an error, if any, and returns `x` invisibly otherwise.
#' @author N. Frerebeau
#' @family checking methods
#' @export
assert_type <- function(x, expected, allow_empty = TRUE, allow_null = FALSE) {
  if (is.null(x) && isTRUE(allow_null)) return(invisible(NULL))
  if (isFALSE(allow_empty)) assert_filled(x)

  arg <- deparse(substitute(x))
  msg <- sprintf(tr_("Can't find a predicate for this type: %s."), expected)
  predicate <- switch(
    expected,
    list = is_list,
    atomic = is_atomic,
    vector = is_vector,
    numeric = is_numeric,
    integer = is_integer,
    double = is_double,
    character = is_character,
    logical = is_logical,
    stop(msg, call. = FALSE)
  )
  if (!predicate(x)) {
    msg <- sprintf(tr_("%s must be %s; not %s."), sQuote(arg), expected, typeof(x))
    throw_error("error_bad_type", msg)
  }
  invisible(x)
}

#' @export
#' @rdname assert_type
assert_scalar <- function(x, expected) {
  arg <- deparse(substitute(x))
  msg <- sprintf(tr_("Can't find a predicate for this scalar: %s."), expected)
  predicate <- switch(
    expected,
    list = is_scalar_list,
    atomic = is_scalar_atomic,
    vector = is_scalar_vector,
    numeric = is_scalar_numeric,
    integer = is_scalar_integer,
    double = is_scalar_double,
    character = is_scalar_character,
    logical = is_scalar_logical,
    stop(msg, call. = FALSE)
  )
  if (!predicate(x)) {
    msg <- sprintf(tr_("%s must be a scalar (%s)."), sQuote(arg), expected)
    throw_error("error_bad_scalar", msg)
  }
  invisible(x)
}

#' @export
#' @rdname assert_type
assert_function <- function(x) {
  arg <- deparse(substitute(x))
  if (!is.function(x)) {
    msg <- sprintf(tr_("%s must be a function."), sQuote(arg))
    throw_error("error_bad_type", msg)
  }
  invisible(x)
}

# Numeric ======================================================================
#' Check Numeric Values
#'
#' @param x A [`numeric`] object to be checked.
#' @param na.rm A [`logical`] scalar: should missing values (including `NaN`)
#'  be omitted?
#' @param ... Extra parameters to be passed to internal methods.
#' @return
#'  Throws an error, if any, and returns `x` invisibly otherwise.
#' @author N. Frerebeau
#' @family checking methods
#' @name assert_numeric
#' @rdname assert_numeric
NULL

#' @export
#' @rdname assert_numeric
assert_count <- function(x, na.rm = FALSE, ...) {
  arg <- deparse(substitute(x))
  if (!all(is_whole(x, ...), na.rm = na.rm)) {
    msg <- sprintf(tr_("%s must contain integers (counts)."), sQuote(arg))
    throw_error("error_bad_numeric", msg)
  }
  invisible(x)
}

#' @export
#' @rdname assert_numeric
assert_whole <- assert_count

#' @export
#' @rdname assert_numeric
assert_positive <- function(x, na.rm = FALSE, ...) {
  arg <- deparse(substitute(x))
  if (!all(is_positive(x, ...), na.rm = na.rm)) {
    msg <- sprintf(tr_("%s must contain positive numbers."), sQuote(arg))
    throw_error("error_bad_numeric", msg)
  }
  invisible(x)
}

#' @export
#' @rdname assert_numeric
assert_negative <- function(x, na.rm = FALSE, ...) {
  arg <- deparse(substitute(x))
  if (!all(is_negative(x, ...), na.rm = na.rm)) {
    msg <- sprintf(tr_("%s must contain negative numbers."), sQuote(arg))
    throw_error("error_bad_numeric", msg)
  }
  invisible(x)
}

#' @export
#' @rdname assert_numeric
assert_odd <- function(x, na.rm = FALSE, ...) {
  arg <- deparse(substitute(x))
  if (!all(is_odd(x, ...), na.rm = na.rm)) {
    msg <- sprintf(tr_("%s must contain odd numbers."), sQuote(arg))
    throw_error("error_bad_numeric", msg)
  }
  invisible(x)
}

#' @export
#' @rdname assert_numeric
assert_even <- function(x, na.rm = FALSE, ...) {
  arg <- deparse(substitute(x))
  if (!all(is_even(x, ...), na.rm = na.rm)) {
    msg <- sprintf(tr_("%s must contain even numbers."), sQuote(arg))
    throw_error("error_bad_numeric", msg)
  }
  invisible(x)
}

#' Check Numeric Trend
#'
#' @param x A [`numeric`] object to be checked.
#' @param ... Extra parameters to be passed to internal methods.
#' @return
#'  Throws an error, if any, and returns `x` invisibly otherwise.
#' @author N. Frerebeau
#' @family checking methods
#' @export
assert_constant <- function(x, ...) {
  arg <- deparse(substitute(x))
  if (!is_constant(x, ...)) {
    msg <- sprintf(tr_("%s must be constant."), sQuote(arg))
    throw_error("error_bad_numeric", msg)
  }
  invisible(x)
}

#' @export
#' @rdname assert_constant
assert_decreasing <- function(x, ...) {
  arg <- deparse(substitute(x))
  if (!is_decreasing(x, ...)) {
    msg <- sprintf(tr_("%s must be decreasing."), sQuote(arg))
    throw_error("error_bad_numeric", msg)
  }
  invisible(x)
}

#' @export
#' @rdname assert_constant
assert_increasing <- function(x, ...) {
  arg <- deparse(substitute(x))
  if (!is_increasing(x, ...)) {
    msg <- sprintf(tr_("%s must be increasing."), sQuote(arg))
    throw_error("error_bad_numeric", msg)
  }
  invisible(x)
}

#' Check Numeric Relations
#'
#' @param x,y A [`numeric`] object to be checked.
#' @param ... Extra parameters to be passed to internal methods.
#' @return
#'  Throws an error, if any, and returns `x` invisibly otherwise.
#' @author N. Frerebeau
#' @family checking methods
#' @export
assert_lower <- function(x, y, ...) {
  arg_x <- deparse(substitute(x))
  arg_y <- deparse(substitute(y))
  if (!is_lower(x, y, ...)) {
    txt <- tr_("%s must be lower than %s.")
    msg <- sprintf(txt, sQuote(arg_x), sQuote(arg_y))
    throw_error("error_bad_numeric", msg)
  }
  invisible(x)
}

#' @export
#' @rdname assert_lower
assert_greater <- function(x, y, ...) {
  arg_x <- deparse(substitute(x))
  arg_y <- deparse(substitute(y))
  if (!is_greater(x, y, ...)) {
    txt <- tr_("%s must be greater than %s.")
    msg <- sprintf(txt, sQuote(arg_x), sQuote(arg_y))
    throw_error("error_bad_numeric", msg)
  }
  invisible(x)
}

# Matrix =======================================================================
#' Check Matrix
#'
#' @param x A [`matrix`] to be checked.
#' @return Throw an error, if any, and returns `x` invisibly otherwise.
#' @author N. Frerebeau
#' @family checking methods
#' @export
assert_square <- function(x) {
  arg <- deparse(substitute(x))
  if (!is_square(x)) {
    k <- paste0(dim(x), collapse = " x ")
    msg <- sprintf(tr_("%s must be a square matrix, not %s."), sQuote(arg), k)
    throw_error("error_bad_matrix", msg)
  }
  invisible(x)
}

#' @export
#' @rdname assert_square
assert_symmetric <- function(x) {
  arg <- deparse(substitute(x))
  if (!is_symmetric(x)) {
    msg <- sprintf(tr_("%s must be a symmetric matrix."), sQuote(arg))
    throw_error("error_bad_matrix", msg)
  }
  invisible(x)
}

# Check Graph
#
# @param x A [`matrix`] to be checked.
# @param expected An appropriate expected value.
# @return Throw an error, if any.
# @author N. Frerebeau
# @family checking methods
# @keywords internal
# @export
# assert_dag <- function(x) {
#   arg <- deparse(substitute(x))
#   if (!is_dag(x)) {
#     msg <- sprintf(tr_("%s must not contain cycles."), sQuote(arg))
#     throw_error("error_bad_graph", msg)
#   }
#   invisible(x)
# }
nfrerebeau/codex documentation built on Feb. 2, 2025, 8:10 p.m.