我有一些看起来像这样的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交换一个变量,因为它将在错误的上下文中求值。在阅读此页面后,我尝试使用substituteeval的不同组合:Non-standard evaluation。我找不到合适的组合。我认为问题可能在于我不只是要通过调用方(Petal.Length)到filter的表达式来进行评估-我想构造一个新的较大表达式(rank(Petal.Length, ties.method = 'random') <= 2),然后将整个表达式传递给到filter进行评估。
  • 如何将该表达式重构为函数?
  • 通常,我应该如何将R表达式提取到函数中?
  • 更一般地说,我是否以错误的思维方式来处理这个问题?在我熟悉的更多主流语言中(例如Python,C++,C#),这是一个相对简单的操作,我想一直在执行以删除代码中的重复项。在R中(至少在我看来),非标准评估可以使它成为一个非常不明显的操作。我应该完全做其他事情吗?
  • 最佳答案

    如@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)
    

    10-04 13:49