Wie bekomme ich eine R Markdown-Datei wie "source ('myfile.r')"?

89

Ich habe oft eine Haupt-R-Markdown-Datei oder eine Knitr-LaTeX-Datei, in der ich sourceeine andere R-Datei habe (z. B. für die Datenverarbeitung). Ich dachte jedoch, dass es in einigen Fällen vorteilhaft wäre, wenn diese Quelldateien ihre eigenen reproduzierbaren Dokumente wären (z. B. eine R-Markdown-Datei, die nicht nur Befehle für die Datenverarbeitung enthält, sondern auch ein reproduzierbares Dokument erstellt, das die Datenverarbeitungsentscheidungen erklärt ).

Daher möchte ich einen Befehl wie source('myfile.rmd')in meiner Haupt-R-Markdown-Datei haben. das würde den gesamten R-Code in den R-Code-Blöcken von extrahieren und beschaffen myfile.rmd. Dies führt natürlich zu einem Fehler.

Der folgende Befehl funktioniert:

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

wo results='hide'könnte weggelassen werden, wenn die Ausgabe gewünscht wurde. Dh knitr gibt den R-Code von myfile.rmdin aus myfile.R.

Es scheint jedoch nicht perfekt zu sein:

  • Dies führt zur Erstellung einer zusätzlichen Datei
  • Es muss in einem eigenen Codeblock angezeigt werden, wenn die Kontrolle über die Anzeige erforderlich ist.
  • Es ist nicht so elegant wie einfach source(...).

Daher meine Frage: Gibt es eine elegantere Möglichkeit, den R-Code einer R-Markdown-Datei zu beschaffen?

Jeromy Anglim
quelle
Es fällt mir wirklich schwer, Ihre Frage zu verstehen (ich habe sie mehrmals gelesen). Sie können andere R-Skripte einfach in eine RmdDatei einbinden. Sie möchten aber auch andere markdownDateien in eine zu strickende Datei einspeisen?
Maiasaura
4
Ich möchte den R-Code in R-Code-Chunks in R-Markdown-Dateien (dh * .rmd) eingeben. Ich habe die Frage ein wenig bearbeitet, um die Dinge klarer zu machen.
Jeromy Anglim
Etwas in der Art von includeLatex. Wenn Markdown die Aufnahme anderer Markdown-Dokumente unterstützt, sollte es relativ einfach sein, eine solche Funktion zu erstellen.
Paul Hiemstra
@PaulHiemstra Ich denke, dass die Fähigkeit, den Text und die R-Code-Chunks zu beschaffen, auch nützlich wäre. Ich denke speziell daran, nur den Code in einem R Markdown-Dokument zu beschaffen.
Jeromy Anglim

Antworten:

35

Es scheint, dass Sie nach einem Einzeiler suchen. Wie wäre es damit, dies in Ihre zu setzen .Rprofile?

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

Ich verstehe jedoch nicht, warum Sie source()den Code in der Rmd-Datei selbst wollen. Ich meine, knit()wird den gesamten Code in diesem Dokument ausführen, und wenn Sie den Code extrahieren und in einem Block ausführen, wird der gesamte Code zweimal ausgeführt, wenn Sie knit()dieses Dokument verwenden (Sie führen sich selbst in sich selbst aus). Die beiden Aufgaben sollten getrennt sein.

Wenn Sie wirklich den gesamten Code ausführen möchten, hat RStudio dies ziemlich einfach gemacht : Ctrl + Shift + R. Es ruft im Grunde purl()und source()hinter den Kulissen.

Yihui Xie
quelle
8
Hallo @Yihui, ich denke, es ist hilfreich, weil Ihre Analyse manchmal in kleinen Skripten organisiert ist, aber in Ihrem Bericht möchten Sie den Code für die gesamte Pipeline haben.
Lucaceron
9
Der Anwendungsfall hier ist also, dass Sie den gesamten Code schreiben und ihn stark dokumentieren und erklären lassen möchten, aber der Code wird von einem anderen Skript ausgeführt.
Dreistes Gleichgewicht
4
@BrashEquilibrium Es geht darum , den Code zu verwenden source()oder knitr::knit()auszuführen. Ich weiß, dass die Leute mit letzterem weniger vertraut sind, aber purl()nicht zuverlässig sind. Sie wurden gewarnt: github.com/yihui/knitr/pull/812#issuecomment-53088636
Yihui Xie
5
@Yihui Was wäre aus Ihrer Sicht die vorgeschlagene Alternative zu 'source (purl (x, ...))'? Wie kann eine Quelle mehrere * .Rmd-Dateien erstellen, ohne dass ein Fehler in Bezug auf doppelte Chunk-Labels auftritt? Ich möchte lieber nicht zu dem zu beschaffenden Dokument zurückkehren und es stricken. Ich verwende * .Rmd für viele Dateien, die ich möglicherweise exportieren und mit anderen diskutieren muss. Daher wäre es großartig, mehrere Rmd-Dateien für alle Schritte der Analyse zu erstellen.
Statistik-hb
knitr gibt beim Rendern der .rmd-Datei den Fehler "Fehler: Erforderliches Paket fehlt" aus. Ich muss Code in der .rmd-Datei ausführen, um die echte Fehlermeldung zu finden, die den Namen des fehlenden Pakets enthält. Ein Fall ist mit svm careterforderlich kernlab.
Charles
19

Berücksichtigen Sie den allgemeinen Code in einer separaten R-Datei und geben Sie diese R-Datei in jede Rmd-Datei ein, in der Sie sie haben möchten.

Nehmen wir zum Beispiel an, ich muss zwei Berichte erstellen: Grippeausbrüche und Guns vs Butter Analysis. Natürlich würde ich zwei Rmd-Dokumente erstellen und damit fertig sein.

Nehmen wir nun an, der Chef kommt vorbei und möchte die Variationen der Grippeausbrüche im Vergleich zu den Butterpreisen sehen (Kontrolle über 9-mm-Munition).

  • Das Kopieren und Einfügen des Codes zur Analyse der Berichte in den neuen Bericht ist eine schlechte Idee für die Wiederverwendung von Code usw.
  • Ich möchte, dass es schön aussieht.

Meine Lösung bestand darin, das Projekt in folgende Dateien einzubeziehen:

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

In jeder Rmd-Datei hätte ich so etwas wie:

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

Das Problem hierbei ist, dass wir die Reproduzierbarkeit verlieren. Meine Lösung besteht darin, ein gemeinsames untergeordnetes Dokument zu erstellen, das in jede Rmd-Datei aufgenommen wird. Am Ende jeder von mir erstellten Rmd-Datei füge ich Folgendes hinzu:

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

Und natürlich 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>

Hinweis: Dies ist für den Rmd -> HTML-Workflow vorgesehen. Dies wird ein hässliches Durcheinander sein, wenn Sie mit Latex oder etwas anderem gehen. Dieses Rmd-Dokument durchsucht die globale Umgebung nach allen ed-Dateien (source ()) und enthält deren Quelle am Ende Ihres Dokuments. Es enthält jquery ui, tablesorter und richtet das Dokument so ein, dass es einen Akkordeonstil zum Ein- / Ausblenden von Quelldateien verwendet. Es ist in Arbeit, aber Sie können es jederzeit an Ihre eigenen Zwecke anpassen.

Ich weiß, kein Einzeiler. Hoffe es gibt dir wenigstens ein paar Ideen :)

Keith Twombley
quelle
4

Wahrscheinlich sollte man anders denken. Mein Problem ist das folgende: Schreiben Sie jeden Code, den Sie normalerweise in einem .Rmd-Block hatten, in eine .R-Datei. Und für das Rmd-Dokument, das Sie zum Stricken verwenden, z. B. ein HTML, haben Sie nur noch übrig

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

Auf diese Weise erstellen Sie wahrscheinlich eine Reihe von .R-Dateien und verlieren den Vorteil, den gesamten Code "Block für Block" mit Strg + Alt + N (oder + C, aber normalerweise funktioniert dies nicht) zu verarbeiten. Aber ich habe das Buch über reproduzierbare Forschung von Herrn Gandrud gelesen und festgestellt, dass er definitiv Knitr- und .Rmd-Dateien ausschließlich zum Erstellen von HTML-Dateien verwendet. Die Hauptanalyse selbst ist eine .R-Datei. Ich denke, RMD-Dokumente werden schnell zu groß, wenn Sie Ihre gesamte Analyse im Inneren durchführen.

Pharcyde
quelle
3

Wenn Sie direkt nach dem Code sind, denke ich, dass etwas in dieser Richtung funktionieren sollte:

  1. Lesen Sie die Markdown / R-Datei mit readLines
  2. Verwenden Sie grepdie Code - Stücke zu finden, die für die Linien zu suchen , die mit beginnen <<<zum Beispiel
  3. Nehmen Sie eine Teilmenge des Objekts, das die ursprünglichen Zeilen enthält, um nur den Code zu erhalten
  4. Speichern Sie dies mithilfe von in eine temporäre Datei writeLines
  5. Geben Sie diese Datei in Ihre R-Sitzung ein

Wenn Sie dies in eine Funktion einwickeln, erhalten Sie das, was Sie benötigen.

Paul Hiemstra
quelle
1
Danke, ich denke das würde funktionieren. Die ersten vier Punkte klingen jedoch so, wie Stangle es Sweave bereits zuverlässig macht und was knit('myfile.rmd', tangle=TRUE)Knitr. Ich schätze, ich suche einen Einzeiler, der sowohl Verwicklungen als auch Quellen enthält und im Idealfall keine Dateien erstellt.
Jeromy Anglim
Sobald Sie es in eine Funktion einwickeln, wird es zu einem Oneliner;). Sie können textConnectioneine Datei imitieren und daraus eine Quelle erstellen. Dies würde verhindern, dass eine Datei erstellt wird.
Paul Hiemstra
Ja. textConnectionkönnte der Ort sein, um zu suchen.
Jeromy Anglim
2

Der folgende Hack hat bei mir gut funktioniert:

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
quelle
2

Ich benutze die folgende benutzerdefinierte Funktion

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

source_rmd("munge_script.Rmd")
Joe
quelle
2

Probieren Sie die Purl-Funktion von Knitr aus:

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

Petr Hala
quelle
1

Ich würde empfehlen, den Hauptanalyse- und Berechnungscode in der .R-Datei zu belassen und die Chunks nach Bedarf in die .Rmd-Datei zu importieren. Ich habe den Prozess hier erklärt .

pbahr
quelle
1

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

Setzen Sie diesen Befehl, bevor Sie die Funktionen aufrufen, die in your_script_file_name.R enthalten sind.

das "./", das vor your_script_file_name.R hinzugefügt wird, um die Richtung zu Ihrer Datei anzuzeigen, wenn Sie bereits ein Projekt erstellt haben.

Sie können diesen Link für weitere Details sehen: https://bookdown.org/yihui/rmarkdown-cookbook/source-script.html

Tranle
quelle
0

das hat bei mir funktioniert

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