Parameterized Reporting
with Quarto

Jadey Ryan // February 21, 2024
R-Ladies Abuja

Acknowledgements

R-Ladies Abuja Logo


R/Medicine Data Cleaning 2023 Workshop taught by Crystal Lewis, Shannon Pileggi, and Peter Higgins


ASA Traveling Courses on Quarto taught by Mine Çetinkaya-Rundel and Andrew Bray

Disclaimer and license

Opinions expressed are solely my own and do not express the views of my employer or any organizations I am associated with.


This work is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA).

Jadey Ryan

Data scientist at WA Dept of Agriculture

The Coding Cats: cat & code themed merch

jadeyryan.com

@jadeynryan

linkedin.com/in/jadey-ryan

jadeynryan

thecodingcats.etsy.com

Three snowshoe siamese cats in loaf mode. From left to right: Tai, Mai, and Skye

Jadey collecting a bulk density soil sample in a field of wheat stubble.

Logistics

Login to Posit Cloud workspace: bit.ly/rladies-parameterized-quarto.

If Posit Cloud doesn’t work, download materials locally:

usethis::use_course(
  "https://github.com/jadeynryan/parameterized-quarto-workshop/raw/main/exercises/exercises.zip"
#  ,destdir = "___"
  )

Ask questions in the public Zoom chat or raise hand ✋.

Workshop structure: presentation, demos/exercises, questions to answer in 💬 Chat.

💪🏼 Exercise 0


💬 Chat: share your name, where you’re calling from, and one thing you’ve made that you’re proud of.

01:00

Learning objectives

  1. Understand what parameterized reporting is and when it is useful.


  1. Learn how to convert a report into a parameterized template.


  1. Render all variations of the report at once using {quarto} and {purrr}.


  1. Generate multiple format outputs from the same template file with conditional content and conditional code execution.

Syntax and RStudio aside

Pipes

  • 2014+ magrittr pipe %>%

  • 2021+ (R \(\geq\) 4.1.0) native R pipe |>

Isabella Velásquez’s blog post Understanding the native R pipe |> (2022)


do_something(arg1, arg2, arg3, ...)

arg1 |>  
  do_something(arg2, arg3)
mean(0:10)

0:10 |> 
  mean()


To change shortcut to the native pipe:

ToolsGlobal OptionsCodeEditingUse Native Pipe Operator

Windows: Ctrl + Shift + M

Mac: Cmd + Shift + M

Namespacing

package::function()

dplyr::select()

  • tells R explicitly to use the function select from the package dplyr

  • helps avoid name conflicts (e.g., MASS::select())

  • does not require library(dplyr)

library(dplyr)

mtcars |>  
  select(mpg, cyl) 
# library(dplyr) not needed

mtcars |>  
  dplyr::select(mpg, cyl) 

RStudio options

ToolsGlobal Options

  • Fussy YAML indentation:

    CodeDisplayIndentation guides:Rainbow lines


  • Match parentheses:

    CodeDisplayIndentation guides: → Check Use rainbow parentheses


  • Matching divs:

    R MarkdownAdvanced → Check Use rainbow fenced divs

R Markdown → Quarto

R Markdown

Hex logos for various packages from the R Markdown ecosystem.

Figure from “Hello, Quarto” keynote by Julia Lowndes and Mine Çetinkaya-Rundel, presented at RStudio::Conf(2022).

Quarto

A schematic representing the multi-language input (e.g. Python, R, Observable, Julia) and multi-format output (e.g. PDF, html, Word documents, and more) versatility of Quarto.

Artwork from “Hello, Quarto” keynote by Julia Lowndes and Mine Çetinkaya-Rundel, presented at RStudio::Conf(2022). Illustrated by Allison Horst.

A schematic representing rendering of Quarto documents from .qmd, to knitr or jupyter, to plain text markdown, then converted by pandoc into any number of output types including html, PDF, or Word document.

R Markdown vs Quarto

R Markdown

  • Vast R Markdown ecosystem

  • Dependent on R

Quarto

  • Command line interface (CLI)

  • Expands R Markdown ecosystem

  • “Batteries included”

  • Multi-language and multi-engine


If you’re happy with R Markdown and it’s not broken, no need to switch!


R Markdown will still be maintained but likely no new features (Xie 2022).

Convert .Rmd.qmd

  1. Change file extension from .Rmd.qmd
  2. Change YAML header (output:format:)
  3. Convert chunk headers with knitr::convert_chunk_header()

R Markdown

```{r, label=rmarkdown, eval=FALSE}
# code
```

Quarto

```{r}
#| label: quarto
#| eval: false

# code
```

One sad difference 😥

No Quarto equivalent to .Rmd Knit with Parameters GUI built with Shiny {miniUI}.


. . .

Workaround: build web app to get input, serialize to YAML, pass to Quarto render.

More info: GitHub discussion

Resources for R Markdown users

Parameterized reports

Many use cases


Different audiences, different reports

Show code for technical staff and hide code for everyone else (StackOverflow example).

Like a custom function

File with the word '.qmd' inside and the word 'Function' above.

An arrow points from 'Input' with 'params$year' to the previous image with 'Function' and '.qmd' file.

In addition to the previous two images, arrows point to five reports with years 2019 through 2023 on them in a flow chart.

What makes a report “parameterized”?

  • YAML header with params key-value pairs

    • Yet Another Markdown Language → YAML Ain’t Markup Language
  • Use these params in your report to create different variations.

Important

Workflow

  1. Write report template with default values hard-coded, and then render & review.

  2. Set default params key-value pairs in YAML.

  3. Replace hard-coded values with the params variables.

  4. Render the single report and review.

  5. Render extreme cases and review.

    • Parameter values with barely any data and with tons of data.
  6. Render all variations of the report at once.

💪🏼 Exercise 1

Explore a report without parameters and see where we could add them.

  1. Open ex-1-swiss-cats.qmd.

  2. Click the Quarto render button in RStudio Render button.

  3. Look at the source markdown & code and the rendered report.

  4. 💬 Chat: what variables could we set as parameters?

    💡 Hint: run the setup chunk and look at the pets dataframe to see what variables it has.

05:00

Set params in YAML header

---
title: "Swiss Cats"             # Metadata
format:                         # Set format types
  html: default                                     
  docx: default                           
params:                         # Set default parameter key-value pairs
  fave_breed: "Snowshoe"                                
---
    
Report content goes here.       # Write content

Important

Your default params key-value pairs must be found in your dataset. Otherwise, code will error or output will be blank.

The variable name for params can be anything you choose. Often, it’s a column name in your dataset.

Access params

Run any line or chunk to add params to your environment.

params object is a list.

str(params)
List of 1
 $ fave_breed: chr "Snowshoe"


Access with $ notation.

params$fave_breed
[1] "Snowshoe"


For inline code in YAML or report content, enclose the expression in `r `.

My favorite cat breed is the **`r params$fave_breed`**.

My favorite cat breed is the Snowshoe.

Replace hard-coded values with params

  • Cmd/Ctrl + F to find where to replace hard-coded values with params.


  • Use inline R code for markdown.

    ## My favorite breed: `r params$fave_breed`


  • Use paste() or glue() for plot and table titles and labels.

    # ggplot code +
    labs(title = paste(params$fave_breed, "population"))

💃🏻 Demo

Modify ex-1-swiss-cats.qmd to add pet_type and fave_breed parameters.

Rendering reports

Four ways to render

  1. Quarto render button in RStudio Render button in RStudio or Cmd/Ctrl + Shift + K keyboard shortcut

  2. Check Render on Save and Cmd/Ctrl + S keyboard shortcut

  3. Quarto CLI

    Terminal
    quarto render ex-2-quarto-render.qmd -P pet_type:'cats' -P fave_breed:'Snowshoe'
  4. quarto::quarto_render()

    Console or R script
    quarto::quarto_render(
      input = here::here("ex-2-quarto-render.qmd"), # Input Quarto file
      execute_params = list(                        # Named list of params
        pet_type = "cats",
        fave_breed = "Snowshoe"))

💪🏼 Exercise 2.1

Change parameters in the YAML and render using Quarto render button in RStudio Render button.

  1. Look at the unique pet breeds and pick your favorite.

    # Run in the console
    pets <- readr::read_rds(here::here("data", "pets.RDS")) |> 
       dplyr::distinct(pet_type, breed) |> View()
  2. In ex-2-quarto-render.qmd Change the default parameters in the YAML to your favorite pet type and breed. Render using the Quarto render button in RStudio Render button.

    params:
      pet_type: "___"
      fave_breed: "___"
  3. 💬 Chat: do you have any pets? what kind? 🐈🐕🐹🐍🐠

05:00

💪🏼 Exercise 2.2

Change parameters and render using quarto_render().

  1. Render with quarto::quarto_render().

    # Run in the console
     quarto::quarto_render(
       input = here::here("ex-2-quarto-render.qmd"),
       execute_params = list(
         pet_type = "___",
         fave_breed = "___"))
  2. 💬 Chat: what kinds of variables are you hoping to use as parameters for your reports?

5-min break after this exercise

05:00

🥱 Break

05:00

Render all 538 reports

One HTML report for each cat breed and each dog breed.

pets <- readr::read_rds(here::here("data", "pets.RDS"))

pets |>
  dplyr::distinct(pet_type, breed) |>
  dplyr::count(pet_type) |>
  janitor::adorn_totals()
pet_type n
cats 104
dogs 434
Total 538
  1. Change the default params in the YAML.

  2. Render button or Cmd/Ctrl + Shift + K keyboard shortcut.

  3. Rename the rendered report to include the parameter & prevent overwriting.

  4. Repeat 537 times.

😭

quarto::quarto_render(
  input = here::here("ex-2-quarto-render.qmd"),
  output_file = "dogs-affenpinscher-report.html",
  execute_params = list(
    pet_type = "dogs",
    fave_breed = "Affenpinscher"))

quarto::quarto_render(
  input = here::here("ex-2-quarto-render.qmd"),
  output_file = "dogs-afghan-hound-report.html",
  execute_params = list(
    pet_type = "dogs",
    fave_breed = "Afghan Hound"))

quarto::quarto_render(
  input = here::here("ex-2-quarto-render.qmd"),
  output_file = "dogs-aidi-chien-de-montagne-de-l-atlas-report.html",
  execute_params = list(
    pet_type = "dogs",
    fave_breed = "Aidi Chien De Montagne De L Atlas"))

quarto::quarto_render(
  input = here::here("ex-2-quarto-render.qmd"),
  output_file = "dogs-akita-report.html",
  execute_params = list(
    pet_type = "dogs",
    fave_breed = "Akita"))

# + 534 more times... 
# 😭

Create a dataframe with three columns that match quarto_render() args:

  • output_format: file type (html, revealjs, pdf, docx, etc.)

  • output_file: file name with extension

  • execute_params: named list of parameters

Map over each row:

  • purrr::pwalk(dataframe, quarto_render, quarto_render_args)

😎

Create dataframe to iterate over

pet_reports <- pets |>
  dplyr::distinct(pet_type, breed) |>   # Get distinct pet/breed combos
  dplyr::mutate(
    output_format = "html",             # Make output_format column
    output_file = paste(                # Make output_file column:
      tolower(pet_type),                # cats-abyssiniane-report.html
      tolower(gsub(" ", "-", breed)),           
      "report.html",
      sep = "-"
    ),
    execute_params = purrr::map2(       # Make execute_params column
      pet_type,
      breed,
      \(pet_type, breed) list(pet_type = pet_type, breed = breed)))

Subset to first 2 cat/dog breeds

pet_reports_subset <- pet_reports |>
  dplyr::slice_head(n = 2, by = pet_type) |>
  dplyr::select(output_file, execute_params)

pet_reports_subset
output_file execute_params
cats-abyssiniane-report.html cats , Abyssiniane
cats-aegean-cat-report.html cats , Aegean Cat
dogs-affenpinscher-report.html dogs , Affenpinscher
dogs-afghan-hound-report.html dogs , Afghan Hound

Map over each row

  • purrr::pwalk() iterates over multiple arguments simultaneously.

  • First .l argument is a list of vectors.

    • Dataframe is a special case of .l that iterates over rows.
purrr::pwalk(
  .l = pet_reports_subset,                      # Dataframe to map over
  .f = quarto::quarto_render,                   # Function we are applying to each row
  input = here::here("ex-2-quarto-render.qmd"), # Named arguments of .f
  .progress = TRUE                              # Show a progress bar :)
)

Note

index is the only named argument of quarto_render() included in pwalk().

output_format, output_file, and execute_params are already passed in through the dataframe.

Multiple formats

Render all reports to all formats

Add to the format: YAML option to render additional output formats from the same .qmd file.

---
format:
  html: 
    embed-resources: true   # Makes the report self-contained
  pdf: default              # If not using any additional format options,
  docx: default             # set value to `default`  
---

Note: the Render button now has a dropdown!

Screenshot of Quarto file with the Render dropdown showing options for HTML, PDF, and MS Word formats.

Quarto docs on multiple formats

💃🏻 Demo

Demo programmatically rendering all reports in all formats in ex-3-render-reports.R.

💪🏼 Exercise 3

Explore the programmatic way of rendering multiple reports at once.

  1. Look through ex-3-render-reports.qmd and ex-3-render-reports.R.

  2. Source the R script (or run line by line).

  3. 💬 Chat: were any of these functions from the .qmd or .R files new to you?

  4. Ask questions or take a break. 😊

10:00

Watchouts

  • Can’t render reports to another directory.


  • If using embed-resources: true YAML option, .qmd can’t be in subfolder, otherwise:

Conditional content

Control content visibility

Special .content-visible and .content-hidden classes with when-format="___" and unless-format="___" attributes can be applied to:

::: {.content-visible when-format="html"}

Will only appear in HTML.

:::
Some text
[in HTML.]{.content-visible when-format="html"}
[in PDF.]{.content-visible when-format="pdf"}

Fenced code blocks purely for documentation.

```{.python .content-visible when-format="html"}
# code shown only in HTML
2 + 2
```

Useful for static/interactive features

Pairs well with {{< include >}} shortcodes to re-use content without copying/pasting.

:::: {.content-visible unless-format="html"}

## Cats

{{< include _cats.qmd >}}

## Dogs

{{< include _dogs.qmd >}}

::::
:::: {.content-visible when-format="html"}
::: panel-tabset

## Cats

{{< include _cats.qmd >}}

## Dogs

{{< include _dogs.qmd >}}

:::
::::

Use an underscore prefix for included files so they are automatically ignored by a Quarto render of a project (Includes Quarto Docs).

💪🏼 Exercise 4

Use conditional content divs to control when tabsets are shown.

  1. Modify ex-4-conditional-content.qmd so that the panel-tabset is visible for HTML reports and hidden for all other formats.


  1. Explore the other options. There are multiple ways to get the same result.

    {.content-visible when-format=“html”} == {.content-hidden unless-format=“html”}


  1. 💬 Chat: what other kinds of content might you want to make visible for only a certain format?
05:00

Conditional code execution

  • More efficient to not execute code that generates interactive outputs for static reports.

  • Useful for executing interactive plot code for HTML reports and static ggplot2 code for all other formats.

  • Not currently a feature of Quarto v1.4. Follow along with this GitHub discussion.

Workaround for conditional R code

Include in the setup chunk of your .qmd file.

Get the format of the Pandoc output:

```{r}
#| label: setup

# Get output format
format <- knitr::opts_knit$get("rmarkdown.pandoc.to")
```

Use eval: !expr chunk option

```{r}
#| eval: !expr format %in% c("latex", "docx")

# code to create static {ggplot2}
```

💡 Pandoc uses LaTeX to create PDFs.

```{r}
#| eval: !expr format == "html"

# code to create interactive {plotly}
```


Can even use params in !expr:

#| eval: !expr params$fave_breed == "Snowshoe"

💪🏼 Exercise 5


Conditionally execute ggplot2 code for static reports & plotly code for interactive reports.

  1. Open ex-5-conditional-code.qmd.

  2. Fill in the blanks for the eval: option for ggplot code chunks and plotly code chunks.

# Options are html, latex, and docx.

#| eval: !expr format == "___"

#| eval: !expr format %in% c("___", "___")
  1. 💬 Chat: what is something you learned from this workshop that you’re excited to apply to your own reports and projects?
05:00

🏁 Recap

Learning objective 1

Understand what parameterized reporting is and when it is useful.

Like very fancy custom functions:

  • Function → .qmd template

  • Input → parameters

  • Output → rendered reports

Useful for creating variations of the same report:

  • country, state, county, or city

  • time periods

  • breeds, species, diseases, trials, etc.

Note

We only covered reports, but you can also parameterize revealjs presentations! See this Jumping Rivers blog post about it.

Learning objective 2

Learn how to convert a report into a parameterized template.

  • Include default params: in YAML

  • Replace hard-coded values with params$pet_type

    • YAML:

      ---
      title: "Report about `r params$pet_type`"
      params:
        pet_type: "cats"
      ---
    • Inline R code:

      I like **`r params$pet_type`**.
    • Code chunks:

      pets |> 
          dplyr::filter(pet_type == params$pet_type)

Learning objective 3

Render all variations of the report at once using {quarto} and {purrr}.

  1. Get all unique parameter combinations into a dataframe:
head(pet_reports, 1)
pet_type breed output_format output_file execute_params
cats Abyssiniane html cats-abyssiniane-report.html cats , Abyssiniane


  1. Use dataframe in pwalk() with quarto_render():
purrr::pwalk(
  pet_reports,
  quarto::quarto_render,
  input = here::here("pet_template.qmd"),
  .progress = TRUE
)

Learning objective 4

Generate multiple format outputs from the same template with conditional content and code.

Content visibility

  • {.content-visible when-format=“___“}

  • {.content-visible unless-format=“___“}

  • {.content-hidden unless-format=“___“}

  • {.content-hidden unless-format=“___“}

Code execution

#| label: setup

format <- knitr::opts_knit$get("rmarkdown.pandoc.to")
#| label: interactive-plot
#| eval: !expr format == "html"

# plotly code
#| label: static-plot
#| eval: !expr format %in% c("latex", "docx")

# ggplot2 code

Thank you!

🙏🏼 Please let me know how I did in this short survey


🏡 Home for all workshop materials: jadeyryan.quarto.pub/rladies-abuja-quarto-params/

From left to right, Mai, Tai, and Skye. Three snowshoe cats cuddling in their warming beds.