Подтвердить что ты не робот

Как можно использовать кешированные результаты, чтобы воспроизвести среду в данном куске?

TL;DR

Мой вопрос: Внутри сеанса R есть способ использовать knitr кешированные результаты для "быстрой перемотки" в среду (т.е. набор объектов) доступный в данном блоке кода, в том же смысле, что и сам knit()?


Настройка:

knitr встроенная кеширование фрагментов кода является одной из его функций-убийц.

Это особенно полезно, когда некоторые куски содержат трудоемкие вычисления. Если они (или фрагмент, на которых они зависят), будут изменены, вычисления должны выполняться только при первом оформлении документа knit ed: при всех последующих вызовах knit объекты, созданные куском, будут загружены из кеша.

Здесь пример с минимальным изображением, файл с именем "lotsOfComps.Rnw":

\documentclass{article}
\begin{document}

The calculations in this chunk take a looooong time.

<<slowChunk, cache=TRUE>>=
Sys.sleep(30)  ## Stands in for some time-consuming computation
x <- sample(1:10, size=2)
@

I wish I could `fast-forward' to this chunk, to view the cached value of 
\texttt{x}

<<interestingChunk>>=
y <- prod(x)^2
y
@

\end{document}

Время, необходимое для вязания и TeXify "lotsOfComps.Rnw":

## First time
system.time(knit2pdf("lotsOfComps.Rnw"))
##   user  system elapsed
##   0.07    0.02   31.81

## Second (and subsequent) runs
system.time(knit2pdf("lotsOfComps.Rnw"))
##   user  system elapsed
##   0.03    0.02    1.28

Мой вопрос:

Внутри сеанса R есть способ использовать кешированные результаты knitr для "быстрой перемотки вперед" в среду (то есть набор объектов), доступную в данном блоке кода, в том же смысл, что сам knit()?


Выполнение purl("lotsOfComps.Rnw"), а затем запуск кода в "lotsOfComps.R" не работает, потому что все объекты по пути должны быть пересчитаны.

В идеале, можно было бы сделать что-то подобное, чтобы оказаться в среде, которая существует в начале <<interestingChunk>>=:

spin("lotsOfComps.Rnw", chunk="interestingChunk")
ls()
# [1] "x"
x
# [1] 3 8

Так как spin() недоступен (пока?), какой лучший способ получить эквивалентный результат?

4b9b3361

Ответ 1

Вот одно решение, которое все еще немного неудобно, но оно работает. Идея состоит в том, чтобы добавить параметр chunk с именем mute, который принимает по умолчанию NULL, но также может принимать выражение R, например. mute_later() ниже. Когда knitr оценивает параметры фрагмента, mute_later() может быть оценен и возвращается NULL; в то же время есть побочные эффекты в opts_chunk (установка глобальных параметров chunk, таких как eval = FALSE).

Теперь вам нужно положить mute=mute_later() в кусок, после которого вы хотите пропустить остальные куски, например. вы можете переместить эту опцию с example-a на example-b. Поскольку mute_later() возвращает NULL, который является значением по умолчанию для параметров mute, кеш не будет разорван, даже если вы переместите эту опцию вокруг.

\documentclass{article}
\begin{document}

<<setup, include=FALSE, cache=FALSE>>=
rm(list = ls(all.names = TRUE), envir = globalenv())
opts_chunk$set(cache = TRUE) # enable cache to make it faster
opts_chunk$set(eval = TRUE, echo = TRUE, include = TRUE)

# set global options to mute later chunks
mute_later = function() {
  opts_chunk$set(cache = FALSE, eval = FALSE, echo = FALSE, include = FALSE)
  NULL
}
# a global option mute=NULL so that using mute_later() will not break cache
opts_chunk$set(mute = NULL)
@

<<example-a, mute=mute_later()>>=
x = rnorm(4)
Sys.sleep(5)
@

<<example-b>>=
y = rpois(10,5)
Sys.sleep(5)
@

<<example-c>>=
z = 1:10
Sys.sleep(3)
@

\end{document}

Это неудобно в том смысле, что вам нужно вырезать и вставить , mute=mute_later(). В идеале вы должны просто установить ярлык фрагмента, как тот, который я написал для Барри.

Причина, по которой мой первоначальный смысл не срабатывает, заключается в том, что куски блоков игнорируются при кешировании. Во второй раз, когда вы knit() файла, кусок куска checkpoint для example-a был пропущен, поэтому eval=TRUE для остальных кусков, и вы видели, что все куски были оценены. Для сравнения, параметры chunk всегда динамически оцениваются.

Ответ 2

Это должно быть одним из самых уродливых kludges, которые я написал через некоторое время...

Основная идея - сканировать файл .Rnw для фрагментов, извлекать их имена, определять, какие из них кэшированы, а затем определять, какие из них нужно загрузить. Как только мы это сделаем, мы шаг за шагом сканируем каждое имя chunk, которое нужно загрузить, определить имя базы данных из папки кэша и загрузить его с помощью lazyLoad. После того, как мы загрузим все куски, нам нужно провести оценку. Ужасно, и я уверен, что есть несколько ошибок, но, похоже, он работает на простом примере, который вы дали, и некоторых других минимальных примерах, которые я создал. Это делает предположение, что файл .Rnw находится в текущем рабочем каталоге...

load_cache_until <- function(file, chunk, envir = parent.frame()){
    require(knitr)

    # kludge to detect chunk names, which come before the chunk of
    # interest, and which are cached... there has to be a nicer way...
    text <- readLines(file)
    chunks <- grep("^<<.*>>=", text, value = T)
    chunknames <- gsub("^<<([^,>]*)[,>]*.*", "\\1", chunks)
    #detect unnamed chunks
    tmp <- grep("^\\s*$", chunknames)
    chunknames[tmp] <- paste0("unnamed-chunk-", seq_along(tmp))
    id <- which(chunk == chunknames)
    previouschunks <- chunknames[seq_len(id - 1)]
    cachedchunks <- chunknames[grep("cache\\s*=\\s*T", chunks)]

    # These are the names of the chunks we want to load
    extractchunks <- cachedchunks[cachedchunks %in% previouschunks]

    oldls <- ls(envir, all = TRUE)
    # For each chunk...
    for(ch in extractchunks){   
        # Detect the file name of the database...
        pat <- paste0("^", ch, ".*\\.rdb")
        val <- gsub(".rdb", "", dir("cache", pattern = pat))
        # Lazy load the database
        lazyLoad(file.path("cache", val), envir = envir)
    }
    # Detect the new objects added
    newls <- ls(envir, all = TRUE)
    # Force evaluation...  There is probably a better way
    # to do this too...
    lapply(setdiff(newls, oldls), get)

    invisible()

}

load_cache_until("lotsOfComps.Rnw", "interestingChunk")

Сделать код более надежным остается в качестве упражнения для читателя.

Ответ 3

Yihui указывает на суть, которая приближается к тому, что я делал точно.

В ответ на вопрос Барри Роулингсона (aka Spacedman), Yihui построил крючок "контрольной точки", который позволяет пользователю установить имя последнего фрагмента, который будет обработан вызовом вязать. Чтобы обработать куски через один из них с именем example-a, просто сделайте opts_chunk$set(checkpoint = 'example-a') где-то в исходном блоке "setup".

Решение работает красиво - при первом запуске с данной контрольной точкой. Второе и последующее время, к сожалению, knit, по-видимому, игнорируют контрольную точку и обрабатывают все куски. (Я обсуждаю обходное решение ниже, но это не идеально).

Вот немного сокращенная версия Yihui gist:

\documentclass{article}
\begin{document}

<<setup, include=FALSE>>=
rm(list = ls(all.names = TRUE), envir = globalenv())
opts_chunk$set(cache = TRUE) # enable cache to make it faster
opts_chunk$set(eval = TRUE, echo = TRUE, include = TRUE)

# Define hook that will skip all chunks after the one named in checkpoint
knit_hooks$set(checkpoint = function(before, options, envir) {
if (!before && options$label == options$checkpoint) {
opts_chunk$set(cache = FALSE, eval = FALSE, echo = FALSE, include = FALSE)
}
})

## Set the checkpoint
opts_chunk$set(checkpoint = 'example-a') # restore objects up to example-a
@

<<example-a>>=
x = rnorm(4)
@

<<example-b>>=
y = rpois(10,5)
@

<<example-c>>=
z = 1:10
@

\end{document}

Поскольку checkpoint="example-a", script выше должен проходить через второй кусок, а затем подавлять все дополнительные куски, включая те, которые создают y и z. Попробуйте пару раз посмотреть, что произойдет:

library(knitr)

## First time, works like a charm
knit("checkpoint.Rnw")
ls()
[1] "x"

## Second time, Oops!, runs right past the checkpoint
knit("checkpoint.Rnw")
ls()
[1] "x" "y" "z"

Обходной путь, о котором я упоминал выше, после первого прогона до

  • Изменить checkpoint.Rnw, чтобы установить другую контрольную точку (например, opts_chunk$set(checkpoint = 'example-b'))
  • Запустите knit("checkpoint.Rnw"),
  • Изменить checkpoint.Rnw, чтобы вернуть контрольную точку на 'example-a, (делая, opts_chunk$set(checkpoint = 'example-a'))
  • Выполнить knit("checkpoint.Rnw) еще раз. Это снова обработает все куски, но не выше example-a.

Это может быть намного быстрее, чем перекомпоновка всех объектов в кусках, поэтому хорошо знать, даже если это не идеально.

Ответ 4

Как добавить следующий фрагмент кода в нижней части файла уценки?

```{r save_workspace_if_not_saved_yet, echo=FALSE}
if(!file.exists('knitr_session.RData')) {
  save.image(file = 'knitr_session.RData')
}
```

В первый раз, когда вы вязаете, состояние рабочей области в конце процесса будет сохранено (при условии, что процесс не вызывает ошибок). Каждый раз, когда вы хотите получить последнюю версию своего рабочего пространства, просто удалите файл в своем рабочем каталоге.

Ответ 5

Они похожи на любой файл данных, созданный save. Если вы возьмете экзамен karit-cache из него новое местоположение, это просто:

> library(knitr)
> knit("./005-latex.Rtex")
> load("cache/latex-my-cache_d9835aca7e54429f59d22eeb251c8b29.RData")
> ls()
 [1] "x"