How to programmatically filter a data frame using dplyr and neat evaluation?

Suppose I want to filter the starwars data frame programmatically. Here is a simple example that allows me to filter based on my native world and views:

 library(tidyverse) # a function that allows the user to supply filters filter_starwars <- function(filters) { for (filter in filters) { starwars = filter_at(starwars, filter$var, all_vars(. %in% filter$values)) } return(starwars) } # filter Star Wars characters that are human, and from either Tatooine or Alderaan filter_starwars(filters = list( list(var = "homeworld", values = c("Tatooine", "Alderaan")), list(var = "species", values = "Human") )) 

But this does not allow me to specify, say, a height filter, because I hardcoded the %in% operator in .vars_predicate of .vars_predicate filter_at() , and the height filter would use one of > , >= , < , <= or ==

What is the best way to write the filter_starwars() function so that the user can provide filters that are common enough to filter along any column and use any operator?

NB, using the now obsolete filter_() method, I can pass the line:

 filter_(starwars, "species == 'Human' & homeworld %in% c('Tatooine', 'Alderaan') & height > 175") 

But then again, it was out of date.

+5
source share
2 answers

Try

 filter_starwars <- function(...) { F <- quos(...) filter(starwars, !!!F) } filter_starwars(species == 'Human', homeworld %in% c('Tatooine', 'Alderaan'), height > 175) # # A tibble: 7 Γ— 13 # name height mass hair_color skin_color eye_color birth_year # <chr> <int> <dbl> <chr> <chr> <chr> <dbl> # 1 Darth Vader 202 136 none white yellow 41.9 # 2 Owen Lars 178 120 brown, grey light blue 52.0 # 3 Biggs Darklighter 183 84 black light brown 24.0 # 4 Anakin Skywalker 188 84 blond fair blue 41.9 # 5 Cliegg Lars 183 NA brown fair blue 82.0 # 6 Bail Prestor Organa 191 NA black tan brown 67.0 # 7 Raymus Antilles 188 79 brown light brown NA # # ... with 6 more variables: gender <chr>, homeworld <chr>, species <chr>, # # films <list>, vehicles <list>, starships <list> 

See https://cran.r-project.org/web/packages/dplyr/vignettes/programming.html . In short, quos commits ... as a list, without evaluating the arguments. !!! splices and does not check the arguments for evaluation in filter() .

+11
source

Here are a few approaches.

1) For this specific task, we really do not need filter_ , rlang or the like. It works:

 filter_starwars <- function(...) { filter(starwars, ...) } # test filter_starwars(species == 'Human', homeworld %in% c('Tatooine', 'Alderaan'), height > 175) ) 

2) If it is important to have character arguments, then:

 library(rlang) filter_starwars <- function(...) { filter(starwars, !!!parse_exprs(paste(..., sep = ";"))) } # test filter_starwars("species == 'Human'", "homeworld %in% c('Tatooine', 'Alderaan')", "height > 175") 

2a) or if you want to pass one character of a character:

 library(rlang) filter_starwars <- function(filters) { filter(starwars, !!!parse_exprs(paste(filters, collapse = ";"))) } # test filter_starwars(c("species == 'Human'", "homeworld %in% c('Tatooine', 'Alderaan')", "height > 175")) 
+6
source

Source: https://habr.com/ru/post/1269881/


All Articles