Migwell
Migwell

Reputation: 20127

Using `dplyr::case_when` to choose a function in R

I want to use dplyr's case_when function to let me dynamically choose between functions.

For example, let's say I want to choose an average function and then call it:

average = function(.data, .avg){
    f = case_when(
        .avg == 'mean' ~ mean,
        .avg == 'median' ~ median,
    )
    f(.data)
}

Now, if I call this function, I get this error:

> average(c(1, 2), 'mean')
Error in value[[1]][rep(NA_integer_, m)] : 
  object of type 'closure' is not subsettable

I don't think I'm doing anything wrong here, but case_when doesn't seem to want me to return a function. I could put the result of each function (e.g. mean(.data)) on the RHS of this case_when, but it's important that I don't evaluate any function that isn't intended. Is there a way around this, or a similarly concise way to do a switch case in R, to choose a function?

Upvotes: 2

Views: 528

Answers (2)

PKumar
PKumar

Reputation: 11128

You can try this, creating a list of functions and then use the data argument while returning the function call:

average = function(.data, .avg){
 list_of_funcs <- list('mean' = mean, 'median' = median)
 return(list_of_funcs[[.avg]](.data))
}
class(average)

> average(c(1,2,3,4, 10), 'mean')
# [1] 4
> average(c(1,2,3,4, 10), 'median')
# [1] 3
  

Upvotes: 0

Ronak Shah
Ronak Shah

Reputation: 388992

You are defining two arguments for the function average but are passing only one. I think you were trying to call average(c(1, 2), 'mean') but this will still not fix the issue. I think case_when cannot return functions.

Use a simple if/else statement.

average = function(.data, .avg){
  f <- if(.avg == 'mean') mean
        else if(.avg == 'median') median
  f(.data)
}

Or use base R scalar alternative of case_when i.e switch :

average = function(.data, .avg){
 f <- switch(.avg, mean = mean, median = median)
 f(.data)
}

For this simple case you can also use match.fun :

average = function(.data, .avg){
  match.fun(.avg)(.data)
}

Use any one of the above function and call it with :

average(c(1, 5, 6), 'mean')
#[1] 4
average(c(1, 5, 6), 'median')
#[1] 5

Upvotes: 4

Related Questions