如何优雅而健壮地在knitr rmd文件中缓存外部脚本?

4

假设我有一个外部R脚本external.R

df.rand <- data.frame(rnorm(n = 100), rnorm(n = 100))

然后还有一个main.Rmd:
\documentclass{article}

\begin{document}

<<setup, include = FALSE>>=
library(knitr)
library(ggplot2)
# global chunk options
opts_chunk$set(cache=TRUE, autodep=TRUE, concordance=TRUE, progress=TRUE, cache.extra = tools::md5sum("external.r"))
@

<<source, include=FALSE>>=
source("external.R")
@


<<plot>>=
ggplot(data = df.rand, mapping = aes(x = x, y = y)) + geom_point()
@

\end{document}

将这些内容放在一个外部脚本中很有帮助,因为实际上,这是一堆导入、数据清理和模拟任务,会使main.Rmd变得混乱。

main.Rmd中的任何代码块都依赖于外部脚本的更改。 为了解决这个依赖关系,我添加了上面的cache.extra = tools::md5sum("external.r")

看起来这样做还不错。

我正在寻找最佳实践。

  • 这样做足够健壮吗?
  • 有没有更加优雅的方法?(例如,遗憾的是,外部.R中的任何更改都会触发完全的缓存失效,而不仅仅是使那些实际上更改的对象失效。)

除了一些library()调用之外,这里没有副作用,但我可以将它们移到main.Rmd中。

我总是担心我可能做错了什么


运行 external.R 的结果是什么?只创建了对象 df.rand 还是还有更多的对象或副作用? - CL.
结果是一堆对象(数据框),不仅仅是 df.rand。据我所知,唯一的副作用是一些 library() 调用(我可以/应该将其移动到 main.Rmd 中)。顺便说一句,有没有一个 R 函数可以测试副作用? - maxheld
1个回答

3

有比您目前使用的自制缓存更好的方法。首先,您可以将 external.R 拆分成多个部分:

# ---- CreateRandomDFs----
df.rand1 <- data.frame(rnorm(n = 100), rnorm(n = 100))
df.rand2 <- data.frame(rnorm(n = 100), rnorm(n = 100))

# ---- CreateOtherObjects----

# stuff

main.Rmd 中添加以下内容(在未缓存的代码块中):read_chunk(path = 'external.R')。然后执行这些代码块:
<<CreateRandomDFs>>=
@
<<CreateOtherObjects>>=
@

如果autodep无法正常工作,请在您的代码块中添加dependson。一个只使用df.rand1df.rand2的代码块需要设置dependson = "CreateRandomDFs";当其他对象也被使用时,需要设置dependson = c("CreateRandomDFs", "CreateOtherObjects")
您还可以在某个对象发生更改时使代码块的缓存失效:cache.whatever = quote(df.rand1)
这样,您就可以避免在external.R中进行任何更改时都会使整个缓存失效。关键在于如何将文件中的代码分割成代码块:如果使用太多的代码块,您将不得不列出许多依赖项;如果使用太少的代码块,则缓存失效的频率过高/过多。

请问您能否详细解释一下您出色答案中的最后两段?它似乎描述了一种从外部R中排除指定对象(或对象)的缓存失效更改的方法。 - lawyeR
1
在这个例子中,我将external.R拆分成了两个块。第二个块的更改不会使第一个块的缓存或依赖于第一个块的块的缓存无效。我们可以将df.rand1df.rand2放入单独的块中。会发生什么?如果其中一个更改,另一个则不受影响。但是同样的道理意味着每个依赖于df.rand1df.rand2的块现在都需要列出两个块作为依赖项。因此,当它们可能被相同块的依赖项所需时,我会将对象分组到同一块中。 - CL.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接