¿Cómo obtener un archivo R Markdown como `source ('myfile.r')`?

89

A menudo tengo un archivo principal R Markdown o un archivo Knitr LaTeX donde tengo sourceotro archivo R (por ejemplo, para el procesamiento de datos). Sin embargo, estaba pensando que en algunos casos sería beneficioso que estos archivos de origen fueran sus propios documentos reproducibles (por ejemplo, un archivo R Markdown que no solo incluye comandos para el procesamiento de datos, sino que también produce un documento reproducible que explica las decisiones de procesamiento de datos ).

Por lo tanto, me gustaría tener un comando como source('myfile.rmd')en mi archivo principal de R Markdown. que extraería y generaría todo el código R dentro de los fragmentos de código R de myfile.rmd. Por supuesto, esto da lugar a un error.

El siguiente comando funciona:

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

donde results='hide'podría omitirse si se desea la salida. Es decir, knitr genera el código R de myfile.rmden myfile.R.

Sin embargo, no parece perfecto:

  • da como resultado la creación de un archivo adicional
  • debe aparecer en su propio fragmento de código si se requiere control sobre la pantalla.
  • No es tan elegante como simple source(...).

Por lo tanto, mi pregunta: ¿Existe una forma más elegante de obtener el código R de un archivo R Markdown?

Jeromy Anglim
fuente
De hecho, me está costando mucho entender tu pregunta (la leí varias veces). Puede obtener otros scripts de R fácilmente en un Rmdarchivo. ¿Pero también desea obtener otros markdownarchivos en un archivo que se está tejiendo?
Maiasaura
4
Quiero obtener el código R dentro de fragmentos de código R en archivos R Markdown (es decir, * .rmd). He editado un poco la pregunta para intentar aclarar las cosas.
Jeromy Anglim
Algo parecido al includelátex. Si Markdown admite la inclusión de otros documentos de Markdown, debería ser relativamente fácil crear dicha función.
Paul Hiemstra
@PaulHiemstra Supongo que la capacidad de obtener el texto y los fragmentos de código R también sería útil. Estoy pensando específicamente en obtener solo el código en un documento de R Markdown.
Jeromy Anglim

Respuestas:

35

Parece que estás buscando una frase de una sola línea. ¿Qué tal poner esto en tu .Rprofile?

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

Sin embargo, no entiendo por qué desea source()el código en el archivo Rmd. Quiero decir knit(), ejecutará todo el código de este documento, y si extrae el código y lo ejecuta en un fragmento, todo el código se ejecutará dos veces cuandoknit() este documento (se ejecutará usted mismo dentro de sí mismo). Las dos tareas deben estar separadas.

Si realmente desea ejecutar todo el código, rstudio ha hecho esta bastante fácil: Ctrl + Shift + R. Básicamente llama purl()y source()detrás de escena.

Yihui Xie
fuente
8
Hola @Yihui, creo que es útil porque a veces su análisis puede estar organizado en pequeños scripts, pero en su informe desea tener el código para todo el pipeline.
lucacerona
9
Entonces, el caso de uso aquí es que desea escribir todo el código y que esté muy documentado y explicado, pero el código se ejecuta mediante algún otro script.
Brash Equilibrium
4
@BrashEquilibrium Es cuestión de usar source()o knitr::knit()ejecutar el código. Sé que la gente está menos familiarizada con este último, pero purl()no es confiable. Has sido advertido: github.com/yihui/knitr/pull/812#issuecomment-53088636
Yihui Xie
5
@Yihui ¿Cuál sería la alternativa propuesta a 'source (purl (x, ...))' en su opinión? ¿Cómo se pueden obtener varios archivos * .Rmd sin encontrar un error con respecto a las etiquetas de fragmentos duplicados? Prefiero no volver al documento de origen y tejerlo. Utilizo * .Rmd para muchos archivos, que potencialmente tengo que exportar y discutir con otros, por lo que sería genial poder obtener varios archivos Rmd para todos los pasos del análisis.
stats-hb
knitr emite el error "Error: Falta el paquete requerido", cuando procesa el archivo .rmd. Tengo que ejecutar código en el archivo .rmd para encontrar el mensaje de error real que contiene el nombre del paquete que falta. Se caretrequiere un caso kernlabcon svm.
Charles
19

Factoriza el código común en un archivo R separado y luego genera ese archivo R en cada archivo Rmd en el que quieras.

así que, por ejemplo, digamos que tengo dos informes que necesito hacer, Brotes de gripe y Análisis de armas contra mantequilla. Naturalmente, crearía dos documentos Rmd y terminaría.

Ahora suponga que llega el jefe y quiere ver las variaciones de los brotes de gripe frente a los precios de la mantequilla (controlando la munición de 9 mm).

  • Copiar y pegar el código para analizar los informes en el nuevo informe es una mala idea para la reutilización del código, etc.
  • Quiero que se vea bien.

Mi solución fue factorizar el proyecto en estos archivos:

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

dentro de cada archivo Rmd tendría algo como:

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

El problema aquí es que perdemos reproducibilidad. Mi solución a eso es crear un documento secundario común para incluirlo en cada archivo Rmd. Entonces, al final de cada archivo Rmd que creo, agrego esto:

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

Y, por supuesto, 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, esto está diseñado para el flujo de trabajo Rmd -> html. Esto será un desastre feo si eliges látex o cualquier otra cosa. Este documento Rmd busca en el entorno global todos los archivos fuente () 'ed e incluye su fuente al final de su documento. Incluye jquery ui, tablesorter y configura el documento para usar un estilo de acordeón para mostrar / ocultar archivos de origen. Es un trabajo en progreso, pero no dude en adaptarlo a sus propios usos.

No es de una sola línea, lo sé. Espero que te dé algunas ideas al menos :)

Keith Twombley
fuente
4

Probablemente uno debería empezar a pensar diferente. Mi problema es el siguiente: escriba cada código que normalmente habría tenido en un fragmento .Rmd en un archivo .R. Y para el documento Rmd que usa para tejer, es decir, un html, solo le queda

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

De esta manera probablemente creará un montón de archivos .R y perderá la ventaja de procesar todo el código "fragmento tras fragmento" usando ctrl + alt + n (o + c, pero normalmente esto no funciona). Pero leí el libro sobre investigaciones reproducibles del Sr. Gandrud y me di cuenta de que definitivamente usa archivos knitr y .Rmd únicamente para crear archivos html. El análisis principal en sí es un archivo .R. Creo que los documentos .Rmd crecen demasiado rápidamente si comienza a hacer todo el análisis en su interior.

Pharcyde
fuente
3

Si está justo después del código, creo que algo así debería funcionar:

  1. Lea el archivo Markdown / R con readLines
  2. Use greppara encontrar los fragmentos de código, buscando líneas que comiencen con<<< por ejemplo
  3. Tome un subconjunto del objeto que contiene las líneas originales para obtener solo el código
  4. Vierta esto en un archivo temporal usando writeLines
  5. Obtenga este archivo en su sesión de R

Envolver esto en una función debería darle lo que necesita.

Paul Hiemstra
fuente
1
Gracias, supongo que funcionaría. Sin embargo, los primeros cuatro puntos suenan a lo que Stangle ya hace de manera confiable para Sweave y lo que knit('myfile.rmd', tangle=TRUE)hace en Knitr. Supongo que estoy buscando un trazador de líneas que se enrede y se origine e idealmente no cree archivos.
Jeromy Anglim
Una vez que lo envuelve en una función, se convierte en un delineador;). Lo que podría hacer es usar textConnectionpara imitar un archivo y obtener la fuente de ese. Esto evitaría la creación de un archivo.
Paul Hiemstra
Si. textConnectionpodría ser el lugar para buscar.
Jeromy Anglim
2

El siguiente truco funcionó bien para mí:

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

Yo uso la siguiente función personalizada

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

source_rmd("munge_script.Rmd")
Joe
fuente
2

Prueba la función purl de knitr:

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

Petr Hala
fuente
1

Recomendaría mantener el código principal de análisis y cálculo en el archivo .R e importar los fragmentos según sea necesario en el archivo .Rmd. He explicado el proceso aquí .

pbahr
fuente
1

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

ponga este comando antes de llamar a las funciones contenidas en your_script_file_name.R.

el "./" que se agrega antes de your_script_file_name.R para mostrar la dirección a su archivo si ya creó un proyecto.

Puede ver este enlace para obtener más detalles: https://bookdown.org/yihui/rmarkdown-cookbook/source-script.html

Tranle
fuente
0

esto funcionó para mí

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