Search code examples
rpurrrrlangtidyeval

how to provide expression to `purrr::pmap` for function using tidy evaluation


I am trying to write a function using rlang so that I can subset data based on supplied expression. Although the actual function is complicated, here is a minimal version of it that illustrates the problem.

minimal version of needed function

library(rlang)

# define a function
foo <- function(data, expr = NULL) {
  if (!quo_is_null(enquo(expr))) {
    dplyr::filter(data, !!enexpr(expr))
  } else {
    data
  }
}

# does the function work? yes

head(foo(mtcars, NULL))     # with NULL
#>                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
#> Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
#> Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
#> Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
#> Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
#> Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
#> Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

head(foo(mtcars, mpg > 20)) # with expression
#>                 mpg cyl  disp  hp drat    wt  qsec vs am gear carb
#> Mazda RX4      21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
#> Mazda RX4 Wag  21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
#> Datsun 710     22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
#> Hornet 4 Drive 21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
#> Merc 240D      24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
#> Merc 230       22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2

problems with purrr::pmap

When used with purrr::pmap(), it works as expected when expr is NULL, but not otherwise. Instead of list, I also tried using alist to supply the input.

library(purrr)

# works when expression is `NULL`
pmap(
  .l = list(data = list(head(mtcars)), expr = list(NULL)),
  .f = foo
) 
#> [[1]]
#>                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
#> Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
#> Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
#> Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
#> Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
#> Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
#> Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

# but not otherwise
pmap(
  .l = list(data = list(head(mtcars)), expr = list("mpg > 20")),
  .f = foo
)
#> Error: Problem with `filter()` input `..1`.
#> ℹ Input `..1` is `"mpg > 20"`.
#> x Input `..1` must be a logical vector, not a character.

Created on 2021-07-20 by the reprex package (v2.0.0)


Solution

  • One way to make this work is by wrapping with quote

    purrr::pmap(
      .l = list(data = list(head(mtcars)), expr = list(quote(mpg > 20))),
      .f = foo
    )
    

    -output

    [[1]]
                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
    Mazda RX4      21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
    Mazda RX4 Wag  21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
    Datsun 710     22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
    Hornet 4 Drive 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
    

    which also works with the NULL

    pmap(
      .l = list(data = list(head(mtcars)), expr = list(quote(NULL))),
       .f = foo
     ) 
    [[1]]
                       mpg cyl disp  hp drat    wt  qsec vs am gear carb
    Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
    Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
    Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
    Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
    Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
    Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
    

    Same output with subset

    subset(head(mtcars), mpg > 20)
                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
    Mazda RX4      21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
    Mazda RX4 Wag  21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
    Datsun 710     22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
    Hornet 4 Drive 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
    

    Or another option is to modify the function by changing the enexpr to parse_expr

    foo1 <- function(data, expr = NULL) {
      if (!quo_is_null(enquo(expr))) {
        dplyr::filter(data, !!parse_expr(expr))
      } else {
        data
      }
    }
    

    -testing

    > pmap(
    +   .l = list(data = list(head(mtcars)), expr = list(NULL)),
    +   .f = foo1
    + ) 
    [[1]]
                       mpg cyl disp  hp drat    wt  qsec vs am gear carb
    Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
    Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
    Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
    Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
    Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
    Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
    
    > 
    > pmap(
    +   .l = list(data = list(head(mtcars)), expr = list("mpg > 20")),
    +   .f = foo1
    + )
    [[1]]
                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
    Mazda RX4      21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
    Mazda RX4 Wag  21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
    Datsun 710     22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
    Hornet 4 Drive 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1