This is a quirky thing about the implementation of ifelse
.
If we look at the function, we can see the part that is responsible for the actual output:
> ifelse
function (test, yes, no)
{
if (is.atomic(test)) {
if (typeof(test) != "logical")
storage.mode(test) <- "logical"
if (length(test) == 1 && is.null(attributes(test))) {
if (is.na(test))
return(NA)
else if (test) {
if (length(yes) == 1 && is.null(attributes(yes)))
return(yes)
}
else if (length(no) == 1 && is.null(attributes(no)))
return(no)
}
}
else test <- if (isS4(test))
methods::as(test, "logical")
else as.logical(test)
ans <- test
ok <- !(nas <- is.na(test))
if (any(test[ok]))
ans[test & ok] <- rep(yes, length.out = length(ans))[test &
ok]
if (any(!test[ok]))
ans[!test & ok] <- rep(no, length.out = length(ans))[!test &
ok]
ans[nas] <- NA
ans
}
This is the relevant part:
ans <- test
ok <- !(nas <- is.na(test))
if (any(test[ok]))
ans[test & ok] <- rep(yes, length.out = length(ans))[test &
ok]
if (any(!test[ok]))
ans[!test & ok] <- rep(no, length.out = length(ans))[!test &
ok]
ans[nas] <- NA
ans
The boolean result from test
is stored in ans
. Then there are some checks about whether there are na
results, which is irrelevant here. Then the result vector is created based on the booleans. But look at the way that is done.
For the TRUE
results:
ans[test & ok] <- rep(yes, length.out = length(ans))[test & ok]
yes
is evaluated and repeated so that it matches the output length, and then subsetted to take the items that were TRUE
in the test.
The point where the warning is generated is right here. ifelse
does evaluate log(-5 + 1)
, generating the warning, but then excludes it from the result because test = FALSE
.
Note that if all entries are FALSE
, the if statement if (any(test[ok]))
prevents the execution of that part, so there is no evaluation of the yes
argument and no warnings.