How can dplyr's if_else() function be used such that its output is a function? With ifelse() from base this is trivial but with if_else() something goes wrong. Is that intended behavior or am I just not able to do that properly?
x <- 2
ifelse(x == 2, min, max)
#> function (..., na.rm = FALSE) .Primitive("min")
dplyr::if_else(x == 2, min, max)
#> Error in true[rep(NA_integer_, length(condition))]: object of type 'builtin' is not subsettable
Created on 2021-12-04 by the reprex package (v2.0.0)
You shouldn't use ifelse for this. ifelse and if_else are for vectors--specifically for when your input is a vector and your output is a vector of the same length. You can't make vectors of functions, so ifelse is a poor choice for returning a function. Your code will only work in the special case when the input has length 1---which is what the control/flow function if() is for.
Your ifelse code will fail with a strange error if x has length > 1, try setting x = 2:3 and running your code.
> x = 2:3
> ifelse(x == 2, min, max)
Error in rep(yes, length.out = len) :
attempt to replicate an object of type 'builtin'
if(), on the other hand, will still check the first element and return the correct result for the first element while giving you a warning about the input length:
> if(x == 2) min else max
function (..., na.rm = FALSE) .Primitive("min")
Warning message:
In if (x == 2) min else max :
the condition has length > 1 and only the first element will be used
When you're checking a condition that should always have length 1 you should use if(){}else{}, and the result can be literally any expression, it can be assigned, or it can be arbitrary code that is run. Save ifelse (and if_else) for vectors as intended.
If x is always a scalar then using if as described in @Gregor Thomas answer is the correct approach.
Another option would be to use switch, which makes sense, if you have more than two options and want to minimize the control flow:
x <- 2
min_max <- switch(as.character(x), `2` = min, max)
If, however, you are actually dealing with a vector for example in a {dplyr} pipeline, then using character vectors instead of functions in combination with do.call is one approach.
Here is a short example where we want to use a different function (min or max) depending on the am group in mtcars:
library(dplyr)
mtcars %>%
nest_by(am) %>%
mutate(min_max = if_else(am == 1, "min", "max"),
mpg = summarise(data, x = do.call(min_max, list(mpg)))$x)
#> # A tibble: 2 x 4
#> # Rowwise: am
#> am data min_max mpg
#> <dbl> <list<tibble[,10]>> <chr> <dbl>
#> 1 0 [19 × 10] max 24.4
#> 2 1 [13 × 10] min 15
Another approach is to wrap each min, max in list(). In this case, we do not need do.call:
library(dplyr)
mtcars %>%
nest_by(am) %>%
mutate(min_max = if_else(am == 1, list(min), list(max)),
mpg = summarise(data, x = min_max(mpg))$x)
#> # A tibble: 2 x 4
#> # Rowwise: am
#> am data min_max mpg
#> <dbl> <list<tibble[,10]>> <list> <dbl>
#> 1 0 [19 × 10] <fn> 24.4
#> 2 1 [13 × 10] <fn> 15
Created on 2021-12-04 by the reprex package (v0.3.0)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With