我在ftable
周围写了一个包装器,因为我需要为许多变量计算带有频率和百分比的平面表。由于类“公式”的ftable
方法使用非标准评估,因此该包装器依赖于do.call
和match.call
来允许使用子集参数(有关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
的包装器不能使用ftable
的子集参数。我的问题总结:
do.call
和match.call
的包装与lapply
和with
一起使用?我有避免使用lapply
和with
的想法,但我希望了解和更正这些错误以提高我对R的了解。lapply
和with
一起使用)?lapply
的错误是否与?lapply
中的以下注释相关?
由于历史原因,lapply创建的调用未评估, 并据此编写了代码(例如bquote)。这个 表示记录的呼叫始终为FUN(X [[i]],...)形式, 用我替换为当前(整数或双精度)索引。这不是 通常是一个问题,但可能是FUN使用sys.call或match.call 或者它是使用该调用的原始函数。这个 意味着通常可以更安全地使用 包装器,例如lapply(ll,function(x)is.numeric(x))是 需要确保为is.numeric进行方法分派 正确。
答案 0 :(得分:2)
将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
获得传递给它的 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.frame
在do.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
,因为它是默认参数。