R - model.frame() 和非标准评估
R - model.frame() and non-standard evaluation
我对我尝试编写的函数的行为感到困惑。我的例子来自 survival
包,但我认为这个问题比那更笼统。基本上就是下面的代码
library(survival)
data(bladder) ## this will load "bladder", "bladder1" and "bladder2"
mod_init <- coxph(Surv(start, stop, event) ~ rx + number, data = bladder2, method = "breslow")
survfit(mod_init)
会产生一个我感兴趣的对象。但是,当我把它写在函数中时,
my_function <- function(formula, data) {
mod_init <- coxph(formula = formula, data = data, method = "breslow")
survfit(mod_init)
}
my_function(Surv(start, stop, event) ~ rx + number, data = bladder2)
该函数将 return 最后一行出错:
Error in eval(predvars, data, env) :
invalid 'envir' argument of type 'closure'
10 eval(predvars, data, env)
9 model.frame.default(formula = Surv(start, stop, event) ~ rx +
number, data = data)
8 stats::model.frame(formula = Surv(start, stop, event) ~ rx +
number, data = data)
7 eval(expr, envir, enclos)
6 eval(temp, environment(formula$terms), parent.frame())
5 model.frame.coxph(object)
4 stats::model.frame(object)
3 survfit.coxph(mod_init)
2 survfit(mod_init)
1 my_function(Surv(start, stop, event) ~ rx + number, data = bladder2)
我很好奇是否有明显的缺失,或者这种行为是否正常。我觉得很奇怪,因为在 my_function
的环境中,当 运行 代码的第一部分时,我将拥有与全局环境中相同的对象。
编辑:我还从 survival
包的作者 Terry Therneau 那里收到了有用的信息。这是他的回答:
这个问题源于model.frame的评价不规范。我发现的唯一出路是将 model.frame=TRUE 添加到原始 coxph 调用中。我认为这是 R 中的一个严重设计缺陷。非标准评估就像阴暗面——一条诱人而容易的道路,但总是以糟糕的方式结束。
特里 T.
诊断
来自错误信息:
2 survfit(mod_init, newdata = base_case)
1 my_function(Surv(start, stop, event) ~ rx + number, data = bladder2)
在模型拟合过程中,问题显然不是 coxph
,而是 survfit
。
从这条消息中:
10 eval(predvars, data, env)
9 model.frame.default(formula = Surv(start, stop, event) ~ rx +
number, data = data)
我可以看出问题是在 survfit
的早期阶段,函数 model.frame.default()
找不到包含相关数据的 模型框架 在公式 Surv(start, stop, event) ~ rx + number
中。因此它抱怨。
什么是模型框?
模型框架由传递给拟合例程的 data
参数形成,如 lm()
、glm()
和 mgcv:::gam()
。它是一个数据框,行数与data
相同,但是:
- 删除所有未被
formula
引用的变量
- 添加很多属性,其中最重要的是
envrionement
大多数模型拟合例程,如 lm()
、glm()
和 mgcv:::gam()
,默认情况下会将模型框架保留在它们的拟合对象中。这样做的好处是,如果我们稍后调用 predict
,并且没有提供 newdata
,它将从该模型框架中查找数据以进行评估。但是,一个明显的缺点是它会大大增加你的拟合对象的大小。
不过,survival:::coxph()
是个例外。它会默认 而不是 在它们的拟合对象中保留这样的模型框架。好吧,很明显,这会使生成的拟合对象的尺寸小得多,但是,让您遇到您遇到的问题。 如果我们要survival:::coxph()
保留这个模型框架,就用model = TRUE
这个函数。
用survial:::coxph()
测试
library(survival); data(bladder)
my_function <- function(myformula, mydata, keep.mf = TRUE) {
fit <- coxph(myformula, mydata, method = "breslow", model = keep.mf)
survfit(fit)
}
现在,这个函数调用将失败,如您所见:
my_function(Surv(start, stop, event) ~ rx + number, bladder2, keep.mf = FALSE)
但是这个函数调用会成功:
my_function(Surv(start, stop, event) ~ rx + number, bladder2, keep.mf = TRUE)
lm()
的行为相同
我们实际上可以在 lm()
:
中演示相同的行为
## generate some toy data
foo <- data.frame(x = seq(0, 1, length = 20), y = seq(0, 1, length = 20) + rnorm(20, 0, 0.15))
## a wrapper function
bar <- function(myformula, mydata, keep.mf = TRUE) {
fit <- lm(myformula, mydata, model = keep.mf)
predict.lm(fit)
}
现在这将成功,保持模型框架:
bar(y ~ x - 1, foo, keep.mf = TRUE)
虽然这会失败,但会丢弃模型框架:
bar(y ~ x - 1, foo, keep.mf = FALSE)
使用参数 newdata
?
请注意,我的 lm()
示例有点人为,因为我们实际上可以使用 predict.lm()
中的 newdata
参数来解决此问题:
bar1 <- function(myformula, mydata, keep.mf = TRUE) {
fit <- lm(myformula, mydata, model = keep.mf)
predict.lm(fit, newdata = lapply(mydata, mean))
}
现在是否保留模型框架,两者都会成功:
bar1(y ~ x - 1, foo, keep.mf = TRUE)
bar1(y ~ x - 1, foo, keep.mf = FALSE)
那么你可能会想:我们可以为 survfit()
做同样的事情吗?
survfit()
是一个通用函数,在您的代码中,您实际上是在调用 survfit.coxph()
。这个函数确实有一个 newdata
参数。文档内容如下:
newdata:
a data frame with the same variable names as those that appear in the
‘coxph’ formula. ... ... Default is the mean of the covariates used in the
‘coxph’ fit.
所以,让我们试试:
my_function1 <- function(myformula, mydata) {
mtrace.off()
fit <- coxph(myformula, mydata, method = "breslow")
survival:::survfit.coxph(fit, newdata = lapply(mydata, mean))
}
我们希望这项工作:
my_function1(Surv(start, stop, event) ~ rx + number, bladder2)
但是:
Error in is.data.frame(data) (from #5) : object 'mydata' not found
1: my_function1(Surv(start, stop, event) ~ rx + number, bladder2)
2: #5: survival:::survfit.coxph(fit, lapply(mydata, mean))
3: stats::model.frame(object)
4: model.frame.coxph(object)
5: eval(temp, environment(formula$terms), parent.frame())
6: eval(expr, envir, enclos)
7: stats::model.frame(formula = Surv(start, stop, event) ~ rx + number, data =
8: model.frame.default(formula = Surv(start, stop, event) ~ rx + number, data
9: is.data.frame(data)
注意,虽然我们传入了newdata
,但在构建模型框架时并没有用到:
3: stats::model.frame(object)
只有 object
,一个拟合模型的副本,被传递给 model.frame.default()
。
这与 predict.lm()
、predict.glm()
和 mgcv:::predict.gam()
中发生的情况非常不同。在这些例程中,newdata
被传递给 model.frame.default()
。例如lm()
中有:
m <- model.frame(Terms, newdata, na.action = na.action, xlev = object$xlevels)
我不使用 survival
包,所以不确定 newdata
在这个包中如何工作。所以我认为我们真的需要一些专家来解释这一点。
我想如果你的
Surv(start, stop, event) ~ rx + number
作为一个参数,它没有被正确创建。尝试把
is.Surv(formula)
作为函数中的第一行。我怀疑它行不通,那么我建议使用 apply 函数族。
我对我尝试编写的函数的行为感到困惑。我的例子来自 survival
包,但我认为这个问题比那更笼统。基本上就是下面的代码
library(survival)
data(bladder) ## this will load "bladder", "bladder1" and "bladder2"
mod_init <- coxph(Surv(start, stop, event) ~ rx + number, data = bladder2, method = "breslow")
survfit(mod_init)
会产生一个我感兴趣的对象。但是,当我把它写在函数中时,
my_function <- function(formula, data) {
mod_init <- coxph(formula = formula, data = data, method = "breslow")
survfit(mod_init)
}
my_function(Surv(start, stop, event) ~ rx + number, data = bladder2)
该函数将 return 最后一行出错:
Error in eval(predvars, data, env) :
invalid 'envir' argument of type 'closure'
10 eval(predvars, data, env)
9 model.frame.default(formula = Surv(start, stop, event) ~ rx +
number, data = data)
8 stats::model.frame(formula = Surv(start, stop, event) ~ rx +
number, data = data)
7 eval(expr, envir, enclos)
6 eval(temp, environment(formula$terms), parent.frame())
5 model.frame.coxph(object)
4 stats::model.frame(object)
3 survfit.coxph(mod_init)
2 survfit(mod_init)
1 my_function(Surv(start, stop, event) ~ rx + number, data = bladder2)
我很好奇是否有明显的缺失,或者这种行为是否正常。我觉得很奇怪,因为在 my_function
的环境中,当 运行 代码的第一部分时,我将拥有与全局环境中相同的对象。
编辑:我还从 survival
包的作者 Terry Therneau 那里收到了有用的信息。这是他的回答:
这个问题源于model.frame的评价不规范。我发现的唯一出路是将 model.frame=TRUE 添加到原始 coxph 调用中。我认为这是 R 中的一个严重设计缺陷。非标准评估就像阴暗面——一条诱人而容易的道路,但总是以糟糕的方式结束。 特里 T.
诊断
来自错误信息:
2 survfit(mod_init, newdata = base_case)
1 my_function(Surv(start, stop, event) ~ rx + number, data = bladder2)
在模型拟合过程中,问题显然不是 coxph
,而是 survfit
。
从这条消息中:
10 eval(predvars, data, env)
9 model.frame.default(formula = Surv(start, stop, event) ~ rx +
number, data = data)
我可以看出问题是在 survfit
的早期阶段,函数 model.frame.default()
找不到包含相关数据的 模型框架 在公式 Surv(start, stop, event) ~ rx + number
中。因此它抱怨。
什么是模型框?
模型框架由传递给拟合例程的 data
参数形成,如 lm()
、glm()
和 mgcv:::gam()
。它是一个数据框,行数与data
相同,但是:
- 删除所有未被
formula
引用的变量
- 添加很多属性,其中最重要的是
envrionement
大多数模型拟合例程,如 lm()
、glm()
和 mgcv:::gam()
,默认情况下会将模型框架保留在它们的拟合对象中。这样做的好处是,如果我们稍后调用 predict
,并且没有提供 newdata
,它将从该模型框架中查找数据以进行评估。但是,一个明显的缺点是它会大大增加你的拟合对象的大小。
不过,survival:::coxph()
是个例外。它会默认 而不是 在它们的拟合对象中保留这样的模型框架。好吧,很明显,这会使生成的拟合对象的尺寸小得多,但是,让您遇到您遇到的问题。 如果我们要survival:::coxph()
保留这个模型框架,就用model = TRUE
这个函数。
用survial:::coxph()
library(survival); data(bladder)
my_function <- function(myformula, mydata, keep.mf = TRUE) {
fit <- coxph(myformula, mydata, method = "breslow", model = keep.mf)
survfit(fit)
}
现在,这个函数调用将失败,如您所见:
my_function(Surv(start, stop, event) ~ rx + number, bladder2, keep.mf = FALSE)
但是这个函数调用会成功:
my_function(Surv(start, stop, event) ~ rx + number, bladder2, keep.mf = TRUE)
lm()
我们实际上可以在 lm()
:
## generate some toy data
foo <- data.frame(x = seq(0, 1, length = 20), y = seq(0, 1, length = 20) + rnorm(20, 0, 0.15))
## a wrapper function
bar <- function(myformula, mydata, keep.mf = TRUE) {
fit <- lm(myformula, mydata, model = keep.mf)
predict.lm(fit)
}
现在这将成功,保持模型框架:
bar(y ~ x - 1, foo, keep.mf = TRUE)
虽然这会失败,但会丢弃模型框架:
bar(y ~ x - 1, foo, keep.mf = FALSE)
使用参数 newdata
?
请注意,我的 lm()
示例有点人为,因为我们实际上可以使用 predict.lm()
中的 newdata
参数来解决此问题:
bar1 <- function(myformula, mydata, keep.mf = TRUE) {
fit <- lm(myformula, mydata, model = keep.mf)
predict.lm(fit, newdata = lapply(mydata, mean))
}
现在是否保留模型框架,两者都会成功:
bar1(y ~ x - 1, foo, keep.mf = TRUE)
bar1(y ~ x - 1, foo, keep.mf = FALSE)
那么你可能会想:我们可以为 survfit()
做同样的事情吗?
survfit()
是一个通用函数,在您的代码中,您实际上是在调用 survfit.coxph()
。这个函数确实有一个 newdata
参数。文档内容如下:
newdata:
a data frame with the same variable names as those that appear in the ‘coxph’ formula. ... ... Default is the mean of the covariates used in the ‘coxph’ fit.
所以,让我们试试:
my_function1 <- function(myformula, mydata) {
mtrace.off()
fit <- coxph(myformula, mydata, method = "breslow")
survival:::survfit.coxph(fit, newdata = lapply(mydata, mean))
}
我们希望这项工作:
my_function1(Surv(start, stop, event) ~ rx + number, bladder2)
但是:
Error in is.data.frame(data) (from #5) : object 'mydata' not found
1: my_function1(Surv(start, stop, event) ~ rx + number, bladder2)
2: #5: survival:::survfit.coxph(fit, lapply(mydata, mean))
3: stats::model.frame(object)
4: model.frame.coxph(object)
5: eval(temp, environment(formula$terms), parent.frame())
6: eval(expr, envir, enclos)
7: stats::model.frame(formula = Surv(start, stop, event) ~ rx + number, data =
8: model.frame.default(formula = Surv(start, stop, event) ~ rx + number, data
9: is.data.frame(data)
注意,虽然我们传入了newdata
,但在构建模型框架时并没有用到:
3: stats::model.frame(object)
只有 object
,一个拟合模型的副本,被传递给 model.frame.default()
。
这与 predict.lm()
、predict.glm()
和 mgcv:::predict.gam()
中发生的情况非常不同。在这些例程中,newdata
被传递给 model.frame.default()
。例如lm()
中有:
m <- model.frame(Terms, newdata, na.action = na.action, xlev = object$xlevels)
我不使用 survival
包,所以不确定 newdata
在这个包中如何工作。所以我认为我们真的需要一些专家来解释这一点。
我想如果你的
Surv(start, stop, event) ~ rx + number
作为一个参数,它没有被正确创建。尝试把
is.Surv(formula)
作为函数中的第一行。我怀疑它行不通,那么我建议使用 apply 函数族。