9

使用 dplyr 的动词时,非标准评估非常方便。但是在使用带有函数参数的动词时可能会出现问题。例如,假设我想创建一个函数,为我提供给定物种的行数。

# Load packages and prepare data
library(dplyr)
library(lazyeval)
# I prefer lowercase column names
names(iris) <- tolower(names(iris))
# Number of rows for all species
nrow(iris)
# [1] 150

示例不起作用

此函数无法按预期工作,因为species 它是在 iris 数据帧的上下文中解释的,而不是在函数参数的上下文中解释的:

nrowspecies0 <- function(dtf, species){
    dtf %>%
        filter(species == species) %>%
        nrow()
}
nrowspecies0(iris, species = "versicolor")
# [1] 150

3 实施实例

为了解决非标准评估,我通常在参数后面加上下划线:

nrowspecies1 <- function(dtf, species_){
    dtf %>%
        filter(species == species_) %>%
        nrow()
}

nrowspecies1(iris, species_ = "versicolor")
# [1] 50
# Because of function name completion the argument
# species works too
nrowspecies1(iris, species = "versicolor")
# [1] 50

它并不完全令人满意,因为它将函数参数的名称更改为不太用户友好的名称。或者它依赖于自动完成,我担心这不是编程的好习惯。为了保持一个好的参数名称,我可以这样做:

nrowspecies2 <- function(dtf, species){
    species_ <- species
    dtf %>%
        filter(species == species_) %>%
        nrow()
}
nrowspecies2(iris, species = "versicolor")
# [1] 50

另一种解决基于此答案的非标准评估的方法。 在函数环境的上下文中interp()解释:species

nrowspecies3 <- function(dtf, species){
    dtf %>%
        filter_(interp(~species == with_species, 
                       with_species = species)) %>%
        nrow()
}
nrowspecies3(iris, species = "versicolor")
# [1] 50

考虑到上面的 3 个功能,实现此过滤器功能的首选 - 最强大 - 方法是什么?还有其他方法吗?

4

3 回答 3

6

@eddi的回答对于这里发生的事情是正确的。我正在写另一个答案,以解决如何使用dplyr动词编写函数的更大要求。你会注意到,最终,它使用了类似的东西nrowspecies2来避免species == species重言式。

编写一个包装 dplyr 动词的函数,该函数将与 NSE 一起使用,请编写两个函数:

首先编写一个需要引用输入的版本,使用动词lazyeval的 SE 版本。dplyr所以在这种情况下,filter_

nrowspecies_robust_ <- function(data, species){ 
  species_ <- lazyeval::as.lazy(species) 
  condition <- ~ species == species_ # *
  tmp <- dplyr::filter_(data, condition) # **
  nrow(tmp)
} 
nrowspecies_robust_(iris, ~versicolor) 

其次制作一个使用 NSE 的版本:

nrowspecies_robust <- function(data, species) { 
  species <- lazyeval::lazy(species) 
  nrowspecies_robust_(data, species) 
} 
nrowspecies_robust(iris, versicolor) 

* = 如果您想做更复杂的事情,您可能需要在lazyeval::interp此处使用,如下面链接的提示所示

** = 另外,如果您需要更改输出名称,请参阅.dots参数

于 2016-04-15T18:30:41.260 回答
5

这个问题与非标准评价完全无关。让我重写您的初始函数以使其清楚:

nrowspecies4 <- function(dtf, boo){
    dtf %>%
        filter(boo == boo) %>%
        nrow()
}
nrowspecies4(iris, boo = "versicolor")
#150

你里面的表达式filter总是计算为TRUE(几乎总是 - 见下面的例子),这就是它不起作用的原因,而不是因为一些 NSE 魔法。

nrowspecies2是要走的路。

Fwiw,species在你nrowspecies0的确实被评估为一列,而不是作为输入变量species,你可以通过比较nrowspecies0(iris, NA)来检查nrowspecies4(iris, NA)

于 2016-04-15T15:26:43.440 回答
1

在 2016 年用户演讲(@38min30s) 中,Hadley Wickham 解释了引用透明度的概念。使用公式,过滤器函数可以重新表述为:

nrowspecies5 <- function(dtf, formula){
    dtf %>%
        filter_(formula) %>%
        nrow()
}

这具有更通用的额外好处

# Make column names lower case
names(iris) = tolower(names(iris)) 
nrowspecies5(iris, ~ species == "versicolor")
# 50
nrowspecies5(iris, ~ sepal.length > 6 & species == "virginica")
# 41
nrowspecies5(iris, ~ sepal.length > 6 & species == "setosa")
# 0
于 2016-08-11T13:56:41.543 回答