D Pinto
D Pinto

Reputation: 901

How to dynamically multiple filters from a list

I have a list that can be arbitrarily long. For this specific example, it has three elements.

filter_conditions <- list(
  list(col = "mpg", value = 17),
  list(col = "cyl", value = 2),
  list(col = "disp", value = 160)
)

I want to create a function my_func out of it that can be applied to a dataframe and apply the filter to the respective column specified in each of filter_conditions elements.

The code below specifies the result I am expecting from calling my_func(mtcars) in the three-element example above.

library(dplyr)

f1 <- function(x) filter(x,  mpg > 17)
f2 <- function(x) filter(x, cyl > 2)
f3 <- function(x) filter(x, disp > 160)

mtcars %>% 
  f1 %>% 
  f2 %>% 
  f3

Again: filter_conditions can be arbitrarily long and I don't want to write down a call to filter for each element in filter_conditions.

Upvotes: 1

Views: 230

Answers (2)

G. Grothendieck
G. Grothendieck

Reputation: 269556

1) Define a function myfilter which takes a data frame and a list which represents a component of filter_conditions. Then combine them using Reduce. No packages are used.

myfilter <- function(data, L) data[data[[L$col]] > L$value, ]
Reduce(myfilter, init = mtcars, filter_conditions)

giving:

                   mpg cyl  disp  hp drat    wt  qsec vs am gear carb
Hornet 4 Drive    21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout 18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
Valiant           18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1
Merc 280          19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4
Merc 280C         17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4
Merc 450SL        17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3
Pontiac Firebird  19.2   8 400.0 175 3.08 3.845 17.05  0  0    3    2

2) A different approach is to generate an SQL where clause and then run it.

library(sqldf)

where.vec <- sapply(filter_conditions, with, sprintf("%s > '%s'", col, value))
where <- paste(where.vec, collapse = " and ")
ix <- fn$sqldf("select rowid from mtcars where $where")$rowid
mtcars[ix, ]

If we know that the values are all numeric (as is the case in the example in the question) then we could omit the single quotes in the definition of where.vec .

Upvotes: 4

AEF
AEF

Reputation: 5650

You can create a composed function with the tidyverse:

library(tidyverse)


my_func <- 
  compose(!!!map(filter_conditions,
                 function(f) function(dat) filter(dat, !!sym(f$col) > f$value)))

mtcars %>% 
  my_func()

Upvotes: 2

Related Questions