如何正确地将拟合线性模型(通过 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

在上述两种情况下,我希望 fit3fit4 都能正常工作,但它们不会重新编译成我可以与 [=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

但是,summarypredict 等通用函数尚未准备好运行。为什么?

关键问题是fit1中的terms对象并不是真正的"terms"对象,而只是一个公式(它甚至不是公式,但只有 "language" 对象没有 "formula" class!)。只需比较 fit$termsfit1$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" 对象将非常大,例如,residualsfitted.values 是长向量,而 qrmodel 是巨大的矩阵/数据框。所以想想这个。

这是一个重要的更新!

如前一个答案所述,最具挑战性的一点是尽我们所能恢复 $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.lmanova.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" 属性也丢失了,但这似乎不会对任何通用函数造成任何问题。