Нестандартная оценка из другой функции в 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
просто обычная переменная, поэтому значение (выражение) подставляется.