【问题标题】:How to source R Markdown file like `source('myfile.r')`?如何获取 R Markdown 文件,如`source('myfile.r')`?
【发布时间】:2012-06-13 12:46:04
【问题描述】:

我经常有一个主要的 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 将 R 代码从 myfile.rmd 输出到 myfile.R

然而,它似乎并不完美:

  • 这会导致创建一个额外的文件
  • 如果需要控制显示,它需要出现在自己的代码块中。
  • 没有source(...)那么简单。

所以我的问题是: 是否有更优雅的方式来获取 R Markdown 文件的 R 代码?

【问题讨论】:

  • 我实际上很难理解你的问题(我读了好几遍)。您可以轻松地将其他 R 脚本源到 Rmd 文件中。但是您还想将其他 markdown 文件中的源文件放入正在编织的文件中?
  • 我想在 R Markdown 文件(即 *.rmd)的 R 代码块中获取 R 代码?我已经稍微编辑了这个问题,试图让事情更清楚。
  • 在乳胶中类似于include。如果markdown支持包含其他markdown文档,那么创建这样的功能应该是比较容易的。
  • @PaulHiemstra 我想获取文本和 R 代码块的能力也会很有用。我特别考虑采购 R Markdown 文档中的代码。

标签: r markdown knitr


【解决方案1】:

如果您只是关注代码,我认为这些内容应该可以工作:

  1. readLines读取markdown/R文件
  2. 使用grep 查找代码块,例如搜索以<<< 开头的行
  3. 获取包含原始行的对象的子集以仅获取代码
  4. 使用writeLines 将其转储到临时文件中
  5. 将此文件导入您的 R 会话

将其包装在一个函数中应该可以满足您的需求。

【讨论】:

  • 谢谢,我想这会奏效。然而,前四点听起来像是 Stangle 已经以可靠的方式为 Sweave 所做的事情以及 knit('myfile.rmd', tangle=TRUE) 在 knitr 中所做的事情。我想我正在寻找一种既能纠缠又能产生源的单一衬里,理想情况下不创建文件。
  • 一旦你将它包装在一个函数中,它就变成了一个单行器;)。你可以做的是使用textConnection 来模拟一个文件,并从中获取。这样可以避免创建文件。
  • 是的。 textConnection 可能是值得一看的地方。
【解决方案2】:

看来您正在寻找单线。把它放在你的.Rprofile 中怎么样?

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

但是,我不明白您为什么要source() Rmd 文件本身中的代码。我的意思是knit() 将运行本文档中的所有代码,如果您提取代码并以块的形式运行,那么当您knit() 本文档时,所有代码将运行两次(您自己在内部运行)。这两个任务应该是分开的。

如果您真的想运行所有代码,RStudio 让这一切变得相当简单:Ctrl + Shift + R。它基本上在幕后调用purl()source()

【讨论】:

  • 嗨@Yihui 我认为这很有帮助,因为有时您的分析可能会以小脚本组织,但在您的报告中您希望拥有整个管道的代码。
  • 所以这里的用例是您想要编写所有代码并对其进行大量记录和解释,但代码由其他脚本运行。
  • @BrashEquilibrium 是使用source()knitr::knit() 运行代码的问题。我知道人们对后者不太熟悉,但purl() 并不可靠。您已被警告:github.com/yihui/knitr/pull/812#issuecomment-53088636
  • @Yihui 在你看来,'source(purl(x,...))' 的替代方案是什么?如何一个来源多个 *.Rmd 文件,而不会遇到有关重复块标签的错误?我宁愿不想回到源文件并编织它。我将 *.Rmd 用于许多文件,我可能必须导出这些文件并与其他人讨论,因此能够为分析的所有步骤提供多个 Rmd 文件会很棒。
  • knitr 在渲染 .rmd 文件时发出错误“错误:缺少所需的包”。我必须在 .rmd 文件中执行代码才能找到包含丢失包名称的真正错误消息。一种情况是 caret 需要 kernlab 和 svm。
【解决方案3】:

将通用代码分解成一个单独的 R 文件,然后将该 R 文件源到您想要的每个 Rmd 文件中。

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

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

  • 复制并粘贴代码以分析报告到新报告中对于代码重用等来说是个坏主意。
  • 我希望它看起来不错。

我的解决方案是将项目分解为这些文件:

  • 流感.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 工作流程设计的。如果您使用乳胶或其他任何东西,这将是一个丑陋的混乱。此 Rmd 文档在全局环境中查找所有 source()'ed 文件,并在文档末尾包含它们的源代码。它包括 jquery ui、tablesorter,并将文档设置为使用手风琴样式来显示/隐藏源文件。这是一项正在进行中的工作,但您可以根据自己的用途随意调整。

我知道,不是单线。希望它至少能给你一些想法:)

【讨论】:

    【解决方案4】:

    也许人们应该开始以不同的方式思考。我的问题如下: 将您通常在 .R 文件中的 .Rmd 块中编写的每个代码都写下来。 而对于你用来编织的 Rmd 文档,即一个 html,你只剩下了

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

    这样您可能会创建一堆 .R 文件,并且您失去了使用 ctrl+alt+n(或 +c,但通常这不起作用)处理所有代码“一个接一个块”的优势。 但是,我阅读了 Gandrud 先生关于可重复研究的书,并意识到他绝对使用 knitr 和 .Rmd 文件来创建 html 文件。主要分析本身是一个 .R 文件。 我认为如果您开始在内部进行整个分析,.Rmd 文档会迅速变得太大。

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

        以下 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)
        }
        

        【讨论】:

        • 这对我来说是最好的答案,因为它支持采购多个 rmarkdown 文件而不会与未命名的块标签发生冲突,如果您直接使用 knitr 就会发生这种情况。
        【解决方案7】:

        我使用以下自定义函数

        source_rmd <- function(rmd_file){
          knitr::knit(rmd_file, output = tempfile())
        }
        
        source_rmd("munge_script.Rmd")
        

        【讨论】:

          【解决方案8】:

          试试 knitr 的 purl 函数:

          source(knitr::purl("myfile.rmd", quiet=TRUE))

          【讨论】:

            【解决方案9】:

            这对我有用

            source("myfile.r", echo = TRUE, keep.source = TRUE)
            

            【讨论】:

              【解决方案10】:

              sys.source("./your_script_file_name.R", envir = knitr::knit_global())

              在调用 your_script_file_name.R 中包含的函数之前放置此命令。

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

              您可以查看此链接了解更多详情:https://bookdown.org/yihui/rmarkdown-cookbook/source-script.html

              【讨论】:

                【解决方案11】:

                我用这个单线:

                ```{r optional_chunklabel_for_yourfile_rmd, child = 'yourfile.Rmd'}
                ```
                

                见: My .Rmd file becomes very lengthy. Is that possible split it and source() it's smaller portions from main .Rmd?

                【讨论】:

                  【解决方案12】:

                  我会说没有更优雅的方式来获取 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 依赖项在您的 R 环境之外(例如,将文件写入磁盘、下载一些外部资源、与 Web api 交互等),那么我会选择 knit() 而不是rmarkdown::render() 改为:

                  rmarkdown::render("myfile.rmd")
                  

                  【讨论】:

                    猜你喜欢
                    • 2019-10-13
                    • 2021-05-31
                    • 2019-08-26
                    • 2015-05-29
                    • 2022-10-06
                    • 1970-01-01
                    • 2011-09-02
                    • 2021-06-14
                    • 1970-01-01
                    相关资源
                    最近更新 更多