Come generare file R Markdown come `source ('myfile.r')`?


89

Spesso ho un file R Markdown principale o un file LaTeX knitr in cui ho sourceun altro file R (ad esempio, per l'elaborazione dei dati). Tuttavia, stavo pensando che in alcuni casi sarebbe stato utile che questi file di origine fossero i propri documenti riproducibili (ad esempio, un file R Markdown che non solo include i comandi per l'elaborazione dei dati ma produce anche un documento riproducibile che spiega le decisioni di elaborazione dei dati ).

Quindi, vorrei avere un comando come source('myfile.rmd')nel mio file R Markdown principale. che estrarrebbe e provocherebbe tutto il codice R all'interno dei blocchi di codice R di myfile.rmd. Naturalmente, questo dà luogo a un errore.

Il seguente comando funziona:

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

dove results='hide'potrebbe essere omesso se l'output fosse desiderato. Cioè, knitr restituisce il codice R da myfile.rmdinto myfile.R.

Tuttavia, non sembra perfetto:

  • risulta nella creazione di un file aggiuntivo
  • deve apparire nel suo blocco di codice se è richiesto il controllo sul display.
  • Non è così elegante come semplice source(...).

Quindi la mia domanda: esiste un modo più elegante per reperire il codice R di un file R Markdown?


In realtà sto facendo davvero fatica a capire la tua domanda (l'ho letta diverse volte). È possibile creare facilmente altri script R in un Rmdfile. Ma vuoi anche inserire altri markdownfile in un file che viene lavorato a maglia?
Maiasaura

4
Voglio generare il codice R all'interno di blocchi di codice R nei file R Markdown (cioè * .rmd)? Ho modificato un po 'la domanda per cercare di rendere le cose più chiare.
Jeromy Anglim

Qualcosa sulla falsariga di includein lattice. Se il markdown supporta l'inclusione di altri documenti markdown, dovrebbe essere relativamente facile creare tale funzione.
Paul Hiemstra

@PaulHiemstra Immagino che la capacità di fornire il testo e pezzi di codice R sarebbe utile anche. Sto specificamente pensando di procurarmi solo il codice in un documento R Markdown.
Jeromy Anglim

Risposte:


35

Sembra che tu stia cercando una battuta. Che ne dici di metterlo nel tuo .Rprofile?

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

Tuttavia, non capisco perché vuoi source()il codice nel file Rmd stesso. Voglio dire knit(), eseguirà tutto il codice in questo documento, e se estrai il codice e lo esegui in un blocco, tutto il codice verrà eseguito due volte quando scrivi knit()questo documento (corri te stesso dentro te stesso). Le due attività dovrebbero essere separate.

Se davvero si vuole eseguire tutto il codice, RStudio ha reso questo abbastanza facile: Ctrl + Shift + R. Fondamentalmente chiama purl()esource() dietro le quinte.


8
Ciao @ Yihui, penso che sia utile perché a volte la tua analisi potrebbe essere organizzata in piccoli script, ma nel tuo rapporto vuoi avere il codice per l'intera pipeline.
lucacerone

9
Quindi il caso d'uso qui è che si desidera scrivere tutto il codice e farlo essere ampiamente documentato e spiegato, ma il codice viene eseguito da qualche altro script.
Brash Equilibrium

4
@BrashEquilibrium Si tratta di utilizzare source()o knitr::knit()eseguire il codice. So che le persone hanno meno familiarità con quest'ultimo, ma purl()non è affidabile. Sei stato avvisato: github.com/yihui/knitr/pull/812#issuecomment-53088636
Yihui Xie

5
@Yihui Quale sarebbe l'alternativa proposta a "source (purl (x, ...))" secondo te? Come si possono generare più file * .Rmd senza incorrere in un errore relativo alle etichette di blocchi duplicati? Preferirei non tornare al documento di origine e lavorarlo a maglia. Uso * .Rmd per molti file, che potenzialmente devo esportare e discutere con altri, quindi sarebbe fantastico poter generare più file Rmd per tutti i passaggi dell'analisi.
stats-hb

knitr emette l'errore "Errore: pacchetto richiesto mancante", quando esegue il rendering del file .rmd. Devo eseguire il codice nel file .rmd per trovare il vero messaggio di errore contenente il nome del pacchetto mancante. Un caso è caretrichiesto kernlabcon svm.
CW

19

Fattorizza il codice comune in un file R separato, quindi inserisci quel file R in ogni file Rmd in cui lo desideri.

quindi, ad esempio, diciamo che ho due rapporti che devo fare, Epidemie di influenza e Guns vs Butter Analysis. Naturalmente creerei due documenti Rmd e finirò con esso.

Ora supponiamo che il capo arrivi e voglia vedere le variazioni dei focolai di influenza rispetto ai prezzi del burro (controllo per munizioni da 9 mm).

  • Copiare e incollare il codice per analizzare i report nel nuovo report è una cattiva idea per il riutilizzo del codice, ecc.
  • Voglio che sia carino.

La mia soluzione è stata quella di scomporre il progetto in questi file:

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

all'interno di ogni file Rmd avrei qualcosa di simile:

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

Il problema qui è che perdiamo la riproducibilità. La mia soluzione è creare un documento figlio comune da includere in ogni file Rmd. Quindi alla fine di ogni file Rmd che creo, aggiungo questo:

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

E, naturalmente, 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, questo è progettato per il flusso di lavoro Rmd -> html. Questo sarà un brutto pasticcio se vai con il lattice o qualsiasi altra cosa. Questo documento Rmd cerca nell'ambiente globale tutti i file ed i sorgenti () e include la loro sorgente alla fine del documento. Include jquery ui, tablesorter e imposta il documento in modo che utilizzi uno stile a fisarmonica per mostrare / nascondere i file originati. È un lavoro in corso, ma sentiti libero di adattarlo ai tuoi usi.

Non una battuta, lo so. Spero che ti dia almeno qualche idea :)


4

Probabilmente si dovrebbe iniziare a pensare in modo diverso. Il mio problema è il seguente: scrivi ogni codice che normalmente avresti avuto in un blocco .Rmd in un file .R. E per il documento Rmd che usi per lavorare a maglia, cioè un html, ti rimane solo

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

In questo modo probabilmente creerai un mucchio di file .R e perderai il vantaggio di elaborare tutto il codice "pezzo dopo pezzo" usando ctrl + alt + n (o + c, ma normalmente questo non funziona). Ma ho letto il libro sulla ricerca riproducibile del signor Gandrud e mi sono reso conto che lui usa decisamente i file knitr e .Rmd esclusivamente per creare file html. La stessa analisi principale è un file .R. Penso che i documenti .Rmd diventino rapidamente troppo grandi se inizi a fare l'intera analisi all'interno.


3

Se stai solo cercando il codice, penso che qualcosa del genere dovrebbe funzionare:

  1. Leggi il file markdown / R con readLines
  2. Utilizzare grepper trovare i blocchi di codice, cercando le righe che iniziano con<<< per esempio
  3. Prendi il sottoinsieme dell'oggetto che contiene le righe originali per ottenere solo il codice
  4. Scaricalo in un file temporaneo usando writeLines
  5. Crea questo file nella tua sessione R.

Avvolgerlo in una funzione dovrebbe darti ciò di cui hai bisogno.


1
Grazie, immagino che funzionerebbe. Tuttavia, i primi quattro punti suonano come quello che Stangle fa già in modo affidabile per Sweave e cosa knit('myfile.rmd', tangle=TRUE)fa in knitr. Immagino di cercare un rivestimento che sia aggrovigliato e generato e idealmente non crei file.
Jeromy Anglim

Una volta racchiuso in una funzione, diventa un oneliner;). Quello che potresti fare è usare textConnectionper imitare un file e fonte da quello. Ciò eviterebbe la creazione di un file.
Paul Hiemstra

Sì. textConnectionpotrebbe essere il posto dove cercare.
Jeromy Anglim

2

Il seguente trucco ha funzionato bene per me:

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

2

Uso la seguente funzione personalizzata

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

source_rmd("munge_script.Rmd")

2

Prova la funzione rovescio da knitr:

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


1

Suggerirei di mantenere l'analisi principale e il codice di calcolo nel file .R e di importare i blocchi secondo necessità nel file .Rmd. Ho spiegato il processo qui .


1

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

inserisci questo comando prima di chiamare le funzioni contenute in your_script_file_name.R.

il "./" che aggiunge prima your_script_file_name.R per mostrare la direzione del tuo file se hai già creato un progetto.

Puoi vedere questo link per maggiori dettagli: https://bookdown.org/yihui/rmarkdown-cookbook/source-script.html


0

questo ha funzionato per me

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.