Проблема использования match.call
с lapply
заключается в том, что match.call
возвращает переданный в него вызов literal без какой-либо интерпретации. Чтобы увидеть, что происходит, давайте сделаем более простую функцию, которая точно показывает, как ваша функция интерпретирует переданные ей аргументы:
match_call_fun <- function(...) {
call = as.list(match.call()[-1])
print(call)
}
Когда мы вызываем его напрямую, match.call
правильно получает аргументы и помещает их в список, который мы можем использовать с do.call
:
match_call_fun(iris['Species'], 9)
[[1]]
iris["Species"]
[[2]]
[1] 9
Но посмотрите, что происходит, когда мы используем lapply
(я включил только вывод внутреннего оператора print
):
lapply('Species', function(x) match_call_fun(iris[x], 9))
[[1]]
iris[x]
[[2]]
[1] 9
Поскольку match.call
получает литерал аргументов, переданных ему, он получает iris[x]
, а не правильно интерпретированный iris['Species']
, который мы хотим. Когда мы передаем эти аргументы в ftable
с do.call
, он ищет объект x
в текущей среде, а затем возвращает ошибку, когда не может его найти. Нам нужно интерпретировать
Как вы уже видели, добавление envir = parent.frame()
устраняет проблему. Это связано с тем, что добавление этого аргумента указывает do.call
вычислять iris[x]
в родительском фрейме, что является анонимной функцией в lapply
, где x
имеет свой правильный смысл. Чтобы увидеть это в действии, давайте сделаем еще одну простую функцию, которая использует do.call
для печати ls
с 3-х различных уровней среды:
z <- function(...) {
print(do.call(ls, list()))
print(do.call(ls, list(), envir = parent.frame()))
print(do.call(ls, list(), envir = parent.frame(2)))
}
Когда мы вызываем z()
из глобальной среды, мы видим пустую среду внутри функции, затем глобальную среду:
z()
character(0) # Interior function environment
[1] "match_call_fun" "y" "z" # GlobalEnv
[1] "match_call_fun" "y" "z" # GlobalEnv
Но когда мы вызываем из lapply
, мы видим, что один уровень parent.frame
является анонимной функцией в lapply
:
lapply(1, z)
character(0) # Interior function environment
[1] "FUN" "i" "X" # lapply
[1] "match_call_fun" "y" "z" # GlobalEnv
Итак, добавив envir = parent.frame()
, do.call
знает, что нужно оценить iris[x]
в среде lapply
, где он знает, что x
на самом деле 'Species'
, и он оценивает правильно.
mytable_envir <- function(...) {
tab <- do.call(what = ftable,
args = as.list(match.call()[-1]),
envir = parent.frame())
prop <- prop.table(x = tab,
margin = 2) * 100
bind <- cbind(as.matrix(x = tab),
as.matrix(x = prop))
margin <- addmargins(A = bind,
margin = 1)
round(x = margin,
digits = 1)
}
# This works!
lapply(X = c("breaks","wool","tension"),
FUN = function(x) mytable_envir(warpbreaks[x],row.vars = 1))
Что касается того, почему добавление envir = parent.frame()
имеет значение, так как это вариант по умолчанию. Я не уверен на 100%, но я предполагаю, что при использовании аргумента по умолчанию parent.frame
оценивается внутри функции do.call
, возвращая среду, в которой запускается do.call
. Однако то, что мы делаем, вызывает parent.frame
за пределами do.call
, что означает, что он возвращает на один уровень выше, чем версия по умолчанию.
Вот тестовая функция, которая принимает parent.frame()
в качестве значения по умолчанию:
fun <- function(y=parent.frame()) {
print(y)
print(parent.frame())
print(parent.frame(2))
print(parent.frame(3))
}
Теперь посмотрим, что происходит, когда мы вызываем его изнутри lapply
как с аргументом parent.frame()
, так и без него:
lapply(1, function(y) fun())
<environment: 0x12c5bc1b0> # y argument
<environment: 0x12c5bc1b0> # parent.frame called inside
<environment: 0x12c5bc760> # 1 level up = lapply
<environment: R_GlobalEnv> # 2 levels up = globalEnv
lapply(1, function(y) fun(y = parent.frame()))
<environment: 0x104931358> # y argument
<environment: 0x104930da8> # parent.frame called inside
<environment: 0x104931358> # 1 level up = lapply
<environment: R_GlobalEnv> # 2 levels up = globalEnv
В первом примере значение y
такое же, как и при вызове parent.frame()
внутри функции. Во втором примере значение y
совпадает со значением среды на один уровень выше (внутри lapply
). Таким образом, хотя они выглядят одинаково, они на самом деле делают разные вещи: в первом примере parent.frame
оценивается внутри функции, когда он видит, что аргумента y=
нет, во втором, parent.frame
- это вычисляется в lapply
анонимной функции сначала , перед вызовом fun
, а затем передается в нее.