Practical #10
Advanced Statistical Programming using R — Statistical Communication & Visualisation
Quiz
Before starting, work through this QUIZ to check your understanding of this week’s lecture on statistical communication & visualisation (with a short recap of last week).
Overview
In yesterday’s lecture you saw how to turn raw analysis output into something an audience can read: choosing between a table and a plot, building a clean table with {gt}, and applying design principles to a default ggplot2 figure. Today you put that into practice on real output — your project’s.
You do it twice. Part 1 is a sandbox: you rehearse the full workflow on the palmerpenguins dataset, where we give you solutions. Part 2 is the real thing: you produce a polished table and improve a plot from your own project, then share one improvement with the group.
Work on the practical on your own and bring questions to the drop-in in this Zoom link. Lisa will be available on Friday from 2-4pm. We still recommend pairing up with your group members to work on the task. At least one person should have the project open in RStudio with a result to communicate when entering the call.
Budget roughly 2 hours: about 35 minutes on the sandbox and the rest on your own project.
You will need gt and ggrepel. Install them inside your project (so renv records them) and snapshot afterwards:
install.packages(c("gt", "ggrepel"))
renv::snapshot() # run in the R consolePart 1: Sandbox — communicating palmerpenguins
~35 min
{palmerpenguins} ships with penguins, measurements of 344 Antarctic penguins. You will make one table and polish one plot.
library(dplyr)
library(ggplot2)
library(gt)
library(palmerpenguins)Exercise 1.1: Table or plot?
For each output below, decide whether a table or a plot communicates it better, and say why in a few words.
- The exact mean and SD of bill length for each of the three species
- Whether body mass and flipper length are related
- A preview of the first six rows of the raw data for a methods appendix
- How the distribution of bill length differs between species
- a — table. Exact values and a small comparison; readers want to read the numbers off.
- b — plot. A relationship/pattern (a scatter plot) is read far better than a correlation buried in a table.
- c — table. A data sample is about showing the values and structure, not a pattern.
- d — plot. A distribution (histogram, density, or boxplot) shows shape and overlap that a table of summary stats hides.
Heuristic from the lecture: exact values or small comparisons → table; patterns, trends, or distributions → plot.
Exercise 1.2: Build a gt summary table
Summarise first, then pipe into gt(). Start from this and add the design layers:
penguins |>
group_by(species) |>
summarise(
n = n(),
mean_bill = mean(bill_length_mm, na.rm = TRUE),
sd_bill = sd(bill_length_mm, na.rm = TRUE),
mean_mass = mean(body_mass_g, na.rm = TRUE)
) |>
gt()Apply the table design rules from the lecture: format the numbers, put units in the column headers, right-align numbers, and write a subtitle that states the finding plus a source note.
penguins |>
group_by(species) |>
summarise(
n = n(),
mean_bill = mean(bill_length_mm, na.rm = TRUE),
sd_bill = sd(bill_length_mm, na.rm = TRUE),
mean_mass = mean(body_mass_g, na.rm = TRUE)
) |>
gt() |>
fmt_number(c(mean_bill, sd_bill, mean_mass), decimals = 1) |>
cols_label(
species = "Species",
n = md("*n*"),
mean_bill = html("Mean bill<br>(mm)"),
sd_bill = html("SD bill<br>(mm)"),
mean_mass = html("Mean mass<br>(g)")
) |>
cols_align(align = "right", columns = where(is.numeric)) |>
tab_header(
title = "Penguin measurements by species",
subtitle = "Chinstrap penguins have the longest bills on average"
) |>
tab_source_note(md("Source: `palmerpenguins` package"))The general gt workflow builds up layer by layer: gt() → fmt_number() → cols_label() → cols_align() → tab_header() → tab_source_note(). Units live in the headers (mm, g) so the cells stay clean, and the subtitle states the finding rather than restating the column names.
Exercise 1.3: Polish a default plot
Here is a default ggplot2 scatter. Run it, then apply at least three design improvements from the lecture.
ggplot(penguins, aes(x = bill_length_mm, y = body_mass_g, color = species)) +
geom_point()Improvements to choose from: a colorblind-safe palette, a minimal theme, a title + subtitle that states the finding, axis labels with units, and a source caption.
ggplot(penguins, aes(x = bill_length_mm, y = body_mass_g, color = species)) +
geom_point(alpha = 0.8) +
scale_color_brewer(palette = "Dark2") +
theme_minimal() +
labs(
title = "Bill length and body mass by species",
subtitle = "Gentoo penguins are heavier and longer-billed than Adelie and Chinstrap",
x = "Bill length (mm)",
y = "Body mass (g)",
color = "Species",
caption = "Source: palmerpenguins package"
)Why these choices:
- Position carries the message. The two most important variables are on the x/y axes — the most accurately-read encoding (Cleveland & McGill). Colour is reserved for the secondary grouping (
species). - Colour done right.
scale_color_brewer(palette = "Dark2")is a qualitative, colourblind-safe palette — right for unordered categories. Usescale_color_brewer(type = "seq")ortype = "div"instead for ordered or diverging values. - The subtitle states the finding, not just what the axes show.
With ≤ 5 groups, labelling the data directly cuts the eye-travel to a legend:
library(ggrepel)
last_plot() +
geom_text_repel(
data = ~slice_max(.x, bill_length_mm, by = species),
aes(label = species), show.legend = FALSE
) +
guides(color = "none")Part 2: Your project — communicate your results
~75 min
Now do the same on your own output. By the end you should have one polished gt table and one improved plot committed to your project repo.
Open your project and pick one analysis result worth communicating — a summary you have computed, a model output, or a figure you have already drafted.
Exercise 2.1: Table or plot?
For the result you picked, decide whether it is better shown as a table or a plot, using the heuristic from Ex 1.1. If it is a small set of exact values or a sample, make a table (Ex 2.2). If it is a pattern, trend, or distribution, improve a plot (Ex 2.3). Most groups will do both on different results.
| Goal | Chart type |
|---|---|
| Compare values across categories | Bar chart (horizontal preferred) |
| Show distribution of one variable | Histogram, density, boxplot, violin |
| Show relationship between two variables | Scatter plot, line chart |
| Show composition or part-of-whole | Stacked bar, treemap |
| Show change over time | Line chart, area chart |
| Show many variables at once | Faceted plots, paired plots |
Exercise 2.2: Build a gt table from your results
Produce at least one gt table from your project — a summary-statistics table or a data sample. Follow the workflow from Ex 1.2.
library(dplyr)
library(gt)
# Summary-stats example — adapt the columns to your data
your_data |>
group_by(your_group) |>
summarise(n = n(), mean_x = mean(your_value, na.rm = TRUE)) |>
gt() |>
fmt_number(where(is.numeric), decimals = 1) |>
cols_label(your_group = "Group", mean_x = html("Mean x<br>(units)")) |>
cols_align(align = "right", columns = where(is.numeric)) |>
tab_header(title = "...", subtitle = "<the finding>") |>
tab_source_note(md("Source: ..."))Use tab_spanner() to group related columns under a shared header instead of repeating units in every label, and choose decimal places deliberately — match the precision that actually matters for your variable.
Exercise 2.3: Improve an existing project plot
Take a plot from your project (or draft one now) and apply at least two design improvements. Work through this output checklist:
Visual encodings ranked from most to least accurately perceived (Cleveland & McGill):
- Position on a common scale — the most accurately read
- Length
- Angle / slope
- Area
- Colour saturation / intensity — the least accurate
Put the most important variable on an axis. Reserve colour for a secondary grouping, and avoid using area or colour alone to encode a key comparison.
Exercise 2.4: Write a caption that states the finding
- Rewrite your table/figure caption so it states the conclusion, not the contents.
- ✗ “Bill length by species”
- ✓ “Chinstrap penguins have longer bills on average”
The reader should be able to take the message from the caption alone. If you cannot write a one-sentence finding, the figure may be trying to say too much — split it (“one figure, one message”).
- Add an alternative text: make sure that your plot is accessible to visually impaired people, by providing all necessary information as alt-text. In Quarto, set it with the
fig-altchunk option:
```{r}
#| fig-alt: "A scatter plot of ..."
ggplot(...) + ...
```Writing good alt text is time-consuming. Emil Hvitfeldt shows how to use Claude Code to auto-generate fig-alt for all figures in a Quarto document in one pass — including running it directly on your project files.
See: emilhvitfeldt.com/post/claude-code-alt-text-quarto
Always review and adjust the generated alt text to make sure it matches your message.
Reflection log
~10 min
Take a few minutes to add this week’s entry to your reflection log. Then commit and push everything — the new gt table, the improved plot, and the reflection log entry can all go in one commit.
End-of-practical checklist
Before you leave today, make sure your group has:
Resources
Visualisation
- Fundamentals of Data Visualization (Wilke) — the design principles behind this week
- ColorBrewer palettes — qualitative / sequential / diverging palette chooser
- viridis color scales — perceptually-uniform, colourblind-safe scales
Tables
gtpackage documentation — the table workflow this practical followsgtsummarypackage — publication-ready summary tables with less boilerplate