Reorder, rename, and lump penguin categories with forcats — for cleaner plots and models
2026-07-05
How to use this worksheet
- Work through the parts in order. Type the code into a new R script in Positron and run it line by line.
- Blocks marked ▶ Run this should be executed as written.
- Blocks marked ✏️ Your turn ask you to write, modify, or interpret.
- Day 1 = Parts 1–6. Day 2 = Parts 7–12.
🔮 Predict before you run — and type, don’t paste
Before each ▶ Run this block, predict the order the plot will use or the output you’ll see. Then type the code yourself. With factors the whole game is the order of the levels — predicting it first is how you learn to control it, and typing builds the muscle memory for the fct_ family.
🧩 Chunk 1 — What a factor is (Day 1, after lecture Chunk 1)
Parts 1–3: load the penguins and inspect their factors.
▶ Run this at the top of your script:
⚠️ Watch out!
forcatsloads withlibrary(tidyverse)— you do NOT need a separatelibrary(forcats).
▶ Run this:
✏️ Your turn: Record what you found.
Is species a factor? Y / N
Levels, in order:
Which species has the most rows?
✏️ Your turn: Run the same three lines on island. How many levels does it have, and what are they?
island levels:
🔮 Predict first: In what order will the three species bars appear if you give no ordering instruction?
▶ Run this:
✏️ Your turn: Was your prediction right? Why is alphabetical rarely the order you actually want?
Order shown:
Why alphabetical is a poor default here:
🧩 Chunk 2 — Reorder for readable plots (Day 1, after lecture Chunk 2)
Parts 4–6: reorder bars and boxplots by frequency and by value.
fct_infreq()▶ Run this:
✏️ Your turn: Add fct_rev() to flip it to least-common-first. Write the one changed line.
fct_reorder()🔮 Predict first: We’ll order species by their median body mass. Which species ends up last (heaviest)?
▶ Run this:
✏️ Your turn: What order did the species come out in? Does the plot read more clearly than the alphabetical one?
New order (lightest -> heaviest):
Clearer? Y / N
✏️ Your turn: Change .fun = median to .fun = mean. Did the order change? Why might mean and median give the same order here?
Your answer:
▶ Run this:
# Mean mass per species, biggest bar on top --------------
penguins_df %>%
group_by(species) %>%
summarize(mean_mass = mean(body_mass_g)) %>%
mutate(species = fct_reorder(species, mean_mass) %>% fct_rev()) %>%
ggplot(aes(x = mean_mass, y = species)) +
geom_col(fill = "steelblue") +
labs(x = "Mean mass (g)", y = NULL) +
theme_minimal()✏️ Your turn: What does fct_rev() do here? Remove it and describe what changes.
Your answer:
🛑 End of Day 1. You can now inspect and reorder factors. Day 2 covers renaming, lumping, and models.
🧩 Chunk 3 — Rename, collapse, lump (Day 2, after lecture Chunk 3)
Parts 7–9: clean up level labels.
fct_recode()▶ Run this:
✏️ Your turn: In fct_recode(), which name goes on the left — the new one or the old one?
Your answer:
▶ Run this (collapse species into size groups):
▶ Run this (lump islands, keep the top 2):
✏️ Your turn: After lumping, what is the third island called, and how many rows landed in it?
Lumped level name:
n in that level:
as.numeric() trap🔮 Predict first:
yearsbelow is a factor holding “2010”, “2011”, “2012”. What willas.numeric(years)return?
▶ Run this:
✏️ Your turn: Explain the difference in one sentence.
as.numeric(years) gave:
as.numeric(as.character(years)) gave:
Why they differ:
💡 Key idea: a factor is stored as integer codes with a labels table.
as.numeric()returns the codes — always go throughas.character()first when a factor holds real numbers.
🧩 Chunk 4 — Factors in models & cleanup (Day 2, after lecture Chunk 4)
Parts 10–12: set the reference, drop ghosts, build the final figure.
fct_relevel()🔮 Predict first: If we make Gentoo the reference group instead of Adelie, will the overall ANOVA p-value change, or only the coefficients?
▶ Run this:
# Default reference = Adelie -----------------------------
coef(lm(body_mass_g ~ species, data = penguins_df))
# Make Gentoo the reference ------------------------------
penguins_relevel <- penguins_df %>%
mutate(species = fct_relevel(species, "Gentoo"))
coef(lm(body_mass_g ~ species, data = penguins_relevel))✏️ Your turn: Compare the two coefficient sets.
What the (Intercept) represents now:
Did the coefficients change? Y / N
Would the overall F-test / p-value change? Y / N
▶ Run this:
✏️ Your turn: Why does filter() leave the level behind, and what problem would that “ghost” level cause in a boxplot?
Your answer:
▶ Run this:
# Reordered + relabeled in one pipeline ------------------
penguins_df %>%
mutate(
species = fct_recode(species, "Adélie" = "Adelie"),
species = fct_reorder(species, body_mass_g, .fun = median)
) %>%
ggplot(aes(x = species, y = body_mass_g, fill = species)) +
geom_boxplot(alpha = 0.6, outlier.shape = NA) +
geom_point(position = position_jitter(width = 0.15, seed = 42),
alpha = 0.3, size = 1.4) +
labs(x = NULL, y = "Body mass (g)") +
theme_minimal() +
theme(legend.position = "none")✏️ Your turn: Which two fct_ functions are doing the work in this figure, and what does each one do?
Function 1: does:
Function 2: does:
At this point you should be able to:
✏️ Your turn — before you move on: Run your whole script top to bottom. Does it run cleanly?
Ran cleanly? Y / N
If not, what error appeared:
Optional — do this if you finish early.
▶ Try this:
# fct_reorder2: order legend to match the line ends ------
penguins_df %>%
group_by(species, year = factor(year)) %>%
summarize(mean_mass = mean(body_mass_g), .groups = "drop") %>%
mutate(species = fct_reorder2(species, year, mean_mass)) %>%
ggplot(aes(x = year, y = mean_mass, color = species, group = species)) +
geom_line(linewidth = 1) +
theme_minimal()✏️ Your turn: How does fct_reorder2() decide the legend order, and why is that nice for line plots?
Your answer:
✏️ Your turn: If you swap penguins for your project data, which categorical column would you turn into an ordered factor, and what would you order it by?
Your answer:
could not find function "fct_reorder" → library(tidyverse) (forcats is inside it).mutate() and then plot the result; reordering a copy doesn’t change the original.filter(); add fct_drop().as.numeric() trap; use as.numeric(as.character(x)).fct_recode did nothing → check the OLD name spelling; unknown old names are silently ignored.💡 Key idea: factors are how you make categorical variables behave — in plots and in models. Reorder for the reader, relabel for the report, relevel for the baseline.
End of Worksheet 10. Next: Worksheet 11 — joins, combining your data with a second table.