差异:RStudio中的“编译PDF”按钮与knit()和knit2pdf()函数

60

简述

使用knit()/knit2pdf()代替RStudio中的“编译PDF”按钮可能会有一些(可能是不需要的)副作用。

动机

大多数knitr用户似乎在RStudio中编写文档,并使用“编译PDF”/“Knit HTML”按钮编译文档。这通常很顺利,但偶尔会有一些特殊要求无法通过编译按钮实现。在这些情况下,解决方案通常是直接调用knit()/knit2pdf()/rmarkdown::render()(或类似函数)。

以下是一些例子:

使用knit2pdf()而不是“编译PDF”按钮通常提供了这些问题的简单解决方案。但是,这是有代价的:与“编译PDF”处理文档在单独的进程和环境中有根本的区别,而knit2pdf()和其它函数则没有。

这有一些影响,问题在于并非所有这些影响都是显而易见的。knit()使用全局环境中的对象(而“编译PDF”不使用)为例。这可能很明显,并且在像上面第二个示例这样的情况下是期望的行为,但当knit()用于解决类似示例1和3中的问题时,这是一个意外后果

此外,还有更微妙的差异:

问题及其目标

每当我读/写建议使用knit2pdf()而不是“编译PDF”时,我想“正确,但用户应该了解后果…”

因此,问题在于:
使用knit()/knit2pdf()而不是RStudio中的“编译PDF”按钮可能会产生哪些(可能不需要的)副作用?
如果有一个全面的(社区维基?)答案来回答这个问题,它可以链接到未来建议使用knit2pdf()的答案中。
相关问题
有数十个与此问题相关的问题。然而,它们要么只提出了代码以(或多或少地)复制RStudio按钮的行为,要么解释了“基本上”发生了什么,而没有提到可能存在的缺陷。其他问题看起来非常类似,但实际上是它的一个(非常)特殊情况。以下是一些例子:

关于答案

我认为这个问题提出了许多应该包含在答案中的问题。然而,可能还有很多我不知道的方面,这就是为什么我不愿意自己回答这个问题的原因(尽管如果没有人回答,我可能会尝试)。

可能,一个答案应该涵盖三个主要点:

  • 新会话与当前会话问题(全局选项,工作目录,已加载的包等)。
  • 第一点的结果:事实上,knit()使用来自调用环境的对象(默认值:envir = parent.frame()),并对可重现性产生影响。我试图解决防止knit()使用文档外部对象的问题,this answer(第二个项目符号)。
  • RStudio秘密处理的事情...
    • ...启动交互式会话时(example)-->在点击“编译PDF”时不可用
    • ...当点击“编译PDF”时(除了设置为处理文件的新会话和工作目录之外,还有什么特殊的东西吗?)
我对这个问题的正确观点不确定。我认为采用“单击‘编译PDF’后会发生什么以及其影响”和“使用knit()时会发生什么以及其影响”两种方法来解决问题是一个好的方式。
1. 当编写RMD文档时,"Knit HTML"按钮也适用于相同的情况。

1
我认为有趣的是,当从一个项目中工作时,Rstudio会有哪些不同之处。例如,工作目录的设置方式就不同。 - snaut
3
我喜欢这个问题,但我认为你应该将大部分写的内容放到回答中,而不是放到问题中。我会说你的“问题”中有超过95%其实是对于一开始提出的问题的回答,你可能加入了这些内容来激发问题,但自问自答并不意味着其他人不能回答问题以填补你回答中的空缺。 - Dason
(作为我的个人书签:也相关。) - CL.
3
我在想是否框架不是反过来了,使用RStudio特殊函数而不是命令行的副作用是什么? - Elin
1个回答

22
首先,我认为如果您将范围限制在“Compile PDF”按钮上,回答这个问题会更容易,因为“Knit HTML”按钮是另一个故事。“Compile PDF”仅适用于Rnw文档(R + LaTeX或Sweave)。
以下是我按照您提供的三个点回答您的问题:
1. 目前RStudio总是启动一个新的R会话来编译Rnw文档,并首先将工作目录更改为Rnw文件所在的目录。您可以将此过程想象成一个类似于此的shell脚本:
cd path/to/your-Rnw-directory
Rscript -e "library(knitr); knit('your.Rnw')"
pdflatex your.tex

请注意,knitr包始终附加在其中,并且pdflatex可能是其他LaTeX引擎(取决于您的Sweave文档的RStudio配置,例如xelatex)。 如果您想要在当前的R会话中复制它,则可以将脚本重写为R:

owd = setwd("path/to/your-Rnw-directory")
system2("Rscript", c("-e", shQuote("library(knitr); knit('your.Rnw')"))
system2("pdflatex", "your.tex")
setwd(owd)

这并不像knitr::knit('path/to/your.Rnw')那样简单,因为此时工作目录没有自动更改,一切都在当前R会话中(默认情况下是在globalenv()中)执行。

由于Rnw文档总是在新的R会话中编译,所以它不会使用当前R会话中的任何对象。这很难仅通过当前R会话中knitr::knit()envir参数来复制。特别地,你不能使用knitr::knit(envir = new.env()),因为尽管new.env()是一个新环境,但它有一个默认的父环境parent.frame(),通常是globalenv();你也不能使用knitr::knit(envir = emptyenv()),因为它太干净了,你甚至会遇到R base包中的对象问题。唯一可靠的复制“编译PDF”按钮功能的方法就是我在第一条中提到的:system2("Rscript", c("-e", shQuote("library(knitr); knit('your.Rnw')")),此时knit()将使用新R会话中的globalenv()

我不完全确定RStudio如何处理repos选项。如果没有设置,它可能会在幕后自动设置这个选项。我认为这是相对较小的问题。你可以在你的.Rprofile中设置它,我认为RStudio应该尊重你的CRAN镜像设置。

用户一直在问为什么Rnw文档(或R Markdown文档)不是在当前R会话中编译。对我们来说,基本上归结为以下哪种结果更令人惊讶或不希望:

  1. 如果我们在当前R会话中编译文档,不能保证你的结果能在另一个R会话中复现(例如,下次你打开RStudio时,或你的合作者在他们的计算机上打开RStudio时)。
  2. 如果我们在新的R会话中编译文档,用户可能会惊讶地发现找不到对象(当他们在R控制台中键入对象名称时,可以看到它们)。这可能很令人惊讶,但也是一个好的、及早的提醒,你的文档可能在下一次无法运行。

总之,我认为:

  • 在新的R会话中编织更有利于可重现性;
  • 在当前R会话中编织有时更方便(例如,在当前会话中尝试使用不同的临时R对象进行编织)。有时你也必须在当前R会话中编织,特别是当你以编程方式生成PDF报告时,例如,你使用(for)循环来生成一系列报告。你无法仅通过“编译PDF”按钮实现这一点(该按钮主要用于单个Rnw文档)。

顺便说一下,我认为我上面所说的也适用于Knit或Knit HTML按钮,但底层函数是rmarkdown::render()而不是knitr::knit()


1
好的回答,谢谢。实际上,我认为这个问题更像是一份相关链接和观察列表,而不是一个真正的问题,但我认为你很好地总结了其中的核心问题。 - CL.
1
@CL。谢谢!几天前,有人通过电子邮件联系我询问这个问题,所以我回来写了一个答案。这可能不是我能提供的最佳答案,但我没有更多时间来完善它。 - Yihui Xie
@YihuiXie。正如您所说,在RStudio中使用编译按钮时,knit()会使用新的R会话的globalenv()。有没有办法让这个新的R会话使用我的项目的packrat库(甚至是我packrat库中包含的knitr版本),而不是我的用户库,以确保最大程度的可重复性? - J.P. Le Cavalier
1
@J.P.LeCavalier 听起来是个好问题,但也听起来像是一个新问题(所以我认为发一个新帖子是更好的主意)。我不是一个收集专家,也没有答案。 - Yihui Xie

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