looping over a list of filter expressions: problem with NSE in map2 call within mutate

試著忘記壹切 提交于 2019-12-11 07:38:43

问题


I have defined a list of expressions containing arguments I want to pass to a dplyr::filter call.

library(tidyverse) # using tidyr 1.0.0

cond_filter <- list(expr(1 > 0), # condition to select all rows
                    expr(Species == "setosa"),
                    expr(Species != "virginica")) 

I further have a data frame that I put into a list-column and which I then expand by the number of filter expressions in said list.

iris_nest <- iris %>% 
               nest(data = everything()) %>% 
               expand_grid(., filters = cond_filter) 

In a last step I then want to filter each nested data set based on the filter expression of my list. Usually I would do this inside a pipe using mutate on the nested tibble and then using map2 to loop over the data sets and filter conditions. However, somehow the calls below are not working. It seems that I do not get the non-standard evaluation to work correctly:

# not working: map2 inside mutate with mapper function 
iris_nest %>% 
  mutate(data = map2(data, filters, ~ filter(.x, !! .y))) 
> Error in splice(dot_call(capture_dots, frame_env = frame_env, named = named,  : 
> object '.y' not found

# not working: map2 inside mutate with anonymous function
iris_nest %>% 
  mutate(data = map2(data, filters, function(x,y) filter(x, !! y)))
> Error in splice(dot_call(capture_dots, frame_env = frame_env, named = named,  : 
> object '.y' not found

If I do it outside of the mutate call everything works fine:

# working: map2 not nested in mutate  
iris_nest$data <- map2(iris_nest$data, iris_nest$filters, ~ filter(.x, !! .y))

I would prefer a solution where I can use map2 inside mutate. What would I need to change to get this running? Maybe defining a helper function could make it work, but I am somehow lacking the imagination of how such a helper function would need to look like.


Summary

smingerson identified the cause of the problem in the way !! is evaluated within mutate. It seems that in a simple map2 call !! can be used to evaluate the filter conditions. However, when using map2 within a mutate the filter conditions .y have to be evaluated using eval explicitly.

The different effects of !! depending on its environment can be demonstrated when using a helper function which already includes !!:

my_filter <- function(df, x) {
  filter(df, !! x)
}

In this case no further evaluation (neither !! nor eval) is needed in the nested map2 call.

iris_nest %>% 
  mutate(data = map2(data, filters, ~ my_filter(.x, .y))) 

回答1:


Expressions are not stored as expressions within a tibble, instead they are stored as call objects. This means that you need to use eval first, as in Ronak's solution. I discovered this by running debugonce(dplyr:::tbl_df) and running class(eval_tidy(quo)), which evaluated to "call".

As for why it doesn't work within the mutate with !!, it is because the outer mutate() call tries to evaluate .y before .y actually exists. In Ronak's example, mutate() does not trigger eval(.y). When eval(.y) is triggered, it is within the context of the data.frame passed in, and the call is evaluated correctly to a logical vector of length equal to the number of rows.

I couldn't find a solution other than Ronak's.




回答2:


I am not sure either why it doesn't work but using eval makes it work inside mutate

library(tidyverse)
iris_nest %>% mutate(filters = map2(data, filters, ~filter(.x, eval(.y))))

# A tibble: 3 x 2
#            data filters           
#  <list<df[,5]>> <list>            
#1      [150 × 5] <tibble [150 × 5]>
#2      [150 × 5] <tibble [50 × 5]> 
#3      [150 × 5] <tibble [100 × 5]>


来源:https://stackoverflow.com/questions/58899541/looping-over-a-list-of-filter-expressions-problem-with-nse-in-map2-call-within

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!