问题
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