Lesson 1 of 3

Quarto Reports

Every Monday, Priya, a data analyst at the Inkwell Books chain, ships the same thing: last week's sales report. For a long time she did it by hand. She ran her R code, copied the numbers into a Word document, pasted a chart, and wrote a summary sentence. Then one Monday the data changed at the last minute. She updated the chart but forgot the sentence, and the report went out with a headline that said one revenue and a chart that showed another.

That mistake has a name: copy-paste drift, and it is exactly what this lesson removes. With Quarto (and its older sibling R Markdown), the prose, the code, and the figures all live in one file. You render that file once and every number in the text is computed by R, never typed. Then you make it a template that re-runs for any store or any week, with no editing at all.

The widget below is Priya's report. Toggle between Source (.qmd), the plain text she writes, and Rendered, the finished page R produces from it.

By the end of this lesson you will be able to:

  • Name the three parts of a Quarto document (the YAML header, the prose, and the code chunks) and say what "render" does
  • Write a code chunk and an inline expression so a figure, a table, or a number is produced by R, not pasted
  • Explain why this makes a report reproducible: change the data once and every number re-flows
  • Describe what render turns your file into, and get HTML, PDF or Word from the same source
  • Parameterize a report with params: so one template re-runs for any input

Prerequisites: you can run R and load a package with library(), you have built a basic ggplot (data, then aes(), then a geom) in The Grammar of Graphics, and you know the dplyr verbs filter() and summarise() from The dplyr Verbs. Every new term is defined as it appears.

What it is

One file, three parts

A Quarto document is a single plain text file with the extension .qmd (Quarto markdown). It is not an app and not a server. You write it, then you render it: render means "run the file and produce a finished document," a web page, a PDF, or a Word file. R Markdown (.Rmd) works the same way and Quarto is its modern successor, so everything here applies to both.

Every .qmd is made of just three kinds of content. Toggle the widget above between Source and Rendered and you can see all three:

  • The YAML header: a small settings block at the very top, fenced by --- lines. It holds the title, the author, and the output format:. (YAML is just a simple key: value text format for settings.)
  • The prose: ordinary markdown text, headings, sentences, lists, the words a reader actually reads.
  • The code chunks: blocks of R fenced by ``` `{r} ```. When you render, R runs each chunk and drops its result, a table or a chart, straight into the document.

Everything in this lesson is built from Priya's six Inkwell Books stores. Each lesson runs in a fresh R session, so we create that data right here (run this once, and every later block can use it):

RInteractive R
sales <- data.frame( store = c("Downtown", "Airport", "Riverside", "Uptown", "Lakeside", "Old Mill"), units = c(420, 310, 180, 260, 150, 230), # books sold last week revenue = c(8400, 6200, 3600, 5200, 3000, 4600) # dollars ) sales #> store units revenue #> 1 Downtown 420 8400 #> 2 Airport 310 6200 #> 3 Riverside 180 3600 #> 4 Uptown 260 5200 #> 5 Lakeside 150 3000 #> 6 Old Mill 230 4600

  
Key Insight
A Quarto report is authored, not assembled. You describe the content in markdown and R, and render does the work of running the code and stitching the results into a finished document. You never copy a result by hand.
The code does the work

A code chunk runs, and its output appears

A code chunk is the engine of a reproducible report. You write the R once; at render time it executes and its output, a table or a figure, lands in the document in place. Nothing is screenshotted, nothing is pasted.

Here is the table chunk for Priya's report. The pipe |> feeds sales into arrange(desc(revenue)), which sorts the stores from highest revenue down, and knitr::kable() turns a data frame into a clean report table:

RInteractive R
library(dplyr) library(knitr) by_store <- sales |> arrange(desc(revenue)) kable(by_store, caption = "Sales by store, last week") #> #> Table: Sales by store, last week #> #> |store | units| revenue| #> |:--------|-----:|-------:| #> |Downtown | 420| 8400| #> |Airport | 310| 6200| #> |Uptown | 260| 5200| #> |Old Mill | 230| 4600| #> |Riverside| 180| 3600| #> |Lakeside | 150| 3000|

  

And here is the figure chunk, the same bar chart you saw on the cover. In the .qmd this is just another ``` `{r} ``` block; render runs it and inserts the plot:

RInteractive R
library(ggplot2) ggplot(sales, aes(reorder(store, revenue), revenue)) + geom_col(fill = "#1f7a55") + coord_flip() + labs(x = NULL, y = "revenue last week ($)")

  

The report you ship contains the output of these chunks, not the code (you can hide the code with a chunk option). Change a sales figure and re-render, and the table and the chart both redraw themselves.

The trick that ends drift

Inline code: numbers written by R

The table and the chart are computed. But what about a sentence like "Total revenue last week was $31,000"? If Priya types that number, she is back to copy-paste drift the moment the data changes. The fix is inline code: a tiny R expression embedded inside the prose that is replaced by its result when the report renders.

Run this to see exactly the value that would be slotted into the sentence:

RInteractive R
library(glue) total_rev <- format(sum(sales$revenue), big.mark = ",") top <- sales$store[which.max(sales$revenue)] glue("Total revenue last week was ${total_rev}, led by {top}.") #> Total revenue last week was $31,000, led by Downtown.

  

In a real .qmd, the prose itself carries the expression. You write inline R between backticks (` r expr `) right in the sentence, and render swaps in the result:

Total revenue last week was $`r sum(sales$revenue)`,
led by `r sales$store[which.max(sales$revenue)]`.
Key Insight
Inline code is what makes the words reproducible, not just the charts. Because the number is produced by the same data the chart uses, the headline and the figure can never disagree again.
Check yourself

Change one number, then re-render

Priya discovers last week's Airport revenue was entered wrong. She fixes that single number in the sales data and re-renders the report. Which parts of the finished report change?

Exactly. The table and chart come from code chunks and the headline number is inline code, so all three recompute from the same data on every render. That is reproducibility: one data change, one re-render, everything consistent.
Manual edits are exactly the drift Quarto prevents. Chunks and inline code recompute from the data every render, so one fix updates the whole report at once.
Your turn

Write the inline number

Priya's summary needs a sentence: "We sold ___ books last week." That blank should be filled by R, not typed, so it stays correct when the data changes. Write the expression that totals the units column across all six stores.

That is it. sum(sales$units) totals the column to 1,550. As inline code in the prose it recomputes every render, so the sentence is always right.You want to ADD the units column up, not count or average it. Use sum(): write sum(sales$units).
Show answer
books_sold <- sum(sales$units)
books_sold
#> [1] 1550
Under the hood

What render actually does

When you render a .qmd, three things happen in order, and knowing them demystifies the whole process. Knit (sometimes called "execute") runs every code chunk and captures its output. The document, now full of real results, becomes plain markdown. Then a tool called Pandoc converts that markdown into your chosen output format. The result is one self-contained file you can email or host anywhere.

You trigger all of that with one command. From the Terminal, or from R, you point Quarto at the file:

library(quarto)
# in the Terminal:
quarto render weekly-report.qmd

# or from R:
quarto::quarto_render("weekly-report.qmd")

Render runs R once, on your machine, and bakes the results into the output file. The person reading the finished HTML or PDF does not need R installed; they just open the document.

Check yourself

One source, two outputs

Priya needs her report two ways: as a web page for the team wiki, and as a Word document her manager can mark up with comments. She has one .qmd already written. What does she change to get both?

Right. One source, many outputs is the heart of Quarto. Only format: changes; Pandoc converts the same content to HTML, PDF, Word and more. You write the report once.
A single .qmd renders to HTML, PDF, Word, slides and more, all by setting format:. The content is authored once.
The automation payoff

One template, any input

Priya does not want one report. She wants the same report for the Downtown store, then Airport, then Riverside, every week. Rewriting the file six times would reintroduce every error she just removed. The answer is a parameter: a value declared in the YAML header that the document reads, so one template produces many reports.

You add a params: block to the YAML, then refer to it anywhere in the document as params$store. Toggle the widget between Source and Rendered to see the parameter sitting in the header and being used in the body:

Inside the document, params$store is just an ordinary value. Here we set it by hand to show the idea, then use it to compute that store's numbers (Quarto would set it from the header for you):

RInteractive R
library(dplyr) # Quarto sets this from the YAML; here we set it by hand to see the idea report_store <- "Airport" sales |> filter(store == report_store) |> summarise(units = sum(units), revenue = sum(revenue)) #> units revenue #> 1 310 6200

  

Now one template can produce a finished report for every store, with no hand-edits, by rendering it once per parameter value:

library(quarto)

# one template -> one finished report per store
for (s in unique(sales$store)) {
  quarto_render("weekly-report.qmd",
                execute_params = list(store = s),
                output_file    = paste0("report-", s, ".html"))
}
Your turn

Make the report for another store

Run the template for the Riverside store. The parameter report_store is already set; complete the filter() so it keeps only the rows where the store column equals the chosen report_store.

Yes. filter(store == report_store) keeps just Riverside, so the summary returns 180 units and $3,600. Swap the parameter and the same template reports any store you like.Keep the rows whose store equals the parameter: filter(store == report_store).
Show answer
report_store <- "Riverside"

sales |>
  filter(store == report_store) |>
  summarise(units = sum(units), revenue = sum(revenue))
#>   units revenue
#> 1   180    3600
Go deeper

References

A few authoritative places to take this further:

Lesson 1 complete

You turned a fragile, hand-assembled report into one that updates itself. A Quarto document is a single .qmd with three parts: the YAML header, the prose, and the code chunks. You render it to run the code and produce a finished file. Inline code makes even the sentences reproducible, so the headline and the chart can never drift apart. The same source gives you HTML, PDF or Word by changing one format: line, and a params: block turns that one file into a template that re-runs for any store or any week.

Next, Lesson 2: Telling a story with data. A reproducible report is only useful if a busy reader acts on it. You will learn to lead with the answer, write the executive summary first, and structure a short data story that lands in thirty seconds.