Strange behavior of ifelse() in R: when are the values evaluated?

Viewed 127

I have the following data:

df = data.frame(
  stat = c('mean', 'var'),
  value = c(-9, 10))

Let say I want to take the square of 'value' if 'stat' is "var", and do nothing if not.

df %>% 
  mutate(
   value = ifelse(stat=='var', sqrt(value), value))

I get a warning:

Warning message:
In sqrt(-9) : NaNs produced

Why am I getting the warning? The value sqrt(-9) is not supposed to be computed as 'value' is "mean"

4 Answers

As per the ifelse documentation

 If ‘yes’ or ‘no’ are too short, their elements are recycled.
 ‘yes’ will be evaluated if and only if any element of ‘test’ is
 true, and analogously for ‘no’.

You have interpreted this to mean that yes or no will be bypassed in an elementwise manner.

However, it seems that this is not what the doc means. It means that, unless all elements in the test are either true or false, then evaluations will occur.

Meaning that, as long as your test has at least one true, and at least one false, then both yes and no will be precomputed for each element in the test, and only after that element has been evaluated, the appropriate response is selected.

The answer to your question "when are the values evaluated?" is: it depends.

If all of the elements are TRUE, then the third argument won't be evaluated at all. If all of the arguments are FALSE, then the second argument won't be evaluated.

However, if there is at least one TRUE and one FALSE, then they are both fully evaluated.

ifelse(c(TRUE, TRUE), "good", stop("error"))
#> [1] "good" "good"

ifelse(c(FALSE, FALSE), "good", stop("error"))
#> Error in ifelse(c(FALSE, FALSE), "good", stop("error")) : error

ifelse(c(TRUE, FALSE), "good", stop("error"))
#> Error in ifelse(c(TRUE, FALSE), "good", stop("error")) : error

you don't need a package to do this:

df = data.frame(stat = c('mean', 'var'),
                value = c(-9, 10))
computed = df[df$stat == 'var','value'] %>% sqrt

if you want the original value to be overwritten then instead of the second row:

df[df$stat == 'var','value'] = df[df$stat == 'var','value'] %>% sqrt

if you want to use native ifelse then:

ifelse(df$stat =="var",sqrt(df$value),df$value)

I have been referred to that question in a comment:

Using ifelse in R when one of the options produces NAs?

To cite the author: "What happens is that we do both the calculations on the entire vector and replace the values of 'p' based on the test condition. For sqrt, the negative values definitely gives warning and output as NaN. While the NaN elements don't show up in the output, the warning was already printed. The warning is a friendly one, but can be suppressed with suppressWarnings"

Related