问题描述
knitr 使用R和LaTEX的组合代码制作PDF.可以从子文档中组合文档.
knitr makes PDFs out of code that is a combination of (in my case) R and LaTEX. One can assemble a document out of child documents.
当我现在使用它时,该文档是由全局变量组装而成的,并传入和传出每个子文档.这样可以轻松生成意大利面条代码.
As I use it right now, the document is assembled out of global variables, passed into and out of every child document. This makes it easy to produce spaghetti code.
有什么办法可以使R变量对子文档本地化"?如何明确导出变量?
Is there any way to make R variables "local" to a child doc? How about make exporting of variables explicit?
我可以在子文档的末尾使每个局部变量为NULL,但是我想知道是否存在某种合理的形式化机制来放松子文档之间的代码耦合.
I could NULL out every local variable at the end of a child doc, but I wonder if there is some reasonable formal mechanism for loosening code coupling between child documents.
推荐答案
knitr
评估公共环境(由knit_global()
返回)中的所有块.这是设计使然;就像源文件中的所有代码都在同一环境中运行一样,所有块都在同一环境中执行.这同样适用于子文档,因为它们(从原则上讲,从技术上来说)只是一部分主文档的文档,外部化到另一个文件.
knitr
evaluates all chunks in a common environment (returned by knit_global()
). This is by design; just like all code in a source file runs in the same environment, all chunks are executed in a common environment. The same applies to child documents because they are (in principle, not technically) just a part of the main document, externalized to another file.
这不一定会导致意大利面条式代码:没有什么可以阻止用户使用函数和其他对象来组织knitr
文档中的代码/数据.但是可能很少有用户这样做……
This does not necessarily lead to spaghetti code: Nothing prevents users from using functions and other objects to organize code/data in knitr
documents. But probably few users do so …
因此,对于块/子文档没有封装机制的原因是,由于它们属于 one (主要)的一部分,因此它们被假定共享一个公共环境.文档.
So the reason why there are no encapsulation mechanisms for chunks/child documents is that they are supposed to share a common environment as they are part of one (main) document.
但是,可以 以允许用户控制对象的子文档和主文档共享的方式包含子文档.该解决方案基于功能knit_child()
,该功能与块选项 child
非常相似.直接调用knit_child()
的优势(相对于通过child
选项隐式调用)的优点是可以设置envir
参数,该参数定义将在其中评估代码块的环境"(来自?knit
)
However, it is possible to include child documents in a way that gives the user control over the objects child documents and the main document share. The solution is based on the function knit_child()
which is very similar to the chunk option child
. The advantage of calling knit_child()
directly (vs. implicitly via the child
option) is the possibility to set the envir
argument which defines "the environment in which the code chunks are to be evaluated" (from ?knit
).
在knit_child()
周围,我编写了包装器IsolatedChild
来简化事情:
Around knit_child()
, I wrote the wrapper IsolatedChild
to simplify matters:
IsolatedChild <- function(input, ...) {
evaluationEnv <- list2env(x = list(...), parent = as.environment(2))
cat(asis_output(knit_child(input = input, envir = evaluationEnv, quiet = TRUE)))
return(evaluationEnv)
}
传递给...
的参数将在子文档中可用. (为它们命名,请参见下面的示例.)该函数返回在其中评估了子文档的环境.
Arguments passed to ...
will be available in the child document. (Name them, see example below.) The function returns the environment in which the child document has been evaluated.
在list2env
中指定parent
是至关重要的,我根据此答案选择了as.environment(2)
.否则,parent
将默认为parent.frame()
,从而将knit_global()
中的对象暴露给子文档.
Specifying parent
in list2env
is crucial and I chose as.environment(2)
according to this answer. Otherwise parent
would default to parent.frame()
, thus exposing objects in knit_global()
to the child document.
assign
可用于使从IsolatedChild
返回的对象在全局环境中可用.
assign
can be used to make objects returned from IsolatedChild
available in the global environment.
请注意knit_child
周围的cat(asis_output())
构造,这可以确保子文档的输出正确包含在主文档中,而与当前块中的results
设置无关.
Note the cat(asis_output())
construction around knit_child
which ensures that the output from the child document is correctly included in the main document, regardless of the results
setting in the current chunk.
在转到示例之前,有两个最后要点:
Before turning to the example, two final remarks:
- 如果子文档和主文档不应该共享任何对象,则此方法过于复杂.只需
knit
子文档,然后使用\include{}
将其包含在主文档中即可. - 这种方法可能会带来一些陷阱.特别是孤立的孩子"的封闭环境需要特别注意,因为搜索路径可能看起来与预期的有所不同.请注意,主文档和子文档共享
knitr
选项.此外,两个文档都可以通过副作用(options()
,par()
,打开的设备...)进行交互.
- If the child and the main document are not supposed to share any objects, this approach is overly complex. Simply
knit
the child document and use\include{}
to include it in the main document. - This approach might come with some pitfalls. Especially the enclosing environment of the "isolated child" needs caution because the search path might look different than expected. Note that main and child document share
knitr
options. Besides, both documents could interact via side effects (options()
,par()
, opened devices, ...).
下面是一个完整的示例/演示:
Below a complete example / demo:
- 块
inputNormal
并没有什么特别的,它只是正常行为的演示.inputHidden
演示了IsolatedChild()
的用法,将两个变量传递给子文档. -
IsolatedChild()
返回这两个值以及在子对象中 创建的第三个对象. -
check
演示传递给隔离子"中/在隔离子"中创建的对象不会污染全局环境. -
import
显示了如何使用assign
将对象从隔离子"导入"到全局环境.
- The chunk
inputNormal
does nothing special, it's just a demonstration of the normal behavior.inputHidden
demonstrates the use ofIsolatedChild()
, passing two variables to the child document. IsolatedChild()
returns these two values along with a third object created in the child.check
demonstrates that the objects passed to/created in the "isolated child" do not pollute the global environment.import
shows howassign
can be used to "import" an object from the "isolated child" to the global environment.
\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}
<<inChildNormal>>=
objInChildNormal <- TRUE # visible in main.Rnw (standard behaviour)
@
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)
@
这篇关于如何在knitr子文档中隐藏和传递变量?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!