Home > Blockchain >  How to make ifelse with three branches or more?
How to make ifelse with three branches or more?

Time:04-20

I am wondering whether there is a variant of ifelse() that can allow three branches instead of two branches.

For example, without the there branch version, I have to do something like this.

R> x=letters[1:3]
R> ifelse(x=="a", 1, ifelse(x=="b", 2, 3))
[1] 1 2 3

If there is a three branch version, then I may write something like ifelseifelse(x=="a", x=="b", 1, 2, 3). What is the best way to handle three-branch more ifelse?

CodePudding user response:

Compare some methods regarding time:

x <- sample(letters[1:3], 1e5, TRUE)

microbenchmark::microbenchmark(
"case_when" = dplyr::case_when(
         x == "a" ~ 1,
         x == "b" ~ 2,
         TRUE     ~ 3,
       )

, "namesSubset" = c(a=1, b=2, c=3)[x] #But no default (maybe NA as def)

, "match" = c(1,2,3)[match(x, c("a", "b"), 3L)]

, "fmatch" = c(1,2,3)[fastmatch::fmatch(x, c("a", "b"), 3L)]

, "fcase" = data.table::fcase(x == 'a', 1
      , x == 'b', 2
      , default = 3
      )

, "ifelse" = ifelse(x=="a", 1, ifelse(x=="b", 2, 3))

, "switch" = sapply(x, \(y) switch(y, "a" = 1, "b" = 2, 3))

, "envir" = unlist(mget(x, list2env(list(a = 1, b = 2)), ifnotfound=3), FALSE, FALSE)

, "hashtab " = local({h1 <- hashtab() #since R 4.2.0
  sethash(h1, "a", 1)
  sethash(h1, "b", 2)
  sapply(x, gethash, h=h1, nomatch = 3, USE.NAMES = FALSE)})

, "chmatch" = data.table::chmatch(x, c("a", "b"), 3L))

Result:

Unit: microseconds
        expr       min         lq        mean      median          uq        max neval
   case_when  5933.597   6306.912   6940.0865   6503.2410   6752.8335  20929.469   100
 namesSubset   967.524   1032.356   2202.6872   1057.2725   1243.1865 101670.424   100
       match  1221.310   1285.947   1378.3095   1328.2960   1379.8575   1956.359   100
      fmatch   763.101    806.007    868.1639    818.5500    872.9625   1608.286   100
       fcase  1380.228   1446.713   1567.9658   1469.8270   1598.8585   6644.801   100
      ifelse  3257.279   3556.419   4798.8369   3813.0255   4141.7075  33525.300   100
      switch 48908.629  67749.721  85646.5315  83486.5935  95161.8280 208888.521   100
       envir  7520.874   7727.216   7907.5232   7921.0045   8010.3320   8562.948   100
    hashtab  86159.386 103942.424 114552.4228 111634.4995 121062.4745 195154.515   100
     chmatch   659.948    703.449    764.3200    714.8205    766.4475   1752.777   100

CodePudding user response:

leveraging data.table's fcase's clean syntax and speed

library(data.table)

# dummy data
x <- letters[1:3]

# fast case
fcase(x == 'a', 1
      , x == 'b', 2
      , default = 3
      )

CodePudding user response:

For a base approach, you could use switch(). One annoyance with switch() is that it doesn't take vector inputs for the first argument, but you can kick around this easily with sapply():

sapply(letters[1:3], function(y) switch(y, "a" = 1, "b" = 2, "c" = 3, NA))

## a b c 
## 1 2 3 

The unnamed final parameter is the catch-all for when no match is found:

sapply(letters[1:4], function(y) switch(y, "a" = 1, "b" = 2, "c" = 3, NA))

## a  b  c  d 
## 1  2  3 NA

EDIT: I should acknowledge, as @GKi has helpfully pointed out, that this approach has remarkably terrible runtimes.

  •  Tags:  
  • r
  • Related