我遇到了以保留非标准评估的方式重构dplyr的问题。假设我想创建一个始终选择和重命名的函数。
library(lazyeval)
library(dplyr)
df <- data.frame(a = c(1,2,3), f = c(4,5,6), lm = c(7, 8 , 9))
select_happy<- function(df, col){
col <- lazy(col)
fo <- interp(~x, x=col)
select_(df, happy=fo)
}
f <- function(){
print('foo')
}
select_happy()
是根据此帖Refactor R code when library functions use non-standard evaluation的答案撰写的。 select_happy()
适用于未定义或在全局环境中定义的列名。但是,当列名也是另一个名称空间中函数的名称时,它会遇到问题。
select_happy(df, a)
# happy
# 1 1
# 2 2
# 3 3
select_happy(df, f)
# happy
# 1 4
# 2 5
# 3 6
select_happy(df, lm)
# Error in eval(expr, envir, enclos) (from #4) : object 'datafile' not found
environment(f)
# <environment: R_GlobalEnv>
environment(lm)
# <environment: namespace:stats>
在f和lm上调用lazy()
显示了惰性对象的差异,其中lm的函数定义出现在惰性对象中,对于f,它只是函数的名称。
lazy(f)
# <lazy>
# expr: f
# env: <environment: R_GlobalEnv>
lazy(lm)
# <lazy>
# expr: function (formula, data, subset, weights, na.action, method = "qr", ...
# env: <environment: R_GlobalEnv>
substitute
似乎与lm一起使用。
select_happy<- function(df, col){
col <- substitute(col) # <- substitute() instead of lazy()
fo <- interp(~x, x=col)
select_(df, happy=fo)
}
select_happy(df, lm)
# happy
# 1 7
# 2 8
# 3 9
但是,在阅读the vignette on lazyeval
之后,似乎lazy
应该成为substitute
的优秀替代品。此外,常规select
功能正常。
select(df, happy=lm)
# happy
# 1 7
# 2 8
# 3 9
我的问题是我如何撰写select_happy()
以便它以select()
所做的所有方式运作?我很难围绕范围和非标准评估。更一般地说,使用dplyr编程可以避免这些和其他问题的可靠策略是什么?
修改
我测试了docendo discimus的解决方案并且效果很好,但我想知道是否有一种方法可以使用参数而不是点来表示函数。我认为能够使用interp()
也很重要,因为您可能希望将输入提供给更复杂的公式,就像我之前链接的帖子一样。我认为问题的核心归结为lazy_dots()
正在以lazy()
的方式捕获表达式。我想了解他们为什么表现不同,以及如何使用lazy()
来获得与lazy_dots()
相同的功能。
g <- function(...){
lazy_dots(...)
}
h <- function(x){
lazy(x)
}
g(lm)[[1]]
# <lazy>
# expr: lm
# env: <environment: R_GlobalEnv>
h(lm)
# <lazy>
# expr: function (formula, data, subset, weights, na.action, method = "qr", ...
# env: <environment: R_GlobalEnv>
即使将.follow__symbols
更改为FALSE
lazy()
以使其与lazy_dots()
相同也无效。
lazy
# function (expr, env = parent.frame(), .follow_symbols = TRUE)
# {
# .Call(make_lazy, quote(expr), environment(), .follow_symbols)
# }
# <environment: namespace:lazyeval>
lazy_dots
# function (..., .follow_symbols = FALSE)
# {
# if (nargs() == 0)
# return(structure(list(), class = "lazy_dots"))
# .Call(make_lazy_dots, environment(), .follow_symbols)
# }
# <environment: namespace:lazyeval>
h2 <- function(x){
lazy(x, .follow_symbols=FALSE)
}
h2(lm)
# <lazy>
# expr: x
# env: <environment: 0xe4a42a8>
我觉得自己真的很不知所措。
答案 0 :(得分:2)
一个选项可能是使select_happy
与标准select
函数几乎相同:
select_happy<- function(df, ...){
select_(df, .dots = setNames(lazy_dots(...), "happy"))
}
f <- function(){
print('foo')
}
> select_happy(df, a)
happy
1 1
2 2
3 3
>
> select_happy(df, f)
happy
1 4
2 5
3 6
>
> select_happy(df, lm)
happy
1 7
2 8
3 9
请注意,标准select
函数的函数定义为:
> select
function (.data, ...)
{
select_(.data, .dots = lazyeval::lazy_dots(...))
}
<environment: namespace:dplyr>
另请注意,根据此定义,select_happy
接受多个列进行选择,但会为任何其他列命名&#34; NA&#34;:
> select_happy(df, lm, a)
happy NA
1 7 1
2 8 2
3 9 3
当然,您可以对此类情况进行一些修改,例如:
select_happy<- function(df, ...){
dots <- lazy_dots(...)
n <- length(dots)
if(n == 1) newnames <- "happy" else newnames <- paste0("happy", seq_len(n))
select_(df, .dots = setNames(dots, newnames))
}
> select_happy(df, f)
happy
1 4
2 5
3 6
> select_happy(df, lm, a)
happy1 happy2
1 7 1
2 8 2
3 9 3