Home > Mobile >  Use dplyr's if_else function with functional true/false values
Use dplyr's if_else function with functional true/false values

Time:12-05

Question

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?

Reprex

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)

CodePudding user response:

I don't think if_else is feasible or preferable over base ifelse in this case. dplyr::if_else uses subsetting in its source (use if_else without brackets). To recreate the error, try running min[].

We can try to "repair" the function by altering the code that intends to coerce the output to be of the same class as the true and false values by subsetting it:

out <- true[rep(NA_integer_, length(condition))]
# to pseudocode
out <- "whatever the typeof(true) value is"

and use ::: to get the appropriate internal functions that dplyr uses.

if_else <- function (condition, true, false, missing = NULL) {
  if (!is.logical(condition)) {
    bad_args("condition", "must be a logical vector, not {friendly_type_of(condition)}.")
  }
  out <- NA_integer_
  out <- dplyr:::replace_with(out, condition, true, dplyr:::fmt_args(~true),
                      glue::glue("length of {fmt_args(~condition)}"))
  out <- dplyr:::replace_with(out, !condition, false, dplyr:::fmt_args(~false),
                      glue::glue("length of {fmt_args(~condition)}"))
  out <- dplyr:::replace_with(out, is.na(condition), missing, dplyr:::fmt_args(~missing),
                      glue::glue("length of {fmt_args(~condition)}"))
  out
}
x = 5
if_else(x == 2, min, max)

Error: `true` must be an integer vector, not a primitive function.

This error is the result of a type check in internal function check_type within replace_with, that looks like this:

if (identical(typeof(x), typeof(template))) {return()}

Here template is out, defined in if_else.

  • typeof Na_Integer_ is "integer"
  • typeof min is "builtin"

A solution to outputting a function min or max from dplyr::if_else would then be how to coerce out to be of type "builtin", and to redefinedplyr internal functions, for example replace_with that uses another another subsetting operation that fails for the same reason as min[].

That is, changing out to a builtin like min still throws an error:

if_else <- function(...){
  ...
  out <- # if out <- min
  ...
}
Error in x[i] <- val : object of type 'builtin' is not subsettable

So, this is intended behavior, but perhaps object of type 'builtin' is not subsettable is not the intended error message to be displayed.

CodePudding user response:

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.

  • Related