This is likely a bug in case_when or its internal helper functions.
We can put a browser in the source code of case_when to see what happens in both cases. Some internal functions must be called via :::.
f <- function (...) {
browser()
fs <- dplyr:::compact_null(rlang::list2(...))
n <- length(fs)
error_call <- rlang::current_env()
if (n == 0) {
abort("No cases provided.", call = error_call)
}
query <- vector("list", n)
value <- vector("list", n)
default_env <- rlang::caller_env()
quos_pairs <- purrr::map2(fs, seq_along(fs), dplyr:::validate_formula, default_env = default_env,
dots_env = rlang::current_env(), error_call = error_call)
for (i in seq_len(n)) {
pair <- quos_pairs[[i]]
query[[i]] <- rlang::eval_tidy(pair$lhs, env = default_env)
value[[i]] <- rlang::eval_tidy(pair$rhs, env = default_env)
if (!is.logical(query[[i]])) {
dplyr:::abort_case_when_logical(pair$lhs, i, query[[i]],
error_call = error_call)
}
}
m <- dplyr:::validate_case_when_length(query, value, fs, error_call = error_call)
out <- value[[1]][rep(NA_integer_, m)]
replaced <- rep(FALSE, m)
for (i in seq_len(n)) {
out <- dplyr:::replace_with(out, query[[i]] & !replaced, value[[i]],
NULL, error_call = error_call)
replaced <- replaced | (query[[i]] & !is.na(query[[i]]))
}
out
}
and the helper internal replace_with in dplyr,
replacer <- function (x, i, val, name, reason = NULL, error_call = rlang::caller_env()) {
if (is.null(val)) {
return(x)
}
dplyr:::check_length(val, x, name, reason, error_call = error_call)
dplyr:::check_type(val, x, name, error_call = error_call)
dplyr:::check_class(val, x, name, error_call = error_call)
i[is.na(i)] <- FALSE
if (length(val) == 1L) {
x[i] <- val
}
else {
x[i] <- val[i]
}
x
}
and then debug via
x <- character(0)
f(rlang::is_empty(x) ~ "Empty", !rlang::is_empty(x) ~ "x")
f(rlang::is_empty(x) ~ "Empty", !rlang::is_empty(x) ~ x)
the key is in value m, that in the working case results in 1L, in the faulty case it is 0L. out becomes character(0) instead of initializing to NA, length 1.
replaced should be a logical vector indicating whether a value has been replaced. In the faulty case rep(FALSE, 0L) is logical(0), which is queried later on via !replaced. FALSE & logical(0) gives logical(0).
When passed to replacer this gives a peculiar subsetting action character(0)[logical(0)], that gives character(0).