lapply 或 R 中的用户定义函数中的非标准评估

Non-standard evaluation in a user-defined function with lapply or with in R

我围绕 ftable 编写了一个包装器,因为我需要为许多变量计算具有频率和百分比的平面表。由于 class "formula" 的 ftable 方法使用非标准评估,包装器依赖 do.callmatch.call 来允许使用 subset ftable 的参数(my previous question 中有更多详细信息)。

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

但是,我不能将此包装器与 lapplywith 一起使用:

# 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 没有在正确的环境中进行评估。

由于这个问题与my previous one密切相关,这里总结一下我的问题:

以及我的问题总结:

因为您只想传递所有传递给 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 分别为每个变量创建一个 table 我不知道这是否是您想要的。

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

如果您想要 1 个中的所有 3 个变量 table

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)")))
)

感谢 this issue,包装器变成了:

# 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
}

我现在可以使用 ftablesubset 参数,并使用 lapply 中的包装器:

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

但是我不明白为什么我必须提供 envir = parent.frame()do.call 因为它是默认参数。

更重要的是,这些方法没有解决另一个问题:

match.calllapply 一起使用的问题是 match.call returns 传递给它的 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']。当我们用do.call将这些参数传递给ftable时,它会在当前环境中寻找一个对象x,然后returns当它找不到时就报错。我们需要解读

如您所见,添加 envir = parent.frame() 可解决问题。这是因为,添加该参数会告诉 do.call 在父框架中计算 iris[x],这是 lapply 中的匿名函数,其中 x 具有正确的含义。为了实际看到这一点,让我们创建另一个使用 do.call 从 3 个不同环境级别打印 ls 的简单函数:

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 知道在 lapply 环境中计算 iris[x],它知道 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,这意味着它returns比默认版本高一级。

这里有一个测试函数,默认值为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.framelapply 匿名函数 中首先被评估 ,然后调用 fun,然后被传递给它。