使用knitr在循环中创建章节

12

看这个可复制的例子:

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

## foo

```{r}
plot(1:3)
```

## bar

```{r}
plot(4:7)
```

## baz

```{r}
plot(8:12)
```
我希望能够自动创建这些部分,因为在进行进一步分析之前,我不能确定它们的数量。
获取此内容的输入将是 :
my_list   <- list(foo = 1:3, bar = 4:7, baz = 8:12)
my_fun    <- plot
my_depth  <- 2

而理想的答案(虽然我欢迎任何改进)将帮助我构建一个mdapply函数,这样我就可以直接运行:

```{r}
mdapply(X = my_list, FUN = my_fun, title_depth = my_depth)
```

并获得相同的输出。


3
也许可以使用以下方法生成动态的R Markdown块(我也需要做这个,但还没开始做):https://dev59.com/DWEi5IYBdhLWcg3wCoQp - Frank
2
谢谢Frank,非常有帮助!我想我已经让它工作了,不过还需要一些测试,请见下文。 - moodymudskipper
2
我发现brew模板在“循环处理事物”方面非常方便;例如,可以查看@Ramnath的好答案:使用knitr进行带标题的循环创建参数化的R markdown文档?。免责声明:自那些答案以来,我几乎没有关注过knitr的发展...;) - Henrik
https://dev59.com/ZLroa4cB1Zd3GeqPlHyt - Brian D
4个回答

17

R包pander可以即时生成Pandoc的Markdown。

关键是使用块选项results='asis'告诉R Markdown将pander的输出呈现为Markdown。您只需要小心生成有效的Markdown!

尝试一下:

---
title: "Test sections"
output: html_document
---

## A function that generates sections

```{r}
library(pander)

create_section <- function() {

   # Inserts "## Title (auto)"
   pander::pandoc.header('Title (auto)', level = 2)

   # Section contents
   # e.g. a random plot
   plot(sample(1000, 10))

   # a list, formatted as Markdown
   # adding also empty lines, to be sure that this is valid Markdown
   pander::pandoc.p('')
   pander::pandoc.list(letters[1:3])
   pander::pandoc.p('')
}
```

## Generate sections

```{r, results='asis'}
n_sections <- 3

for (i in seq(n_sections)) {
   create_section()
}
```

它仍然看起来有些粗糙,但Markdown也有其局限性...


2
我不认为这是hacky的,实际上它看起来相当符合惯用法。非常感谢!我认为我实际上可以将其调整为一个apply函数。 - moodymudskipper
当在Rmd文件上调用render()时,这不起作用。有什么想法为什么? - its.me.adam

9

看起来我找到了一种方法!

整个想法是将手动输入的内容作为字符串传递给内联代码中使用的knit(text = the_string)

所以,该函数基本上将一堆字符串粘在一起,再加上一点substitute魔法,使其感觉就像是apply系列的一部分。

  • 参数depth决定您想要多少个#

  • 参数options包含块选项,作为向量。

向量不应该能够同时包含逻辑和字符,但这里并不重要,因为它们最终都会被强制转换为字符,所以c(echo = FALSE,results =“hide”)是可以的。

我认为很容易破坏,但在轻松处理时似乎运行良好。

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

```{r setup, include = FALSE}
library(knitr)    
mdapply <- function(X, FUN, depth, options=""){
  FUN       <- as.character(substitute(FUN))
  list_name <- as.character(substitute(X))
  if(options != "")
    options <- paste(",",names(options),"=",options,collapse="")
  build_chunk <- function(nm)
  {
    paste0(
      paste0(rep("#",depth), collapse=""),
      " ",
      nm,
      "\n\n```{r", options, "}\n",
      FUN,
      "(", list_name, "[['", nm, "']])\n```")
  }      
  parts <- sapply(names(X), build_chunk)
  whole <- paste(parts, collapse="\n\n")
  knit(text=whole)
  }
```

```{r code}
my_list   <- list(foo = 1:3, bar = 4:7, baz = 8:12)
```

`r mdapply(my_list, plot, 2, c(echo=FALSE))`

2
我建议使用一种略有不同的解决方案,即从R脚本创建R-Markdown文件,然后从同一个R脚本中进行渲染:
# function that creates the markdown header
rmd_header <- function(title){
paste0(
"---
title: \"", title, "\"
output: html_document
---
"
)
}

# function that creates the Rmd code for the plots
rmd_plot <- function(my_list, my_fun){
paste0(
"
## ", names(my_list), "

```{r}
", deparse(substitute(my_fun)), "(", deparse(substitute(my_list)), "[[", seq_along(my_list), "]])
```
"
)
}

# your objects
my_list   <- list(foo = 1:3, bar = 4:7, baz = 8:12)
my_fun    <- plot
my_depth  <- 2 # I actually don't get what this is for

# now write everything into an rmd file
cat(rmd_header("Your Title")
    , rmd_plot(my_list, plot)
    , file = "test.rmd")

# and then create the html from that
rmarkdown::render("test.rmd", output_file = "test.html")

这里需要注意的一点是,Rmd文件中的缩进很重要。当你复制代码到这里时,请确保R-Studio按照预期将其插入到R脚本中(因为通常情况下它不会这样做)。


这真的很酷,我认为Lorenzo提供了灵活的解决方案,可能是做这种事情的惯用方式,但我绝对可以看到在数据探索期间生成快速报告的价值,干得好,感谢meriops扩展这种方法。 - moodymudskipper

1
采用与@Georgery类似的方法...但是以一种过度工程化的方式(也许更加通用?)总之,就这样吧。
make_template <- function(my_list, my_fun, my_depth, my_title, my_output_type, my_template_file){

  require(glue)

  n <- length(my_list)


  # --- Rmd header ---
  make_header <- function(my_title, my_output_type){
    #
    my_header <- glue(
                      "---", "\n",
                      "title: ", deparse({my_title}), "\n",
                      "output: ", deparse({my_output_type}), "\n",
                      "---", "\n",
                      "\n",
                      "\n"
    )
    return(my_header)
  }

  # --- one section only ---
  make_section <- function(i){
    one_section <- glue(
                        "\n",
                        "\n",
                        paste0(rep("#", times = {my_depth}), collapse = ""), " ", names({my_list})[[i]], "\n",
                        "\n",
                        "```{{r}}", "\n",
                        paste0({my_fun}, "(", deparse({my_list}[[i]]), ")"), "\n",
                        "```", "\n",
                        "\n",
                        "\n"
    )
    return(one_section)
  }


  # --- produce whole template ---

  my_header <- make_header(my_title, my_output_type)

  all_my_sections <- ""
  for (i in seq_along(my_list)) {
    all_my_sections <- paste0(all_my_sections, make_section(i))
  }

  my_template <- paste0(my_header, "\n", "\n", all_my_sections)

  # --- write out
  cat(my_template, file = my_template_file)
}




# --- try it

make_template(my_list = list(foo = 1:3, bar = 4:7, baz = 8:12, glop = 1:7),
              my_fun = "plot",
              my_depth = 4,
              my_title = "super cool title",
              my_output_type = "html_document",
              my_template_file = "my_template_file.Rmd"
)

非常好,谢谢。我认为它应该有更好的参数名称和默认值,这将是创建多图报告的一个很棒的临时函数。我还会让myfun更加灵活,因为你的方法在没有提前包装成命名函数的情况下创建复杂的ggplots并不是很方便。 - moodymudskipper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接