Comment créer un fichier R Markdown comme `source ('myfile.r')`?

89

J'ai souvent un fichier principal R Markdown ou un fichier Knitr LaTeX où j'ai sourceun autre fichier R (par exemple, pour le traitement des données). Cependant, je pensais que dans certains cas, il serait avantageux que ces fichiers source 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 les décisions de traitement des données. ).

Ainsi, j'aimerais avoir une commande comme source('myfile.rmd')dans mon fichier principal R Markdown. 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. Ie, knitr sort le code R de myfile.rmddans 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(...).

Ainsi ma question: existe-t-il une manière plus élégante de rechercher le code R d'un fichier R Markdown?

Jeromy Anglim
la source
J'ai vraiment du mal à comprendre votre question (je l'ai lue plusieurs fois). Vous pouvez facilement créer d'autres scripts R dans un Rmdfichier. Mais vous souhaitez également générer d'autres markdownfichiers dans un fichier en cours de tricotage?
Maiasaura
4
Je veux trouver le code R dans des morceaux de code R dans des fichiers R Markdown (c'est-à-dire * .rmd)? J'ai édité un peu la question pour essayer de clarifier les choses.
Jeromy Anglim
Quelque chose du genre includeen latex. Si le démarque prend en charge l'inclusion d'autres documents de démarque, il devrait être relativement facile de créer une telle fonction.
Paul Hiemstra
@PaulHiemstra Je suppose que la possibilité de trouver le texte et les morceaux de code R serait également utile. Je pense spécifiquement à la recherche du code dans un document R Markdown.
Jeromy Anglim

Réponses:

35

Il semble que vous recherchiez un monoplace. 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 knit(), exécutera tout le code de ce document, et si vous extrayez le code et l'exécutez en un morceau, tout le code sera exécuté deux fois lorsque vous knit()présenterez ce document (vous vous exécutez vous-même). Les deux tâches doivent être séparées.

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

Yihui Xie
la source
8
Salut @Yihui Je pense que c'est utile parce que parfois votre analyse peut être organisée en petits scripts, mais dans votre rapport, vous voulez avoir le code pour l'ensemble du pipeline.
lucacerone
9
Donc, le cas d'utilisation ici est que vous voulez écrire tout le code et le faire documenter et expliquer en profondeur, mais le code est exécuté par un autre script.
Brash Equilibrium
4
@BrashEquilibrium Il s'agit d'utiliser source()ou knitr::knit()d'exécuter le code. Je sais que les gens sont moins familiers avec ce dernier, mais ce purl()n'est pas fiable. Vous avez été prévenu: github.com/yihui/knitr/pull/812#issuecomment-53088636
Yihui Xie
5
@Yihui Quelle serait l'alternative proposée à «source (purl (x, ...))» selon vous? Comment peut-on générer plusieurs fichiers * .Rmd, sans se heurter à une erreur concernant les étiquettes de bloc en double? Je préfère ne pas revenir au document à trouver et le tricoter. J'utilise * .Rmd pour de nombreux fichiers, que je dois potentiellement exporter et discuter avec d'autres, donc ce serait formidable de pouvoir trouver plusieurs fichiers Rmd pour toutes les étapes de l'analyse.
stats-hb
knitr émet une erreur "Erreur: le package requis est manquant", lors du rendu du fichier .rmd. Je dois exécuter du code dans le fichier .rmd pour trouver le vrai message d'erreur contenant le nom du package manquant. Un cas est caretrequis kernlabavec svm.
Charles
19

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

Donc, par exemple, disons que j'ai deux rapports à faire, les épidémies de grippe et l'analyse des armes à feu et du beurre. Naturellement, je créerais deux documents Rmd et j'en aurais terminé.

Supposons maintenant que le patron vienne et veuille voir les variations des épidémies de grippe par rapport aux prix du beurre (en contrôlant les 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 soit joli.

Ma solution était 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 à cela 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>

NB, ceci est conçu pour le workflow Rmd -> html. Ce sera un désordre laid si vous optez pour du latex ou autre chose. Ce document Rmd recherche dans l'environnement global tous les fichiers ed source () et inclut leur source à la fin de votre document. Il inclut jquery ui, tablesorter et configure le document pour qu'il utilise un style accordéon pour afficher / masquer les fichiers source. C'est un travail en cours, mais n'hésitez pas à l'adapter à vos propres usages.

Pas un one-liner, je sais. J'espère que cela vous donne au moins des idées :)

Keith Twombley
la source
4

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 plus 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 par M. Gandrud et 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.

Pharcyde
la source
3

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

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

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

Paul Hiemstra
la source
1
Merci, je suppose que cela fonctionnerait. Cependant, les quatre premiers points ressemblent à ce que Stangle fait déjà de manière fiable pour Sweave et à ce que knit('myfile.rmd', tangle=TRUE)fait Knitr. Je suppose que je recherche une ligne unique qui à la fois s'emmêle et source et ne crée idéalement aucun fichier.
Jeromy Anglim
Une fois que vous l'enveloppez dans une fonction, il devient un oneliner;). Ce que vous pouvez faire, c'est utiliser textConnectionpour imiter un fichier et en tirer la source. Cela éviterait la création d'un fichier.
Paul Hiemstra
Oui. textConnectionpourrait être l'endroit où chercher.
Jeromy Anglim
2

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)
}
qed
la source
2

J'utilise la fonction personnalisée suivante

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

source_rmd("munge_script.Rmd")
Joe
la source
2

Essayez la fonction purl de knitr:

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

Petr Hala
la source
1

Je recommanderais de conserver le code d'analyse et de calcul principal dans le fichier .R et d'importer les morceaux au besoin dans le fichier .Rmd. J'ai expliqué le processus ici .

pbahr
la source
1

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

mettez cette commande avant d'appeler les fonctions contenues dans le fichier your_script_file_name.R.

le "./" ajoutant avant votre_nom_fichier_script.R pour indiquer la direction de votre fichier si vous avez déjà créé un projet.

Vous pouvez voir ce lien pour plus de détails: https://bookdown.org/yihui/rmarkdown-cookbook/source-script.html

Tranle
la source
0

cela a fonctionné pour moi

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