One template, many stories

Parameterized reports with

Leopold Salzenstein

2026-05-30

Problem n°1:


Image from “Enough Markdown to Write a Thesis” by Richard J. Telford (2025)

Solution: Quarto



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

Quarto rendering



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

a simple quarto (.qmd) file

---
title: "A simple yet cool Quarto file"
author: "Leo"
---

## A cool barplot 

```{r}
cool_data |>
  ggplot(aes(x = cool_variable)) +
  geom_bar()
```

Ingredients of a Quarto file:

  1. YAML header

  2. Markdown language

  3. Code chunks (R, Python, SQL, Bash, Julia, etc.)

YAML header


---
title: "My Report"              # Metadata
author: Leo
date: "Saturday May 30, 2026"
format:                             # Set format types
  html:                                     
  docx:                            
params:                             # (Optional) Parameter key-value pairs
  year: 2023                                
---


YAML = Yet Another Markdown Language

A small detour: Markdown 101

# Big Title
## Title
### Sub-title
#### Sub-sub-title

Big Title

Title

Sub-title

Sub-sub-title


normal

**bold**

*italic*

***bold & italic***

~~strikethrough~~

> a quote

`a code chunk`

normal

bold

italic

bold & italic

strikethrough

a quote

a code chunk

Render: Set format in YAML header

---
title: "A simple yet cool Quarto file"
author: "Leo"
format: html
---

## A cool barplot 

```{r}
cool_data |>
  ggplot(aes(y = fct_infreq(cool_things) |> fct_rev())) +
  geom_bar(fill = "violet") +
  labs(
    x = "Scale of cool",
    y = NULL,
    title = "Cool things in this presentation"
  ) +
  theme_minimal() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.grid.minor = element_blank(),
    plot.title = element_text(face = "bold")
  )
```

Render: press the button

# Python
import quarto
quarto.render()

# R
quarto::quarto_render()

a simple quarto (.qmd) file in HTML

---
title: "A simple yet cool Quarto file"
author: "Leo"
format: html
---

a simple quarto (.qmd) file in PDF

---
title: "A simple yet cool Quarto file"
author: "Leo"
format: pdf
---

a simple quarto (.qmd) file in DOCX

---
title: "A simple yet cool Quarto file"
author: "Leo"
format: docx
---

a simple quarto (.qmd) file in EPUB

---
title: "A simple yet cool Quarto file"
author: "Leo"
format: epub
---

a simple quarto (.qmd) file in (self-contained) HTML


---
title: "A simple yet cool Quarto file"
author: "Leo"
format:
  html:
    embed-resources: true
---
```


Tip: for HTML, set embed-resources: true to include all the dependency files (images, etc.) inside the HTML.

This presentation is a quarto file (in Reveal.js)


---
title: "One template, many stories"
subtitle: "Parameterized reports with ![](https://quarto.org/quarto.png){.quarto-logo}"
author: "Leopold Salzenstein"
date: "Saturday May 30, 2026"
engine: knitr
format:
  revealjs: 
    theme: [solarized, custom.scss]
    include-after-body: header-links.html
---

Where we are now


Image from “Enough Markdown to Write a Thesis” by Richard J. Telford (2025)

Mid-presentation conclusion: Quarto is cool

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

Problem n°2:


Image from the “R Productive Workflow” by Yan Holtz

Solution: Parameterized reports in Quarto

What makes a report “paramaterized”?


  1. YAML header with params key-value pairs

  2. Use those params in your report to create different variations


… by geography, by date, by audience type, etc.

Example: Nitrogen oxides (NOX) emissions



.. in Belgium and its neighbours (NL, DE, FR, LU)

Step 1: make template analysis


## Top 10 facilities emitting NOX in Belgium in 2023

```{r}
nox_emissions |>
  filter(country == Belgium) |>
  summarise(total_emissions = sum(total_emissions, na.rm = TRUE),
            .by = c(facility, city)) |>
  slice_max(total_emissions, n = 10) |>
  mutate(facility_label = str_glue("{facility} ({city})")) |>
  ggplot(aes(
    x = fct_reorder(facility_label, total_emissions),
    y = total_emissions
  )) +
  geom_col(fill = "darkgreen") +
  coord_flip() +
  labs(
    x = NULL,
    y = "NOX emissions (kg)",
    title = "Top NOX emitting facilities"
  ) +
  theme_minimal() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.grid.minor = element_blank(),
    plot.title = element_text(face = "bold")
  )
````

Step 1: make template analysis


## Top 10 facilities emitting NOX in `r params$country` in 2023

```{r}
nox_emissions |>
  filter(country == params$country) |>
  summarise(total_emissions = sum(total_emissions, na.rm = TRUE),
            .by = c(facility, city)) |>
  slice_max(total_emissions, n = 10) |>
  mutate(facility_label = str_glue("{facility} ({city})")) |>
  ggplot(aes(
    x = fct_reorder(facility_label, total_emissions),
    y = total_emissions
  )) +
  geom_col(fill = "darkgreen") +
  coord_flip() +
  labs(
    x = NULL,
    y = "NOX emissions (kg)",
    title = "Top NOX emitting facilities"
  ) +
  theme_minimal() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.grid.minor = element_blank(),
    plot.title = element_text(face = "bold")
  )
````

Step 1: make template analysis

```{r}
top_facility <- nox_emissions |>
  filter(country == params$country) |>
  arrange(desc(total_emissions)) |>
  head(1) |>
  select(facility, city, total_emissions)
````


For inline code, enclose the expression in `r ` / `python `.

`r top_facility$facility` is the facility that emitted most NOX in 
`r params$country` in 2023. 

It is located in `r top_facility$city`. 

In 2023, it emitted approximately 
`r format(round(top_facility$total_emissions), big.mark=" ")` 
kg of NOX in the atmosphere.

Step 2: Set params in YAML header


---
title: "Report: NOX emissions in `r params$country` (2023)"
format:
  html:
    embed-resources: true
params:
  country: "Belgium"
---

Report content goes here.

Step 3: render (one report)

Tip: also render some outliers (e.g. countries with little data) to check them.

Step 3: render (several reports)


In a separate file (ex. render_country_files.R):


countries_to_analyze <- c("Belgium", "France", "Germany", "Luxembourg", "Netherlands")

template_file <- "path/to/country_template.qmd"

output_dir <- "path/to/Results"

Step 3: render (several reports)


  render_country <- function(country_name) {
  
  filename <- str_glue("RESULTS_{str_replace_all(country_name, ' ', '_')}.html")
  
  quarto::quarto_render(
    input = template_file,
    execute_params = list(country = country_name),
    output_file = filename
  )
  
  file.rename(from = filename, to = file.path(output_dir, filename))
}

walk(countries_to_analyze, render_country)

Step 3: render (several reports)


Step 3: render (several reports)


Step 3: render (several reports)


Step 3: render (several reports)


Bonus #1: render with multiple params


---
title: "Report: NOX emissions in `r params$country` (2023)"
format:
  html:
    embed-resources: true
params:
  country: "Belgium"
---

Report content goes here.

Bonus #1: render with multiple params


---
title: "Report: `r params$pollutant` emissions 
in `r params$country` (2023)"
format: html
params:
  country: "Belgium"
  pollutant: "NOX"
---

Report content goes here.

Bonus #1: render with multiple params


---
title: "Report: `r params$pollutant` emissions 
in `r params$country` (`r params$year`)"
format: html
params:
  country: "Belgium"
  pollutant: "NOX"
  year: "2023"
---

Report content goes here.

Bonus #1: render with multiple params


params_grid <- tibble(
  country   = c("Belgium",  "France", "Germany"),
  pollutant = c("PM2.5",    "NOX",    "CO2")
)

render_report <- function(country_name, pollutant_name) {
  filename <- str_glue("RESULTS_{str_replace_all(country_name, ' ', '_')}_{pollutant_name}_2023.html")
  quarto::quarto_render(
    input = template_file,
    execute_params = list(country = country_name, pollutant = pollutant_name),
    output_file = filename
  )
  file.rename(from = filename, to = file.path(output_dir, filename))
}

pwalk(params_grid, render_report)

Bonus #2: render for tech vs non-tech audience


---
title: "My report"
params:
  audience: "tech"
---

```{r}
#| include: false

is_tech <- `r params$audience` == "tech"
knitr::opts_chunk$set(
  echo    = is_tech,
  warning = is_tech,
  message = is_tech
)
```

Bonus #2: render for tech vs non-tech audience

---
title: "My report"
params:
  audience: "non-tech"
---

Bonus #2: render for tech vs non-tech audience

---
title: "My report"
params:
  audience: "tech"
---

Where we are now


Image from the “R Productive Workflow” by Yan Holtz

Use cases


Questions?

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