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.