【问题标题】:Refactor R code when library functions use non-standard evaluation当库函数使用非标准评估时重构 R 代码
【发布时间】:2014-10-31 01:40:44
【问题描述】:

我有一些看起来像这样的 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') &lt;= 2 表达式是在不同的上下文中计算的,由 filter 函数引入,它为 Petal.Length 表达式提供了含义。我不能只为 Petal.Length 换一个变量,因为它将在错误的上下文中进行评估。我尝试使用substituteeval 的不同组合,并阅读了此页面:Non-standard evaluation。我想不出合适的组合。我认为问题可能是我不只是想将调用者 (Petal.Length) 的表达式传递到 filter 进行评估 - 我想构造一个新的更大的表达式 (rank(Petal.Length, ties.method = 'random') &lt;= 2) 和然后将整个表达式传递给 filter 进行评估。

  1. 如何将 this 表达式重构为函数?
  2. 更一般地说,我应该如何将 R 表达式提取到函数中?
  3. 更一般地说,我是不是抱着错误的心态来处理这个问题?在我熟悉的更主流的语言(例如 Python、C++、C#)中,这是一个相对简单的操作,我一直希望这样做以消除代码中的重复。在 R 中,似乎(至少对我来说)非标准评估可以使它成为一个非常不明显的操作。我应该完全做其他事情吗?

【问题讨论】:

标签: r dplyr


【解决方案1】:

dplyr 0.3 版开始使用lazyeval 包解决这个问题,正如@baptiste 所提到的,以及使用标准评估的新函数系列(与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)

【讨论】:

  • 我会做expr &lt;- lazyeval::lazy(expr)(所以你不需要引用它)并在filter_()调用之外创建公​​式。
  • 啊,非常好 - 谢谢!我在我的答案中添加了我认为您所了解的内容的更新。
  • 完美 :) 我就是这样做的。
  • 我想我之前错过了编辑。我现在已将此作为公认的答案,因为编辑看起来更容易理解、更容易记住并且得到了哈德利的祝福。
【解决方案2】:

怎么样

keep_min_n_by_species <- function(expr, n) {
    mc <- match.call()
    fx <- bquote(rank(.(mc$expr), ties.method = 'random') <= .(mc$n))
    iris %.% group_by(Species) %.% filter(fx) %.% ungroup()
}

这似乎让所有语句都可以正常运行

keep_min_n_by_species(Petal.Width, 2)
keep_min_n_by_species(-Petal.Width, 2)
keep_min_n_by_species(Petal.Width, 3)
keep_min_n_by_species(-Petal.Width, 3)

我们的想法是我们使用match.call() 来捕获传递给函数的未计算表达式。然后我们使用bquote()将过滤器构建为调用对象。

【讨论】:

  • 太棒了!我有点难过,它看起来如此神秘。如果没有更多的 R 经验,我认为我无法弄清楚这一点。如果一天左右没有更详细的答案,我会回来接受这个。
  • 我建议远离match.call() 并改用更具体的功能。 (在这种情况下,评估n 会更安全)
猜你喜欢
  • 1970-01-01
  • 2016-09-18
  • 1970-01-01
  • 1970-01-01
  • 2018-08-01
  • 2016-04-27
  • 1970-01-01
相关资源
最近更新 更多