【问题标题】:Programming with dplyr and lazyeval使用 dplyr 和 lazyeval 进行编程
【发布时间】:2016-02-04 08:01:34
【问题描述】:

我在以保留非标准评估的方式重构 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> 

即使将lazy().follow__symbols 更改为FALSE 以使其与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>

我真的有点不知道该怎么做。

【问题讨论】:

  • @Henrik 这就是我的意思!虽然它仍然输出错误,但总的来说问题是一样的。我已更新问题以反映更正。

标签: r dplyr lazy-evaluation


【解决方案1】:

一种选择可能是将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 接受要选择的多个列,但会将任何其他列命名为“NA”:

> 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

【讨论】:

  • 此解决方案有效,但我不喜欢它强制使用省略号。小插图使它看起来应该是lazy() 的有效使用。对于需要使用省略号的函数,尽管这似乎是一个不错的解决方案。
猜你喜欢
  • 1970-01-01
  • 2023-03-08
  • 2018-11-17
  • 2014-02-23
  • 2018-05-04
  • 1970-01-01
  • 1970-01-01
  • 2018-06-09
  • 1970-01-01
相关资源
最近更新 更多