Search code examples
rdplyrrlangtidyevalnon-standard-evaluation

curly curly tidy evaluation programming with multiple inputs and custom function across columns


My question is similar to this question but I need to apply a more complex function across columns and I can't figure out how to apply Lionel's suggested solution to a custom function with a scoped verb like filter_at() or a filter()+across() equivalent. It doesn't look like a "superstache"/{{{}}} operator has been introduced.

Here is a non-programmed example of what I want to do (doesn't use NSE):

library(dplyr)
library(magrittr)

foo <- tibble(group = c(1,1,2,2,3,3),
              a = c(1,1,0,1,2,2),
              b = c(1,1,2,2,0,1))

foo %>%
  group_by(group) %>%
  filter_at(vars(a,b), any_vars(n_distinct(.) != 1)) %>%
  ungroup
#> # A tibble: 4 x 3
#>   group     a     b
#>   <dbl> <dbl> <dbl>
#> 1     2     0     2
#> 2     2     1     2
#> 3     3     2     0
#> 4     3     2     1

I haven't found an equivalent of this filter_at line with filter+across() yet, but since the new(ish) tidyeval functions predate dplyr 1.0 I assume that issue can be set aside. Here is my attempt to make a programmed version where the filtering variables are user-supplied with dots:

my_function <- function(data, ..., by) {
  dots <- enquos(..., .named = TRUE)
  
  helperfunc <- function(arg) {
    return(any_vars(n_distinct(arg) != length(arg)))
  }
  
  dots <- lapply(dots, function(dot) call("helperfunc", dot))
  
  data %>%
    group_by({{ by }}) %>%
    filter(!!!dots) %>%
    ungroup
}

foo %>%
  my_function(a, b, group)
#> Error: Problem with `filter()` input `..1`.
#> x Input `..1` is named.
#> i This usually means that you've used `=` instead of `==`.
#> i Did you mean `a == helperfunc(a)`?

I'd love if there were a way to just plug in an NSE operator inside the vars() argument in filter_at and not have to make all these extra calls (I assume this is what a {{{}}} function would do?)


Solution

  • Maybe I'm misunderstanding what the issue is, but the standard pattern of forwarding the dots seems to work fine here:

    my_function <- function(data, ..., by) {
      data %>%
        group_by({{ by }}) %>%
        filter_at(vars(...), any_vars(n_distinct(.) != 1)) %>%
        ungroup
    }
    
    foo %>%
      my_function( a, b, by=group )     # works