ggplot2 Customization Exercises: 10 Theme & Scale Practice Problems — Solved Step-by-Step
Themes control how a ggplot2 chart looks — fonts, backgrounds, grid lines, legend placement — and scales control how data maps to colours, axis limits, and labels. These 10 exercises build your customization skills from basic theme swaps to a full reusable house style, each with runnable starter code and a step-by-step solution.
What Do Themes and Scales Control?
Themes and scales are the two customization layers in ggplot2. A theme changes the non-data elements — everything around your data points, bars, or lines. A scale changes how data values translate into visual properties like position, colour, and size.
Here's how a single plot transforms when you add one theme and one scale function. Run the code to see the before-and-after.
The data didn't change — only the presentation did. theme_minimal() stripped the grey background and border, and scale_colour_brewer() replaced the default hue cycle with a ColorBrewer palette designed for readability.
Here's a quick reference for the functions you'll practice in these exercises.
| Customization | Function | Controls |
|---|---|---|
| Complete theme swap | theme_minimal(), theme_classic(), etc. |
All non-data elements at once |
| Individual element | theme(element = ...) |
One specific visual property |
| Text styling | element_text(size, face, angle, colour) |
Fonts, rotation, colour |
| Background/border | element_rect(fill, colour, linewidth) |
Rectangles (panels, legend, strips) |
| Line styling | element_line(colour, linewidth, linetype) |
Grid lines, axis lines |
| Remove element | element_blank() |
Delete any theme element |
| Categorical colour | scale_colour_brewer(palette) |
Discrete colour palette |
| Manual colour | scale_fill_manual(values) |
Hand-picked colours |
| Axis formatting | scale_y_continuous(labels, limits, breaks) |
Numeric axis display |
Try it: Take p_base and apply theme_classic() with scale_colour_brewer(palette = "Set2"). How does the look change from the styled version above?
Click to reveal solution
Explanation: theme_classic() removes grid lines and the background box, leaving only axis lines — a common journal style. "Set2" uses softer pastel tones compared to the bolder "Set1".
How Do You Switch and Layer Themes? (Exercises 1–3)
These first three exercises focus on theme() and its element functions. You'll apply built-in themes, remove elements with element_blank(), and style text with element_text(). Each exercise builds on a familiar built-in dataset.
Exercise 1: Apply a Built-In Theme
Dataset: mpg
Task: Create a boxplot of hwy (y-axis) grouped by class (x-axis). Apply theme_light(). Add the title "Highway MPG by Vehicle Class" and label the axes "Vehicle Class" and "Highway MPG".
Click to reveal solution
Explanation: theme_light() gives a clean white background with subtle grey grid lines — less stark than theme_bw(), more structured than theme_minimal(). The fill argument colours the boxes, making group boundaries easier to see.
Exercise 2: Remove Grid Lines and Style Line Elements
Dataset: mtcars
Task: Create a scatter plot of wt (x) vs mpg (y). Start with theme_bw(), then add theme() to:
- Remove minor grid lines entirely
- Change major grid lines to dashed, light grey (
"grey85") - Make the panel border thicker (linewidth 1.5)
Click to reveal solution
Explanation: element_blank() removes an element completely — no line, no space. element_line() controls line properties: linetype = "dashed" draws dashes instead of solid lines, and colour = "grey85" makes them subtle. element_rect() styles rectangles like the panel border.
Exercise 3: Customize Axis and Title Text
Dataset: diamonds (random sample of 500 rows)
Task: Create a bar chart counting diamonds by cut. Then customize the text:
- Rotate x-axis labels 45 degrees (with
hjust = 1for alignment) - Set the plot title to 16pt bold
- Make axis titles 12pt and bold
- Apply
theme_minimal()as the base
Click to reveal solution
Explanation: axis.text.x controls only x-axis tick labels. angle = 45 rotates them, and hjust = 1 right-aligns so they don't overlap the ticks. plot.title and axis.title each accept element_text() — face = "bold" makes text bold, size sets the point size.
hjust = 1, the right edge of each label sits directly above its tick.How Do You Control Colours with Scales? (Exercises 4–6)
Scales control the mapping between data values and visual properties. These exercises focus on colour and axis scales — the two you'll use most often when customizing real charts.
Exercise 4: Apply a ColorBrewer Palette
Dataset: iris
Task: Create a scatter plot of Sepal.Length (x) vs Petal.Length (y), coloured by Species. Apply scale_colour_brewer(palette = "Dark2"). Set the legend title to "Iris Species" and apply theme_minimal().
Click to reveal solution
Explanation: scale_colour_brewer() picks a palette from the ColorBrewer project, designed by cartographers for visual clarity. "Dark2" is a qualitative palette — distinct, saturated colours for categorical data. The colour argument inside labs() renames the legend title.
Exercise 5: Assign Custom Colours with scale_fill_manual
Dataset: mtcars
Task: Compute mean mpg by cyl using aggregate(). Create a bar chart (geom_col) of mean MPG per cylinder group with fill = factor(cyl). Assign these exact colours:
- 4 cylinders →
"#2C7BB6"(blue) - 6 cylinders →
"#ABD9E9"(light blue) - 8 cylinders →
"#D7191C"(red)
Apply theme_minimal() and remove the legend (it's redundant with the x-axis).
Click to reveal solution
Explanation: scale_fill_manual() accepts a named vector — each name matches a level of the fill variable, each value is a colour. Using named values ensures the right colour always maps to the right group, even if the data order changes. legend.position = "none" hides the legend when it's redundant.
Exercise 6: Format Axis Labels and Set Limits
Dataset: diamonds (random sample of 1000 rows)
Task: Create a scatter plot of carat (x) vs price (y). Then:
- Format the y-axis as dollars with commas using
scale_y_continuous(labels = scales::dollar) - Set the x-axis range from 0 to 3 using
scale_x_continuous(limits = ...) - Apply
theme_light()
Click to reveal solution
Explanation: scales::dollar is a label formatter from the scales package — it adds a $ prefix and comma separators. limits = c(0, 3) restricts the x-axis range and drops points outside that window. Any scales:: formatter (percent, comma, scientific) works the same way inside labels.
coord_cartesian(xlim = c(0, 3)) instead.How Do You Customize Legends, Backgrounds, and Strips? (Exercises 7–8)
Legends, panel backgrounds, and facet strips are all theme elements controlled by element_rect() and element_text(). These two exercises practise the elements most people struggle with — positioning and styling the decorations around the data.
Exercise 7: Move and Style the Legend
Dataset: mpg
Task: Create a scatter plot of displ (x) vs hwy (y), coloured by drv (drive type). Then:
- Move the legend to the bottom of the plot
- Give the legend a light grey background (
"grey95") with a thin grey border - Set legend text to 11pt
- Apply
theme_minimal()
Click to reveal solution
Explanation: legend.position = "bottom" moves the legend below the plot area. legend.background takes element_rect() — fill sets the background colour, colour the border, and linewidth the border thickness. legend.text controls the category labels inside the legend.
Exercise 8: Style Facet Strip Panels
Dataset: mpg
Task: Create a scatter plot of displ (x) vs hwy (y) faceted by drv. Customize:
- Set strip background to navy (
"#2C3E50") - Set strip text to white, bold, 11pt
- Set panel background to white
- Keep
theme_minimal()as the base
Click to reveal solution
Explanation: strip.background controls the coloured bar above each facet panel. strip.text styles the text inside that bar. Setting both fill and colour of the strip to the same navy prevents a contrasting border from showing. panel.background fills each panel's plotting area.
element_rect() draws a thin black border. If your strip fill is dark, that border is invisible. But on lighter fills, a mismatched border looks messy. Setting colour = fill_colour gives a clean edge.Can You Build a Reusable Custom Theme? (Exercises 9–10)
Real-world reporting means applying the same visual style to dozens of charts. Writing a custom theme function saves you from copy-pasting theme() calls across every plot. These final exercises combine everything above into a reusable workflow.
Exercise 9: Build a theme_report() Function
Task: Write a function called theme_report() that returns a ggplot2 theme with these settings:
- Base:
theme_minimal(base_size = 13) - Plot title: 16pt, bold
- Plot subtitle: 12pt, italic, grey (
"grey40") - Axis titles: 12pt, bold
- Plot caption: 9pt, grey (
"grey50") - Major grid: light grey (
"grey90"), dashed - Minor grid: removed
- Legend position: bottom
Test it by applying theme_report() to a scatter of mtcars wt vs mpg.
Click to reveal solution
Explanation: A custom theme function wraps theme_minimal() and layers your theme() tweaks on top. Because it's a function, you call theme_report() on any plot — no copy-pasting. The + operator inside the function merges the base theme with your overrides.
theme_report(), every chart in your project gets consistent styling with one line of code. Update the function once, and every plot updates automatically.Exercise 10: Publication-Ready Plot — Theme + Scale Combined
Dataset: airquality (built-in, May through September temperatures)
Task: Build a complete, publication-ready line chart:
- Compute mean
TempperMonthusingaggregate() - Create a line chart with points:
geom_line() + geom_point(), colour byfactor(Month) - Apply
theme_report()from Exercise 9 - Use
scale_colour_brewer(palette = "YlOrRd")(yellow-orange-red for temperature) - Label the x-axis with month names using
scale_x_continuous(breaks = 5:9, labels = month.abb[5:9]) - Add title "Average Temperature by Month", subtitle "New York, May–September 1973", and caption "Source: airquality dataset"
- Remove the legend — colour is redundant with the x-axis labels
Click to reveal solution
Explanation: The line connects months in grey (a neutral connector), while the points carry the colour encoding. scale_colour_brewer("YlOrRd") is a sequential palette — light yellow for cooler months, deep red for the hottest. month.abb[5:9] converts numeric months to abbreviations. Removing the legend avoids redundancy since the x-axis already shows month names.
Complete Example
Here's a full pipeline combining themes, scales, and facets into one polished chart. This uses concepts from every exercise above.
This chart reveals a counterintuitive pattern: lower-quality cuts can have higher average prices because cut quality and carat weight are negatively correlated. The customization layers — theme, scales, facets, strip styling — work together to make this insight immediately visible.
Summary
| Exercise | Concept | Key Functions |
|---|---|---|
| 1 | Apply built-in theme | theme_light(), labs() |
| 2 | Remove and restyle grid lines | element_blank(), element_line(linetype) |
| 3 | Customize text elements | element_text(angle, size, face, hjust) |
| 4 | ColorBrewer categorical palette | scale_colour_brewer(palette) |
| 5 | Custom colours with named vector | scale_fill_manual(values) |
| 6 | Axis formatting and limits | scale_y_continuous(labels), scale_x_continuous(limits) |
| 7 | Legend position and background | legend.position, legend.background |
| 8 | Facet strip styling | strip.background, strip.text |
| 9 | Reusable custom theme function | theme_minimal() + theme(...) in a function |
| 10 | Publication-ready combined plot | Theme + scale + labs + legend removal |
The customization pattern in every exercise is the same: start with a complete theme, then layer theme() tweaks and scale_*() functions on top. Once that pattern clicks, you can style any ggplot2 chart.
References
- Wickham, H. — ggplot2: Elegant Graphics for Data Analysis, 3rd Edition. Chapter 17: Themes. Link
- ggplot2 documentation — theme() reference. Link
- ggplot2 documentation — Scales reference. Link
- ggplot2 documentation — Complete themes. Link
- ColorBrewer 2.0 — Colour palettes for cartography and data visualization. Link
- Scherer, C. — A ggplot2 Tutorial for Beautiful Plotting in R (2019). Link
Continue Learning
- ggplot2 Themes — Deep dive into the theme() system, all 8 built-in themes, the element inheritance tree, and building custom house styles from scratch.
- ggplot2 Scales — Full reference for every scale function: position, colour, fill, size, alpha, plus breaks, labels, limits, and transformations.
- Publication-Ready Figures — Export polished charts for papers and presentations with the right DPI, dimensions, and file format.