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

Вот пример из продвинутой 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.

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

3 ответа

ТЛ; др

когда subset2() вызывается изнутри subscramble(), condition_call значение является символом condition (а не звонок a >= 4 что получается, когда он вызывается напрямую). subset() призыв к 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, ]
}

Вызов subset2() напрямую

Когда пользователь звонит 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

Вызов subset2() из 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's an object named "condition"
## [1] "condition" "x"

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

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

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

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

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

Ключевые моменты оттуда:

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

а также

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

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

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

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

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

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

В случае, если кто-то еще наткнется на эту тему, вот ответ на задачу № 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 Msgstr "По умолчанию используется текущая среда оценки". Вместо этого мы используем среду вызова.

Проверьте это так:

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

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

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

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

Другие вопросы по тегам