Как внедрить мутированную цепочку?
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
... но такая "наивная" реализация не работает и код C++ позади mutate_impl
это довольно сложно. Я понимаю, что это не работает, потому что lazy_eval
на "lazy_dots"
использования lapply
Т.е. каждое из выражений оценивается независимо друг от друга, в то время как мне скорее потребуется цепная оценка с возвратом результата обратно в общую среду. Как заставить это работать?
2 ответа
Я не совсем уверен, что это то, что вы хотите, но вот 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
Прочитав ответ 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
})
}
Следующий, lazy_eval
в my_mutate
должен быть заменен my_eval
чтобы все работало как положено.