Нестандартная оценка в пользовательской функции с помощью lapply или с помощью R - PullRequest
2 голосов
/ 23 апреля 2019

Я написал оболочку около ftable, потому что мне нужно вычислять плоские таблицы с частотой и процентом для многих переменных.Так как метод ftable для класса «формула» использует нестандартную оценку, оболочка использует do.call и match.call, чтобы разрешить использование аргумента subset ftable (подробнее в моего предыдущеговопрос ).

mytable <- function(...) {
    do.call(what = ftable,
            args = as.list(x = match.call()[-1]))
    # etc
}

Однако я не могу использовать эту оболочку с lapply или with:

# example 1: error with "lapply"
lapply(X = warpbreaks[c("breaks",
                        "wool",
                        "tension")],
       FUN = mytable,
       row.vars = 1)

Error in (function (x, ...)  : object 'X' not found

# example 2: error with "with"
with(data = warpbreaks[warpbreaks$tension == "L", ],
     expr = mytable(wool))

Error in (function (x, ...)  : object 'wool' not found

Эти ошибки, вероятно, связаны с match.callне оценивается в правильной среде.

Поскольку этот вопрос тесно связан с моим предыдущим , вот сумма моих проблем:

  • Оболочка с do.call и match.call не может использоваться с lapply или with.
  • Оболочка без do.call и match.call не может использовать аргумент subset ftable.

И сумма моих вопросов:

  • Как написать оболочку, которая позволяет использовать аргумент subset ftable и использоваться сlapply и with?У меня есть идеи, чтобы избежать использования lapply и with, но я пытаюсь понять и исправить эти ошибки, чтобы улучшить мои знания о R.
  • Является ли ошибка с lapply связанной со следующимзаписка от ?lapply?

    По историческим причинам вызовы, созданные lapply, не оценены, и был написан код (например, bquote), который полагается на это.Это означает, что записанный вызов всегда имеет форму FUN (X [[i]], ...), где i заменяется текущим (целым или двойным) индексом.Обычно это не проблема, но это может быть, если FUN использует sys.call или match.call или если это примитивная функция, которая использует вызов.Это означает, что часто безопаснее вызывать примитивные функции с помощью обертки, поэтому, например, требуется lapply (ll, function (x) is.numeric (x)), чтобы гарантировать, что отправка метода для is.numeric происходит правильно.

Ответы [ 3 ]

2 голосов
/ 24 апреля 2019

Проблема использования 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, а затем передается в нее.

0 голосов
/ 24 апреля 2019

Благодаря этой проблеме оболочка стала:

# function 1
mytable <- function(...) {
    do.call(what = ftable,
            args = as.list(x = match.call()[-1]),
            envir = parent.frame())
    # etc
}

Или:

# function 2
mytable <- function(...) {
    mc <- match.call()
    mc[[1]] <- quote(expr = ftable)
    eval.parent(expr = mc)
    # etc
}

Теперь я могу использовать аргумент subset ftable и использовать оболочку в lapply:

lapply(X = warpbreaks[c("wool",
                        "tension")],
       FUN = function(x) mytable(formula = x ~ breaks,
                                 data = warpbreaks,
                                 subset = breaks < 15))

Однако я не понимаю, почему я должен предоставить envir = parent.frame() для do.call, поскольку это аргумент по умолчанию.

Что еще более важно, эти методы не решают другую проблему: Я не могу использовать аргумент subset ftable с mapply .

0 голосов
/ 24 апреля 2019

Поскольку вам нужно только передать все аргументы, передаваемые в ftable, вам не нужен do.call ().

mytable <- function(...) {
  tab <- ftable(...)
  prop <- prop.table(x = tab,
                     margin = 2) * 100
  bind <- cbind(as.matrix(x = tab),
                as.matrix(x = prop))
  margin <- addmargins(A = bind,
                       margin = 1)
  return(round(x = margin,
               digits = 1))
}

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

lapply(X = c("breaks",
             "wool",
             "tension"),
       FUN = function(x) mytable(warpbreaks[x],
                                 row.vars = 1))

Если вы хотите, чтобы все 3 переменные были в 1 таблице

warpbreaks$newVar <- LETTERS[3:4]

lapply(X = cbind("c(\"breaks\", \"wool\", \"tension\")",
             "c(\"newVar\", \"tension\",\"wool\")"),
       FUN = function(X)
        eval(parse(text=paste("mytable(warpbreaks[,",X,"],
                                 row.vars = 1)")))
)
...