Como obter o arquivo R Markdown como `source ('myfile.r')`?

89

Freqüentemente, tenho um arquivo R Markdown principal ou um arquivo Knitr LaTeX onde eu sourcealgum outro arquivo R (por exemplo, para processamento de dados). No entanto, eu estava pensando que, em alguns casos, seria benéfico ter esses arquivos originados como seus próprios documentos reproduzíveis (por exemplo, um arquivo R Markdown que não apenas inclui comandos para processamento de dados, mas também produz um documento reproduzível que explica as decisões de processamento de dados )

Portanto, gostaria de ter um comando como source('myfile.rmd')no meu arquivo R Markdown principal. que extrairia e forneceria todo o código R dentro dos pedaços de código R de myfile.rmd. Claro, isso dá origem a um erro.

O seguinte comando funciona:

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

onde results='hide'poderia ser omitido se a saída fosse desejada. Ou seja, o knitr gera o código R de myfile.rmdem myfile.R.

No entanto, não parece perfeito:

  • resulta na criação de um arquivo extra
  • ele precisa aparecer em seu próprio bloco de código se o controle sobre a exibição for necessário.
  • Não é tão elegante quanto simples source(...).

Portanto, minha pergunta: Existe uma maneira mais elegante de obter o código R de um arquivo R Markdown?

Jeromy Anglim
fonte
Na verdade, estou tendo muita dificuldade em entender sua pergunta (li várias vezes). Você pode originar outros scripts R facilmente em um Rmdarquivo. Mas você também deseja originar outros markdownarquivos em um arquivo que está sendo tricotado?
Maiasaura
4
Eu quero fonte do código R dentro de pedaços de código R em arquivos R Markdown (ou seja, * .rmd)? Eu editei a pergunta um pouco para tentar tornar as coisas mais claras.
Jeromy Anglim
Algo parecido com o includelátex. Se a remarcação permitir a inclusão de outros documentos de remarcação, deve ser relativamente fácil criar tal função.
Paul Hiemstra
@PaulHiemstra Acho que a capacidade de originar o texto e os blocos de código R também seria útil. Estou pensando especificamente em fornecer apenas o código em um documento R Markdown.
Jeromy Anglim de

Respostas:

35

Parece que você está procurando por um one-liner. Que tal colocar isso no seu .Rprofile?

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

No entanto, não entendo por que você deseja source()o código no próprio arquivo Rmd. Quero dizer knit(), executarei todo o código neste documento, e se você extrair o código e executá-lo em um bloco, todo o código será executado duas vezes quando você knit()este documento (você executa dentro de você mesmo). As duas tarefas devem ser separadas.

Se você realmente deseja executar todo o código, rstudio fez esta bastante fácil: Ctrl + Shift + R. Basicamente, chama purl()e nos source()bastidores.

Yihui Xie
fonte
8
Olá @Yihui, acho que é útil porque às vezes sua análise pode ser organizada em pequenos scripts, mas em seu relatório você deseja ter o código de todo o pipeline.
lucacerone
9
Portanto, o caso de uso aqui é que você deseja escrever todo o código e fazer com que seja amplamente documentado e explicado, mas o código é executado por algum outro script.
Brash Equilibrium
4
@BrashEquilibrium É uma questão de usar source()ou knitr::knit()executar o código. Eu sei que as pessoas estão menos familiarizadas com o último, mas purl()não é confiável. Você foi avisado: github.com/yihui/knitr/pull/812#issuecomment-53088636
Yihui Xie
5
@Yihui Qual seria a alternativa proposta para 'source (purl (x, ...))' em sua opinião? Como um pode originar vários arquivos * .Rmd, sem encontrar um erro em relação a rótulos de pedaços duplicados? Prefiro não querer voltar ao documento a ser obtido e tricotá-lo. Eu uso * .Rmd para muitos arquivos, que potencialmente tenho que exportar e discutir com outras pessoas, então seria ótimo poder fornecer vários arquivos Rmd para todas as etapas da análise.
stats-hb
O knitr emite o erro "Erro: pacote obrigatório ausente", ao renderizar o arquivo .rmd. Tenho que executar o código no arquivo .rmd para encontrar a mensagem de erro real que contém o nome do pacote ausente. Um caso é caretnecessário kernlabcom svm.
CW de
19

Fatore o código comum em um arquivo R separado e, em seguida, forneça esse arquivo R em cada arquivo Rmd que você deseja.

então, por exemplo, digamos que eu tenho dois relatórios que preciso fazer, surtos de gripe e análise de armas contra manteiga. Naturalmente, eu criaria dois documentos Rmd e encerraria o processo.

Agora, suponha que o chefe apareça e queira ver as variações dos preços dos surtos de gripe versus manteiga (controlando para munição de 9 mm).

  • Copiar e colar o código para analisar os relatórios no novo relatório é uma má ideia para reutilização de código, etc.
  • Eu quero que fique bem.

Minha solução foi fatorar o projeto nestes arquivos:

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

dentro de cada arquivo Rmd, eu teria algo como:

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

O problema aqui é que perdemos reprodutibilidade. Minha solução para isso é criar um documento filho comum para incluir em cada arquivo Rmd. Portanto, no final de cada arquivo Rmd que crio, adiciono este:

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

E, é claro, 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, isso é projetado para o fluxo de trabalho Rmd -> html. Isso vai ser uma bagunça feia se você for com látex ou qualquer outra coisa. Este documento Rmd examina o ambiente global para todos os arquivos source () 'ed e inclui sua fonte no final do seu documento. Inclui jquery ui, tablesorter e configura o documento para usar um estilo acordeão para mostrar / ocultar arquivos originados. É um trabalho em andamento, mas sinta-se à vontade para adaptá-lo às suas necessidades.

Não é uma linha curta, eu sei. Espero que tenha pelo menos algumas ideias :)

Keith Twombley
fonte
4

Provavelmente, devemos começar a pensar diferente. Meu problema é o seguinte: Escreva todos os códigos que você normalmente teria em um trecho .Rmd em um arquivo .R. E para o documento Rmd que você usa para tricotar, ou seja, um html, você só deixou

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

Dessa forma, você provavelmente criará um monte de arquivos .R e perderá a vantagem de processar todo o código "pedaço após pedaço" usando ctrl + alt + n (ou + c, mas normalmente isso não funciona). Mas, eu li o livro sobre pesquisas reproduzíveis do Sr. Gandrud e percebi que ele definitivamente usa arquivos knitr e .Rmd exclusivamente para criar arquivos html. A análise principal em si é um arquivo .R. Acho que os documentos .Rmd rapidamente ficam muito grandes se você começar a fazer toda a sua análise internamente.

Pharcyde
fonte
3

Se você estiver logo atrás do código, acho que algo nesse sentido deve funcionar:

  1. Leia o arquivo markdown / R com readLines
  2. Use greppara encontrar os blocos de código, procurando linhas que começam com, <<<por exemplo
  3. Pegue o subconjunto do objeto que contém as linhas originais para obter apenas o código
  4. Despeje isso em um arquivo temporário usando writeLines
  5. Fonte este arquivo em sua sessão R

Envolver isso em uma função deve fornecer o que você precisa.

Paul Hiemstra
fonte
1
Obrigado, acho que funcionaria. No entanto, os primeiros quatro pontos soam como o que Stangle já faz de maneira confiável para Sweave e o que knit('myfile.rmd', tangle=TRUE)faz em knitr. Acho que estou procurando por um liner que se confunda e origine e, idealmente, não crie arquivos.
Jeromy Anglim
Depois de envolvê-lo em uma função, ele se torna um oneliner;). O que você pode fazer é textConnectionimitar um arquivo e fonte a partir dele. Isso evitaria a criação de um arquivo.
Paul Hiemstra
Sim. textConnectionpode ser o lugar para procurar.
Jeromy Anglim de
2

O seguinte hack funcionou bem para mim:

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

Eu uso a seguinte função personalizada

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

source_rmd("munge_script.Rmd")
Joe
fonte
2

Experimente a função purl do knitr:

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

Petr Hala
fonte
1

Eu recomendaria manter a análise principal e o código de cálculo no arquivo .R e importar os pedaços conforme necessário no arquivo .Rmd. Eu expliquei o processo aqui .

pbahr
fonte
1

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

coloque este comando antes de chamar as funções contidas em your_script_file_name.R.

o "./" adicionado antes de your_script_file_name.R para mostrar a direção para seu arquivo se você já criou um projeto.

Você pode ver este link para mais detalhes: https://bookdown.org/yihui/rmarkdown-cookbook/source-script.html

Tranle
fonte
0

isso funcionou para mim

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