使用传递给 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 一起使用,请编写两个函数:
首先编写一个需要引用输入的版本,使用 lazyeval
和
dplyr
动词的 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,它说明了.dots
、interp
,以及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()