R/utils.R

Defines functions neg_indices_to_pos `%!||%` exprs df_add_cols combine_factors combine_levels as_list_of is_simple_atomic r_cut_breaks n_dots funique.POSIXlt funique.vctrs_rcrd as.character.vctrs_rcrd fill_posixlt posixlt_is_balanced list_is_df_like cpp_list_rm_null cheapr_recycle cheapr_rep_len tzone which_not_in which_in check_is_df check_length allv2 all_integerable is_integerable deparse2

#' @noRd

# Like deparse1 but has a cutoff in case of massive strings

deparse2 <- function(expr, collapse = " ", width.cutoff = 500L, nlines = 10L, ...){
  paste(deparse(expr, width.cutoff, nlines = nlines, ...), collapse = collapse)
}

is_integerable <- function(x){
  abs(x) <= .Machine$integer.max
}
all_integerable <- function(x, shift = 0){
  all(
    (abs(collapse::frange(x, na.rm = TRUE)) + shift ) <= .Machine$integer.max,
    na.rm = TRUE
  )
}

allv2 <- function(x, value){
  if (!length(x)) {
    return(FALSE)
  }
  collapse::allv(x, value)
}

list_as_df <- cpp_list_as_df

check_length <- function(x, n){
  if (length(x) != n){
    stop(paste(deparse2(substitute(x)), "must have length", n))
  }
}
check_is_df <- function(x){
  if (!inherits(x, "data.frame")){
    stop(paste(deparse2(substitute(x)), "must be a data frame."))
  }
}

which_in <- function(x, table){
  which_not_na(collapse::fmatch(x, table, overid = 2L, nomatch = NA_integer_))
}
which_not_in <- function(x, table){
  which_na(collapse::fmatch(x, table, overid = 2L, nomatch = NA_integer_))
}
tzone <- function(x){
  out <- attr(x, "tzone")
  if (is.null(out)) {
    ""
  }
  else {
    out[[1]]
  }
}

cheapr_rep_len <- function(x, length.out){
  if (inherits(x, "data.frame")){
    sset(x, rep_len(attr(x, "row.names"), length.out))
  } else {
    rep(x, length.out = length.out)
  }
}

cheapr_recycle <- function(x, length){
  if (length == vector_length(x)){
    x
  } else {
    cheapr_rep_len(x, length)
  }
}

set_attr <- cpp_set_add_attr
set_attrs <- cpp_set_add_attributes
set_rm_attr <- cpp_set_rm_attr
set_rm_attrs <- cpp_set_rm_attributes

cpp_list_rm_null <- function(x, always_shallow_copy = TRUE){
  cpp_drop_null(x, always_shallow_copy)
}
list_is_df_like <- function(x){
  collapse::fnunique(lengths_(x)) <= 1
}
posixlt_is_balanced <- function(x){
  isTRUE(attr(x, "balanced")) && list_is_df_like(x)
}
fill_posixlt <- function(x, classed = TRUE){
  if (!inherits(x, "POSIXlt")){
    stop("x must be a POSIXlt")
  }
  out <- unclass(x)
  out <- do.call(recycle, out)
  attributes(out) <- attributes(x)
  if (!posixlt_is_balanced(x)){
    attr(out, "balanced") <- NA
  }
  if (!classed){
    class(out) <- NULL
  }
  out
}

# balance_posixlt <- function(x, fill.only = FALSE, classed = TRUE){
#   balance_pos <- tryCatch(get("balancePOSIXlt",
#                               asNamespace("base"),
#                               inherits = FALSE),
#                           error = function(e) return(".r.error"))
#   if (is.character(balance_pos) && length(balance_pos) == 1 && balance_pos == ".r.error"){
#     fill_posixlt(x, classed = classed)
#   } else {
#     balance_pos(x, fill.only = fill.only, classed = classed)
#   }
# }

#' @exportS3Method base::as.character
as.character.vctrs_rcrd <- function(x, ...){
  format(x, ...)
}
#' @exportS3Method collapse::funique
funique.vctrs_rcrd <- function(x, sort = FALSE, ...){
  out <- unique(x, ...)
  if (sort){
    out <- sort(out)
  }
  out
}
#' @exportS3Method collapse::funique
funique.POSIXlt <- function(x, sort = FALSE, ...){
  out <- fill_posixlt(x, classed = FALSE)
  # out <- balance_posixlt(x, fill.only = TRUE, classed = FALSE)
  out_attrs <- attributes(out)
  out <- list_as_df(out)
  out <- collapse::funique(out, sort = FALSE)
  if (sort){
    o <- collapse::radixorderv(
      sset(out, j = c("year", "yday", "hour", "min", "sec")),
      ...
    )
    out <- sset(out, o)
  }
  attributes(out) <- out_attrs
  class(out) <- class(x)
  out
}


n_dots <- function(...){
  nargs()
}

# Keep this in-case anyone was using it
fill_with_na <- na_insert

# The breaks the `cut(x, n)` produces
r_cut_breaks <- function(x, n){
  check_length(n, 1)
  if (is.na(n) || n < 2){
    stop(paste("number of breaks must be >= 2, not", n))
  }
  breaks <- get_breaks(x, n, pretty = FALSE, expand_min = FALSE, expand_max = FALSE)
  nb <- length(breaks)
  adj <- (breaks[nb] - breaks[1]) * 0.001
  breaks[1] <- breaks[1] - adj
  breaks[nb] <- breaks[nb] + adj
  breaks
}

# Is x a simple atomic type?
# logical, integer, double, character, raw, complex
# including Dates, factors and POSIXcts

# Simple here means it's
# a) atomic
# b) attributes aren't data-dependent and so can be all copied

is_simple_atomic <- function(x){
  is.atomic(x) && (
    !is.object(x) || inherits(x, c("Date", "POSIXct", "factor", "integer64"))
  )
}

# If args is a plain list of items then extract the first element of
# the top list

as_list_of <- function(...){
  dots <- list(...)
  if (length(dots) == 1 && !is.object(dots[[1L]]) && is.list(dots[[1L]])){
    dots[[1L]]
  } else {
    dots
  }
}

# Combine levels of factors
# Converts non factors into character vectors
combine_levels <- function(...){
  dots <- as_list_of(...)
  get_levels <- function(x){
    if (is.factor(x)) attr(x, "levels", TRUE) else as.character(x)
  }
  # Unique combined levels
  levels <- as.character(unlist(lapply(dots, get_levels), recursive = FALSE))
  collapse::funique(levels)
}

combine_factors <- function(...){
  factors <- as_list_of(...)
  if (length(factors) == 1){
    return(factors[[1L]])
  }
  to_char <- function(x){
    if (is.factor(x)) factor_as_character(x) else as.character(x)
  }
  combined_levels <- do.call(combine_levels, factors)
  characters <- lapply(factors, to_char)

  # Combine all factor elements (as character vectors)
  factor_(
    unlist(characters, recursive = FALSE), levels = combined_levels,
    na_exclude = !any_na(combined_levels)
  )
}


df_add_cols <- function(data, cols){
  if (!(is.list(cols) && !is.null(names(cols)))){
    stop("cols must be a named list")
  }
  N <- length(attr(data, "row.names"))
  out <- unclass(data)
  temp <- unclass(cols)
  for (col in names(temp)){
    out[[col]] <- if (is.null(temp[[col]])) NULL else cheapr_recycle(temp[[col]], length = N)
  }
  class(out) <- class(data)
  out
}

# unevaluated expression list
exprs <- function(...){
  as.list(substitute(alist(...)))[-1]
}

# Sort of the inverse of %||%
`%!||%` <- function(x, y) if (x) NULL else y

# Just a wrapper with a cheaper alternative to `c.factor()`
# cheapr_c <- function(..., .check = TRUE){
#   dots <- list(...)
#   if (.check){
#     for (vec in dots){
#       if (is.factor(vec)){
#         return(combine_factors(dots))
#       }
#       if (is.object(vec)){
#         return(do.call(c, dots))
#       }
#     }
#   }
#   `attributes<-`(collapse::vec(dots), NULL)
# }

# Turn negative indices to positives
neg_indices_to_pos <- function(exclude, n){
  if (n == 0){
    integer()
  } else {
    which_not_in(
      seq.int(from = -1L, to = -as.integer(n), by = -1L),
      as.integer(exclude)
    )
  }
}

Try the cheapr package in your browser

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

cheapr documentation built on April 4, 2025, 4:25 a.m.