我有一些看起来像这样的R代码:
library(dplyr)
library(datasets)
iris %.% group_by(Species) %.% filter(rank(Petal.Length, ties.method = 'random')<=2) %.% ungroup()
给予:
Source: local data frame [6 x 5]
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 4.3 3.0 1.1 0.1 setosa
2 4.6 3.6 1.0 0.2 setosa
3 5.0 2.3 3.3 1.0 versicolor
4 5.1 2.5 3.0 1.1 versicolor
5 4.9 2.5 4.5 1.7 virginica
6 6.0 3.0 4.8 1.8 virginica
这按物种分组,并且每组只保留最短的花瓣长度(Petal.Length)的两个。我的代码中有些重复,因为我对不同的列和数字重复了几次。例如。:
iris %.% group_by(Species) %.% filter(rank(Petal.Length, ties.method = 'random')<=2) %.% ungroup()
iris %.% group_by(Species) %.% filter(rank(-Petal.Length, ties.method = 'random')<=2) %.% ungroup()
iris %.% group_by(Species) %.% filter(rank(Petal.Width, ties.method = 'random')<=3) %.% ungroup()
iris %.% group_by(Species) %.% filter(rank(-Petal.Width, ties.method = 'random')<=3) %.% ungroup()
我想将其提取为一个函数。天真的方法不起作用:
keep_min_n_by_species <- function(expr, n) {
iris %.% group_by(Species) %.% filter(rank(expr, ties.method = 'random') <= n) %.% ungroup()
}
keep_min_n_by_species(Petal.Width, 2)
Error in filter_impl(.data, dots(...), environment()) :
object 'Petal.Width' not found
据我了解,表达式
rank(Petal.Length, ties.method = 'random') <= 2
是在filter
函数引入的不同上下文中评估的,该上下文为Petal.Length
表达式提供了含义。我不能只为Petal.Length交换一个变量,因为它将在错误的上下文中求值。在阅读此页面后,我尝试使用substitute
和eval
的不同组合:Non-standard evaluation。我找不到合适的组合。我认为问题可能在于我不只是要通过调用方(Petal.Length
)到filter
的表达式来进行评估-我想构造一个新的较大表达式(rank(Petal.Length, ties.method = 'random') <= 2
),然后将整个表达式传递给到filter
进行评估。最佳答案
如@baptiste所述,dplyr
版本0.3开始使用lazyeval
包解决此问题,并开始使用标准评估(与NSE版本名称相同,但以_
结尾)的新功能家族。这里有一个小插图:https://github.com/hadley/dplyr/blob/master/vignettes/nse.Rmd
话虽这么说,我不知道您尝试做的最佳实践(尽管我正在尝试做同样的事情)。我有一些工作要做,但是就像我说的那样,我不知道这是否是最好的方法。请注意使用filter_()
而不是filter()
,并将参数作为带引号的字符串传递:
devtools::install_github("hadley/dplyr")
devtools::install_github("hadley/lazyeval")
library(dplyr)
library(lazyeval)
keep_min_n_by_species <- function(expr, n, rev = FALSE) {
iris %>%
group_by(Species) %>%
filter_(interp(~rank(if (rev) -x else x, ties.method = 'random') <= y, # filter_, not filter
x = as.name(expr), y = n)) %>%
ungroup()
}
keep_min_n_by_species("Petal.Width", 3) # "Petal.Width" as character string
keep_min_n_by_species("Petal.Width", 3, rev = TRUE)
根据@hadley的评论进行更新:
keep_min_n_by_species <- function(expr, n) {
expr <- lazy(expr)
formula <- interp(~rank(x, ties.method = 'random') <= y,
x = expr, y = n)
iris %>%
group_by(Species) %>%
filter_(formula) %>%
ungroup()
}
keep_min_n_by_species(Petal.Width, 3)
keep_min_n_by_species(-Petal.Width, 3)