【问题标题】:How do you hide and pass variables in knitr child documents?如何在 knitr 子文档中隐藏和传递变量?
【发布时间】:2015-12-28 18:28:05
【问题描述】:

knitr 使用 R 和 LaTEX 组合的代码制作 PDF。可以将子文档组合成一个文档。

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

有没有办法让 R 变量对子文档“本地化”?如何明确导出变量?

我可以在子文档的末尾将每个局部变量清空,但我想知道是否有一些合理的正式机制可以放松子文档之间的代码耦合。

【问题讨论】:

  • 如果不想主文档和子文档交互,为什么要使用子文档呢?如果您只想将多个源文件 (Rnw) 合并到一个 LaTeX 文档中,您可以分别knit Rnw 文件,然后通过 \include{} 将它们包含在主文档中。
  • 我确实希望他们互动,但以一种可控的方式。在普通的编程语言中,人们使用参数和返回值来创建函数。这样可以清楚地了解耦合。

标签: r knitr


【解决方案1】:

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

这并不一定会导致意大利面条式代码:没有什么可以阻止用户使用函数和其他对象来组织knitr 文档中的代码/数据。但可能很少有用户这样做……

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

但是,可以通过一种方式包含子文档,使用户能够控制对象子文档和主文档共享。该解决方案基于与chunk optionchild 非常相似的函数knit_child()。直接调用knit_child()(相对于通过child 选项隐式调用)的优点是可以设置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)
}

传递给... 的参数将在子文档中可用。 (命名它们,参见下面的示例。)该函数返回评估子文档的环境。

list2env 中指定parent 至关重要,我根据this answer 选择了as.environment(2)。否则parent 将默认为parent.frame(),从而将knit_global() 中的对象暴露给子文档。

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

注意knit_child 周围的cat(asis_output()) 构造,它确保子文档的输出正确包含在主文档中,而不管当前块中的results 设置如何。

在转到示例之前,最后要说两句:

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

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

  • inputNormal 没有什么特别的,它只是一个正常行为的演示。 inputHidden 演示了IsolatedChild() 的使用,将两个变量传递给子文档。
  • IsolatedChild() 返回这两个值以及第三个对象子中创建。
  • check 证明传递给/在“孤立子”中创建的对象不会污染全局环境。
  • import 展示了如何使用 assign 将对象从“孤立的孩子”“导入”到全局环境中。

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:

【讨论】:

  • 这太棒了,谢谢!我今天才开始学习环境。它们似乎是传递事物的正确机制。
  • 感谢这篇精彩的帖子。我希望能够与孩子共享父文档的环境,但我想将孩子与彼此的环境隔离;这是正确的方法吗?在运行IsolatedChild 之前,我是否应该复制全局环境,并将其作为parent 传递到list2env 中?
  • @user5359531 我认为这取决于:如果一个孩子修改了父文档环境中的对象,您期望会发生什么?但是您可能应该针对该问题提出一个新问题,并链接到这个问题。
  • 我实际上最终实现了这个并且它到目前为止运行良好。我不打算让孩子修改对象,但我希望 R 足够聪明,可以将父 env 命名空间的真实副本传递给孩子,而不仅仅是引用。
  • @user5359531 我不这么认为;环境通过引用传递:adv-r.had.co.nz/Environments.html#explicit-envs
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-24
  • 1970-01-01
  • 2015-12-26
  • 2011-06-03
  • 2012-04-19
相关资源
最近更新 更多