创建不改变随机种子的 knitr 块?
create knitr chunks that don't change the random seed?
在 rmarkdown 文档中,我想有选择地创建块,这些块在 运行 时不会影响全局随机种子。这个想法在逻辑上类似于 R.utils
的 withSeed 函数,运行 使用指定的随机种子发送一些代码,然后随机种子被重置为它之前的状态代码是 运行.
我想到了一种使用 knitr
钩子的方法:
---
title: Test
output: html_document
---
```{r setup, include=FALSE}
set.seed(123)
knitr::opts_chunk$set(echo = TRUE)
knitr::knit_hooks$set(no_seed = function(before, options, envir) {
# See http://www.cookbook-r.com/Numbers/Saving_the_state_of_the_random_number_generator/
if (before){
initial_seed <- NULL
if(exists(".Random.seed", .GlobalEnv))
initial_seed = get(".Random.seed", .GlobalEnv)
assign(".initial_seed", initial_seed, envir = envir)
}else{
if(exists(".initial_seed", envir = envir)){
initial_seed = get(".initial_seed", envir)
if(!is.null(initial_seed)){
assign(".Random.seed", initial_seed, envir = .GlobalEnv)
}else{
rm(".Random.seed", envir = .GlobalEnv)
}
rm(".initial_seed", envir = envir)
}
}
})
# A function to summarise the state of the RNG in a hash
seed_digest = function(){
ifelse(exists(".Random.seed", .GlobalEnv),
digest::digest(.GlobalEnv$.Random.seed),
NA)
}
```
## Chunk 1
```{r}
seed_digest()
rnorm(3)
seed_digest()
```
## Interloper chunk
```{r no_seed=TRUE}
seed_digest()
rnorm(3)
```
## Chunk 2
```{r}
seed_digest()
rnorm(3)
```
knitr hook no_seed
有一个“之前”和“之后”的组件。在之前的组件中,它将 RNG 在环境中的当前状态保存在一个名为 .initial_seed
的变量中。在块为 运行 之后,读取变量并将随机种子重置为原来的样子。
但是,这会在代码运行的环境中产生副作用 运行,这是不可取的。有没有没有副作用、更优雅或更健壮的更好方法?
如果函数需要持久存储,您可以使用local()
创建一个环境来容纳它,它不会干扰其他任何东西。这是对执行此操作的代码的修改:
hook_fn <- local({
initial_seed <- NULL
function(before, options, envir) {
if (before){
initial_seed <<- if(exists(".Random.seed", envir = .GlobalEnv, inherits=FALSE))
.Random.seed
}else{
if(!is.null(initial_seed))
.Random.seed <<- initial_seed
else
rm(".Random.seed", envir = .GlobalEnv)
}
}
})
knitr::knit_hooks$set(no_seed = hook_fn)
这样可以稍微简化一下:
initial_seed
已知存在于函数环境(求值环境的父级)中,因此 <<-
将对其进行赋值。
.GlobalEnv
已知是该本地环境的父级,因此如果 .Random.seed
存在于此,函数将看到它。
- 如果
.Random.seed
不存在于.GlobalEnv
,initial_seed
将被设置为NULL
,因为if
没有else
条款。
在 rmarkdown 文档中,我想有选择地创建块,这些块在 运行 时不会影响全局随机种子。这个想法在逻辑上类似于 R.utils
的 withSeed 函数,运行 使用指定的随机种子发送一些代码,然后随机种子被重置为它之前的状态代码是 运行.
我想到了一种使用 knitr
钩子的方法:
---
title: Test
output: html_document
---
```{r setup, include=FALSE}
set.seed(123)
knitr::opts_chunk$set(echo = TRUE)
knitr::knit_hooks$set(no_seed = function(before, options, envir) {
# See http://www.cookbook-r.com/Numbers/Saving_the_state_of_the_random_number_generator/
if (before){
initial_seed <- NULL
if(exists(".Random.seed", .GlobalEnv))
initial_seed = get(".Random.seed", .GlobalEnv)
assign(".initial_seed", initial_seed, envir = envir)
}else{
if(exists(".initial_seed", envir = envir)){
initial_seed = get(".initial_seed", envir)
if(!is.null(initial_seed)){
assign(".Random.seed", initial_seed, envir = .GlobalEnv)
}else{
rm(".Random.seed", envir = .GlobalEnv)
}
rm(".initial_seed", envir = envir)
}
}
})
# A function to summarise the state of the RNG in a hash
seed_digest = function(){
ifelse(exists(".Random.seed", .GlobalEnv),
digest::digest(.GlobalEnv$.Random.seed),
NA)
}
```
## Chunk 1
```{r}
seed_digest()
rnorm(3)
seed_digest()
```
## Interloper chunk
```{r no_seed=TRUE}
seed_digest()
rnorm(3)
```
## Chunk 2
```{r}
seed_digest()
rnorm(3)
```
knitr hook no_seed
有一个“之前”和“之后”的组件。在之前的组件中,它将 RNG 在环境中的当前状态保存在一个名为 .initial_seed
的变量中。在块为 运行 之后,读取变量并将随机种子重置为原来的样子。
但是,这会在代码运行的环境中产生副作用 运行,这是不可取的。有没有没有副作用、更优雅或更健壮的更好方法?
如果函数需要持久存储,您可以使用local()
创建一个环境来容纳它,它不会干扰其他任何东西。这是对执行此操作的代码的修改:
hook_fn <- local({
initial_seed <- NULL
function(before, options, envir) {
if (before){
initial_seed <<- if(exists(".Random.seed", envir = .GlobalEnv, inherits=FALSE))
.Random.seed
}else{
if(!is.null(initial_seed))
.Random.seed <<- initial_seed
else
rm(".Random.seed", envir = .GlobalEnv)
}
}
})
knitr::knit_hooks$set(no_seed = hook_fn)
这样可以稍微简化一下:
initial_seed
已知存在于函数环境(求值环境的父级)中,因此<<-
将对其进行赋值。.GlobalEnv
已知是该本地环境的父级,因此如果.Random.seed
存在于此,函数将看到它。- 如果
.Random.seed
不存在于.GlobalEnv
,initial_seed
将被设置为NULL
,因为if
没有else
条款。