Почему `substitute` работает в нескольких строках, а не в одной?

Я пытался ответить на этот хороший вопрос о создании нестандартной функции оценки для объекта data.table, выполняя сгруппированное сумма. Акрун придумал прекрасный ответ, который я упрощу здесь:

akrun <- function(data, var, group){
 var <- substitute(var)
 group <- substitute(group)
 data[, sum(eval(var)), by = group]
}

library(data.table)
mt = as.data.table(mtcars)
akrun(mt, cyl, mpg)
#    group    V1
# 1:     6 138.2
# 2:     4 293.3
# 3:     8 211.4

Я также работал над ответом, и у меня был почти такой же ответ, но с substitute, встроенными в остальные. Мой приводит к ошибке:

gregor = function(data, var, group) {
  data[, sum(eval(substitute(var))), by = substitute(group)]
} 

gregor(mt, mpg, cyl)
# Error in `[.data.table`(data, , sum(eval(substitute(var))), by = substitute(group)) : 
#  'by' or 'keyby' must evaluate to vector or list of vectors 
#  (where 'list' includes data.table and data.frame which are lists, too) 

На первый взгляд, моя функция — простая замена функции Акруна. Почему это не работает?


Обратите внимание, что обе замены вызывают проблемы, как показано здесь:

gregor_1 = function(data, var, group) {
  var = substitute(var)
  data[,sum(eval(var)), 
       by = substitute(group)]
} 
gregor_1(mt, mpg, cyl)
# Same error as above


gregor_2 = function(data, var, group) {
  group = substitute(group)
  data[,sum(eval(substitute(var))), 
       by = group]
} 
gregor_2(mt, mpg, cyl)
# Error in eval(substitute(var)) : object 'mpg' not found 

person Gregor Thomas    schedule 31.10.2019    source источник
comment
У меня нет времени (или, возможно, возможности) копаться в этом больше прямо сейчас, но кажется, что в вызове data.table любые переменные, которые вы передаете в substitute, остаются символами (например, если вы заключаете их в rleid или as.integer предупреждение немного понятнее нубам вроде меня). Это правильно? Итак, substitute не смотрит в среду функций при использовании внутри вызова data.table? Кроме того, я очень зеленый во всем, что касается data.table, поэтому прошу прощения, если это уже очевидно.   -  person Andrew    schedule 31.10.2019
comment
Я думаю, что это хороший вклад. Первоначальное предложение Акруна касалось и среды, я пробовал такие версии, как substitute(var, env = parent.frame(environment())), тоже безуспешно. Но, похоже, это правильный путь.   -  person Gregor Thomas    schedule 31.10.2019
comment
Привет, Грегор! (Майкл из UW STATR). В последнее время я много думал о написании функций для процедур data.table и пошел по пути создания функций, которые принимают в качестве аргументов символьные векторы имен столбцов, а не символы без кавычек. В общем, написание функций с data.table имеет некоторые потенциальные проблемы, когда функция может использовать базовый объект data.table, создавая временные столбцы, чтобы избежать копирования всей таблицы. потенциальные конфликты имен столбцов и изменение ключа входной таблицы. Я бы хотел, чтобы больше обсуждались лучшие практики.   -  person Michael    schedule 21.02.2020
comment
см. мой ответ 60344884" title="простой воспроизводимый пример для передачи аргументов в таблицу данных в самоопределяемом"> stackoverflow.com/questions/58648886/   -  person Michael    schedule 21.02.2020


Ответы (3)


В документации substitute вы можете прочитать, как он решает, что заменить, и тот факт, что по умолчанию он ищет среду, в которой он вызывается. Если вы вызовете substitute внутри кадра data.table (т.е. внутри []), он не сможет найти символы, потому что они не присутствуют в среде оценки data.table, они находятся в среде, где был вызван [.

Вы можете «инвертировать» порядок вызова функций, чтобы получить желаемое поведение:

library(data.table)

foo <- function(dt, group, var) {
    eval(substitute(dt[, sum(var), by = group]))
}

foo(as.data.table(mtcars), cyl, mpg)
   cyl    V1
1:   6 138.2
2:   4 293.3
3:   8 211.4
person Alexis    schedule 01.11.2019

Кажется, что substitute не работает в таблице данных так, как можно было бы ожидать от того, как это работает в других контекстах, но вы можете использовать enexpr из пакета rlang вместо substitute:

library(data.table)
library(rlang)

gregor_rlang = function(data, var, group) {
  data[, sum(eval(enexpr(var))), by = .(group = eval(enexpr(group)))]
} 

gregor_rlang(mt, mpg, cyl)
##    group    V1
## 1:     6 138.2
## 2:     4 293.3
## 3:     8 211.4

среды

Проблема, по-видимому, связана с окружением, так как это работает там, где мы специально указали окружение, которое substitute следует использовать.

gregor_pf = function(data, val, group) {
  data[, sum(eval(substitute(val, parent.env(environment())))), 
    by = c(deparse(substitute(group)))]
} 
gregor_pf(mt, mpg, cyl)
##      cyl    V1
## 1:     6 138.2
## 2:     4 293.3
## 3:     8 211.4
person G. Grothendieck    schedule 31.10.2019

data.table использует NSE, потому что ему необходимо анализировать/манипулировать аргументом by, прежде чем выбрать, будет ли он оценивать его или нет (например, если вы дадите ему символ, он не будет его оценивать).

Следствием этого является то, что если аргумент необходимо оценить, он должен быть оценен в правильной среде, и это является обязанностью функции. data.table оценивает свой аргумент by в данных, а не в вызывающей среде.

В большинстве случаев вы не видите проблемы, так как символ будет оцениваться в родительской среде, если он не найден, но substitute() более чувствителен.

См. пример ниже:

fun <- function(x){
  standard_eval(x)
  non_standard_eval_safe(x)
  non_standard_eval_not_safe(x)
}

standard_eval          <- function(expr) print(expr)

non_standard_eval_safe <- function(expr) {
  expr <- bquote(print(.(substitute(expr)))) # will be quote(print(x)) in our example
  eval.parent(expr)
}

non_standard_eval_not_safe <- function(expr) {
  expr <- bquote(print(.(substitute(expr))))  # will be quote(print(x)) in our example
  eval(expr)
}

standard_eval(1+1)          
#> [1] 2

non_standard_eval_safe(1+1)
#> [1] 2

non_standard_eval_not_safe(1+1)
#> [1] 2

fun(1+1)
#> [1] 2
#> [1] 2
#> Error in print(x): object 'x' not found


Создана 20 февраля 2020 г. в пакете reprex (v0.3.0)

person Moody_Mudskipper    schedule 20.02.2020