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

89

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

Таким образом, я хотел бы иметь команду, как source('myfile.rmd')в моем основном файле R Markdown. который будет извлекать и исходить весь код R внутри фрагментов кода Rmyfile.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?

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

Ответы:

35

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

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

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

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

Ихуэй Се
источник
8
Привет, @Yihui, я думаю, это полезно, потому что иногда ваш анализ может быть организован в виде небольших скриптов, но в вашем отчете вы хотите, чтобы код для всего конвейера.
lucacerone
9
Таким образом, вариант использования здесь заключается в том, что вы хотите написать весь код, чтобы он был тщательно задокументирован и объяснен, но код запускается каким-то другим скриптом.
Brash Equilibrium
4
@BrashEquilibrium Это вопрос использования source()или knitr::knit()запуска кода. Я знаю, что люди менее знакомы с последним, но purl()он ненадежен. Вы были предупреждены: github.com/yihui/knitr/pull/812#issuecomment-53088636
Yihui Xie
5
@Yihui Какой, по вашему мнению, будет предложенная альтернатива 'source (purl (x, ...))'? Как можно получить несколько * .Rmd-файлов, не столкнувшись с ошибкой, связанной с дублированием меток фрагментов? Я бы предпочел не возвращаться к исходному документу и связывать его. Я использую * .Rmd для многих файлов, которые мне потенциально придется экспортировать и обсуждать с другими, поэтому было бы здорово иметь возможность создавать несколько Rmd-файлов для всех этапов анализа.
stats-hb
knitr выдает ошибку «Ошибка: отсутствует требуемый пакет» при отображении файла .rmd. Мне нужно выполнить код в файле .rmd, чтобы найти настоящее сообщение об ошибке, содержащее имя отсутствующего пакета. caretТребуется один случай kernlabс svm.
Чарльз
19

Разложите общий код на отдельный 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>

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

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

Кейт Туомбли
источник
4

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

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

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

Pharcyde
источник
3

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

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

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

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

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

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)
}
qed
источник
2

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

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

source_rmd("munge_script.Rmd")
Джо
источник
2

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

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

Петр Хала
источник
1

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

пбар
источник
1

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

Tranle
источник
0

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

source("myfile.r", echo = TRUE, keep.source = TRUE)
user63230
источник