27

In my dataset, I have 60 groups that I want to analyze in put into an HTML report using R Markdown. Because I want to apply the same analysis to each group, I am hoping that there is a way I can dynamically generate the code blocks/analysis.

Simply, I want to avoid replicating the block 60 times.

I came across this this question which uses children in knitr. I have attempted to replicate this with the iris dataset. In my example below, all I wanted to do was generate three H4 titles, one for each species.

It's worth noting that I am not married to this approach, it just appears to be related to what I am looking to do.

Here are the files I used:

parent.RMD file. This would be my "master" report.

Automate Chunks of Analysis in R Markdown 
========================================================


```{r setup, echo=FALSE}
library(knitr)
```


```{r run-numeric-md, include=FALSE}
out = NULL
for (i in as.character(unique(iris$Species))) {
  out = c(out, knit_child('child.Rmd'))
}

```

And here is child.Rmd.

#### Species = `r [i]`
Community
  • 1
  • 1
Btibert3
  • 38,798
  • 44
  • 129
  • 168
  • An alternative solution would be using my `pander` pkg with only one `brew` file that support loops: http://rapporter.github.io/pander/#brew-to-pandoc. See the "short-code-long-report" example. – daroczig Feb 12 '14 at 14:08
  • Awesome, I will take a look – Btibert3 Feb 12 '14 at 16:40
  • 1
    relevant: https://stackoverflow.com/questions/51158486/create-sections-through-a-loop-with-knitr/51159788#51159788 – moodymudskipper Jul 03 '18 at 17:32

2 Answers2

28

Try knit_expand():

Automate Chunks of Analysis in R Markdown 
========================================================

```{r setup, echo=FALSE}
library(knitr)
```

```{r run-numeric-md, include=FALSE}
out = NULL
for (i in as.character(unique(iris$Species))) {
  out = c(out, knit_expand(text='#### Species = {{i}}'))
}
```

`r paste(knit(text = out), collapse = '\n')`

You can also create a template file like 'child.rmd' and put this in your for loop so you don't have to put a complicated analysis in quotes:

out = c(out, knit_expand('template.rmd'))

Then have your 'template.rmd' be:

#### Species = {{i}}
Sam Dickson
  • 5,082
  • 1
  • 27
  • 45
  • 1
    Ok, that worked exactly as I had desired. My question. Why is `r paste(knit(text = out), collapse = '\n')` required? When I compile, is it knitting the loop beforehand? I just want to wrap my head around this piece of code. Thanks! – Btibert3 Feb 12 '14 at 16:45
  • 2
    What you're creating in the `for` loop is a character vector, but you want it to act like the rest of the non-code portion of the file with character returns (`\n`) separating the different lines, so make that happen by collapsing the vector on `\n`. – Sam Dickson Feb 12 '14 at 17:38
  • This turned out to be super useful for automating outputs in `flexdashboard`. But what I would like to understand is why it is necessary to write `\`r paste(knit...\`` vs just `\`r knit..\`` (no paste). FYI, I collapse all my text prior to running knit i.e. `knit(paste(out_list, collapse = "\n"))`. – Danton Noriega Aug 15 '16 at 22:04
  • @DantonNoriega I'd have to see the rest of your code to know if it's necessary. If it runs fine without it, then exclude. Putting it in ensures that the text gets interpreted properly, and won't, for instance, assume that you were ending your knit with the characters `\n` rather than a line break. – Sam Dickson Aug 25 '16 at 20:42
  • @Sam Dickson my current guess is that it has something to do when calling a child document. In my more complex flexdashboard, it breaks without `paste` but when I make a simpler version, it doesn't seem to matter. The only major difference is that my more complicated one is calling the `Rmd` file as a child document. Otherwise, works fine regardless when in standalone `Rmd`. See [example gist](https://gist.githubusercontent.com/ultinomics/61559f39c06df92cca1f594bc63bcb8f/raw/bf0003df3193b65268decd83ceebb6d076ab1f9d/auto-tab-flexdashboard.Rmd) – Danton Noriega Sep 14 '16 at 20:29
6

Taking @sam's solution, I made the following generic function. Say you have a data frame called grfDf with DiagrammeR graph objects in the column graph. The following is all you need to plot all the graphs in Rmd : r require(DiagrammeR); renderHtmlWidgetList(grfDf$graph, render_graph). See the code for caveats.

```
require(knitr)

#' Render a list of htmlWidgets using various tricks
#'
#' @param widgetList A list of htmlWidget objects to be rendered
#' @param renderFunction The function to render individual widgets. It can be either a name
#'   of the rendering function, e.g., "render_graph" in DiagrammeR, or the actual function to
#'   be passed to this call.
#' @return The knitted string. This is to be included in the output by using `r renderHtmlWidgetList(...)`;
#' @details This is a collection of various tricks. See the URL citations in the code.
#'   Note that this code does alliterate global variables starting with "renderHtmlWidgetList_".
#'   You may want to delete them using rm(list = ls(pattern="renderHtmlWidgetList_*")).
#' @examples Inlcude the following in the Rmd directly
#'   `r require(DiagrammeR); renderHtmlWidgetList(grfDf$graph, render_graph)`
#'
#' @export

renderHtmlWidgetList <- function(widgetList, renderFunction){
  # error checking
  stopifnot(is.list(widgetList))
  # handles if the renderFunction is actually a function
  # http://stackoverflow.com/questions/10520772/in-r-how-to-get-an-objects-name-after-it-is-sent-to-a-function
  if(is.function(renderFunction)) {
    # convert back to string, because we need to knit it later
    renderFunction <- deparse(substitute(renderFunction))
  }
  stopifnot(is.character(renderFunction) & length(renderFunction)==1)
  stopifnot(exists(renderFunction, mode = "function"))
  # inject global vars; make sure we have a unique global var name
  gVarName<- paste0("renderHtmlWidgetList_", sample(1:10000, 1))
  while (exists(gVarName)) {
    gVarName<- paste0("renderHtmlWidgetList_", sample(1:10000, 1))
  }
  # assigning widgetList to a global temp var
  # http://stackoverflow.com/questions/5510966/create-a-variable-name-with-paste-in-r
  assign(gVarName, widgetList, envir = .GlobalEnv)
  # solution from https://gist.github.com/ReportMort/9ccb544a337fd1778179
  out <- NULL
  knitPrefix <- "\n```{r results='asis', cache=FALSE, echo=FALSE}\n\n"
  knitSuffix <- "\n\n```"
  for (i in 1:length(widgetList)) {
    knit_expanded <- paste0(knitPrefix, renderFunction, "(", gVarName, "[[", i, "]])")
    out = c(out, knit_expanded)
  }
  #invisible(out)
  paste(knitr::knit(text = out), collapse = '\n')
}
```
Gary Feng
  • 420
  • 5
  • 8