Unexpected behavior of dplyr::case_when


#1

This surprised me:

x = 1:2

dplyr::case_when(
  length(x)==1 ~ x,
  length(x)==2 ~ x[2]
)
#> [1] 2 2

It seems that case_when is evaluating each RHS and using that to determine the length of the result. Is there a way to avoid this? In my actual use case I am always expecting a character(1) so I subscript the result but it feels pretty hacky. It's easy to imagine situations where not every RHS is a valid expression in the current environment.

Any thoughts?
Kent


#2

How do you expect the RHS to be evaluated? (Genuine question, I'm not quite clear!)

Couple of GH issues that might be illuminating and/or you might want to jump in on:

(plus StackOverflow thread related to the above)


#3

I expected that the result of the case_when would be the same as the result of evaluating the relevant RHS. In my example, the second LHS is true so I expect the value to be x[2] which is 2. Instead, case_when seems to be evaluating every RHS, deciding that the result should have length 2, and extending the value to c(2, 2).

My actual use case was formatting a result which has varying number of values. Here is an example which is closer to what I was doing:

format_x = function(x) {
  dplyr::case_when(
    length(x)==1 ~ as.character(x),
    length(x)==2 ~ paste(x[1], 'and', x[2])
  )
}

format_x(2)
#> [1] "2"
format_x(1:2)
#> [1] "1 and 2" "1 and 2"

The repeated result for format_x(1:2) is confusing and not what I intended.

I don't see the relevance of the linked issues and SO, they all seem to have to do with NSE which I am not using here.


#4

Oh, I thought you wanted to change the evaluation of the RHS.


#5

case when is vectorized. The output of case_when() will always have the same length as x. You are checking for each element of x whether its length equals 1. This is nonsensical.

For your application you likely do not want to use case_when() but a normal if (...) else construct


#6

Ah, <light goes on>. Thank you @hoelk.


#7

For your case there is an even simpler (but not very flexible) solution btw

paste(x, collapse = " and ")

:slight_smile:


#8

My real use case is a little more complex. I rewrote it using switch(length(x)+1, ...) and it works as I wanted :slight_smile:. (Using length(x)+1 because the length can be 0.)