Search code examples
rfunctiontidyeval

pass input parameter of function 1 to function 2 without explicitly passing it as an input to function 2


I'm chaining two functions together where both use the same column to perform some operation. I'd like to be able to specify this column as the input parameter only on the call to the first function but also have it accessible within the second function when I pass the output of function 1 into function 2.

The code below demonstrates what I mean and where i'm getting different results from what I want.

filter_function <- function(.x, .y){
  .x %>%
    dplyr::filter({{ .y }} != 4)
}

count_function <- function(.x, .y){
  .x %>%
    dplyr::count({{ .y }})
}

filter_function(mtcars %>% as_tibble(), cyl) %>%
  count_function(.)

This returns a single value of 21 which is the count of all rows remaining in the tibble after filtering on cyl != 4, as per the filter_function. But what I want it to output is the same as:

mtcars %>% 
  as_tibble() %>%
  filter(cyl != 4) %>%
  count(cyl)

# A tibble: 2 × 2
    cyl     n
  <dbl> <int>
1     6     7
2     8    14

So is there is a way I can access .y in count_function (within the context of chaining two functions together like in this example), without explicitly passing it to count_function as a parameter?


Solution

  • 1) If we reverse the order of the functions then the column of interest will always be first so we won't need to know which one it is:

    library(dplyr)
    
    mtcars %>%
      count_function(cyl) %>%
      filter_function(.[[1]])
    ##   cyl  n
    ## 1   6  7
    ## 2   8 14
    

    2) The same idea holds more generally even if we keep the order of the functions:

    mtcars %>%
      relocate(cyl) %>%
      filter_function(.[1]) %>%
      count_function(.[1])
    

    3) If the reason to do this is just that cyl is only referred to once to make it easier to change later we could put it in a variable.

    v <- "cyl"
    mtcars %>%
      filter_function(!!sym(v)) %>%
      count_function(!!sym(v))
    

    or we could perform the assignment within the pipeline in which case it will not be visible outside the pipeline.

    mtcars %>% { 
      v <- "cyl"
      filter_function(., !!sym(v)) %>%
      count_function(!!sym(v))
    }
      
    

    4) It would also be possible to pass information via attributes

    mtcars %>%
      structure(var = "cyl") %>%
      filter_function(!!sym(attr(., "var"))) %>%
      count_function(!!sym(attr(., "var")))
    

    5) A dummy column could be used.

    mtcars %>%
      mutate(dummy = "cyl") %>%
      filter_function(!!sym(first(.$dummy))) %>%
      count_function(!!sym(first(.$dummy)))