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

Posted

技术标签:

【中文标题】使用传递给 dplyr::filter 的参数创建一个函数 解决 nse 的最佳方法是啥?【英文标题】:Creating a function with an argument passed to dplyr::filter what is the best way to work around nse?使用传递给 dplyr::filter 的参数创建一个函数 解决 nse 的最佳方法是什么? 【发布时间】:2016-08-07 10:41:35 【问题描述】:

非标准评估真的很方便 使用 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 answer。 interp() 在上下文中解释 species 功能环境:

nrowspecies3 <- function(dtf, species)
    dtf %>%
        filter_(interp(~species == with_species, 
                       with_species = species)) %>%
        nrow()

nrowspecies3(iris, species = "versicolor")
# [1] 50

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

【问题讨论】:

数据框列名引用是我开始偏爱python的原因之一。请参阅Tidyverse style pandas:“”“Tidyverse 允许对变量名称的引用和未引用的混合引用。在我的(不)经验中,这带来的便利伴随着同样的惊愕。在我看来,如果所有变量都像 pandas 一样一直被引用,那么 tidyeval 解决的很多问题就不会存在,但我可能在这里遗漏了更深层次的真相……“”“ 【参考方案1】:

这个问题与非标准评价完全无关。让我重写你的初始函数来说明这一点:

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) 进行比较来检查。

【讨论】:

不知道为什么,但这对我不起作用。我最终使用了filter_,如下面的the answer 中所建议的那样。 (E:我的函数也使用了group_by 并进一步传递了结果,所以也许这就是原因)【参考方案2】:

@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 参数

以上,我关注some tips from Hadley

另一个很好的资源是the dplyr vignette on NSE,它说明了.dotsinterp,以及lazyeval包中的其他功能

更多关于lazyeval的详细信息see it's vignette

有关用于使用 NSE 的基本 R 工具的全面讨论(其中许多 lazyeval 可帮助您避免),请参阅高级 R 中的 the chapter on NSE

【讨论】:

谢谢,你提到的来自 Hadley 的电子邮件让我看到了 vignette("lazyeval"),它解释说“每个使用 NSE 的函数都应该有一个标准评估 (SE) 逃生舱口来进行实际计算。SE -函数名称应以 _ 结尾。”我想解释一下 lazyeval 小插图末尾的“适合编程”是什么意思。这是否意味着我不应该在函数内部使用 nse? 是的,或者至少你应该尽可能避免它。还可以在这里查看“非标准评估的缺点”部分adv-r.had.co.nz/Computing-on-the-language.html 正如 Hadley 在该章中解释的那样,基本问题是 NSE 在程序中很难推理,因为函数可能在不同的上下文中表现不同。也就是说,当以交互方式使用时,NSE 函数的行为可能与在函数中使用时不同。 Hadley 在his keynote at the 2016 UseR conference 中解释了“参照透明”的概念(在 38 分钟 30 秒时)。 “公式保留代码和应该评估此代码的环境,而无需实际进行评估。”我使用公式创建了一个示例并将其粘贴到新答案中。【参考方案3】:

in his 2016 UseR talk (@38min30s),Hadley Wickham 解释了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

【讨论】:

这会引发错误Error: object 'species' not found 那是因为我喜欢将所有列名都设为小写,无论如何我都用names(iris) = tolower(names(iris)) 更新了答案,filter_() 已被弃用,所以我可能应该更广泛地修改答案。

以上是关于使用传递给 dplyr::filter 的参数创建一个函数 解决 nse 的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式(RegEx)和dplyr :: filter()

r dplyr filter:正则表达式排除AND匹配

使用dplyr在R中的所有列上应用iqr过滤器

dplyr 过滤器:值包含在向量中

data.table相当于dplyr :: filter_at

在传递给 google.setOnLoadCallback() 的函数中使用参数;