如何像`source('myfile.r')`一样从R Markdown文件中获取数据?

106

我经常有一个主要的R Markdown文件或knitr LaTeX文件,其中我会source一些其他的R文件(例如数据处理)。然而,我认为在某些情况下,将这些被引用的文件变成它们自己的可再现文档会很有益处(例如,一个R Markdown文件不仅包括数据处理命令,还可以生成一个可再现的文档,解释数据处理决策)。

因此,我想在我的主要R Markdown文件中拥有一个像source('myfile.rmd')这样的命令,它可以提取并引用myfile.rmd中R代码块内的所有R代码。当然,这会导致一个错误。

以下命令有效:

```{r message=FALSE, results='hide'}
knit('myfile.rmd', tangle=TRUE)
source('myfile.R')
```

如果需要输出,可以省略results='hide'。例如,knitr将myfile.rmd中的R代码输出到myfile.R中。

但是这种方法并不完美:

  • 它会创建一个额外的文件
  • 如果需要控制显示效果,它需要出现在自己的代码块中。
  • 它不如简单的source(...)那样优雅。

因此我的问题是: 有没有更优雅的方法来使用R Markdown文件的R代码?


我实际上很难理解你的问题(我读了好几遍)。您可以轻松地将其他R脚本源入到Rmd文件中。但是,您还想将其他markdown文件源入到正在编织的文件中吗? - Maiasaura
4
我想在R Markdown文件(即*.rmd)中的R代码块中引用R代码?我稍微编辑了问题以尝试使事情更清晰。 - Jeromy Anglim
如果Markdown支持包含其他Markdown文档,那么创建这样一个功能应该相对容易,类似于LaTeX中的include - Paul Hiemstra
@PaulHiemstra 我想,能够获取文本和 R 代码块的能力也会很有用。我特别考虑在 R Markdown 文档中仅获取代码。 - Jeromy Anglim
我编写了一个函数,用于在RMD中获取特定的块,您可以在此处找到:https://gist.github.com/brshallo/e963b9dca5e4e1ab12ec6348b135362e - Bryan Shalloway
14个回答

43

看起来你正在寻找一行代码的解决方案。将以下代码放入你的 .Rprofile 中如何?

ksource <- function(x, ...) {
  library(knitr)
  source(purl(x, output = tempfile()), ...)
}

然而,我不明白为什么你想在Rmd文件本身中使用source()代码。我的意思是,knit()将运行该文档中的所有代码,如果你提取代码并在一个块中运行它,当你knit()此文档时,所有代码将会运行两次(你自己在内部运行自己)。这两个任务应该是分开的。

如果你真的想要运行所有的代码,RStudio使这件事情相当容易:Ctrl + Shift + R。它基本上在幕后调用purl()source()


11
嗨 @Yihui,我认为这很有帮助,因为有时您的分析可能会以小脚本的形式组织,但在报告中,您希望拥有整个流程的代码。 - lucacerone
11
这里的使用情境是你想编写所有代码并加上详细注释和说明,但该代码将由其他脚本运行。 - Brash Equilibrium
4
使用 source()knitr::knit() 运行代码,这是关键。我知道人们不太熟悉后者,但 purl() 不可靠。你已经被警告了:https://github.com/yihui/knitr/pull/812#issuecomment-53088636 - Yihui Xie
6
在您看来,“source(purl(x,...))” 的替代方案是什么?如何在不遇到重复块标签错误的情况下源多个*.Rmd文件?我不想返回要源化的文档并进行编织。我使用*.Rmd文件进行许多文件,这些文件可能需要导出和与他人讨论,因此最好能够源多个Rmd文件以执行分析的所有步骤。 - stats-hb
当knitr渲染.rmd文件时,它会发出错误"Error: Required package is missing"。我必须在.rmd文件中执行代码以查找包含缺失包名称的实际错误消息。一个例子是caret需要使用svm的kernlab - C.W.
NoamRoss为source_rmd()创建了一个要点,它可以执行此操作,但还有一个忽略绘图的选项:“https://gist.github.com/noamross/a549ee50e8a4fd68b8b1” - Bryan Shalloway

23

将公共代码提取到单独的 R 文件中,然后将该 R 文件源入到每个需要它的 Rmd 文件中。

例如,假设我需要制作两份报告,一份是流感爆发情况报告,另一份是枪支与黄油分析报告。自然而然地,我会创建两个 Rmd 文档并完成它。

现在,假设老板来了,想要看到流感爆发情况与黄油价格的变化(控制 9mm 弹药)。

  • 将报告分析代码复制粘贴到新报告中是重用代码的不好方法等等。
  • 我希望看起来漂亮。

我的解决方案是将项目划分为以下文件:

  • Flu.Rmd
    • flu_data_import.R
  • Guns_N_Butter.Rmd
    • guns_data_import.R
    • butter_data_import.R

在每个 Rmd 文件中,我会有这样的东西:

```{r include=FALSE}
source('flu_data_import.R')
```

这里的问题是我们失去了可重复性。我的解决方法是创建一个通用的子文档,包含在每个Rmd文件中。因此,在我创建的每个Rmd文件的末尾,我都会添加如下内容:

```{r autodoc, child='autodoc.Rmd', eval=TRUE}
``` 

当然,还有自动文档生成器 autodoc.Rmd:

Source Data & Code
----------------------------
<div id="accordion-start"></div>

```{r sourcedata, echo=FALSE, results='asis', warnings=FALSE}

if(!exists(autodoc.skip.df)) {
  autodoc.skip.df <- list()
}

#Generate the following table:
for (i in ls(.GlobalEnv)) {
  if(!i %in% autodoc.skip.df) {
    itm <- tryCatch(get(i), error=function(e) NA )
    if(typeof(itm)=="list") {
      if(is.data.frame(itm)) {
        cat(sprintf("### %s\n", i))
        print(xtable(itm), type="html", include.rownames=FALSE, html.table.attributes=sprintf("class='exportable' id='%s'", i))
      }
    }
  }
}
```
### Source Code
```{r allsource, echo=FALSE, results='asis', warning=FALSE, cache=FALSE}
fns <- unique(c(compact(llply(.data=llply(.data=ls(all.names=TRUE), .fun=function(x) {a<-get(x); c(normalizePath(getSrcDirectory(a)),getSrcFilename(a))}), .fun=function(x) { if(length(x)>0) { x } } )), llply(names(sourced), function(x) c(normalizePath(dirname(x)), basename(x)))))

for (itm in fns) {
  cat(sprintf("#### %s\n", itm[2]))
  cat("\n```{r eval=FALSE}\n")
  cat(paste(tryCatch(readLines(file.path(itm[1], itm[2])), error=function(e) sprintf("Could not read source file named %s", file.path(itm[1], itm[2]))), sep="\n", collapse="\n"))
  cat("\n```\n")
}
```
<div id="accordion-stop"></div>
<script type="text/javascript">
```{r jqueryinclude, echo=FALSE, results='asis', warning=FALSE}
cat(readLines(url("http://code.jquery.com/jquery-1.9.1.min.js")), sep="\n")
```
</script>
<script type="text/javascript">
```{r tablesorterinclude, echo=FALSE, results='asis', warning=FALSE}
cat(readLines(url("http://tablesorter.com/__jquery.tablesorter.js")), sep="\n")
```
</script>
<script type="text/javascript">
```{r jqueryuiinclude, echo=FALSE, results='asis', warning=FALSE}
cat(readLines(url("http://code.jquery.com/ui/1.10.2/jquery-ui.min.js")), sep="\n")
```
</script>
<script type="text/javascript">
```{r table2csvinclude, echo=FALSE, results='asis', warning=FALSE}
cat(readLines(file.path(jspath, "table2csv.js")), sep="\n")
```
</script>
<script type="text/javascript">
  $(document).ready(function() {
  $('tr').has('th').wrap('<thead></thead>');
  $('table').each(function() { $('thead', this).prependTo(this); } );
  $('table').addClass('tablesorter');$('table').tablesorter();});
  //need to put this before the accordion stuff because the panels being hidden makes table2csv return null data
  $('table.exportable').each(function() {$(this).after('<a download="' + $(this).attr('id') + '.csv" href="data:application/csv;charset=utf-8,'+encodeURIComponent($(this).table2CSV({delivery:'value'}))+'">Download '+$(this).attr('id')+'</a>')});
  $('#accordion-start').nextUntil('#accordion-stop').wrapAll("<div id='accordion'></div>");
  $('#accordion > h3').each(function() { $(this).nextUntil('h3').wrapAll("<div>"); });
  $( '#accordion' ).accordion({ heightStyle: "content", collapsible: true, active: false });
</script>

注意,这是为Rmd -> html工作流程设计的。如果你选择使用latex或其他任何东西,它会变得非常混乱。此Rmd文档在全局环境中查找所有source()'ed文件,并在您的文档末尾包含它们的源代码。它包括jquery ui、tablesorter,并设置文档以使用手风琴样式来显示/隐藏来源文件。这是一个正在进行的工作,但请随意根据自己的需求进行调整。

我知道这不是一行代码。希望它至少能给你一些想法 :)


12
尝试使用knitr的purl函数: source(knitr::purl("myfile.rmd", quiet=TRUE))

4

也许我们应该开始有所思考。我的问题是这样的: 将通常在.Rmd块中编写的所有代码写在一个.R文件中。 对于你用于编织即html的Rmd文档,只剩下

```{R Chunkname, Chunkoptions}  
source(file.R)  
```

这样做,您可能会创建大量的.R文件,失去了使用ctrl + alt + n(或+c,但通常不起作用)“逐个代码块”处理所有代码的优势。但是,我读了甘德鲁德先生关于可重复研究的书,意识到他绝对仅使用knitr和.Rmd文件来创建html文件。主要分析本身是一个.R文件。如果您开始在.Rmd文档中进行整个分析,它们很快就会变得过于庞大。

3

以下Hack对我很有效:

library(readr)
library(stringr)
source_rmd <- function(file_path) {
  stopifnot(is.character(file_path) && length(file_path) == 1)
  .tmpfile <- tempfile(fileext = ".R")
  .con <- file(.tmpfile) 
  on.exit(close(.con))
  full_rmd <- read_file(file_path)
  codes <- str_match_all(string = full_rmd, pattern = "```(?s)\\{r[^{}]*\\}\\s*\\n(.*?)```")
  stopifnot(length(codes) == 1 && ncol(codes[[1]]) == 2)
  codes <- paste(codes[[1]][, 2], collapse = "\n")
  writeLines(codes, .con)
  flush(.con)
  cat(sprintf("R code extracted to tempfile: %s\nSourcing tempfile...", .tmpfile))
  source(.tmpfile)
}

对我来说,这是最好的答案,因为它支持在不使用knitr时避免未命名块标签冲突,可以同时源多个rmarkdown文件。 - mone27

3
如果您只需要代码,我认为以下的代码可以解决问题:
  1. 使用 readLines读取markdown/R文件。
  2. 使用 grep查找代码块,搜索以<<<开头的行。
  3. 从包含原始行的对象中获取代码子集。
  4. 使用writeLines将其转储到临时文件中。
  5. 在R会话中源代码文件。
将此包装在函数中应该能够满足您的需求。

1
谢谢,我想那应该可以。然而,前四个点听起来像是Stangle已经可靠地为Sweave做的事情,以及knit('myfile.rmd', tangle=TRUE)在knitr中所做的事情。我想我正在寻找一行代码,既可以编织又可以源化,并且最好不创建任何文件。 - Jeromy Anglim
一旦你将它包装在一个函数中,它就变成了一行代码 ;). 你可以使用 textConnection 来模拟一个文件,并从中进行源代码读取。这样可以避免创建一个文件。 - Paul Hiemstra
是的,textConnection 可能是需要查看的地方。 - Jeromy Anglim

3
我使用下面的自定义函数。
source_rmd <- function(rmd_file){
  knitr::knit(rmd_file, output = tempfile())
}

source_rmd("munge_script.Rmd")

1
我建议将主要的分析和计算代码放在 .R 文件中,并根据需要在 .Rmd 文件中导入块。我已经在这里解释了这个过程。

1

请在调用your_script_file_name.R中包含的函数之前使用此命令:sys.source("./your_script_file_name.R", envir = knitr::knit_global())。

如果您已经创建了一个项目,则在your_script_file_name.R之前添加"./"以显示文件的方向。

您可以查看此链接获取更多详细信息:https://bookdown.org/yihui/rmarkdown-cookbook/source-script.html


1

我认为没有比获取 Rmarkdown 文件更优雅的方式了。Rmd 的理念是报告可重现,并且在最好的情况下将是自包含的。然而,除了 OP 的原始解决方案之外,以下方法避免了在磁盘上永久创建中间文件。它还会额外努力确保块输出不会出现在渲染文档中:

knit_loc <- tempfile(fileext = ".R")
knitr::knit("myfile.rmd",
            output = knit_loc,
            quiet = TRUE,
            tangle = TRUE)
invisible(capture.output(source(knit_loc, verbose = FALSE)))

我还想补充一点,如果子Markdown的依赖项是外部的(例如将文件写入磁盘、下载某些外部资源、与Web API交互等),则应该选择使用rmarkdown::render()而不是knit()

rmarkdown::render("myfile.rmd")

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