web-dev-qa-db-fra.com

Comment source le fichier R Markdown comme `source ('myfile.r')`?

J'ai souvent un fichier R Markdown principal ou un fichier knitr LaTeX où je source un autre fichier R (par exemple, pour le traitement des données). Cependant, je pensais que dans certains cas, il serait avantageux que ces fichiers sources soient leurs propres documents reproductibles (par exemple, un fichier R Markdown qui comprend non seulement des commandes pour le traitement des données, mais produit également un document reproductible qui explique le traitement des données les décisions).

Ainsi, je voudrais avoir une commande comme source('myfile.rmd') dans mon fichier R Markdown principal. qui extraire et source tout le code R à l'intérieur des morceaux de code R de myfile.rmd. Bien sûr, cela donne lieu à une erreur.

La commande suivante fonctionne:

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

results='hide' pourrait être omis si la sortie était souhaitée. C'est-à-dire que knitr génère le code R de myfile.rmd Dans myfile.R.

Cependant, cela ne semble pas parfait:

  • il en résulte la création d'un fichier supplémentaire
  • il doit apparaître dans son propre bloc de code si le contrôle de l'affichage est requis.
  • Ce n'est pas aussi élégant que simple source(...).

D'où ma question: Existe-t-il une manière plus élégante de trouver le code R d'un fichier R Markdown?

71
Jeromy Anglim

Il semble que vous recherchiez une doublure. Que diriez-vous de mettre cela dans votre .Rprofile?

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

Cependant, je ne comprends pas pourquoi vous voulez source() le code dans le fichier Rmd lui-même. Je veux dire que knit() exécutera tout le code de ce document, et si vous extrayez le code et l'exécutez en bloc, tout le code sera exécuté deux fois lorsque vous knit() ce document ( vous vous exécutez à l'intérieur de vous-même). Les deux tâches doivent être séparées.

Si vous voulez vraiment exécuter tout le code, RStudio a rendu cela assez facile: Ctrl + Shift + R. Il appelle essentiellement purl() et source() derrière la scène.

29
Yihui Xie

Factorisez le code commun dans un fichier R séparé, puis sourcez ce fichier R dans chaque fichier Rmd dans lequel vous le souhaitez.

ainsi, par exemple, disons que j'ai deux rapports à faire, les épidémies de grippe et Guns vs Butter Analysis. Naturellement, je créerais deux documents Rmd et j'en aurais fini.

Supposons maintenant que le patron vienne et veut voir les variations des épidémies de grippe par rapport au prix du beurre (contrôle des munitions de 9 mm).

  • Copier et coller le code pour analyser les rapports dans le nouveau rapport est une mauvaise idée pour la réutilisation du code, etc.
  • Je veux que ça a l'air sympa.

Ma solution a été de factoriser le projet dans ces fichiers:

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

dans chaque fichier Rmd, j'aurais quelque chose comme:

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

Le problème ici est que nous perdons la reproductibilité. Ma solution est de créer un document enfant commun à inclure dans chaque fichier Rmd. Donc, à la fin de chaque fichier Rmd que je crée, j'ajoute ceci:

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

Et, bien sûr, 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., ceci est conçu pour le workflow Rmd -> html. Ce sera un désordre laid si vous allez avec du latex ou autre chose. Ce document Rmd parcourt l'environnement global pour tous les fichiers ed source () et inclut leur source à la fin de votre document. Il comprend jquery ui, tablesorter et configure le document pour utiliser un style accordéon pour afficher/masquer les fichiers d'origine. C'est un travail en cours, mais n'hésitez pas à l'adapter à vos propres usages.

Pas une ligne, je sais. J'espère que ça vous donne au moins quelques idées :)

17
Keith Twombley

On devrait probablement commencer à penser différemment. Mon problème est le suivant: Écrivez chaque code que vous auriez normalement eu dans un bloc .Rmd dans un fichier .R. Et pour le document Rmd que vous utilisez pour tricoter, c'est-à-dire un html, il ne vous reste que

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

De cette façon, vous créerez probablement un tas de fichiers .R et vous perdrez l'avantage de traiter tout le code "morceau après morceau" en utilisant ctrl + alt + n (ou + c, mais normalement cela ne fonctionne pas). Mais, j'ai lu le livre sur la recherche reproductible de M. Gandrud et j'ai réalisé qu'il utilise définitivement les fichiers knitr et .Rmd uniquement pour créer des fichiers html. L'analyse principale elle-même est un fichier .R. Je pense que les documents .Rmd deviennent rapidement trop volumineux si vous commencez à faire toute votre analyse à l'intérieur.

3
Pharcyde

Si vous êtes juste après le code, je pense que quelque chose dans ce sens devrait fonctionner:

  1. Lisez le fichier markdown/R avec readLines
  2. Utilisez grep pour trouver les morceaux de code, en recherchant les lignes commençant par <<< par exemple
  3. Prenez un sous-ensemble de l'objet qui contient les lignes d'origine pour obtenir uniquement le code
  4. Vider cela dans un fichier temporaire en utilisant writeLines
  5. Sourcez ce fichier dans votre session R

Envelopper cela dans une fonction devrait vous donner ce dont vous avez besoin.

2
Paul Hiemstra

Le hack suivant a bien fonctionné pour moi:

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
qed

Je recommanderais de conserver l'analyse principale et le code de calcul dans le fichier .R et d'importer les morceaux selon les besoins dans le fichier .Rmd. J'ai expliqué le processus ici .

1
pbahr

J'utilise la fonction personnalisée suivante

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

source_rmd("munge_script.Rmd")
0
Joe