Timm S.
Timm S.

Reputation: 5415

Use both empty and string filters in dplyr's filter

I'm updating an old script using the deprecated dplyr::filter_() to use dplyr::filter(). But I can't get it to work for empty filter strings anymore:

Example:

library(dplyr)
my_df <- tibble::tibble(x = sample(c(0:9), 100, replace = TRUE))

deprecated filter_() works for both string and empty strings

fil1 <- "x == 5"
filter_(mydf, .dots = fil1) # works

fil2 <- NULL
filter_(mydf, .dots = fil2) # works, returns all values

NSE version works only with quoted filter values, but not with empty ones

fil1 = quo(x == 5)
filter(my_df, !!enquo(fil1)) # works

fil2 = NULL
filter(my_df, !!enquo(fil2)) 
Error: Argument 2 filter condition does not evaluate to a logical vector

fil2 = quo(NULL)
filter(my_df, !!enquo(fil2))
Error: Argument 2 filter condition does not evaluate to a logical vector

I see three possible approaches to this:

Upvotes: 6

Views: 969

Answers (2)

Artem Sokolov
Artem Sokolov

Reputation: 13691

If you specify your filters as lists of expressions (or NULL), you can use the unquote-splice operator to effectively "paste" them as arguments to filter. Use parse_exprs() to convert strings to expressions:

fil1 <- rlang::exprs(x == 5)          # Note the s on exprs
filter(my_df, !!!fil1)                # Works

fil2 <- NULL                          # NULL
filter(my_df, !!!fil2)                # Also works

fil3 <- rlang::parse_exprs("x==5")    # Again, note the plural s
filter(my_df, !!!fil3)                # Also works

The first and the third calls are effectively filter(my_df, x==5), while the second call is effectively filter(my_df,).

Upvotes: 3

Chuck P
Chuck P

Reputation: 3923

If I understand you correctly @timm-s the second bullet option means I can offer this solution.

set.seed(2020)
library(dplyr)

my_df <- tibble::tibble(x = sample(c(0:9), 100, replace = TRUE))

fil1 <- quo(x == 5)
filter(my_df, !!enquo(fil1)) # works
#> # A tibble: 11 x 1
#>        x
#>    <int>
#>  1     5
#>  2     5
#>  3     5
#>  4     5
#>  5     5
#>  6     5
#>  7     5
#>  8     5
#>  9     5
#> 10     5
#> 11     5

fil2 <- TRUE
filter(my_df, !!enquo(fil2)) 
#> # A tibble: 100 x 1
#>        x
#>    <int>
#>  1     6
#>  2     5
#>  3     7
#>  4     0
#>  5     0
#>  6     3
#>  7     9
#>  8     5
#>  9     0
#> 10     7
#> # … with 90 more rows

It simply relies on the fact that filter relies on true/false so instead of telling it nothing tell it true. For me the real question was why filter_ thought NULL was true LOL.

A little more playing revealed it's possible to simplify more for the empty case

fil3 <- TRUE
filter(my_df, fil3) 

will also work but may not fit your circumstances.

Upvotes: 2

Related Questions