rr-markdownknitrhtml-rendering

How to Knit a single .Rmd to a batch of multiple .HTML at once?


I have a .Rmd chunk that loads a list of data frames from an analysis function in another .R file. In a separate .R file, I have code to knit the .Rmd file with certain parameters.

The objective is to save an .html file with the output from the function for each value in the range of 1 to 17.

Currently, my approach causes the .Rmd file to iterate over the entire range and load all 17 outputs, but it only exports the last one. On the other hand, the .R file knits the .Rmd to .html 17 times, saving the export over the same file each time.

Here is the approach that I am using.

.Rmd code

{r report1, echo=FALSE, message=FALSE, warning=FALSE}
#Source file to load function
source('function_file.R')

# Iterate over 1 to 17
for (i in 1:17) {
  # load function from file and save output in a list for each obs.
  output_list <- list_function(i)
}
# Next comes printing from output_list

.R code



# Write function to render .Rmd file 
render_report <- function(index) {
  # Parameters
  params <- list(index = index)
  
  # Render .Rmd file into HTML
  rendered_report <- rmarkdown::render("rmd_file.Rmd", params = params)
  
  # output name for .HTML file
  html_output <- paste0("rep_", indice, ".html")
  
  # save as .HTML
  file.copy(rendered_report, html_output)
  
  # delete temp generated by render()
  file.remove(rendered_report)
}

# vector with desired indexes
indexes <- 1:17

# Iterate over indexes and render .Rmd for each one
for (index in indexes) {
  render_report(index)
} 

I need that each time the file is rendered, one of the function's index is changed, so that the next output in the range from 1 to 17 is rendered.

I would appreciate any help in advance.


Solution

  • With parametrized reports, all that functionality is already there. Here's an example of a single Rmd notebook including all the code for generating a series of HTML reports.

    Parameters can be defined in YAML block at the start of the document together with default values, that parameter (here: report_index) can later referenced in R as params$report_index; it also works for inline R expressions and in YAML (check how it's used for title in YAML).

    Report generation loop just calls rmarkdown::render() with each desired report_index parameter value. There's no need to copy or move rendered reports, instead, we can set output_file argument and let rmarkdown::render() handle this.

    The last code block from the following example is for rendering; it's excluded from knitr output and evaluation by setting eval=FALSE, echo=FALSE, so it has to be executed manually. Or it can be kept in a separate R file.

    multi-report.Rmd :
    ---
    output: html_document
    params:
      report_index : 1
    title: "Report `r params$report_index`"
    ---
    
    ```{r setup, echo=FALSE, message=FALSE, warning=FALSE}
    # something to simulate a function in function_file.R
    # returns a list of 3 vectors
    list_function <- function(i){
      list(".5" = .5, "1" = 1, "2" = 2) |>
        lapply(\(sd_) rnorm(100, i, sd_)) 
    }
    ```
    
    ```{r report, echo=FALSE, message=FALSE, warning=FALSE}
    # for parametrized report, use params$report_index to refer to report index:
    data_list <- list_function(params$report_index)
    df <- stack(data_list)
    
    hist(df$values, main = paste("Report index", params$report_index))
    boxplot(values ~ ind, data = df, main = "By ind")
    ```
    
    ```{r render, eval=FALSE, echo=FALSE}
    # rendering block, only for manual execution, not evaluated during knitting
    
    # generate 5 reports:
    for (i in 1:5){
      rmarkdown::render("multi-report.Rmd", 
                      output_file = sprintf("rep_%.2d", i),
                      params = list(report_index = i),
                      quiet = TRUE)
    }
    
    # list resulting files:
    fs::dir_info(glob = "*rep*")[1:3]
    
    # screenshots of first three:
    webshot2::webshot(list.files(pattern =  "html$")[1:3], zoom = .5) |>
      lapply(png::readPNG) |>
      lapply(grid::rasterGrob) |> gridExtra::marrangeGrob(nrow = 1, ncol = 3, top = NA)
    ```
    

    Result of the last block and screenshots from the first three reports:

    # A tibble: 6 × 3
      path             type         size
      <fs::path>       <fct> <fs::bytes>
    1 multi-report.Rmd file        1.35K
    2 rep_01.html      file      639.74K
    3 rep_02.html      file      639.95K
    4 rep_03.html      file      639.55K
    5 rep_04.html      file      639.53K
    6 rep_05.html      file      640.62K
    

    report screenshots