18  Basics of Quarto

Class Objectives
  1. The Quarto Setup: Learn about the key parts of a Quarto document.
  2. Mixing Text, Code, Visuals & Equations: See how you can combine explanations, code, and mathematical formulas in one neat file.
  3. Making Reproducible Reports: Render your analysis into polished HTML, PDF, or Word documents.
  4. Recording Package Versions: Use renv to save and restore the R packages needed by a project.
Packages for this chapter

If you haven’t installed the following packages, run this code:

pak::pak(c("tidyverse", "palmerpenguins", "gt", "renv"))

18.1 What’s Quarto All About?

Imagine a tool that lets you write your story and run your code at the same time. That’s Quarto for you! It works with R, Python, Julia, and more, and lets you output to HTML, PDF, Word, presentations, and even websites.

Why should you care?
Because Quarto keeps your analysis, visualizations, and text all in one place. Readers can rerun the steps that produced your results. Reproducing those results also requires the data and a compatible software environment; we will use renv to record the R package versions.

18.1.1 A Brief History

The idea of mixing prose and code in one document goes back surprisingly far. Donald Knuth introduced literate programming in the 1980s, arguing that programs should be written for humans to read, not just for machines to execute. In the R world, this philosophy took off with Sweave (2002), which let you embed R code inside LaTeX documents. Then came knitr (2012) by Yihui Xie, which made literate programming much more flexible and user-friendly.

The real game-changer was R Markdown (2014), built on top of knitr and Pandoc. R Markdown made it dead simple to write a document in Markdown, sprinkle in some R code, and render it to HTML, PDF, or Word. It became the standard tool for reproducible research in the R community.

Quarto (2022) is the next evolution, developed by Posit (formerly RStudio). Think of it as R Markdown’s successor—but with a crucial difference: Quarto is language-agnostic. While R Markdown was tightly coupled to R and the knitr engine, Quarto is a standalone tool that works equally well with R, Python, Julia, and Observable JavaScript. Under the hood, Quarto still uses knitr for R code and Jupyter for Python, but the user-facing experience is unified.

18.1.2 Quarto vs. R Markdown

If you’ve used R Markdown before, you’ll feel right at home with Quarto. Here’s the key difference:

R Markdown Quarto
File extension .Rmd .qmd
Languages Primarily R R, Python, Julia, Observable
Engine knitr (other languages via knitr engines) knitr, Jupyter, or Observable
Requires R? Yes No (standalone CLI tool)
Maintained by Posit Posit
Should I Switch from R Markdown?

If you’re already comfortable with R Markdown, don’t panic—your .Rmd files still work perfectly. But for new projects, Quarto is the way forward. It has better cross-referencing, more output formats, and an active development community. Plus, most of what you know about R Markdown (YAML headers, Markdown syntax, the ideas behind chunk options) transfers to Quarto. One difference: chunk options move from the {r, echo=FALSE} header into #| lines inside the chunk, and use hyphens instead of dots (e.g., #| fig-width: 8 instead of fig.width=8).

18.1.3 Installing Quarto

Quarto is a standalone command-line tool—it’s not an R package. Here’s how to install it:

  1. Go to quarto.org/docs/get-started
  2. Download the installer for your operating system (macOS, Windows, or Linux)
  3. Run the installer

That’s it! If you’re using a recent version of RStudio (v2022.07 or later), Quarto is already bundled with it, so you may not even need a separate installation. You can verify your installation by opening a terminal and running:

quarto --version

18.2 Getting Started with a Quarto Document

A typical Quarto file has two parts:

  1. YAML Header:
    This is where you set up your document’s settings—think title, author, format, and more.
  2. Document Body:
    Here’s where you mix regular text, code, cool visuals, and even equations!

18.2.1 The YAML Header

The header sits at the very top and sits between two lines of three dashes (---). Here’s a simple example:

---
title: "My First Quarto Document"
author: "Your Name"
format: html
---

In this section, you tell Quarto:

  • Which format you’re aiming for (HTML, PDF, etc.)
  • What your document’s title, author, and date are
  • What style or theme you’d like to use
  • And even settings for your table of contents!

18.2.2 Writing with Markdown

In the body of your document, you write in Markdown—a simple way to format your text. For example:

# Level 1 Heading

## Level 2 Heading

This is your regular paragraph text. You can make text **bold** or *italic*.

- Bullet point one
- Bullet point two
  - Nested bullet

1. First numbered item
2. Second numbered item

> This is a blockquote that highlights something cool.

[Check out Quarto](https://quarto.org)

Markdown makes it super easy to structure your document without any fuss.

18.3 Mixing in Some Code: Code Chunks

Now, the magic happens when you include code chunks. These let you run code and show the results directly in your document.

18.3.1 How to Add a Code Chunk

Here’s a basic example in R:

library(tidyverse)
library(palmerpenguins)

penguins |>
    group_by(species) |>
    summarize(
        count = n(),
        mean_bill_length = mean(bill_length_mm, na.rm = TRUE),
        mean_body_mass = mean(body_mass_g, na.rm = TRUE)
    )

This code chunk groups the penguins dataset by species and summarizes key stats. Notice the #| options—these let you control what shows up in your final document.

18.3.2 Customizing Your Code Chunks

You can tweak each chunk with options like:

  • echo: false – Run the code, but hide it.
  • eval: false – Show the code without running it.
  • warning: false – Skip those pesky warning messages.
  • message: false – Keep it clean by hiding messages.
  • fig-width / fig-height – Set your figure dimensions.

These options can be set per chunk or globally in your YAML header. For example, to hide code and warnings in the whole document:

execute:
  echo: false
  warning: false

18.4 Adding Figures and Tables

18.4.1 Including Figures

It’s super simple to add plots. Just write the code for your plot in a code chunk, and Quarto will display it in your document.

```{r}
#| label: fig-penguin-plot
#| fig-cap: "Bill length vs. bill depth by species"
#| fig-width: 8
#| fig-height: 5
#| warning: false
#| message: false

ggplot(penguins, aes(x = bill_length_mm, y = bill_depth_mm, color = species)) +
    geom_point(alpha = 0.7) +
    geom_smooth(method = "lm", formula = y ~ x, se = FALSE) +
    theme_minimal()
```

Here is the result of that chunk (Figure 18.1):

Figure 18.1: Bill length vs. bill depth by species

This chunk adds a neat caption and custom dimensions for the plot.

Cross-Referencing Figures

Use the fig- prefix in your label (e.g., #| label: fig-penguin-plot) and then reference it in text with @fig-penguin-plot. Quarto automatically numbers them for you!

18.4.2 Creating Tables

You can easily turn a tibble into a table in your document with knitr::kable(). Here’s an example:

penguins |>
    group_by(species) |>
    summarise(
        number = n(),
        mean_bill_length = mean(bill_length_mm, na.rm = TRUE),
    ) |>
    knitr::kable()
species number mean_bill_length
Adelie 152 38.79139
Chinstrap 68 48.83382
Gentoo 124 47.50488

Quarto can also display stunning tables. In R, there is a package called gt (Grammar of Tables) that makes it easy to create and style tables. Here’s an example:

Show the code
penguin_counts_wider <- penguins |>
    filter(!is.na(sex)) |>
    mutate(year = as.character(year)) |>
    group_by(species, island, sex, year) |>
    summarise(n = n(), .groups = "drop") |>
    pivot_wider(
        names_from = c(species, sex),
        values_from = n
    ) |>
    arrange(island, year)

actual_colnames <- colnames(penguin_counts_wider)
desired_colnames <- actual_colnames |>
    str_remove("(Adelie|Gentoo|Chinstrap)_") |>
    str_to_title()
names(desired_colnames) <- actual_colnames

spanners_and_header <- function(gt_tbl) {
    gt_tbl |>
        gt::tab_spanner(
            label = gt::md("**Adelie**"),
            columns = 3:4
        ) |>
        gt::tab_spanner(
            label = gt::md("**Chinstrap**"),
            columns = c("Chinstrap_female", "Chinstrap_male")
        ) |>
        gt::tab_spanner(
            label = gt::md("**Gentoo**"),
            columns = contains("Gentoo")
        ) |>
        gt::tab_header(
            title = "Penguins in the Palmer Archipelago",
            subtitle = "Data is courtesy of the {palmerpenguins} R package"
        )
}

penguin_counts_wider |>
    mutate(
        island = as.character(island),
        year = as.numeric(year),
        island = paste0("Island: ", island)
    ) |>
    gt::gt(groupname_col = "island", rowname_col = "year") |>
    gt::cols_label(.list = desired_colnames) |>
    spanners_and_header() |>
    gt::sub_missing(missing_text = "-") |>
    gt::summary_rows(
        groups = TRUE,
        fns = list(
            "Maximum" = ~ max(.),
            "Total" = ~ sum(.)
        ),
        formatter = gt::fmt_number,
        decimals = 0,
        missing_text = "-"
    ) |>
    gt::tab_options(
        data_row.padding = gt::px(2),
        summary_row.padding = gt::px(3), # A bit more padding for summaries
        row_group.padding = gt::px(4) # And even more for our groups
    ) |>
    gt::opt_stylize(style = 6, color = "gray")
1
Penguins with unknown sex (NA) are dropped first; otherwise pivot_wider() would create extra Adelie_NA and Gentoo_NA columns that fall outside the spanners.
Table 18.1: Counts of penguins by island, year, species and sex
Penguins in the Palmer Archipelago
Data is courtesy of the {palmerpenguins} R package
Adelie
Chinstrap
Gentoo
Female Male Female Male Female Male
Island: Biscoe
2007 5 5 - - 16 17
2008 9 9 - - 22 23
2009 8 8 - - 20 21
Island: Dream
2007 9 10 13 13 - -
2008 8 8 9 9 - -
2009 10 10 12 12 - -
Island: Torgersen
2007 8 7 - - - -
2008 8 8 - - - -
2009 8 8 - - - -

Check the official document and tutorial for how to create tables with gt.

18.5 Writing Equations

Quarto supports LaTeX-style math notation, which is perfect for academic writing. Use single dollar signs for inline math (e.g., $\bar{x} = \frac{1}{n}\sum x_i$ renders as \(\bar{x} = \frac{1}{n}\sum x_i\)) and double dollar signs for display math:

\[ \hat{\beta} = (X^TX)^{-1}X^Ty \]

If you need a literal dollar sign in normal text (e.g., a price), escape it as \$ so it is not treated as math. This is especially useful when you want to show the formulas behind your statistical analyses right alongside the code that implements them.

18.6 Rendering Your Document

To turn your .qmd file into a polished document, click the Render button in RStudio (or run quarto render in the terminal). Quarto will:

  1. Execute all code chunks
  2. Capture outputs (plots, tables, printed results)
  3. Combine everything into your chosen format

You can switch formats just by changing the format: field in your YAML header:

format: html   # Interactive, web-friendly
format: typst  # Fast PDFs for reports and assignments
format: docx   # For collaborators who love Word
The Power of Reproducibility

Render from a fresh R session to check that the document contains every step it needs, including package loading and data import. Objects created only in the Console will not be available during rendering. The next section adds package version tracking to this workflow.

18.7 Reproducing Package Environments with renv

Your report works today, but a future package update might break it. The renv package saves the package versions your project uses in a file called renv.lock, so you can install those versions again later.

Open your .Rproj file and save your .qmd report inside the project. Run the commands below in the R Console; keep them out of your report’s code chunks.

18.7.1 Set up once

pak::pak("renv") # If renv is not already installed
renv::init()

This gives the project its own collection of packages. Follow any setup prompts, then keep using library() in your report as usual.

18.7.2 Save the versions that work

Render your report and check that it works. Then run:

renv::snapshot()

This updates renv.lock with the package versions used by your saved code. Repeat this step after adding or updating packages, once you have checked the report again.

18.7.3 Restore on another computer

Open the shared project, follow any renv setup prompts, and run:

renv::restore()

This installs the versions saved in renv.lock. Once it finishes, render the report.

Along with your report, data, and .Rproj file, include renv.lock, .Rprofile, renv/activate.R, and renv/settings.json. Keep the generated .gitignore files, but leave out renv/library/; the recipient installs those packages with restore().

They will still need R and Quarto installed. renv handles the R packages.