Activity 07 — Your First Quarto Report
Read the data, plot it, describe it, test it — then render to Word, PDF, and slides
Follow-along activity paralleling the Quarto lecture. Students fill in a starter .qmd that takes the leaf data from start to finish — read it in, box plot, mean ± SE plot, histogram, summary statistics table, and a Welch’s t-test — and render it to Word, PDF, and RevealJS slides.
How this activity works
You are building one Quarto file this whole class, and you turn it in:
leaf_report.qmd.
- Everything you have done so far — reading data, plotting, summary statistics — goes into one document that also holds your writing.
- The analysis code is already in the starter file, and it runs. You add the finishing touches (colors, labels, theme, table formatting), write the text, and render. Whatever time is left is for Part 9 — play.
- Start every code chunk with a short
#comment that says what it does. Same rule as your scripts; it is part of the grade.- Render often — after every chunk. A break is easy to find when you only changed one thing.
🔮 Predict before you Render
Before each Render, say what the document will show — a heading, a plot, a table, a number. Then check.
- Starter file →
leaf_report_skeleton.qmd— save it at the top of your project folder asleaf_report.qmd. It already has the YAML, every section heading, and working code for every step — plots, summary table, and t-test — ready for you to finish. - Leaf data →
2026_09_03_data_sci_leaf_area.xlsx— should already be in yourdata/folder. r_themes_for_3_sizes.Randsummary_stats_function.R— should already be in yourthemes/folder (Activities 04 and 06).
What you turn in (Part 8): leaf_report.qmd, leaf_report.docx, and leaf_report.pdf.
Part 1 · Put the file in the right place, then Render it
Save the starter file at the top level of your project — the same folder that holds data/ and themes/:
leaf_project/
├── data/
│ └── 2026_09_03_data_sci_leaf_area.xlsx
├── figures/
├── scripts/
├── themes/
│ ├── r_themes_for_3_sizes.R
│ └── summary_stats_function.R
└── leaf_report.qmd <- HERE, at the top
Quarto runs your code from the folder the .qmd is saved in. Saved at the top of the project, "data/2026_09_03_data_sci_leaf_area.xlsx" and "themes/..." work exactly as they do in your scripts — no here package, no ../.
If you saved it inside documents/ instead, every path would need ../data/... to climb back up a folder. Keep it simple: top of the project.
Open leaf_report.qmd in Positron and press Render (Ctrl/Cmd + Shift + K) before you change anything. You should get a document with a title, headings, three plain plots, two plain tables, and a t-test printout. It all works — it just isn’t finished.
Did it render? Y / N
If not, what did the error message name?
Part 2 · The YAML — one switch for all the code
Look at the block between the two --- lines at the top. Change author: to your name.
title: "Leaf Mass on the Sunny and Shady Sides of a Tree"
author: "Your Name"
date: today
format:
docx:
toc: true
number-sections: true
pdf:
toc: true
number-sections: true
fig-pos: "H" # keep each figure right under its heading
revealjs:
scrollable: true
smaller: true
execute:
echo: true # false = hide ALL the code
warning: false
message: falseformat:lists three outputs — Word, PDF, and RevealJS slides — from the same file.execute:sets the default for every chunk.echo: trueshows the code;echo: falsehides all of it at once.
🔮 Predict: If you change echo: true to echo: false and Render, what disappears from the document, and what stays?
✏️ Your turn: Make the change, Render, look — then set it back to true for now. You will turn it off for good in Part 8.
What disappeared:
What stayed:
YAML is space-sensitive. Indent with two spaces, never tabs. If Render fails right away, the YAML is the first place to look.
Part 3 · Markdown — write the Introduction and Methods
Replace the <!-- comment --> under Introduction with two or three sentences, and fill in the hypotheses:
# Introduction
Leaves on the **shady** side of a tree may grow larger and
heavier to catch more of the limited light. We collected leaves
from the sunny and shady sides of a tree and weighed each one.
- **Null hypothesis:** mean leaf mass is the same on both sides
- **Alternate hypothesis:** mean leaf mass differs between sides✏️ Your turn: Write two or three sentences under Methods in your own words: how the leaves were weighed, which plots and summary statistics you made, and that you compared the sides with a Welch’s two-sample t-test. Render.
<!-- ... --> is a Markdown comment — it never shows up in the rendered document. The skeleton uses them as notes to you. Delete them as you fill each section in.
Part 4 · Chunk options — your reference card
Every chunk in the skeleton starts with lines that begin #|. Those are chunk options. Keep this table handy — these are the ones you will use all term:
| Option | What it does | Example |
|---|---|---|
label |
Names the chunk. fig- or tbl- at the front numbers it as a Figure or Table |
#| label: fig-boxplot |
echo |
Show (true) or hide (false) the code |
#| echo: false |
eval |
Run the code (true) or only show it (false) |
#| eval: false |
include |
false runs the code but hides both code and output |
#| include: false |
output |
false hides the results but keeps the code |
#| output: false |
warning |
Show or hide warnings | #| warning: false |
message |
Show or hide package start-up messages | #| message: false |
fig-width |
Figure width in inches | #| fig-width: 5 |
fig-height |
Figure height in inches | #| fig-height: 4 |
fig-cap |
Figure caption | #| fig-cap: "Leaf mass by side" |
tbl-cap |
Table caption | #| tbl-cap: "Summary statistics" |
tbl-colwidths |
Column widths (percent) so Word tables line up | #| tbl-colwidths: [30, 70] |
Look at the first two chunks in the skeleton — they are already filled in:
```{r}
#| label: setup
# load packages ----
library(readxl) # read Excel files
library(tidyverse) # dplyr + ggplot2
library(janitor) # clean_names()
library(broom) # tidy() turns test output into a table
# load our theme file and summary_stats() helper ----
source("themes/r_themes_for_3_sizes.R")
source("themes/summary_stats_function.R")
``````{r}
#| label: load-data
# read the leaf data from the data/ folder ----
leaf_df <-
read_excel("data/2026_09_03_data_sci_leaf_area.xlsx") %>%
clean_names()
```🔮 Predict: The setup chunk prints nothing useful to a reader. Which one option from the table would make it run but leave no trace in the document?
✏️ Your turn: Add that option to the setup chunk and Render. Did the library code disappear?
Option I added:
Did the packages still load (do later chunks still work)? Y / N
Labels must be unique. Two chunks named plot stop the Render with “Duplicate chunk label”. The error names the label — go straight to it.
Part 5 · Finish the plots
The three plots are already in your skeleton, and they run. Render and look at them. They are correct, but they are not ready for a report: default colors, axis labels that read mass_g and shade, and a legend that repeats what the x-axis already says.
Your job is the finishing touches — the same layers you learned in Activities 03–04. Each chunk ends with a # TODO: comment showing where they go.
🔮 Predict: Before you change anything, list three things about the plain box plot a reader would find confusing.
1.
2.
3.
Box plot
Here is the finished chunk. The last four lines are yours to add in place of the # TODO: comment:
```{r}
#| label: fig-boxplot
#| fig-cap: "Leaf mass by side; each point is one leaf."
#| fig-width: 5
#| fig-height: 4
# box plot with raw points on top ----
leaf_df %>%
ggplot(aes(x = shade, y = mass_g, fill = shade)) +
geom_boxplot(alpha = 0.6, outlier.shape = NA) +
geom_jitter(width = 0.15, alpha = 0.6) +
scale_fill_manual(values = c("sunny" = "goldenrod",
"shady" = "forestgreen")) +
labs(x = "Side of tree", y = "Leaf mass (g)") +
theme_regular() +
theme(legend.position = "none")
```Adding a layer means adding a + to the end of the line above it. Forget it and R runs the plot without your new lines — or stops with an error.
Render. Notice the caption starts “Figure 1:” — that number came from the fig- at the front of the label.
Mean ± SE — your turn
✏️ Add the same four finishing lines to the fig-mean-se chunk. Careful: this plot maps color = shade, not fill = shade. Which scale_..._manual() do you need?
```{r}
#| label: fig-mean-se
#| fig-cap: "Mean leaf mass (± 1 SE) by side."
#| fig-width: 5
#| fig-height: 4
# mean +/- standard error plot ----
leaf_df %>%
ggplot(aes(x = shade, y = mass_g, color = shade)) +
geom_jitter(width = 0.15, alpha = 0.35, size = 2) +
stat_summary(fun = mean, geom = "point", size = 4) +
stat_summary(fun.data = mean_se, geom = "errorbar",
width = 0.15, linewidth = 0.9) +
scale_color_manual(values = c("sunny" = "goldenrod",
"shady" = "forestgreen")) +
labs(x = "Side of tree", y = "Leaf mass (g)") +
theme_regular() +
theme(legend.position = "none")
```Histogram — your turn
✏️ Finish the fig-histogram chunk on your own. This time the x-axis is leaf mass and the y-axis is a count.
```{r}
#| label: fig-histogram
#| fig-cap: "Distribution of leaf mass on each side."
#| fig-width: 6
#| fig-height: 3.5
# one histogram per side ----
leaf_df %>%
ggplot(aes(x = mass_g, fill = shade)) +
geom_histogram(binwidth = 0.05, color = "white") +
facet_wrap(~shade) +
scale_fill_manual(values = c("sunny" = "goldenrod",
"shady" = "forestgreen")) +
labs(x = "Leaf mass (g)", y = "Count") +
theme_regular() +
theme(legend.position = "none")
```✏️ Your turn: Change fig-width on the histogram from 6 to 3 and Render. Then put it back.
What happened to the histogram at fig-width: 3?
Looking at all three plots: which side looks heavier, if either?
Part 6 · Finish the summary statistics table
The table chunk is already in your skeleton — the same summary_stats() helper from Activity 06, printed with knitr::kable(), which turns a data frame into a real table in Word, PDF, and slides. Render and look: seven decimal places and headers like ci_lower.
✏️ Finish it by filling in kable():
```{r}
#| label: tbl-summary
#| tbl-cap: "Summary statistics for leaf mass (g) by side."
#| tbl-colwidths: [12, 7, 12, 15, 12, 12, 15, 15]
# summary statistics by side, printed as a table ----
mass_stats_df <- leaf_df %>%
group_by(shade) %>%
summary_stats(mass_g)
mass_stats_df %>%
knitr::kable(
digits = 3,
col.names = c("Side", "n", "Mean", "Variance",
"SD", "SE", "CI low", "CI high"))
```digits = 3rounds every number in the table — noround()neededcol.names =gives the columns readable names (one per column, in order)tbl-colwidths:(already in the skeleton) sets each column’s width in percent — eight columns, eight numbers. Without it, Word guesses and often squeezes the last column.
Under the table, point the reader to it — let Quarto fill in the number:
@tbl-summary gives the sample size, mean, variance, standard
deviation, standard error, and 95% confidence interval for leaf
mass on each side.Render. @tbl-summary turns into “Table 1” — and stays correct if you ever add a table above it.
Mean mass, shady: sunny:
Which side has the larger SD?
Part 7 · Test it — Welch’s two-sample t-test
You met the t-test in introductory biology: does the difference between two means look bigger than chance? We use Welch’s version, which does not assume the two sides have equal variances — that is what var.equal = FALSE says. (Next lecture takes the t-test apart properly.)
The test is already in your skeleton:
```{r}
#| label: ttest
# Welch's t-test (does NOT assume equal variances) ----
leaf_ttest <- t.test(mass_g ~ shade, data = leaf_df,
var.equal = FALSE)
leaf_ttest
```mass_g ~ shadereads “mass by shade” — the number on the left, the two groups on the right- The printout says Welch Two Sample t-test at the top — check that it does
The printout is fine for you, but ugly in a report. The next chunk uses tidy() from broom to turn it into a data frame, so kable() can make it a table.
✏️ Your turn: Finish the tbl-ttest chunk the same way you finished Part 6 — digits = 3 and six readable col.names.
```{r}
#| label: tbl-ttest
#| tbl-cap: "Welch's t-test of leaf mass by side of the tree."
#| tbl-colwidths: [20, 20, 15, 15, 15, 15]
# the same result as a clean table ----
leaf_ttest %>%
tidy() %>%
select(estimate1, estimate2, statistic, parameter,
p.value, conf.low) %>%
knitr::kable(
digits = 3,
col.names = c("Mean shady", "Mean sunny", "t",
"df", "p-value", "CI low"))
```🔮 Predict before you Render: Look back at your mean ± SE plot. Do the error bars overlap a lot or a little? Will the p-value be below 0.05?
My prediction:
t = df = p-value =
Is p below 0.05? Y / N
✏️ Your turn — Discussion: Under Discussion, write two or three sentences. Point to @fig-mean-se and @tbl-ttest, say which side had heavier leaves and by how much, and whether the p-value is below 0.05. Do the data support the alternate hypothesis? Refer to the tables — don’t type the numbers in by hand.
Our leaves give a p-value well above 0.05, so we fail to reject the null hypothesis. That is a real result, not a failed analysis — report it honestly.
Part 8 · Hide the code and render all three formats
A report is for a reader who wants results, not R. Go back to the YAML and make the one-line change:
execute:
echo: false # hide ALL the codeOne more tidy-up: the raw t.test() printout was for you. Now that @tbl-ttest holds the same numbers, hide the printout but keep the chunk running (it still has to make leaf_ttest). From your reference card:
```{r}
#| label: ttest
#| output: false
# Welch's t-test (does NOT assume equal variances) ----
leaf_ttest <- t.test(mass_g ~ shade, data = leaf_df,
var.equal = FALSE)
leaf_ttest
```The Render button in Positron previews one format. To build all three outputs listed under format:, open the Terminal tab (not the Console) and run:
quarto render leaf_report.qmdYou should now have three new files next to your .qmd:
| File | What it is |
|---|---|
leaf_report.docx |
Word report — hand this in |
leaf_report.pdf |
PDF report — hand this in |
leaf_report.html |
RevealJS slide show — open it in a browser, press F for full screen and the arrow keys to move |
When Word opens leaf_report.docx it may ask to update fields — say Yes. That fills in the table of contents.
PDF output needs a small LaTeX install, once per computer. In the Terminal:
quarto install tinytexThen run quarto render leaf_report.qmd again.
✏️ Your turn: Open the slides. Each # heading became a section slide and each ## heading became its own slide — the same file is now a presentation.
How many slides did you get?
Which slide would you show first in lab meeting?
Part 9 · Play — if you finish early
Render and hand in first. Then File → Save As leaf_report_play.qmd so your hand-in stays safe, and try any of these in the copy:
- A different variable. Change
mass_gtopetiole_mmin every chunk (Ctrl/Cmd + F to find them all), fix the axis labels and captions, and Render. The whole report — plots, tables, test — rebuilds for the new variable. - A new figure. Add a chunk with
#| label: fig-scatterand afig-cap, plotmass_gagainstpetiole_mmwithgeom_point(), and refer to it in the text with@fig-scatter. - Theme sizes. Swap
theme_regular()fortheme_small()ortheme_large(). Which one fits a 5-inch-wide figure best? - Slide style. Under
revealjs:in the YAML addtheme: dark(orsky,serif,solarized) and render the slides again. - Chunk options. Put
#| echo: trueon just one chunk while the YAML saysecho: false. What shows up?
leaf_report.qmd is now your template for every analysis this term — the t-test activities next week, and your final project. Copy it, change the data file in load-data and the column names in the chunks, rewrite the text, and Render.
Check yourself
You are done when your report has:
Turn it in
Submit all three:
leaf_report.qmd— your source fileleaf_report.docx— the Word reportleaf_report.pdf— the PDF report
References: 📖 R4DS Ch 28 — Quarto · 📖 R4DS Ch 29 — Formats · 🌐 Quarto code-chunk options · 🌐 Quarto RevealJS