default arguments not being recognized in custom function using dplyr

Viewed 116

Take this function foo(). I want it to have a default argument of cyl because that's the name of the field it will usually process.

library(tidyverse)

foo <- function(x = cyl){
    case_when(
        x == 6 ~ TRUE,
        x == 8 ~ FALSE,
        x == 4 ~ NA
    )
}

# works: 
mtcars %>% 
    mutate(cyl_refactor = foo(cyl)) %>% 
    select(cyl, cyl_refactor)

But I am surprised that the function will not work unless I explicitly supply the default argument. See failing code below

# fails:
mtcars %>% 
    mutate(cyl_refactor = foo()) %>% 
    select(cyl, cyl_refactor)

Error: Problem with `mutate()` column `cyl_refactor`. ℹ `cyl_refactor = foo()`. x object 'cyl' not found

It seems that default arguments are only processed when there is also a data parameter as below.

foo2 <- function(data, x = cyl){
    data %>% 
        mutate(cyl_refactor = case_when(
        {{x}} == 6 ~ TRUE,
        {{x}} == 8 ~ FALSE,
        {{x}} == 4 ~ NA
    ))
}

mtcars %>% 
    foo2() %>% 
    select(cyl, cyl_refactor)

I am sure there is some gap in my knowledge of quasiquotation, but I would like to understand how to use a default argument in foo().

2 Answers

Here's one that will "work" though I woudn't recommend it

foo <- function(x = cyl){
  x <- enquo(x)
  eval.parent(rlang::quo_squash(rlang::quo(case_when(
    !!x == 6 ~ TRUE,
    !!x == 8 ~ FALSE,
    !!x == 4 ~ NA
  ))))
}

# Both run without error
mtcars %>% 
  mutate(cyl_refactor = foo(cyl)) %>% 
  select(cyl, cyl_refactor)

mtcars %>% 
  mutate(cyl_refactor = foo()) %>% 
  select(cyl, cyl_refactor)

The problem is that in order for case_when to work, you can't just pass in a column name without also passing in the data. In order to "find" the data in this case, I've used eval.parent() to go up the call chain to try to find the cyl variable.

It's better to make proper functions where you pass in the input data directly (rather than variable names they need to look up themselves).

We could do this with missing and cur_data_all

foo <- function(x = cyl){
   if(missing(x)) x <- cur_data_all()[["cyl"]]
   
    case_when(
        x == 6 ~ TRUE,
        x == 8 ~ FALSE,
        x == 4 ~ NA
    )
}

-testing

> out1 <- mtcars %>% 
+     mutate(cyl_refactor = foo(cyl)) %>% 
+     select(cyl, cyl_refactor)
> out2 <- mtcars %>% 
+     mutate(cyl_refactor = foo()) %>% 
+     select(cyl, cyl_refactor)
> 
> identical(out1, out2)
[1] TRUE
Related