如何正确地将拟合线性模型(通过 lm)“dput”到 ASCII 文件并稍后重新创建?
How to correctly `dput` a fitted linear model (by `lm`) to an ASCII file and recreate it later?
我想将一个 lm
对象保存到一个文件中并将其重新加载到另一个程序中。我知道我可以通过 saveRDS
/readRDS
通过 writing/reading 二进制文件来做到这一点,但我想要一个 ASCII 文件而不是二进制文件。在更一般的层面上,我想知道为什么我在 dput
输出中阅读的习语通常没有像我期望的那样表现。
以下是进行简单拟合以及成功和不成功重新创建模型的示例:
dat_train <- data.frame(x=1:4, z=c(1, 2.1, 2.9, 4))
fit <- lm(z ~ x, dat_train)
rm(dat_train) # Just to make sure fit is not dependent upon `dat_train existence`
dat_score <- data.frame(x=c(1.5, 3.5))
## This works (of course)
predict(fit, dat_score)
# 1 2
# 1.52 3.48
保存到二进制文件有效:
##
saveRDS(fit, "model.RDS")
fit2 <- readRDS("model.RDS")
predict(fit2, dat_score)
# 1 2
# 1.52 3.48
这也是如此(dput
它在 R 会话中而不是文件):
fit2 <- eval(dput(fit))
predict(fit2, dat_score)
# 1 2
# 1.52 3.48
但是如果我将文件保存到磁盘,我不知道如何恢复到正常状态:
dput(fit, file = "model.R")
fit3 <- source("model.R")$value
# Error in is.data.frame(data): object 'dat_train' not found
predict(fit3, dat_score)
# Error in predict(fit3, dat_score): object 'fit3' not found
尝试用 eval
明确说明也不起作用:
##
dput(fit, file="model.R")
fit4 <- eval(parse(text=paste(readLines("model.R"), collapse=" ")))
# Error in is.data.frame(data): object 'dat_train' not found
predict(fit4, dat_score)
# Error in predict(fit4, dat_score): object 'fit4' not found
在上述两种情况下,我希望 fit3
和 fit4
都能正常工作,但它们不会重新编译成我可以与 [=24= 一起使用的 lm
对象].
任何人都可以告诉我如何将模型保存到具有 structure(...)
类 ASCII 结构的文件中,然后将其作为我可以使用的 lm
对象重新读回在 predict()
?为什么我目前的方法不起作用?
第 1 步:
您需要控制 de-parsing 个选项:
dput(fit, control = c("quoteExpressions", "showAttributes"), file = "model.R")
您可以在 ?.deparseOpts
中阅读有关所有可能选项的更多信息。
"quoteExpressions" 用 quote
包装所有调用/表达式/语言,这样当你稍后 re-parse 时它们就不会被计算。注:
source
正在解析;
call
字段在你适合的 "lm" 对象是一个调用:
fit$call
# lm(formula = z ~ x, data = dat_train)
因此,如果没有 "quoteExpressions",R 将尝试在解析期间评估 lm
调用。如果我们评估它,它正在拟合一个线性模型,R 将旨在找到 dat_train
,这将不存在于您的新 R 会话中。
"showAttributes" 是另一个强制选项,因为 "lm" 对象具有 class 属性。您当然不想丢弃所有 class 属性而只导出一个普通的 "list" 对象,对吗?此外,"lm" 对象中的许多元素,如 model
(模型框架)、qr
(紧凑的 QR 矩阵)和 terms
(术语信息)等都具有属性。你想把它们全部保留下来。
如果不设置control
,默认设置为:
control = c("keepNA", "keepInteger", "showAttributes")
将被使用。如您所见,没有"quoteExpressions",所以您会遇到麻烦。
您还可以指定 "keepInteger" 和 "keepNA",但我认为不需要 "lm" 对象。
------
第 2 步:
上述步骤将使 source
正常工作。您可以恢复您的模型:
fit1 <- source("model.R")$value
但是,summary
和 predict
等通用函数尚未准备好运行。为什么?
关键问题是fit1
中的terms
对象并不是真正的"terms"对象,而只是一个公式(它甚至不是公式,但只有 "language" 对象没有 "formula" class!)。只需比较 fit$terms
和 fit1$terms
,您就会发现差异。不要惊讶;我们之前设置了 "quoteExpressions"。虽然这绝对有助于防止 call
的计算,但它具有 terms
的 side-effect。所以我们需要尽可能地重建terms
。
幸运的是,这样做就足够了:
fit1$terms <- terms.formula(fit1$terms)
虽然这仍然不能恢复 fit$terms
中的所有信息(比如变量 classes 丢失),但它很容易成为一个有效的 "terms" 对象。
为什么 "terms" 对象是关键的?因为所有通用功能都依赖它。您可能不需要了解更多,因为它确实是技术性的,所以我将在这里停止。
完成后,我们就可以成功使用 predict
(和 summary
):
predict(fit1) ## no `newdata` given, using model frame `fit1$model`
# 1 2 3 4
#1.03 2.01 2.99 3.97
predict(fit1, dat_score) ## with `newdata`
# 1 2
#1.52 3.48
--------
结束语:
虽然我已经向您展示了如何让事情正常进行,但我一般不建议您这样做。当您将模型拟合到大型数据集时,"lm" 对象将非常大,例如,residuals
、fitted.values
是长向量,而 qr
和 model
是巨大的矩阵/数据框。所以想想这个。
这是一个重要的更新!
如前一个答案所述,最具挑战性的一点是尽我们所能恢复 $terms
。使用 terms.formula
的建议方法适用于 OP 的示例,但不适用于以下 bs()
和 poly()
:
dat <- data.frame(x1 = runif(20), x2 = runif(20), x3 = runif(20), y = rnorm(20))
library(splines)
fit <- lm(y ~ bs(x1, df = 3) + poly(x2, degree = 3) + x3, data = dat)
rm(dat)
如果我们按照前面的回答:
dput(fit, control = c("quoteExpressions", "showAttributes"), file = "model.R")
fit1 <- source("model.R")$value
fit1$terms <- terms.formula(fit1$terms)
我们会看到 summary.lm
和 anova.lm
可以正常工作,但 predict.lm
:
predict(fit1, newdata = data.frame(x1 = 0.5, x2 = 0.5, x3 = 0.5))
Error in bs(x1, df = 3) : could not find function "bs"
这是因为缺少 $terms
的 ".Environment"
属性。我们需要
environment(fit1$terms) <- .GlobalEnv
现在 运行 以上 predict
我们再次看到不同的错误:
Error in poly(x2, degree = 3) :
'degree' must be less than number of unique points
这是因为我们缺少 "predvars"
属性来安全/正确预测 bs()
和 poly()
。
补救措施是我们需要dput
这样的特殊属性另外:
dput(attr(fit$terms, "predvars"), control = "quoteExpressions", file = "predvars.R")
然后阅读并添加它
attr(fit1$terms, "predvars") <- source("predvars.R")$value
现在 运行ning predict
工作正常。
请注意 $terms
的 "dataClass"
属性也丢失了,但这似乎不会对任何通用函数造成任何问题。
我想将一个 lm
对象保存到一个文件中并将其重新加载到另一个程序中。我知道我可以通过 saveRDS
/readRDS
通过 writing/reading 二进制文件来做到这一点,但我想要一个 ASCII 文件而不是二进制文件。在更一般的层面上,我想知道为什么我在 dput
输出中阅读的习语通常没有像我期望的那样表现。
以下是进行简单拟合以及成功和不成功重新创建模型的示例:
dat_train <- data.frame(x=1:4, z=c(1, 2.1, 2.9, 4))
fit <- lm(z ~ x, dat_train)
rm(dat_train) # Just to make sure fit is not dependent upon `dat_train existence`
dat_score <- data.frame(x=c(1.5, 3.5))
## This works (of course)
predict(fit, dat_score)
# 1 2
# 1.52 3.48
保存到二进制文件有效:
##
saveRDS(fit, "model.RDS")
fit2 <- readRDS("model.RDS")
predict(fit2, dat_score)
# 1 2
# 1.52 3.48
这也是如此(dput
它在 R 会话中而不是文件):
fit2 <- eval(dput(fit))
predict(fit2, dat_score)
# 1 2
# 1.52 3.48
但是如果我将文件保存到磁盘,我不知道如何恢复到正常状态:
dput(fit, file = "model.R")
fit3 <- source("model.R")$value
# Error in is.data.frame(data): object 'dat_train' not found
predict(fit3, dat_score)
# Error in predict(fit3, dat_score): object 'fit3' not found
尝试用 eval
明确说明也不起作用:
##
dput(fit, file="model.R")
fit4 <- eval(parse(text=paste(readLines("model.R"), collapse=" ")))
# Error in is.data.frame(data): object 'dat_train' not found
predict(fit4, dat_score)
# Error in predict(fit4, dat_score): object 'fit4' not found
在上述两种情况下,我希望 fit3
和 fit4
都能正常工作,但它们不会重新编译成我可以与 [=24= 一起使用的 lm
对象].
任何人都可以告诉我如何将模型保存到具有 structure(...)
类 ASCII 结构的文件中,然后将其作为我可以使用的 lm
对象重新读回在 predict()
?为什么我目前的方法不起作用?
第 1 步:
您需要控制 de-parsing 个选项:
dput(fit, control = c("quoteExpressions", "showAttributes"), file = "model.R")
您可以在 ?.deparseOpts
中阅读有关所有可能选项的更多信息。
"quoteExpressions" 用 quote
包装所有调用/表达式/语言,这样当你稍后 re-parse 时它们就不会被计算。注:
source
正在解析;call
字段在你适合的 "lm" 对象是一个调用:fit$call # lm(formula = z ~ x, data = dat_train)
因此,如果没有 "quoteExpressions",R 将尝试在解析期间评估 lm
调用。如果我们评估它,它正在拟合一个线性模型,R 将旨在找到 dat_train
,这将不存在于您的新 R 会话中。
"showAttributes" 是另一个强制选项,因为 "lm" 对象具有 class 属性。您当然不想丢弃所有 class 属性而只导出一个普通的 "list" 对象,对吗?此外,"lm" 对象中的许多元素,如 model
(模型框架)、qr
(紧凑的 QR 矩阵)和 terms
(术语信息)等都具有属性。你想把它们全部保留下来。
如果不设置control
,默认设置为:
control = c("keepNA", "keepInteger", "showAttributes")
将被使用。如您所见,没有"quoteExpressions",所以您会遇到麻烦。
您还可以指定 "keepInteger" 和 "keepNA",但我认为不需要 "lm" 对象。
------
第 2 步:
上述步骤将使 source
正常工作。您可以恢复您的模型:
fit1 <- source("model.R")$value
但是,summary
和 predict
等通用函数尚未准备好运行。为什么?
关键问题是fit1
中的terms
对象并不是真正的"terms"对象,而只是一个公式(它甚至不是公式,但只有 "language" 对象没有 "formula" class!)。只需比较 fit$terms
和 fit1$terms
,您就会发现差异。不要惊讶;我们之前设置了 "quoteExpressions"。虽然这绝对有助于防止 call
的计算,但它具有 terms
的 side-effect。所以我们需要尽可能地重建terms
。
幸运的是,这样做就足够了:
fit1$terms <- terms.formula(fit1$terms)
虽然这仍然不能恢复 fit$terms
中的所有信息(比如变量 classes 丢失),但它很容易成为一个有效的 "terms" 对象。
为什么 "terms" 对象是关键的?因为所有通用功能都依赖它。您可能不需要了解更多,因为它确实是技术性的,所以我将在这里停止。
完成后,我们就可以成功使用 predict
(和 summary
):
predict(fit1) ## no `newdata` given, using model frame `fit1$model`
# 1 2 3 4
#1.03 2.01 2.99 3.97
predict(fit1, dat_score) ## with `newdata`
# 1 2
#1.52 3.48
--------
结束语:
虽然我已经向您展示了如何让事情正常进行,但我一般不建议您这样做。当您将模型拟合到大型数据集时,"lm" 对象将非常大,例如,residuals
、fitted.values
是长向量,而 qr
和 model
是巨大的矩阵/数据框。所以想想这个。
这是一个重要的更新!
如前一个答案所述,最具挑战性的一点是尽我们所能恢复 $terms
。使用 terms.formula
的建议方法适用于 OP 的示例,但不适用于以下 bs()
和 poly()
:
dat <- data.frame(x1 = runif(20), x2 = runif(20), x3 = runif(20), y = rnorm(20))
library(splines)
fit <- lm(y ~ bs(x1, df = 3) + poly(x2, degree = 3) + x3, data = dat)
rm(dat)
如果我们按照前面的回答:
dput(fit, control = c("quoteExpressions", "showAttributes"), file = "model.R")
fit1 <- source("model.R")$value
fit1$terms <- terms.formula(fit1$terms)
我们会看到 summary.lm
和 anova.lm
可以正常工作,但 predict.lm
:
predict(fit1, newdata = data.frame(x1 = 0.5, x2 = 0.5, x3 = 0.5))
Error in bs(x1, df = 3) : could not find function "bs"
这是因为缺少 $terms
的 ".Environment"
属性。我们需要
environment(fit1$terms) <- .GlobalEnv
现在 运行 以上 predict
我们再次看到不同的错误:
Error in poly(x2, degree = 3) :
'degree' must be less than number of unique points
这是因为我们缺少 "predvars"
属性来安全/正确预测 bs()
和 poly()
。
补救措施是我们需要dput
这样的特殊属性另外:
dput(attr(fit$terms, "predvars"), control = "quoteExpressions", file = "predvars.R")
然后阅读并添加它
attr(fit1$terms, "predvars") <- source("predvars.R")$value
现在 运行ning predict
工作正常。
请注意 $terms
的 "dataClass"
属性也丢失了,但这似乎不会对任何通用函数造成任何问题。