Jak pozyskać plik R Markdown, taki jak `source ('myfile.r')`?

89

Często mam główny plik R Markdown lub knitr LaTeX, gdzie mam sourceinny plik R (np. Do przetwarzania danych). Pomyślałem jednak, że w niektórych przypadkach korzystne byłoby, gdyby te pliki źródłowe były ich własnymi, odtwarzalnymi dokumentami (np. Plik R Markdown, który nie tylko zawiera polecenia dotyczące przetwarzania danych, ale także tworzy odtwarzalny dokument wyjaśniający decyzje dotyczące przetwarzania danych ).

Dlatego chciałbym mieć polecenie takie jak source('myfile.rmd')w moim głównym pliku R Markdown. że ekstrakt i źródło cały kod R wewnątrz fragmentów kodu r o myfile.rmd. Oczywiście powoduje to błąd.

Działa to polecenie:

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

gdzie results='hide'można pominąć, jeśli dane wyjściowe były pożądane. To znaczy knitr wyprowadza kod R z myfile.rmddo myfile.R.

Jednak nie wydaje się to idealne:

  • Powoduje to utworzenie dodatkowego pliku
  • musi pojawić się we własnym fragmencie kodu, jeśli wymagana jest kontrola nad wyświetlaczem.
  • To nie jest tak eleganckie, jak proste source(...).

Dlatego moje pytanie: czy istnieje bardziej elegancki sposób pozyskiwania kodu R pliku R Markdown?

Jeromy Anglim
źródło
Naprawdę ciężko mi zrozumieć twoje pytanie (czytałem je kilka razy). Możesz łatwo pobrać inne skrypty języka R do Rmdpliku. Ale chcesz również źródłować w innych markdownplikach do pliku, który jest łączony?
Maiasaura
4
Chcę pobrać kod R wewnątrz fragmentów kodu R w plikach R Markdown (tj. * .Rmd)? Trochę zredagowałem pytanie, aby wyjaśnić sprawę.
Jeromy Anglim
Coś na wzór includelateksu. Jeśli przecena obsługuje włączanie innych dokumentów przecenowych, utworzenie takiej funkcji powinno być stosunkowo łatwe.
Paul Hiemstra
@PaulHiemstra Myślę, że przydałaby się również możliwość pozyskania tekstu i fragmentów kodu R. W szczególności myślę o pozyskaniu tylko kodu w dokumencie R Markdown.
Jeromy Anglim

Odpowiedzi:

35

Wygląda na to, że szukasz jednej linijki. Co powiesz na umieszczenie tego w swoim .Rprofile?

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

Jednak nie rozumiem, dlaczego chcesz source()kod w samym pliku Rmd. Chodzi mi o to, knit()że uruchomi cały kod w tym dokumencie, a jeśli wyodrębnisz kod i uruchomisz go w kawałku, cały kod zostanie uruchomiony dwukrotnie, gdy będziesz knit()ten dokument (uruchomisz się w sobie). Te dwa zadania powinny być oddzielne.

Jeśli naprawdę chcesz, aby uruchomić cały kod, RStudio uczynił to dość proste: Ctrl + Shift + R. Zasadniczo dzwoni purl()i source()za sceną.

Yihui Xie
źródło
8
Cześć @Yihui Myślę, że jest to pomocne, ponieważ czasami twoja analiza może być zorganizowana w małych skryptach, ale w raporcie chcesz mieć kod dla całego potoku.
lucacerone
9
Więc przypadek użycia jest taki, że chcesz napisać cały kod i mieć go dobrze udokumentowane i wyjaśnione, ale kod jest uruchamiany przez inny skrypt.
Brash Equilibrium
4
@BrashEquilibrium Jest to kwestia użycia source()lub knitr::knit()uruchomienia kodu. Wiem, że ludzie są mniej zaznajomieni z tym drugim, ale purl()nie można na nim polegać. Zostałeś ostrzeżony: github.com/yihui/knitr/pull/812#issuecomment-53088636
Yihui Xie
5
@Yihui Jaka byłaby według Ciebie proponowana alternatywa dla „source (purl (x, ...))”? W jaki sposób można pozyskać wiele plików * .Rmd bez wystąpienia błędu dotyczącego zduplikowanych etykiet porcji? Wolałbym nie wracać do dokumentu, który ma zostać pozyskany, i robić go na drutach. Używam * .Rmd dla wielu plików, które potencjalnie muszę wyeksportować i omówić z innymi, więc byłoby wspaniale móc pozyskiwać wiele plików Rmd dla wszystkich etapów analizy.
stats-hb
knitr wyświetla komunikat o błędzie „Błąd: brak wymaganego pakietu” podczas renderowania pliku .rmd. Muszę wykonać kod w pliku .rmd, aby znaleźć prawdziwy komunikat o błędzie zawierający nazwę brakującego pakietu. W przypadku svm caretwymagany jest jeden przypadek kernlab.
Charles
19

Uwzględnij wspólny kod w osobnym pliku R, a następnie umieść ten plik R w każdym pliku Rmd, w którym chcesz go umieścić.

na przykład, powiedzmy, że muszę sporządzić dwa raporty: Epidemie grypy i Pistolety kontra Analiza masła. Oczywiście stworzyłbym dwa dokumenty Rmd i skończyłbym z tym.

Teraz przypuśćmy, że szef przyjdzie i chce zobaczyć różnice między epidemiami grypy a cenami masła (kontrolując amunicję 9mm).

  • Kopiowanie i wklejanie kodu w celu analizy raportów do nowego raportu to zły pomysł na ponowne użycie kodu itp.
  • Chcę, żeby ładnie wyglądało.

Moim rozwiązaniem było uwzględnienie projektu w tych plikach:

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

w każdym pliku Rmd miałbym coś takiego:

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

Problem w tym, że tracimy powtarzalność. Moim rozwiązaniem jest utworzenie wspólnego dokumentu podrzędnego do dołączenia do każdego pliku Rmd. Więc na końcu każdego pliku Rmd, który tworzę, dodaję to:

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

I oczywiście 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>

Uwaga, jest to przeznaczone dla przepływu pracy Rmd -> html. Będzie to brzydki bałagan, jeśli wybierzesz lateks lub cokolwiek innego. Ten dokument Rmd przeszukuje globalne środowisko dla wszystkich plików ed source () i dołącza ich źródło na końcu dokumentu. Zawiera interfejs jquery, tableorter i ustawia dokument tak, aby używał stylu akordeonu do pokazywania / ukrywania plików źródłowych. Jest to praca w toku, ale możesz ją dostosować do własnych potrzeb.

Wiem, że nie jest to jedna kreska. Mam nadzieję, że podsunie Ci przynajmniej kilka pomysłów :)

Keith Twombley
źródło
4

Chyba trzeba zacząć myśleć inaczej. Mój problem jest następujący: Napisz każdy kod, który normalnie miałbyś w porcji .Rmd w pliku .R. A dla dokumentu Rmd, którego używasz do robienia na drutach, tj. HTML, zostało tylko

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

W ten sposób prawdopodobnie utworzysz kilka plików .R i stracisz przewagę przetwarzania całego kodu „kawałek po kawałku” za pomocą ctrl + alt + n (lub + c, ale normalnie to nie działa). Ale przeczytałem książkę o powtarzalnych badaniach pana Gandruda i zdałem sobie sprawę, że zdecydowanie używa on plików knitr i .Rmd wyłącznie do tworzenia plików html. Sama analiza główna to plik .R. Myślę, że dokumenty .Rmd szybko stają się zbyt duże, jeśli zaczniesz robić całą analizę w środku.

Pharcyde
źródło
3

Jeśli jesteś tuż po kodzie, myślę, że coś w tym stylu powinno działać:

  1. Przeczytaj plik markdown / R za pomocą readLines
  2. Służy grepdo znajdowania fragmentów kodu, wyszukując wiersze zaczynające się <<<na przykład od
  3. Weź podzbiór obiektu, który zawiera oryginalne wiersze, aby uzyskać tylko kod
  4. Zrzuć to do pliku tymczasowego za pomocą writeLines
  5. Umieść ten plik w swojej sesji języka R.

Opakowanie tego w funkcję powinno dać ci to, czego potrzebujesz.

Paul Hiemstra
źródło
1
Dziękuję, myślę, że to zadziała. Jednak pierwsze cztery punkty brzmią jak to, co Stangle już robi w niezawodny sposób w Sweave, a co knit('myfile.rmd', tangle=TRUE)robi w dzianinie. Myślę, że szukam jednej wkładki, która jednocześnie plącze się i tworzy źródła, a idealnie nie tworzy plików.
Jeromy Anglim
Po umieszczeniu go w funkcji staje się onelinerem;). Możesz użyć textConnectiondo naśladowania pliku i źródła z niego. Pozwoliłoby to uniknąć tworzenia pliku.
Paul Hiemstra
Tak. textConnectionmoże być odpowiednim miejscem.
Jeromy Anglim
2

Poniższy hack zadziałał dla mnie dobrze:

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)
}
co było do okazania
źródło
2

Używam następującej funkcji niestandardowej

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

source_rmd("munge_script.Rmd")
Joe
źródło
2

Wypróbuj funkcję bełkot z knitr:

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

Petr Hala
źródło
1

Zalecałbym przechowywanie głównego kodu analizy i obliczeń w pliku .R oraz importowanie potrzebnych fragmentów do pliku .Rmd. Tutaj wyjaśniłem ten proces .

pbahr
źródło
1

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

umieść to polecenie przed wywołaniem funkcji zawartych w your_script_file_name.R.

dodanie „./” przed twoją_skryptową_nazwa_pliku.R, aby pokazać kierunek do twojego pliku, jeśli już utworzyłeś projekt.

Możesz zobaczyć ten link, aby uzyskać więcej szczegółów: https://bookdown.org/yihui/rmarkdown-cookbook/source-script.html

Tranle
źródło
0

to działało dla mnie

source("myfile.r", echo = TRUE, keep.source = TRUE)
user63230
źródło