Bagaimana cara sumber file R Markdown seperti `sumber ('myfile.r')`?

89

Saya sering memiliki file R Markdown utama atau file LaTeX knitr di mana saya memiliki sourcebeberapa file R lainnya (misalnya, untuk pemrosesan data). Namun, saya berpikir bahwa dalam beberapa kasus akan bermanfaat untuk memiliki file bersumber ini menjadi dokumen mereka sendiri yang dapat direproduksi (misalnya, file R Markdown yang tidak hanya menyertakan perintah untuk pemrosesan data tetapi juga menghasilkan dokumen yang dapat direproduksi yang menjelaskan keputusan pemrosesan data ).

Jadi, saya ingin memiliki perintah seperti source('myfile.rmd')di file R Markdown utama saya. yang akan mengekstrak dan sumber semua kode R dalam potongan kode R dari myfile.rmd. Tentu saja hal ini menimbulkan kesalahan.

Perintah berikut berfungsi:

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

di mana results='hide'dapat dihilangkan jika output diinginkan. Yaitu, knitr mengeluarkan kode R dari myfile.rmdke myfile.R.

Namun, tampaknya tidak sempurna:

  • itu menghasilkan pembuatan file tambahan
  • ia harus muncul dalam potongan kodenya sendiri jika kontrol atas tampilan diperlukan.
  • Tidak seanggun sesederhana itu source(...).

Jadi pertanyaan saya: Apakah ada cara yang lebih elegan untuk mencari kode R dari file R Markdown?

Jeromy Anglim
sumber
Sebenarnya saya kesulitan memahami pertanyaan Anda (saya membacanya beberapa kali). Anda dapat mencari skrip R lain dengan mudah ke dalam sebuah Rmdfile. Tetapi Anda juga ingin membuat sumber di markdownfile lain menjadi file yang dirajut?
Maiasaura
4
Saya ingin sumber kode R di dalam potongan kode R di file R Markdown (mis., * .Rmd)? Saya telah mengedit pertanyaan sedikit untuk mencoba membuat segalanya lebih jelas.
Jeromy Anglim
Sesuatu di sepanjang garis includedalam lateks. Jika penurunan harga mendukung penyertaan dokumen penurunan harga lainnya, seharusnya relatif mudah untuk membuat fungsi seperti itu.
Paul Hiemstra
@PaulHiemstra Saya rasa kemampuan untuk sumber teks dan potongan kode R akan berguna juga. Saya secara khusus berpikir untuk hanya mencari kode dalam dokumen R Markdown.
Jeromy Anglim

Jawaban:

35

Tampaknya Anda sedang mencari satu baris. Bagaimana kalau memasukkan ini ke dalam dirimu .Rprofile?

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

Namun, saya tidak mengerti mengapa Anda ingin source()kode di file Rmd itu sendiri. Maksud saya knit()akan menjalankan semua kode dalam dokumen ini, dan jika Anda mengekstrak kode dan menjalankannya dalam potongan, semua kode akan dijalankan dua kali saat Anda knit()menjalankan dokumen ini (Anda menjalankan diri sendiri di dalam). Kedua tugas itu harus terpisah.

Jika Anda benar-benar ingin menjalankan semua kode, RStudio telah membuat ini cukup mudah: Ctrl + Shift + R. Ini pada dasarnya memanggil purl()dan di source()belakang layar.

Yihui Xie
sumber
8
Hai @Yihui, menurut saya ini membantu karena terkadang analisis Anda mungkin diatur dalam skrip kecil, tetapi dalam laporan Anda, Anda ingin memiliki kode untuk keseluruhan pipeline.
Lucacerone
9
Jadi kasus penggunaan di sini adalah Anda ingin menulis semua kode dan membuatnya terdokumentasi dan dijelaskan secara mendalam, tetapi kode tersebut dijalankan oleh beberapa skrip lain.
Brash Equilibrium
4
@BrashEquilibrium Ini adalah masalah menggunakan source()atau knitr::knit()menjalankan kode. Saya tahu orang-orang kurang paham dengan yang terakhir, tetapi purl()tidak dapat diandalkan. Anda telah diperingatkan: github.com/yihui/knitr/pull/812#issuecomment-53088636
Yihui Xie
5
@Yihui Apa alternatif yang diusulkan untuk 'sumber (purl (x, ...))' dalam pandangan Anda? Bagaimana satu sumber beberapa file * .Rmd, tanpa mengalami kesalahan terkait label potongan duplikat? Saya lebih suka tidak ingin kembali ke dokumen yang akan menjadi sumber dan merajutnya. Saya menggunakan * .Rmd untuk banyak file, yang berpotensi harus saya ekspor dan diskusikan dengan orang lain, jadi akan sangat bagus untuk dapat membuat beberapa file Rmd untuk semua langkah analisis.
stats-hb
knitr mengeluarkan kesalahan "Kesalahan: Paket yang dibutuhkan hilang", saat merender file .rmd. Saya harus mengeksekusi kode di file .rmd untuk menemukan pesan kesalahan sebenarnya yang berisi nama paket yang hilang. Satu kasus caretdiperlukan kernlabdengan svm.
CW
19

Faktorkan kode umum ke dalam file R terpisah, lalu sumber file R tersebut ke dalam setiap file Rmd yang Anda inginkan.

jadi misalnya saya punya dua laporan yang harus saya buat, Wabah Flu dan Analisis Senjata vs Mentega. Secara alami saya akan membuat dua dokumen Rmd dan selesai dengannya.

Sekarang misalkan bos datang dan ingin melihat variasi harga Wabah Flu versus Mentega (mengendalikan amunisi 9mm).

  • Menyalin dan menempelkan kode untuk menganalisis laporan ke dalam laporan baru adalah ide yang buruk untuk menggunakan kembali kode, dll.
  • Saya ingin terlihat bagus.

Solusi saya adalah memfaktorkan proyek ke dalam file-file ini:

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

dalam setiap file Rmd saya akan memiliki sesuatu seperti:

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

Masalahnya di sini adalah kita kehilangan reproduktifitas. Solusi saya untuk itu adalah dengan membuat dokumen anak umum untuk disertakan ke dalam setiap file Rmd. Jadi di akhir setiap file Rmd yang saya buat, saya menambahkan ini:

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

Dan, tentu saja, 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, ini dirancang untuk alur kerja Rmd -> html. Ini akan menjadi kekacauan yang buruk jika Anda menggunakan lateks atau apa pun. Dokumen Rmd ini memeriksa lingkungan global untuk semua file source () dan menyertakan sumbernya di akhir dokumen Anda. Ini termasuk jquery ui, tablesorter, dan mengatur dokumen untuk menggunakan gaya akordeon untuk menampilkan / menyembunyikan file bersumber. Ini sedang dalam proses, tetapi jangan ragu untuk menyesuaikannya dengan penggunaan Anda sendiri.

Bukan satu kalimat, saya tahu. Semoga ini memberi Anda beberapa ide setidaknya :)

Keith Twombley
sumber
4

Mungkin orang harus mulai berpikir berbeda. Masalah saya adalah sebagai berikut: Tulis setiap kode yang biasanya Anda miliki dalam potongan .Rmd dalam file .R. Dan untuk dokumen Rmd yang Anda gunakan untuk merajut yaitu html, Anda hanya memiliki sisa

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

Dengan cara ini Anda mungkin akan membuat sekumpulan file .R dan Anda kehilangan keuntungan dari memproses semua kode "potongan demi potongan" menggunakan ctrl + alt + n (atau + c, tetapi biasanya ini tidak berhasil). Tapi, saya membaca buku tentang penelitian yang dapat direproduksi oleh Pak Gandrud dan menyadari, bahwa dia pasti menggunakan file knitr dan .Rmd hanya untuk membuat file html. Analisis Utama itu sendiri adalah file .R. Saya pikir dokumen .Rmd dengan cepat tumbuh terlalu besar jika Anda mulai melakukan seluruh analisis Anda di dalamnya.

Pharcyde
sumber
3

Jika Anda hanya setelah kode, saya pikir sesuatu di sepanjang baris ini harus berfungsi:

  1. Baca file markdown / R dengan readLines
  2. Gunakan grepuntuk menemukan potongan kode, mencari baris yang dimulai dengan <<<misalnya
  3. Ambil subset dari objek yang berisi baris asli untuk mendapatkan kodenya saja
  4. Buang ini ke file sementara menggunakan writeLines
  5. Sumber file ini ke sesi R.

Membungkus ini dalam suatu fungsi harus memberi Anda apa yang Anda butuhkan.

Paul Hiemstra
sumber
1
Terima kasih, saya rasa itu akan berhasil. Namun, empat poin pertama terdengar seperti apa yang sudah dilakukan Stangle dengan cara yang dapat diandalkan untuk Sweave dan apa yang knit('myfile.rmd', tangle=TRUE)dilakukannya di knitr. Saya rasa saya sedang mencari satu liner yang kusut dan bersumber dan idealnya tidak membuat file.
Jeromy Anglim
Setelah Anda membungkusnya dalam suatu fungsi, itu menjadi satu perjalanan;). Apa yang dapat Anda lakukan adalah menggunakan textConnectionuntuk meniru file, dan sumber dari itu. Ini akan menghindari pembuatan file.
Paul Hiemstra
Iya. textConnectionmungkin tempat yang tepat untuk melihat.
Jeromy Anglim
2

Peretasan berikut berfungsi dengan baik untuk saya:

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

Saya menggunakan fungsi kustom berikut

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

source_rmd("munge_script.Rmd")
Joe
sumber
2

Coba fungsi purl dari knitr:

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

Petr Hala
sumber
1

Saya akan merekomendasikan menyimpan analisis utama dan kode perhitungan dalam file .R dan mengimpor potongan sesuai kebutuhan dalam file .Rmd. Saya telah menjelaskan prosesnya di sini .

pbahr
sumber
1

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

letakkan perintah ini sebelum memanggil fungsi yang terdapat dalam nama_script_file_name.RAnda.

yang "./" menambahkan sebelum your_script_file_name.R untuk menunjukkan arah ke file Anda jika Anda sudah membuat sebuah Proyek.

Anda dapat melihat tautan ini untuk detail lebih lanjut: https://bookdown.org/yihui/rmarkdown-cookbook/source-script.html

Tranle
sumber
0

ini berhasil untuk saya

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