Нестандартная оценка от другой функции в R

Вот пример из книги Hadley advanced R:

sample_df <- data.frame(a = 1:5, b = 5:1, c = c(5, 3, 1, 4, 1))

subset2 <- function(x, condition) {
  condition_call <- substitute(condition)
  r <- eval(condition_call, x, parent.frame())
  x[r, ]
}

scramble <- function(x) x[sample(nrow(x)), ]

subscramble <- function(x, condition) {
  scramble(subset2(x, condition))
}

subscramble(sample_df, a >= 4)
# Error in eval(expr, envir, enclos) : object 'a' not found

Хэдли объясняет:

Вы видите, в чем проблема? condition_call содержит условие выражения. Поэтому, когда мы вычисляем condition_call, оно также вычисляет условие, которое имеет значение a >= 4. Однако это не может быть вычислено, поскольку в родительской среде нет объекта, называемого a.

Я понимаю, что в родительском env нет a, но eval(condition_call, x, parent.frame()) evals conditional_call в x (используемый в качестве среды data.frame), заключенный в parent.frame(). До тех пор, пока в x есть столбец с именем a, почему возникает какая-либо проблема?

Ответы

Ответ 1

TL;DR

Когда subset2() вызывается из subscramble(), Значением condition_call является символ condition (а не вызов a >= 4, который возникает, когда он вызывается напрямую). subset() 's вызов eval() выполняет поиск condition сначала в envir=x ( data.frame sample_df). Не найдя его там, он будет искать в enclos=parent.frame(), где он найдет объект с именем condition.

Этот объект является объектом обещания, чей слот выражения a >= 4 и среда оценки которого .GlobalEnv. Если объект с именем a находится в .GlobalEnv или дальше по поиску путь, оценка обещания затем не выполняется с наблюдаемым сообщением что: Error in eval(expr, envir, enclos) : object 'a' not found.


Подробное объяснение

Хороший способ узнать, что здесь не так, - вставить browser() вызов прямо перед строкой, в которой subset2() выходит из строя. Таким образом, мы можем назвать это прямо и косвенно (из в рамках другой функции), и проанализируйте, почему это удается в первом случае и не работает во втором.

subset2 <- function(x, condition) {
  condition_call <- substitute(condition)
  browser()
  r <- eval(condition_call, x, parent.frame())  ## <- Point of failure
  x[r, ]
}

Прямой вызов подмножества2()

Когда пользователь вызывает subset2() напрямую, condition_call <- substitute(condition) присваивает condition_call объект "вызов" содержащий неоценимый вызов a >= 4. Этот вызов передается в eval(expr, envir, enclos), которому в качестве первого аргумента символ, который оценивается объектом класса call, name или expression. Пока все хорошо.

subset2(sample_df, a >= 4)
## Called from: subset2(sample_df, a >= 4)
Browse[1]> is(condition_call)
## [1] "call"     "language"
Browse[1]> condition_call
## a >= 4

eval() теперь работает, ищет значения любых символов содержащейся в expr=condition_call сначала в envir=x, а затем (если необходимо) в enclos=parent.frame() и окружающих средах. В в этом случае он находит символ a в envir=x (и символ >= в package:base) и успешно завершает оценку.

Browse[1]> ls(x)
## [1] "a" "b" "c"
Browse[1]> get("a", x)
## [1] 1 2 3 4 5
Browse[1]> eval(condition_call, x, parent.frame())
## [1] FALSE FALSE FALSE  TRUE  TRUE

Вызов подмножества2() изнутри subscramble()

Внутри тела subscramble(), subset2() вызывается так: subset2(x, condition). Скрытый, этот вызов действительно эквивалентен до subset2(x=x, condition=condition). Поскольку аргумент (т.е. значение, переданное в аргумент формальный с именем condition) является выражением condition, condition_call <- substitute(condition) присваивает condition_call объект-символ condition. (Понимание этой точки довольно важно для понимания того, как сбой вложенного вызова.)

Так как eval() счастлив иметь символ (ака "имя" ) в качестве своего первого аргумент, еще раз настолько хороший.

subscramble(sample_df, a >= 4)
## Called from: subset2(x, condition)
Browse[1]> is(condition_call)
## [1] "name"      "language"  "refObject"
Browse[1]> condition_call
## condition

Теперь eval() переходит в работу, ища неразрешенный символ condition. Нет столбца в envir=x (data.frame sample_df) , поэтому он переходит на enclos=parent.frame(). Сложные причины, что окружающая среда оказывается оценкой кадра вызова subscramble(). Там делает объект с именем condition.

Browse[1]> ls(x)
## [1] "a" "b" "c"
Browse[1]> ls(parent.frame()) ## Aha! Here an object named "condition"
## [1] "condition" "x"

Как важно в стороне, оказывается, что в стеке вызовов над средой, из которой был вызван browser(), есть несколько объектов с именем condition.

Browse[1]> sys.calls()
# [[1]]
# subscramble(sample_df, a >= 4)
# 
# [[2]]
# scramble(subset2(x, condition))
# 
# [[3]]
# subset2(x, condition)               
# 
Browse[1]> sys.frames()
# [[1]]
# <environment: 0x0000000007166f28>   ## <- Envt in which `condition` is evaluated
# 
# [[2]]
# <environment: 0x0000000007167078>
# 
# [[3]]
# <environment: 0x0000000007166348>   ## <- Current environment


## Orient ourselves a bit more
Browse[1]> environment()            
# <environment: 0x0000000007166348>
Browse[1]> parent.frame()           
# <environment: 0x0000000007166f28>

## Both environments contain objects named 'condition'
Browse[1]> ls(environment())
# [1] "condition"      "condition_call" "x"             
Browse[1]> ls(parent.frame())
# [1] "condition" "x"  

Чтобы проверить объект condition, найденный eval() (тот, который находится в parent.frame(), который оказывается рамкой оценки subscramble()), требует особой осторожности. Я использовал recover() и pryr::promise_info(), как показано ниже.

Эта проверка показывает, что condition является обещанием, чей слот выражения a >= 4 и чья среда .GlobalEnv. Наш поиск a к этому моменту переместился хорошо прошлое sample_df (где должно было быть найдено значение a), поэтому оценка (если объект с именем a не найден в .GlobalEnv или где-нибудь еще дальше путь поиска).

Browse[1]> library(pryr) ## For is_promise() and promise_info()  
Browse[1]> recover()
# 
# Enter a frame number, or 0 to exit   
# 
# 1: subscramble(sample_df, a >= 4)
# 2: #2: scramble(subset2(x, condition))
# 3: #1: subset2(x, condition)
# 
Selection: 1
# Called from: top level 
Browse[3]> is_promise(condition)
# [1] TRUE
Browse[3]> promise_info(condition)
# $code
# a >= 4
# 
# $env
# <environment: R_GlobalEnv>
# 
# $evaled
# [1] FALSE
# 
# $value
# NULL
# 
Browse[3]> get("a", .GlobalEnv)
# Error in get("a", .GlobalEnv) : object 'a' not found

Для еще одного доказательства того, что объект обетования condition найден в enclos=parent.frame() можно указать enclos где-нибудь еще дальше путь поиска, так что parent.frame() пропускается во время оценки condition_call. Когда человек что subscramble() снова терпит неудачу, но на этот раз с сообщением, что condition сам не был найден.

## Compare
Browse[1]> eval(condition_call, x, parent.frame())
# Error in eval(expr, envir, enclos) (from #4) : object 'a' not found

Browse[1]> eval(condition_call, x, .GlobalEnv)
# Error in eval(expr, envir, enclos) (from #4) : object 'condition' not found

Ответ 2

Это было сложно, поэтому спасибо за вопрос. Ошибка связана с тем, как подстановка действует при вызове аргумента. Если мы посмотрим на текст справки из подстановки():

Замещение происходит путем изучения каждого компонента дерева разбора следующим образом: если он не является связанным символом в env, он не изменяется. Если это объект обещания, т.е. формальный аргумент функции или явно созданный с использованием delayedAssign(), слот выражения обещания заменяет символ.

Это означает, что когда вы оцениваете condition внутри функции вложенного подмножества2, substitute устанавливает condition_call как объект обещания неоцениваемого аргумента "условие". Поскольку объекты обещаний довольно неясны, определение здесь: http://cran.r-project.org/doc/manuals/r-release/R-lang.html#Promise-objects

Ключевыми моментами здесь являются:

Объекты Promise являются частью Rs ленивого механизма оценки. Они содержат три слота: значение, выражение и среду.

и

При доступе к аргументу хранимое выражение оценивается в сохраненной среде, и результат возвращается

В принципе, внутри вложенной функции condition_call устанавливается на объект обещания condition, а не на замещение фактического выражения, содержащегося в condition. Поскольку объекты обещаний "запоминают" среду, из которой они происходят, кажется, что это переопределяет поведение eval() - поэтому независимо от второго аргумента eval(), condition_call оценивается в родительской среде, из которой был передан аргумент, в котором нет "а".

Вы можете создавать объекты обещания с помощью delayedAssign() и наблюдать это прямо:

delayedAssign("condition", a >= 4)
substitute(condition)
eval(substitute(condition), sample_df)

Вы можете видеть, что substitute(condition) не возвращает a >= 4, а просто condition, а попытка оценить его в среде sample_df завершается неудачно, как в примере Хэдли.

Надеюсь, это полезно, и я уверен, что кто-то еще сможет прояснить ситуацию.

Ответ 3

В случае, если кто-то другой наткнется на этот поток, вот ответ на задание № 5 ниже этого раздела в книге Хэдли. Он также содержит возможное общее решение проблемы, обсуждавшейся выше.

subset2 <- function(x, condition, env = parent.frame()) {
  condition_call <- substitute(condition, env)
  r <- eval(condition_call, x, env)
  x[r, ]
}
scramble <- function(x) x[sample(nrow(x)), ]
subscramble <- function(x, condition) {
  scramble(subset2(x, condition))
}
subscramble(sample_df, a >= 3)

Магия происходит во второй строке subset2. Там substitute получает объяснительный аргумент env. В разделе справки для substitute: "substitute возвращает дерево разбора для (неоцененного) выражения expr, заменяя любые переменные, связанные в env." env "По умолчанию текущая среда оценки". Вместо этого мы используем вызывающую среду.

Проверьте это следующим образом:

debugonce(subset2)
subscramble(sample_df, a >= 3)
Browse[2]> substitute(condition)
condition
Browse[2]> substitute(condition, env)
a >= 3

Я не уверен на 100% объяснения здесь. Я думаю, что именно так работает substitute. На странице справки substitute:

Замена происходит путем изучения каждого компонента дерева разбора следующим образом: (...) Если это объект обещания, т.е. формальный аргумент к функции или явно созданному с помощью delayedAssign(), слот выражения обещания заменяет символ. Если это обычная переменная, ее значение заменяется (...).

В текущей среде condition является обещанием, поэтому слот выражения заполняется, и, что более важно, условие_call получает символ как значение. В вызывающей среде condition является просто обычной переменной, поэтому значение (выражение) заменяется.