lapply 或 R 中的用户定义函数中的非标准评估
Non-standard evaluation in a user-defined function with lapply or with in R
我围绕 ftable
编写了一个包装器,因为我需要为许多变量计算具有频率和百分比的平面表。由于 class "formula" 的 ftable
方法使用非标准评估,包装器依赖 do.call
和 match.call
来允许使用 subset
ftable
的参数(my previous question 中有更多详细信息)。
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
没有在正确的环境中进行评估。
由于这个问题与my previous one密切相关,这里总结一下我的问题:
do.call
和 match.call
的包装不能与 lapply
或 with
一起使用。
- 没有
do.call
和match.call
的wrapper不能使用ftable
的subset
参数。
以及我的问题总结:
- 我如何编写一个包装器,既允许使用
ftable
的 subset
参数又允许与 lapply
和 with
一起使用?我有避免使用 lapply
和 with
的想法,但我希望了解并更正这些错误以提高我对 R 的了解。
lapply
的错误是否与 ?lapply
的以下注释有关?
For historical reasons, the calls created by lapply are unevaluated,
and code has been written (e.g., bquote) that relies on this. This
means that the recorded call is always of the form FUN(X[[i]], ...),
with i replaced by the current (integer or double) index. This is not
normally a problem, but it can be if FUN uses sys.call or match.call
or if it is a primitive function that makes use of the call. This
means that it is often safer to call primitive functions with a
wrapper, so that e.g. lapply(ll, function(x) is.numeric(x)) is
required to ensure that method dispatch for is.numeric occurs
correctly.
因为您只想传递所有传递给 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
}
我现在可以使用 ftable
的 subset
参数,并使用 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.call
与 lapply
一起使用的问题是 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.frame
在 lapply
匿名函数 中首先被评估 ,然后调用 fun
,然后被传递给它。
我围绕 ftable
编写了一个包装器,因为我需要为许多变量计算具有频率和百分比的平面表。由于 class "formula" 的 ftable
方法使用非标准评估,包装器依赖 do.call
和 match.call
来允许使用 subset
ftable
的参数(my previous question 中有更多详细信息)。
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
没有在正确的环境中进行评估。
由于这个问题与my previous one密切相关,这里总结一下我的问题:
do.call
和match.call
的包装不能与lapply
或with
一起使用。- 没有
do.call
和match.call
的wrapper不能使用ftable
的subset
参数。
以及我的问题总结:
- 我如何编写一个包装器,既允许使用
ftable
的subset
参数又允许与lapply
和with
一起使用?我有避免使用lapply
和with
的想法,但我希望了解并更正这些错误以提高我对 R 的了解。 lapply
的错误是否与?lapply
的以下注释有关?For historical reasons, the calls created by lapply are unevaluated, and code has been written (e.g., bquote) that relies on this. This means that the recorded call is always of the form FUN(X[[i]], ...), with i replaced by the current (integer or double) index. This is not normally a problem, but it can be if FUN uses sys.call or match.call or if it is a primitive function that makes use of the call. This means that it is often safer to call primitive functions with a wrapper, so that e.g. lapply(ll, function(x) is.numeric(x)) is required to ensure that method dispatch for is.numeric occurs correctly.
因为您只想传递所有传递给 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
}
我现在可以使用 ftable
的 subset
参数,并使用 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.call
与 lapply
一起使用的问题是 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.frame
在 lapply
匿名函数 中首先被评估 ,然后调用 fun
,然后被传递给它。