使用lapply或R中的用户定义函数中的match.call求值

时间:2019-04-23 14:48:49

标签: r function lapply evaluation do.call

我在ftable周围写了一个包装器,因为我需要为许多变量计算带有频率和百分比的平面表。由于类“公式”的ftable方法使用非标准评估,因此该包装器依赖于do.callmatch.call来允许使用子集参数(有关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紧密相关,因此以下是我的问题的总结:

  • 带有do.callmatch.call的包装器不能与lapplywith一起使用。
  • 没有do.callmatch.call的包装器不能使用ftable的子集参数。

我的问题总结:

  • 如何将do.callmatch.call的包装与lapplywith一起使用?我有避免使用lapplywith的想法,但我希望了解和更正这些错误以提高我对R的了解。
  • 更一般地说,我如何编写一个解决这两个问题的包装器(即,它既可以使用子集参数,也可以与lapplywith一起使用)?
  • lapply的错误是否与?lapply中的以下注释相关?
      

    由于历史原因,lapply创建的调用未评估,   并据此编写了代码(例如bquote)。这个   表示记录的呼叫始终为FUN(X [[i]],...)形式,   用我替换为当前(整数或双精度)索引。这不是   通常是一个问题,但可能是FUN使用sys.call或match.call   或者它是使用该调用的原始函数。这个   意味着通常可以更安全地使用   包装器,例如lapply(ll,function(x)is.numeric(x))是   需要确保为is.numeric进行方法分派   正确。

3 个答案:

答案 0 :(得分:2)

match.calllapply一起使用的问题是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获得传递给它的 literal 参数,因此它接收到iris[x],而不是我们想要的经过正确解释的iris['Species']。当我们使用ftable将这些参数传递到do.call时,它将在当前环境中寻找对象x,然后在找不到对象时返回错误。我们需要解释

如您所见,添加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知道在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.framedo.call函数的内部中求值,返回{{ 1}}运行。但是,我们正在调用do.call outside 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求值,然后将其传递给它。

答案 1 :(得分:0)

由于您只想将所有传递给ftable的参数传递给u,所以不需要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(X = c("breaks",
             "wool",
             "tension"),
       FUN = function(x) mytable(warpbreaks[x],
                                 row.vars = 1))

如果要将所有3个变量都放在一个表中

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

答案 2 :(得分:0)

由于SandBox,包装程序变成了:

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

我现在可以做所有需要的事情,例如子设置和使用lapply

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

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