使用传递给 dplyr::filter 的参数创建函数什么是解决 nse 的最佳方法?

Creating a function with an argument passed to dplyr::filter what is the best way to work around nse?

非标准评价真的好用 使用 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

解决非标准评估的另一种方法 基于 this answerinterp() 在上下文中解释 species 功能环境:

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

考虑到上面的3个函数, 实现此过滤功能的首选(最可靠)方法是什么? 还有其他方法吗?

本题与非标准评价完全无关。让我重写您的初始函数以明确说明:

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

您的 filter 中的表达式总是求值为 TRUE(几乎总是 - 请参见下面的示例),这就是它不起作用的原因,而不是因为某些 NSE 魔术。

您的 nrowspecies2 是正确的选择。

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

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

写一个函数包装 dplyr 动词,它将与 NSE 一起工作,写两个函数:

首先 编写一个需要引用输入的版本,使用 lazyevaldplyr 动词的 SE 版本。所以在这种情况下,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 参数

in his 2016 UseR talk (@38min30s), Hadley Wickham explains the concept of referential transparency。使用公式,过滤函数可以重新表述为:

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