Как получить исходный файл R Markdown, например `source ('myfile.r')`?

У меня часто есть основной файл R Markdown или файл Knitr LaTeX, где я source другой файл R (например, для обработки данных). Однако я думал, что в некоторых случаях было бы полезно, чтобы эти исходные файлы были их собственными воспроизводимыми документами (например, файл R Markdown, который не только включает команды для обработки данных, но также создает воспроизводимый документ, который объясняет решения по обработке данных. ).

Таким образом, я хотел бы иметь такую ​​команду, как source('myfile.rmd'), в моем основном файле R Markdown. который будет извлекать и исходить весь код R внутри фрагментов кода R myfile.rmd. Конечно, это приводит к ошибке.

Следующая команда работает:

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

где results='hide' можно было бы опустить, если желательно было получить результат. Т.е., knitr выводит код R из myfile.rmd в myfile.R.

Однако это не кажется идеальным:

  • это приводит к созданию дополнительного файла
  • он должен отображаться в собственном фрагменте кода, если требуется управление отображением.
  • Это не так элегантно, как просто source(...).

Итак, мой вопрос: Есть ли более элегантный способ получения кода R для файла R Markdown?


person Jeromy Anglim    schedule 10.06.2012    source источник
comment
Мне действительно очень трудно понять ваш вопрос (я читал его несколько раз). Вы можете легко передать другие сценарии R в файл Rmd. Но вы также хотите использовать другие markdown файлы в связываемом файле?   -  person Maiasaura    schedule 10.06.2012
comment
Я хочу получить код R внутри фрагментов кода R в файлах R Markdown (т.е. * .rmd)? Я немного отредактировал вопрос, чтобы прояснить ситуацию.   -  person Jeromy Anglim    schedule 10.06.2012
comment
Что-то вроде include из латекса. Если уценка поддерживает включение других документов уценки, создать такую ​​функцию должно быть относительно легко.   -  person Paul Hiemstra    schedule 10.06.2012
comment
@PaulHiemstra Я предполагаю, что возможность получать текст и фрагменты кода R также была бы полезна. Я специально думаю о поиске только кода в документе R Markdown.   -  person Jeromy Anglim    schedule 10.06.2012


Ответы (11)


Похоже, вы ищете однострочный. Как насчет того, чтобы поместить это в свой .Rprofile?

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

Однако я не понимаю, почему вы хотите source() код в самом файле Rmd. Я имею в виду, что knit() запустит весь код в этом документе, и если вы извлечете код и запустите его фрагментом, весь код будет запущен дважды, когда вы knit() этот документ (вы запускаете себя внутри себя). Эти две задачи должны быть разделены.

Если вы действительно хотите запустить весь код, RStudio сделал это довольно просто: Ctrl + Shift + R. Он в основном вызывает purl() и source() за кулисами.

person Yihui Xie    schedule 10.06.2012
comment
Привет, @Yihui, я думаю, это полезно, потому что иногда ваш анализ может быть организован в виде небольших скриптов, но в вашем отчете вы хотите, чтобы код для всего конвейера. - person lucacerone; 24.07.2014
comment
Таким образом, здесь вариант использования состоит в том, что вы хотите написать весь код, чтобы он был тщательно задокументирован и объяснен, но код запускается каким-то другим сценарием. - person Brash Equilibrium; 20.08.2014
comment
@BrashEquilibrium Это вопрос использования source() или knitr::knit() для запуска кода. Я знаю, что люди менее знакомы с последним, но purl() ненадежен. Вас предупредили: github.com/yihui/knitr/pull/812#issuecomment -53088636 - person Yihui Xie; 22.08.2014
comment
@Yihui Какой, на ваш взгляд, будет предложенная альтернатива 'source (purl (x, ...))'? Как можно создать несколько * .Rmd-файлов, не столкнувшись с ошибкой, связанной с дублированием меток фрагментов? Я бы предпочел не возвращаться к исходному документу и связывать его. Я использую * .Rmd для многих файлов, которые мне потенциально придется экспортировать и обсуждать с другими, поэтому было бы здорово иметь возможность создавать несколько Rmd-файлов для всех этапов анализа. - person stats-hb; 09.01.2017
comment
knitr выдает ошибку Ошибка: отсутствует необходимый пакет при рендеринге файла .rmd. Мне нужно выполнить код в файле .rmd, чтобы найти настоящее сообщение об ошибке, содержащее имя отсутствующего пакета. Один случай caret требуется kernlab с svm. - person C.W.; 22.06.2020
comment
У NoamRoss есть суть для source_rmd (), которая делает это, но также есть возможность игнорировать графики: gist.github. ru / noamross / a549ee50e8a4fd68b8b1 - person Bryan Shalloway; 02.12.2020

Вынесите общий код в отдельный R-файл, а затем отправьте этот R-файл в каждый Rmd-файл, который вы хотите использовать.

так, например, предположим, что у меня есть два отчета, которые мне нужно сделать: «Вспышки гриппа» и «Анализ пистолетов и масла». Естественно, я бы создал два документа Rmd и покончил с этим.

Теперь предположим, что приходит босс и хочет увидеть разницу в цене между вспышками гриппа и маслом (с учетом 9-мм патронов).

  • Копирование и вставка кода для анализа отчетов в новый отчет - плохая идея для повторного использования кода и т. Д.
  • Я хочу, чтобы это выглядело красиво.

Мое решение заключалось в том, чтобы разложить проект на эти файлы:

  • 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>

N.B., это разработано для рабочего процесса Rmd -> html. Это будет ужасный беспорядок, если вы выберете латекс или что-нибудь еще. Этот документ Rmd просматривает глобальную среду для всех файлов source () и включает их источник в конце вашего документа. Он включает jquery ui, tablesorter и настраивает документ для использования стиля аккордеона для отображения / скрытия исходных файлов. Работа над ним еще не завершена, но не стесняйтесь адаптировать ее для своих нужд.

Я знаю, что не однострочник. Надеюсь, это даст вам хоть какие-то идеи :)

person Keith Twombley    schedule 31.05.2013

Попробуйте функцию изнаночной вязки:

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

person Petr Hala    schedule 03.02.2020

Наверное, надо думать иначе. Моя проблема заключается в следующем: напишите каждый код, который у вас обычно был бы в блоке .Rmd в файле .R. А для документа Rmd, который вы используете для вязания, то есть html, у вас осталось только

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

Таким образом вы, вероятно, создадите кучу файлов .R и потеряете преимущество обработки всего кода «кусок за фрагментом» с помощью ctrl + alt + n (или + c, но обычно это не работает). Но я прочитал книгу г-на Гандруда о воспроизводимых исследованиях и понял, что он определенно использует файлы knitr и .Rmd исключительно для создания файлов html. Сам основной анализ - это файл .R. Я думаю, что .Rmd-документы быстро станут слишком большими, если вы начнете анализировать их внутри.

person Pharcyde    schedule 29.04.2015

Если вы сразу после кода, я думаю, что что-то в этом роде должно сработать:

  1. Прочтите файл markdown / R с помощью readLines
  2. Используйте grep для поиска фрагментов кода, например, для поиска строк, начинающихся с <<<.
  3. Возьмите подмножество объекта, содержащее исходные строки, чтобы получить только код
  4. Выгрузите это во временный файл, используя writeLines
  5. Загрузите этот файл в свой сеанс R

Обертывание этого в функции должно дать вам то, что вам нужно.

person Paul Hiemstra    schedule 10.06.2012
comment
Спасибо, я думаю, это сработает. Однако первые четыре пункта звучат как то, что Stangle уже делает надежным способом для Sweave и что knit('myfile.rmd', tangle=TRUE) делает в knitr. Думаю, я ищу один лайнер, который запутывает и источники, и в идеале не создает никаких файлов. - person Jeromy Anglim; 10.06.2012
comment
Как только вы обернете его в функцию, он станет одиночным лайнером;). Что вы можете сделать, так это использовать textConnection для имитации файла и источника из него. Это позволит избежать создания файла. - person Paul Hiemstra; 10.06.2012
comment
да. textConnection может быть тем местом, где стоит поискать. - person Jeromy Anglim; 10.06.2012

У меня отлично сработал следующий хак:

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)
}
person qed    schedule 19.10.2016
comment
Для меня это лучший ответ, так как он поддерживает поиск нескольких файлов rmarkdown без конфликтов с меткой un named-chunks, как это происходит, если вы используете knitr напрямую. - person mone27; 01.07.2021

Я использую следующую пользовательскую функцию

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

source_rmd("munge_script.Rmd")
person Joe    schedule 30.08.2019

Я бы рекомендовал хранить основной код анализа и расчета в файле .R и импортировать фрагменты по мере необходимости в файл .Rmd. Я объяснил процесс здесь.

person pbahr    schedule 19.05.2016

sys.source ("./ ваш_скрипт_имя_файла.R", envir = knitr :: knit_global ())

поместите эту команду перед вызовом функций, содержащихся в your_script_file_name.R.

добавление "./" перед your_script_file_name.R, чтобы показать направление к вашему файлу, если вы уже создали проект.

Вы можете увидеть эту ссылку для получения более подробной информации: https://bookdown.org/yihui/rmarkdown-cookbook/source-script.html

person Tranle    schedule 08.06.2020

это сработало для меня

source("myfile.r", echo = TRUE, keep.source = TRUE)
person user63230    schedule 26.02.2020

Я использую этот однострочный:

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

См. Мой файл .Rmd становится очень длинным. Возможно ли разделить его и source () на меньшие части из основного .Rmd?

person IVIM    schedule 12.06.2020