如何在 knitr 子文档中隐藏和传递变量?

How do you hide and pass variables in knitr child documents?

knitr 使用结合了(在我的例子中)R 和 LaTEX 的代码生成 PDF。可以 assemble 个子文档中的一个文档。

在我现在使用它时,文档是 assembled 全局变量,传入和传出每个子文档。这使得生成意大利面条代码变得容易。

有什么方法可以让 R 变量 "local" 成为子文档吗?如何显式导出变量?

我可以将子文档末尾的每个局部变量置为 NULL,但我想知道是否存在某种合理的正式机制来放松子文档之间的代码耦合。

knitr 评估公共环境中的所有块(由 knit_global() 返回)。这是设计使然;就像源文件中的所有代码都在同一环境中运行一样,所有块都在公共环境中执行。这同样适用于 child documents 因为它们(原则上,不是技术上)只是主文档的一部分,外部化到另一个文件。

这不一定会导致意大利面条代码:没有什么能阻止用户使用函数和其他 objects 来组织 code/data 在 knitr 文档中。但可能很少有用户这样做......

所以 chunks/child 文档没有封装机制的原因是它们 应该 共享一个公共环境,因为它们是 一个(主要)文档。

但是, 可以包含 child 文档,使用户能够控制 object 的 child 文档和主要文件共享。该解决方案基于与 chunk option child 非常相似的函数 knit_child()。直接调用 knit_child()(相对于通过 child 选项隐式调用)的优点是可以设置定义 "the environment in which the code chunks are to be evaluated" 的 envir 参数(来自 ?knit) .

knit_child() 附近,我写了包装器 IsolatedChild 来简化事情:

IsolatedChild <- function(input, ...) {

  evaluationEnv <- list2env(x = list(...), parent = as.environment(2))
  cat(asis_output(knit_child(input = input, envir = evaluationEnv, quiet = TRUE)))
  return(evaluationEnv)
}

传递给 ... 的参数将在 child 文档中可用。 (给它们命名,参见下面的示例。)函数 returns 计算 child 文档的环境。

list2env中指定parent很关键,我根据this answer选择了as.environment(2)。否则 parent 将默认为 parent.frame(),从而将 knit_global() 中的 object 暴露给 child 文档。

assign 可用于使从 IsolatedChild 返回的 objects 在全局环境中可用。

请注意围绕 knit_childcat(asis_output()) 构造,它确保 child 文档的输出正确包含在主文档中,而不管 results 中的设置如何当前区块。

在转向示例之前,最后两点说明:

  • 如果 child 和主文档不应该共享 any object,则此方法过于复杂。只需 knit child 文档并使用 \include{} 将其包含在主文档中。
  • 这种方法可能存在一些缺陷。特别是 "isolated child" 的封闭环境需要小心,因为搜索路径可能看起来与预期的不同。请注意,主文档和 child 文档共享 knitr 选项。此外,两个文档都可以通过副作用进行交互(options()par()、打开的设备……)。

下面是一个完整的示例/演示:

  • inputNormal没有做任何特别的事情,它只是正常行为的演示。 inputHidden 演示了 IsolatedChild() 的用法,将两个变量传递给 child 文档。
  • IsolatedChild() returns 这两个值连同第三个 object 在 child.[=104= 中创建 ]
  • check证明在"isolated child"中通过to/created的object不污染全球环境
  • import 展示了如何将 assign 用于 "import" 从 "isolated child" 到全局环境的 object。

main.Rnw:

\documentclass{article}
\begin{document}

<<setup>>=
library(knitr)

objInMain <- TRUE

IsolatedChild <- function(input, ...) {

  evaluationEnv <- list2env(x = list(...), parent = as.environment(2))
  cat(asis_output(knit_child(input = input, envir = evaluationEnv, quiet = TRUE)))
  return(evaluationEnv)
}

@

<<inputNormal, child="child_normal.Rnw">>=
@

<<inputHidden, results = "asis">>=

returned <- IsolatedChild(input = "child_hidden.Rnw",
                          passedValue = 42,
                          otherPassedValue = 3.14)
cat(sprintf("Returned from hidden child: \texttt{%s}",
            paste(ls(returned), collapse = ", ")))
@

<<check, results = "asis">>=
cat(sprintf("In global evaluation environment: \texttt{%s}",
            paste(ls(), collapse = ", ")))
@

<<import, results = "asis">>=
assign("objInChildHidden", returned$objInChildHidden)
cat(sprintf("In global evaluation environment: \texttt{%s}",
            paste(ls(), collapse = ", ")))
@
\end{document}

child_normal.Rnw:

<<inChildNormal>>=
objInChildNormal <- TRUE # visible in main.Rnw (standard behaviour)
@

child_hidden.Rnw:

Text in \texttt{child\_hidden.Rnw}.

<<inChildHidden>>=
objInChildHidden <- TRUE

print(sprintf("In hidden child: %s",
              paste(ls(), collapse = ", ")))


# Returns FALSE.
# Would be TRUE if "parent" weren't specifiet in list2env().
exists("objInMain", inherits = TRUE)
@

main.pdf: