【问题标题】:How to implement mutate-like chain evaluation?如何实现类似变异的链式评估?
【发布时间】:2017-07-14 20:08:45
【问题描述】:

Dplyr 的 mutate 函数可以计算“链式”表达式,例如

library(dplyr)

data.frame(a = 1) %>%
   mutate(b = a + 1, c = b * 2)
##   a b c
## 1 1 2 4 

如何实现?快速浏览一下 dplyr 的源代码,可以发现候选代码的基本结构:

library(lazyeval)
library(rlang)

compat_as_lazy <- function(quo) {
  structure(class = "lazy", list(
    expr = f_rhs(quo),
    env = f_env(quo)
  ))
}

compat_as_lazy_dots <- function(...) {
  structure(class = "lazy_dots", lapply(quos(...), compat_as_lazy))
}

my_mutate <- function(.data, ...) {
  lazy_eval(compat_as_lazy_dots(...), data = .data)
}

data.frame(a = 1) %>%
  my_mutate(b = a + 1, c = b * 2)
## Error in eval(x$expr, data, x$env) : object 'b' not found

...但是这种“幼稚”的实现不起作用,mutate_impl 背后的 C++ 代码非常复杂。我知道它不起作用,因为"lazy_dots" 上的lazy_eval 使用lapply,即每个表达式都相互独立地评估,而我宁愿需要链式评估并将结果返回到共享环境。如何让它发挥作用?

【问题讨论】:

  • 哦,您正在尝试制作自己的变异函数....

标签: r dplyr rlang lazyeval


【解决方案1】:

我不完全确定这是您想要的,但这里有 3 个基础 R 中的变异克隆,适用于您的示例:

mutate_transform <- function(df,...){
  lhs <- names(match.call())[-1:-2]
  rhs <- as.character(substitute(list(...)))[-1]
  args = paste(lhs,"=",rhs)
  for(arg in args){
    df <- eval(parse(text=paste("transform(df,",arg,")")))
  }
df
}

mutate_within <- function(df,...){
  lhs <- names(match.call())[-1:-2]
  rhs <- as.character(substitute(list(...)))[-1]
  args = paste(lhs,"=",rhs)
  df <- eval(parse(text=paste("within(df,{",paste(args,collapse=";"),"})")))
  df
}

mutate_attach <- function(df,...){
  lhs <- names(match.call())[-1:-2]
  rhs <- as.character(substitute(list(...)))[-1]
  new_env <- new.env()
  with(data = new_env,attach(df,warn.conflicts = FALSE))
  for(i in 1:length(lhs)){
    assign(lhs[i],eval(parse(text=rhs[i]),envir=new_env),envir=new_env)
  }
  add_vars <- setdiff(lhs,names(df))
  with(data = new_env,detach(df))
  for(var in add_vars){
    df[[var]] <- new_env[[var]]
  }
  df
}  

data.frame(a = 1) %>%  mutate_transform(b = a + 1, c = b * 2)
#   a b c
# 1 1 2 4
data.frame(a = 1) %>%  mutate_within(b = a + 1, c = b * 2)
#   a c b   <--- order is different here 
# 1 1 4 2
data.frame(a = 1) %>%  mutate_attach(b = a + 1, c = b * 2)
#   a b c
# 1 1 2 4

【讨论】:

    【解决方案2】:

    在阅读了 Moody_Mudskipper 的回答后,我提出了自己的解决方案,该解决方案重新实现了 lazyeval::lazy_eval 函数,以获得“记住”过去评估的表达式列表:

    my_eval <- function(expr, .data = NULL) {
      idx <- structure(seq_along(expr),
                       names = names(expr))
      lapply(idx, function(i) {
        evl <- lazy_eval(expr[[i]], data = .data)
        .data[names(expr)[i]] <<- evl
        evl
      })
    }
    

    接下来,需要将 my_mutate 中的 lazy_eval 替换为 my_eval 以使一切按预期工作。

    【讨论】:

      猜你喜欢
      • 2013-02-04
      • 2011-12-02
      • 1970-01-01
      • 2014-01-19
      • 1970-01-01
      • 2013-12-15
      • 1970-01-01
      • 2018-04-29
      • 1970-01-01
      相关资源
      最近更新 更多