简体   繁体   中英

R rlang: use .x in map() with quosure?

I am trying to pass a set of variables/values in a data.frame to a map function, but am not sure how to deal with the fact that .x refers to a quosure that needs to be evaluated: mutate(df2 = map2(variable, value, ~filter(df1, .x==.y))) A naive !!.x will not work.

Here my data.frame has one column for variable , one for value , that will be mapped in a filter call:

tibble(variable=c("wool", "tension"), 
       value= c("A", "L")) 
#> # A tibble: 2 x 2
#>   variable value
#>   <chr>    <chr>
#> 1 wool     A    
#> 2 tension  L

How can I pass these to filter? Should I declare instead variable as quosure? I tried a few approaches:

library(tidyverse)
data(warpbreaks)

tibble(variable=c("wool", "tension"), 
       value= c("A", "L")) %>% 
  mutate(data_filtered=map2(variable, value, ~filter(warpbreaks, .x==.y)))
#> # A tibble: 2 x 3
#>   variable value data_filtered       
#>   <chr>    <chr> <list>              
#> 1 wool     A     <data.frame [0 × 3]>
#> 2 tension  L     <data.frame [0 × 3]>

tibble(variable=c(quo(wool), quo(tension)), 
       value= c("A", "L")) %>% 
  mutate(data_filtered=map2(variable, value, ~filter(warpbreaks, eval_tidy(.x)==.y)))
#> Error in eval_tidy(.x): object 'wool' not found

Something weird goes on with the anonymous function evaluation of .x . To be honest I'm not sure what, but defining a function outside of the map2 call seems to work alright (credit to @Lionel Henry for the ~ filter(df1, !!sym(.x) == .y) bit:

library(tidyverse)

df <- tibble(variable=c("wool", "tension"), 
       value= c("A", "L")) 

data(warpbreaks)

# doesn't work with anonymous function
tibble(variable=c("wool", "tension"), 
       value= c("A", "L")) %>% 
  mutate(data_filtered=map2(variable, value, ~ filter(warpbreaks, !!sym(.x) == .y)))
#> Error in is_symbol(x): object '.x' not found

# works when you define function outside of map2
temp <- function(x, y, data){
  filter(data, !!sym(x) == y)
}

tibble(variable=c("wool", "tension"), 
       value= c("A", "L")) %>% 
  mutate(data_filtered=map2(variable, value, temp, warpbreaks))
#> # A tibble: 2 x 3
#>   variable value data_filtered        
#>   <chr>    <chr> <list>               
#> 1 wool     A     <data.frame [27 x 3]>
#> 2 tension  L     <data.frame [18 x 3]>

Created on 2019-05-07 by the reprex package (v0.2.1)

You can also do the following without the externally defined function:

tibble(variable=c("wool", "tension"), 
       value= c("A", "L")) %>% 
  mutate(data_filtered = map2(variable, value, ~ filter(..3, ..3[[..1]] == ..2), warpbreaks))
#> # A tibble: 2 x 3
#>   variable value data_filtered        
#>   <chr>    <chr> <list>               
#> 1 wool     A     <data.frame [27 x 3]>
#> 2 tension  L     <data.frame [18 x 3]>

In your example you're trying to use dplyr verbs in a nested way: there's a filter() inside mutate() . This works well for the normal use, but we need to be a little careful when using tidy eval features because they are applied very early, when the outer function is called. For this reason there's often a timing problem if you try to use !! or .data in the inner verb.

@zack's answer shows how you can decompose the problem in two steps to avoid the nested issue. In this case, another possibility is to omit the mutate() step by mapping directly over df (credit to @Spacedman for the idea). Here we're going to use pmap() which maps in parallel over a list or data frame:

# For pretty-printing
options(tibble.print_max = 5, tibble.print_min = 5)
warpbreaks <- as_tibble(warpbreaks)

pmap(df, ~ filter(warpbreaks, .data[[.x]] == .y))
#> [[1]]
#> # A tibble: 27 x 3
#>   breaks wool  tension
#>    <dbl> <fct> <fct>
#> 1     26 A     L
#> 2     30 A     L
#> 3     54 A     L
#> 4     25 A     L
#> 5     70 A     L
#> # … with 22 more rows
#>
#> [[2]]
#> # A tibble: 18 x 3
#>   breaks wool  tension
#>    <dbl> <fct> <fct>
#> 1     26 A     L
#> 2     30 A     L
#> 3     54 A     L
#> 4     25 A     L
#> 5     70 A     L
#> # … with 13 more rows

You can use R 's native substitution tools, rlang is more valuable when dealing with environments but for more complex symbol substitution (nested for example) base R is easier (for me at least).

tibble(variable=c("wool", "tension"), 
       value= c("A", "L")) %>% 
  mutate(data_filtered=map2(variable, value, ~eval(bquote(
    filter(warpbreaks, .(sym(.x)) ==.y)))))

tibble(variable=c("wool", "tension"), 
       value= c("A", "L")) %>% 
  mutate(data_filtered=map2(variable, value, ~eval(substitute(
    filter(warpbreaks, X ==.y), list(X = sym(.x))))))

# output for either
# # A tibble: 2 x 3
#       variable value data_filtered        
#          <chr>    <chr> <list>               
#   1 wool     A     <data.frame [27 x 3]>
#   2 tension  L     <data.frame [18 x 3]>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM