Используйте `rmarkdown :: render` в ограниченном окружении - PullRequest
0 голосов
/ 09 октября 2018

У меня есть следующий Rmd файл, который я назвал test.Rmd:

---
title: "test"
output: html_document
---

```{r}
print(y)
```

```{r}
x <- "don't you ignore me!"
print(x)
```

Я хочу вызвать рендер следующим образом:

render('test.Rmd', output_format = "html_document",
        output_file = 'test.html',
        envir = list(y="hello"))

, но он не работает:

processing file: test.Rmd
  |................                                                 |  25%
  ordinary text without R code

  |................................                                 |  50%
label: unnamed-chunk-1
  |.................................................                |  75%
  ordinary text without R code

  |.................................................................| 100%
label: unnamed-chunk-2
Quitting from lines 11-13 (test.Rmd) 
Error in print(x) : object 'x' not found

Первый кусок прошел нормально, так что что-то сработало.Если я определю y в моей глобальной среде, я смогу запустить его без аргумента envir, и он будет работать нормально.

Я подумал, что render не любит списки, поэтому давайте создадим правильную среду:

y_env <- as.environment(list(y="hello"))
ls(envir = y_env)
# [1] "y"

render('test.Rmd', output_format = "html_document",
       output_file = 'test.html',
       envir = y_env)

Но что еще хуже, он не находит print!

processing file: test.Rmd
  |................                                                 |  25%
  ordinary text without R code

  |................................                                 |  50%
label: unnamed-chunk-1
Quitting from lines 7-8 (test.Rmd) 
Error in eval(expr, envir, enclos) : could not find function "print"

Теперь в документах упоминается использование функции new.env, поэтому из отчаяния я пробую это:

y_env <- new.env()
y_env$y <- "hello"
render('test.Rmd', output_format = "html_document",
       output_file = 'test.html',
       envir = y_env)

И теперь это работает!

processing file: test.Rmd
  |................                                                 |  25%
  ordinary text without R code

  |................................                                 |  50%
label: unnamed-chunk-1
  |.................................................                |  75%
  ordinary text without R code

  |.................................................................| 100%
label: unnamed-chunk-2

output file: test.knit.md

"C:/Program Files/RStudio/bin/pandoc/pandoc" +RTS -K512m -RTS test.utf8.md --to html --from markdown+autolink_bare_uris+ascii_identifiers+tex_math_single_backslash --output test.html --smart --email-obfuscation none --self-contained --standalone --section-divs --template "**redacted**\RMARKD~1\rmd\h\DEFAUL~1.HTM" --no-highlight --variable highlightjs=1 --variable "theme:bootstrap" --include-in-header "**redacted**\AppData\Local\Temp\RtmpGm9aXz\rmarkdown-str3f6c5101cb3.html" --mathjax --variable "mathjax-url:https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML" 

Output created: test.html

Итак, я запутался в нескольких вещах, резюмируя:

  • Почему renderраспознавать списки (первый блок не потерпел неудачу), но затем игнорирует обычные назначения в фрагментах
  • Почему моя вторая попытка не работает и чем она отличается от моей третьей попытки?
  • Этоошибка?
  • Какой идиоматический способ сделать это?

1 Ответ

0 голосов
/ 09 октября 2018

Ваши первые два примера не работают по разным причинам.Чтобы понять обе ошибки, сначала важно немного узнать о том, как куски кода оцениваются knitr и rmarkdown .


общая процедура оценки чанка кода knitr.

Когда вы вызываете rmarkdown::render() в своем файле, каждый фрагмент кода в конечном итоге оценивается вызовом evaluate::evaluate().С точки зрения поведения при оценке и правил видимости, evaluate() ведет себя почти так же, как базовая функция R eval().

(где evaluate::evaluate() больше всего отличается от eval() тем, как он обрабатывает выходные данные каждого вычисленного выражения. Как объяснено в ?evaluate, в дополнение к оценке выражения, переданного в качестве первого аргумента, он "захватывает всю информацию, необходимую для воссоздания вывода, как если бы вы скопировали и вставили код в терминал R ". Эта информация включает в себя графики, предупреждения и сообщения об ошибках, поэтому она так удобна в пакете, подобном knitr !)

В любом случае возможный вызов evaluate() из функции knitr:::block_exec() выглядит примерно так

evaluate::evaluate(code, envir = env, ...)

, в котором:

  • code - это вектор символьных строк, дающий (возможно, несколько) выражений, составляющих текущий фрагмент.

  • env - это указанное вами значениеформальный аргумент envir в исходном вызове rmarkdown::render().


Ваш первый пример

В вашем первом примере envir - это список, а не среда.В этом случае оценка выполняется в локальной среде, созданной вызовом функции.Неразрешенные символы (как описано в ?eval и ?evaluate) ищутся сначала в списке, переданном envir, а затем в цепочке окружений, начинающихся с аргумента enclos.Решающее значение имеют локальные назначения для среды временной оценки, которая прекращается после завершения вызова функции.

Поскольку evaluate() работает по одному на символьном векторе выражений, когда envir - список, переменные, созданные в одном из этих выражений, не будут доступны для использования в последующих выражениях.

Когда аргумент envir для rmarkdown::render() является списком, ваш блок кода в конечном итоге оценивается следующим вызовом:

library(evaluate)
code <- c('x <- "don\'t you ignore me!"',
          'print(x)')
env <- list(y = 1:10)
evaluate(code, envir = env)

## Or, for prettier printing:
replay(evaluate(code, envir = env))
## > x <- "don't you ignore me!"
## > print(x)
## Error in print(x): object 'x' not found

Эффект точно такой же, как если бы высделал это с eval():

env <- list(y =1 :10)
eval(quote(x <- "don't you ignore me"), envir = env)
eval(quote(x), envir = env)
## Error in eval(quote(x), envir = env) : object 'x' not found

Ваш второй пример

Когда envir= - это среда, возвращаемая as.environment(list()), вы получаете ошибки по другой причине.В этом случае ваш кодовый блок в конечном итоге оценивается с помощью вызова, подобного следующему:

library(evaluate)
code <- c('x <- "don\'t you ignore me!"',
          'print(x)')
env <- as.environment(list(y = 1:10))
evaluate(code, envir = env)

## Or, for prettier printing:
replay(evaluate(code, envir = env))
## > x <- "don't you ignore me!"
## Error in x <- "don't you ignore me!": could not find function "<-"
## > print(x)
## Error in print(x): could not find function "print"

Как вы заметили, это происходит сбой, потому что as.environment() возвращает среду, в которой окружающая среда является пустой средой (т.е.среда возвращается emptyenv()).evaluate() (как и eval()) ищет символ <- в env и, когда он не находит его там, запускает цепочку окружающих сред, которые здесь не содержат совпадений.(Напомним также, что когда envir является средой, а не списком, аргумент enclos не используется.)


Рекомендуемое решение

Чтобы делать то, что вы хотите,вам нужно создать среду, которая: (1) содержит все объекты в вашем списке и что;(2) имеет в качестве окружающей среды родительскую среду вашего вызова render() (т.е. среду, в которой обычно оценивается вызов render()).Самый краткий способ сделать это - использовать изящную функцию list2env(), например, так:

env <- list2env(list(y="hello"), parent.frame())
render('test.Rmd', output_format = "html_document",
        output_file = 'test.html',
        envir = env)

В результате ваши куски кода будут оцениваться с помощью кода, подобного следующему, что вам и нужно:

library(evaluate)
code <- c('x <- "don\'t you ignore me!"',
          'print(x)')
env <- list2env(list(y = 1:10), envir = parent.frame())
evaluate(code, envir = env)
replay(evaluate(code, envir = env))
## > x <- "don't you ignore me!"
## > print(x)
## [1] "don't you ignore me!"
...