Publishing content in Quarto using conditionals and if/else code flow

A selection of simple methods using the knitr engine and HTML output
Quarto
R
knitr
Author
Published

April 24, 2023

A cropped photograph of the eyes and surrounding fur of a cat's face. The cat is a chimera with green eyes, the left side of her face coloured black and the right side orange. The cat's name is Damsel.

Damsel the chimera. Copyright Andy Stacey.

In this post I will describe some of the ways I’ve found to conditionally include content in a Quarto-generate HTML document.

I’m using RStudio and the knitr rendering engine. If you know of Jupyter solutions please let me know! I will happily link to them for the disappointed Pythonistas who have ended up on my blog ;)

Quarto

The {{< include >}} shortcode

This isn’t conditional, but it is really useful.

If you have company marketing boilerplate, or headers that start all of your reports, a neat solution is to keep these in their own qmd file and bring them into any other qmd using the include shortcode.

In your report you would use the code below

{{< include _content.qmd >}}

This is akin to manually copying and pasting in the contents of content.qmd prior to the report getting rendered.

We’ll come back to the shortcode later.

Conditional content

To my mind this one unfairly hogs the “conditional” title.

This is for when you have parts of a document that you only want rendered (or not) to a specific format, such as HTML, PDF, etc.

If you’re interested in this, here’s the Quarto guide.

Project profiles

In this scenario you will have content to be included (or excluded) based on the profile/s specified. The div below would only be included if the “advanced” profile is in use

::: {.content-visible when-profile="advanced"}
This content will only appear in the advanced version.
:::

You can also specify configurations in a profile-specific YAML that can be fully, or selectively used, to override those in the default profile. Here are the docs.

Code flow

Most of the following examples follow the usual if/else structure.

I’ll be referring to params$my_boolean throughout this section so let me take a moment to explain.

In the YAML header of a Quarto document you can pass in parameters. These get stored in a list params that can be called throughout the document, just like any variables you make and keep in the global environment.

---
params:
  my_boolean: FALSE
---

Reports made this way are known as parameterised reports.

If you’re using a Quarto project it is also possible to include variables in a root directory file _variables.yml. I’m not doing that here, but if it sounds appropriate for you, here are the docs.

Inline code

You can conditionally execute inline code. If my_boolean is TRUE the example below results in a level 2 heading that says “Plots for mtcars”.

## Plots `r if (params$my_boolean) "for mtcars" else "for iris"`

In functions

Sometimes you just need a bit of perspective. Rather than something convoluted, do you just need to make a function with flow control of some kind?

plotting_func <- function(dat, col1, col2) {
  
  if (params$my_boolean) {
    
    dat |>
      ggplot2::ggplot() +
      ggplot2::geom_point(ggplot2::aes({{ col1 }}, {{ col2 }}))
    
  } else {
    
    dat |>
      ggplot2::ggplot() +
      ggplot2::geom_boxplot(ggplot2::aes({{ col1 }}, {{ col2 }}))
    
  }
  
}

Or if the content is amenable to it, something like this

plotting_func <- function(dat, col1, col2) {
  
  dat |>
    ggplot2::ggplot() +
    if (params$my_boolean) {
      ggplot2::geom_point(ggplot2::aes({{ col1 }}, {{ col2 }}))
    } else {
      ggplot2::geom_boxplot(ggplot2::aes({{ col1 }}, {{ col2 }}))
    }
  
}

You could put the function into a code block. Or you could hide it away in a folder R/ and a script functions.R. That’s useful if you write long ugly functions like me (lol), but also if you have multiple functions. In this case, you could just source the functions silently along with library calls at the start of the Quarto doc.

```{r}
#| include: false
library(dplyr)
library(downloadthis)
library(reactable)
source(file.path("R", "functions.R"))
```

If your content is too complicated for flow control inside of a function, then just put the content into separate functions in functions.R, source them as above, and use flow control in the Quarto doc

```{r}
if (params$my_boolean) {
  fancy_model_a(mtcars, wt, mpg) 
} else {
  fancy_model_b(iris, Species, Sepal.Length)
}
```

knitr::knit_child

Saving the best for last, because this one is really cool!

Remember how I started with the {{< include >}} shortcode and said that it wasn’t conditional?

{{< include _content.qmd >}}

If anyone knows how to (simply) use the shortcode in a conditional manner I’d love to hear about it. In the meantime I consider the conditional version of this is using knitr::knit_child.

Let’s say I make interactive plots of my data using Observable code blocks and keep this code in a separate Quarto doc ojs-cells.qmd. The code can be added to the main Quarto doc under certain conditions, as dictated by a boolean param ojs_include in the YAML header

```{r}
#| output: asis
if (params$ojs_include) { 
  
  cat(sep = "\n",
      knitr::knit_child(quiet = TRUE,
                        text = readr::read_file("ojs-cells.qmd")
      )
  )
  
}
```

What’s happening here is

  1. readr::read_file brings in ojs-cells.qmd as a string
  2. cat restores the line-by-line formatting and ‘prints’ the content
  3. The output: asis chunk option means Quarto treats the content as raw markdown output

Worth a mention - though not about conditionality - is this demo by Mickaël Canouil. It shows how the knit_child method can be called in a for loop to create n code chunks, and therefore n outputs (like tables and plots). This is great when you don’t know how many plots there will be or even what they are called.

Summary

A blurry, close-up photograph of a cat's face, taken unflatteringly from below. Her mouth is slightly ajar and the overall look is goofy. The cat is a chimera with green eyes, the left side of her face coloured black and the right side orange. The cat's name is Damsel.

Damsel the chimera. Copyright me!

I hope these approaches give you a practical set of tools for including conditional content in Quarto-published HTML documents.

You can use

  1. The shortcode {{< include >}} to bring in a sub-document

  2. Publish content dependent on output format using “conditional content”

  3. if/else statements either inline or in code chunks. This can be combined with

    1. Functional programming by putting code into functions - either in code blocks in the document, or in a separate sourced file
    2. With knitr’s knit_child to bring in entire sub-documents as required

Thank you to Bruno Rodrigues and John MacKintosh for proofreading a draft of this post.