0

Mathematica's Which function is a generalized If:

Which[test_1, value_1, test_2, value_2, …]

evaluates each of the test_i in turn, returning the value of the value_i corresponding to the first one that yields True.

It's nothing more than a handy way to get rid of superfluous syntax from long sequences of nested simple if-else tests.

Does R have an equivalent function?


BTW, I know that I can always do something like

if (test_1) value_1 else if (test_2) value_2 else ... value_n else default

or, equivalently,

if (test_1) value_1 else
if (test_2) value_2 else
            ...
if (test_n) value_n else
            default

...but, as I already alluded to, when compared to Which, nested if-else statements bring in a lot of superfluous syntax.

Also, I'm aware of

ifelse(t_1, v_1, ifelse(t_2, v_2, ..., ifelse(t_n, v_n, default)...))

...but the results are sensitive to the shape of the tests, so it is not strictly equivalent to nested if-else statements.

Lastly, R's switch statement is similar to what I'm looking for, in that it encapsulates a dispatch over a sequence of tests, but it's not quite the same thing. In

switch(expr,
       case_1 = value_1,
       case_2 = value_2,
       ...
       case_n = value_n,
       default)

...the tests are all equality comparisons of expr against the case_i, whereas in Which, etc., the tests are arbitrary boolean expressions.

Community
  • 1
  • 1
kjo
  • 33,683
  • 52
  • 148
  • 265
  • @JohnColeman: because, AFAICT, the `case_i` must be labels; they can't be arbitrary expressions... (I could be wrong about this) – kjo Sep 29 '16 at 14:41
  • @kjo You are correct. – John Coleman Sep 29 '16 at 14:42
  • @ZheyuanLi: despite their names, R's `which` function and Mathematica's `Which` do very different things. – kjo Sep 29 '16 at 14:44
  • @ZheyuanLi: I regret having asked the question. If I had suspected that this simple question was going to cause so much confusion and opposition (i.e. downvotes), I would have never have asked. I would gladly delete it, but it already got a few answers. – kjo Sep 29 '16 at 15:05

2 Answers2

3

According to the Mathematica help,

Which[test1,value1,test2,value2,…] evaluates each of the testi in turn, returning the value of the valuei corresponding to the first one that yields True.

We can do this in R, i.e. get the value corresponding to the position of the 1st expression that evaluates to true, like this:

1. Simple version that evaluates all expressions:

values = c("value1", "value2", "value3", "value4", "value5", "value6", "value7")
expressions = c(1==2, 1==3, 1==1, 1==4, T==F, F==T, T==T)
values[which.max(expressions)]
# [1] "value3"

Although, if none of the expressions is true, which.max will return the 1st false, so we should also check for this

if (any(expressions))  values[which.max(expressions)] else NA

2. Version that 'short circuits'

However, there is one difference in the behaviour of the above from Mathematica: which in mathematica short circuits - i.e. it only evaluates as many expressions as it needs to to find the first TRUE. If the expressions are computationally expensive or a speed bottleneck, then we may also like to replicate this behaviour in R. We can do this using Position which does short circuit, in combination with eval(parse) to ensure that we do not evaluate expressions until we are ready to test for them

values = c("value1", "value2", "value3", "value4", "value5", "value6", "value7")
expressions = c("1==2", "1==3", "1==1", "1==4", "T==F", "F==T", "T==F")

values[Position(function(text) eval(parse(text=text)), expressions, T)]
dww
  • 30,425
  • 5
  • 68
  • 111
1

You can write your own function which can be used as such a control structure. The followed is based on the fact that match.call supports lazy evaluation. (See this accepted answer):

which.val <- function(...){
  clauses <- match.call(expand.dots = FALSE)$`...`
  n <- length(clauses)
  for(i in seq(1,n,2)){
    condition = eval(clauses[[i]], envir = parent.frame())
    if(condition) return(eval(clauses[[i+1]], envir = parent.frame()))
  }
}

For testing purposes:

test <- function(a,b){
  print(b)
  a == b
}

The side effect can be used to see what is actually evaluated.

For example:

> x <- 3
> which.val(test(x,1),10,test(x,2),20,test(x,3),30,test(x,4),40)
[1] 1
[1] 2
[1] 3
[1] 30

Note how test(x,4) is never evaluated.

Community
  • 1
  • 1
John Coleman
  • 51,337
  • 7
  • 54
  • 119
  • Exactly. Thank you. I was not aware of R's support for lazy evaluation in user-defined functions. That's great. – kjo Sep 29 '16 at 15:36
  • Question: don't you want `return(eval(clauses[[i+1]]))`? – kjo Sep 29 '16 at 15:42
  • You are right. I tested it with constant values and it worked correctly, but you wouldn't want to return an unevaluated expression. I switched my example so that the return values look like `x+10` rather than just `10`, etc. and my code didn't return the intended value. I will edit. Thanks. – John Coleman Sep 29 '16 at 15:49
  • Sorry, one more thing needs to be fixed: the evals need `envir = parent.frame()`; otherwise I'd get errors if I called the function from within another function. (The docs for `eval` say that `envir` defaults to `parent.frame()`, so I don't entirely understand why I need to specify it explicitly...) – kjo Sep 29 '16 at 16:34
  • @kjo You are right again. This is the first time I've played around with `match.call`. I've used `...` before but always used `list(...)` in the function body (which evaluates the whole list). It has been a fun learning experience. Thanks for your input. – John Coleman Sep 29 '16 at 17:59