8

I'm new to tidy eval and trying to write generic functions- one thing I'm struggling with right now is writing multiple filter conditions for categorical variables. This is what I'm using right now-

create_expr <- function(name, val){
   if(!is.null(val))
     val <- paste0("c('", paste0(val, collapse = "','"), "')")
   paste(name, "%in%", val)
}

my_filter <- function(df, cols, conds){
#   Args: 
#     df: dataframe which is to be filtered
#     cols: list of column names which are to be filtered
#     conds: corresponding values for each column which need to be filtered

cols <- as.list(cols)
conds <- as.list(conds)

args <- mapply(create_expr, cols, conds, SIMPLIFY = F)

if(!length(args))
  stop(cat("No filters provided"))

df <- df %>% filter_(paste(unlist(args), collapse = " & "))
return(df)
}

my_filter(gapminder, cols = list("continent", "country"), 
                     conds = list("Europe", c("Albania", "France")))

I want to know how this could be re-written using tidy eval practices. I've found material on using quos() for multiple arguments but as you can see I have two different lists of arguments here which need to be mapped to each other.

Any help is appreciated, Thanks!

MrFlick
  • 195,160
  • 17
  • 277
  • 295
Mridul Garg
  • 477
  • 1
  • 8
  • 17

1 Answers1

14

Using the tidyverse, you could re-write that function as

library(dplyr)
library(purrr) # for map2()

my_filter <- function(df, cols, conds){     
  fp <- map2(cols, conds, function(x, y) quo((!!(as.name(x))) %in% !!y))
  filter(df, !!!fp)
}

my_filter(gapminder::gapminder, cols = list("continent", "country"), 
          conds = list("Europe", c("Albania", "France")))

This is calling the equivalent of

filter(gapminder, continent %in% "Europe", country %in% c("Albania", "France"))

The main reason this works is that you can pass multiple arguments to filter() and they are implicitly combined with &. And map2() is just a tidyverse equivalent for mapply with two objects to iterate.

MrFlick
  • 195,160
  • 17
  • 277
  • 295
  • Thank you so much for this @MrFlick . So is map2() something like an extended version of quos(), helping map two different quos() to each other? – Mridul Garg Mar 02 '18 at 19:36
  • 1
    `map2` isn't related to `quos` generally speaking. It just iterates over `cols` and `conds`. and applies a function. In this case the function just happens to return a `quo` so we get a list of `quos` in the end. – MrFlick Mar 02 '18 at 19:38
  • This is a very helpful answer, @MrFlick. I wonder how it is possible to extend this approach to multiple and arbitrary operators (`%in%`, `==`, `>`, ...)?? I just posted this as a new question [here](https://stackoverflow.com/q/69583424/7007501) – Till Oct 15 '21 at 10:39