Facets, Scales, and Themes in ggplot2
Facets allow you to split data across multiple panels, scales give you precise control over how data maps to visual properties, and themes let you customize the overall appearance of your plots. Together, these three components transform basic ggplot2 visualizations into polished, publication-ready graphics.
This tutorial builds on the ggplot2 fundamentals covered in earlier tutorials. We’ll use the mpg dataset throughout to demonstrate how these features work in practice.
What you’ll learn
This tutorial covers the key concepts and practical techniques for working with Facets, Scales, and Themes in ggplot2. By the end, you will know how to apply the core functions in real data analysis workflows.
Facets: creating multi-Panel plots
Facets are essential when you want to compare subgroups across the same visual encoding. ggplot2 provides two main facet functions: facet_wrap() and facet_grid().
Using facet_wrap()
facet_wrap() arranges panels in a grid based on a single categorical variable or a combination of variables. It’s ideal when you have one dimension of variation:
library(ggplot2)
# Create a basic scatter plot
p <- ggplot(mpg, aes(displ, hwy)) +
geom_point() +
labs(x = "Engine displacement (L)", y = "Highway MPG")
# Facet by a single variable
p + facet_wrap(~ class, nrow = 2)
This creates separate panels for each vehicle class. The nrow and ncol arguments control the layout. You can also use dir = "v" for vertical arrangement.
Using facet_grid()
facet_grid() creates a two-dimensional grid based on two variables, making it perfect for comparing across two categorical dimensions:
# Facet by two variables: drv (drive type) and cyl (cylinder count)
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
facet_grid(drv ~ cyl) +
labs(x = "Engine displacement (L)", y = "Highway MPG")
The syntax drv ~ cyl reads as “rows ~ columns”. This generates a grid where each cell represents a unique combination of drive type and cylinder count.
Controlling facet labels and scales
By default, facets share the same scales across panels. You can modify this behavior with the scales argument:
"fixed"(default): all panels share the same scale"free": scales adjust independently for each panel"free_x": x-axis scales vary; y-axis is shared"free_y": y-axis scales vary; x-axis is shared
# Allow scales to vary freely across panels
p + facet_wrap(~ class, scales = "free")
You can also control whether facet labels appear inside or outside the panels using the margins argument or by specifying strip.position.
Scales: precise control over data mapping
Scales control how data values map to visual properties. Every aesthetic in ggplot2 has an associated scale, and customizing these gives you fine-grained control over your visualizations.
Axis scales
Axis scale functions follow the pattern scale_<aesthetic>_<type>(). For continuous axes:
# Customize x-axis with specific limits and breaks
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
scale_x_continuous(
name = "Engine Size (liters)",
limits = c(1, 8),
breaks = seq(1, 8, by = 1),
expand = expansion(mult = 0.05)
) +
scale_y_continuous(
name = "Highway MPG",
limits = c(10, 50),
breaks = c(15, 25, 35, 45)
)
The expand argument controls the padding around the data limits, ensuring points don’t sit exactly on the axis line.
Color scales
Color aesthetics support both continuous and discrete mappings. For discrete (categorical) variables:
# Use a custom color palette for manufacturer
ggplot(mpg, aes(displ, hwy, color = manufacturer)) +
geom_point() +
scale_color_brewer(palette = "Dark2")
The scale_color_brewer() function provides access to ColorBrewer palettes, which are designed for optimal color perception. For custom colors, use scale_color_manual():
# Define specific colors for each manufacturer
ggplot(mpg, aes(displ, hwy, color = manufacturer)) +
geom_point() +
scale_color_manual(
values = c("audi" = "#E41A1C", "ford" = "#377EB8",
"chevrolet" = "#4DAF4A", "honda" = "#984EA3")
)
For continuous color gradients, use scale_color_gradient() for a two-color gradient or scale_color_viridis() for perceptually uniform colors:
# Continuous color scale for highway mpg
ggplot(mpg, aes(displ, hwy, color = hwy)) +
geom_point(size = 3) +
scale_color_viridis_c(option = "plasma")
Legend control
Scales also control legend appearance. The name argument overrides the default legend title, while labels customizes legend text:
ggplot(mpg, aes(displ, hwy, color = class)) +
geom_point() +
scale_color_manual(
name = "Vehicle Class",
labels = c("2seater" = "Two-seater", "compact" = "Compact",
"midsize" = "Mid-size", "minivan" = "Minivan",
"pickup" = "Pickup", "subcompact" = "Subcompact",
"suv" = "SUV"),
values = c("#E41A1C", "#377EB8", "#4DAF4A", "#984EA3",
"#FF7F00", "#FFFF33", "#A65628")
)
Themes: customizing plot appearance
Themes control the non-data aspects of your plot: backgrounds, grid lines, text formatting, and more. ggplot2 includes several built-in themes and allows complete customization.
Built-in themes
The quickest way to change your plot’s appearance is using a complete theme:
# Various built-in themes
p <- ggplot(mpg, aes(displ, hwy, color = class)) + geom_point()
p + theme_bw() # Black and white theme
p + theme_minimal() # Minimal theme (no background)
p + theme_dark() # Dark background
p + theme_classic() # Classic theme (no grid lines)
p + theme_void() # Empty theme (only data elements)
Each theme provides a different visual style. theme_minimal() and theme_bw() are popular choices for publications.
Customizing theme elements
For precise control, use the theme() function with element_*() functions:
ggplot(mpg, aes(displ, hwy, color = class)) +
geom_point(size = 2) +
theme(
# Panel appearance
panel.background = element_rect(fill = "white", color = "gray80"),
panel.grid.major = element_line(color = "gray90"),
panel.grid.minor = element_blank(),
# Axis text and title
axis.title = element_text(size = 12, face = "bold"),
axis.text = element_text(color = "gray40"),
# Legend
legend.position = "bottom",
legend.title = element_text(face = "bold"),
legend.key = element_rect(fill = NA),
# Plot title
plot.title = element_text(size = 14, hjust = 0.5)
) +
labs(title = "Engine Size vs Highway Fuel Efficiency")
Key element functions include:
element_rect(): rectangles (panels, legends)element_line(): lines (grid, axes)element_text(): text (labels, titles)element_blank(): removes an element entirely
Saving theme customizations
If you frequently use the same theme settings, save them as an object:
# Create a custom theme
my_theme <- theme(
panel.background = element_rect(fill = "#f8f9fa"),
panel.grid.major = element_line(color = "#dee2e6"),
axis.title = element_text(size = 11),
legend.position = "bottom",
legend.background = element_rect(fill = NA)
)
# Apply to any plot
p + my_theme
You can also set a theme globally with theme_set(), affecting all subsequent plots in your session.
Bringing it all together
Let’s create a publication-ready visualization combining facets, scales, and themes:
# Comprehensive example
ggplot(mpg, aes(displ, hwy, color = factor(cyl))) +
geom_point(size = 2, alpha = 0.7) +
geom_smooth(method = "lm", se = FALSE) +
# Facet by drive type
facet_wrap(~ drv, nrow = 1, labeller = labeller(
drv = c("4" = "4-Wheel Drive",
"f" = "Front-Wheel Drive",
"r" = "Rear-Wheel Drive")
)) +
# Custom color scale
scale_color_brewer(name = "Cylinders", palette = "Dark2") +
# Apply custom theme
theme_minimal() +
theme(
panel.grid = element_line(color = "gray90"),
legend.position = "bottom",
plot.title = element_text(size = 14, face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5)
) +
labs(
title = "Engine Displacement vs Highway MPG",
subtitle = "By Drive Type and Cylinder Count",
x = "Engine Displacement (L)",
y = "Highway Miles per Gallon"
)
This example demonstrates how facets create comparison panels, scales customize the color mapping and legend, and themes provide consistent visual styling.
Facets in depth
facet_wrap(~ var, nrow = 2) wraps panels into rows. ncol controls columns instead. facet_grid(row_var ~ col_var) creates a matrix of panels, rows vary by row_var, columns by col_var. Use . in facet_grid to facet by only one variable while keeping the other as a single axis: facet_grid(. ~ col_var).
Scales per facet
By default, all facets share the same axis scales. scales = "free" gives each panel its own range. scales = "free_x" frees only the x-axis. This is useful when variables have very different ranges, but it makes comparisons between facets harder, choose fixed scales when cross-facet comparison is important.
Theme components
Every theme element in ggplot2 falls into four categories: element_text() for text, element_line() for lines, element_rect() for rectangles, and element_blank() for removing an element. Apply theme changes with theme(component = element_type(...)). Base themes (theme_minimal(), theme_bw(), theme_classic()) set sensible defaults; layer theme() calls on top for specific adjustments.
Extending themes
Save a custom theme as an object: my_theme <- theme_minimal() + theme(text = element_text(family = "Arial")). Apply it to all plots in a session: theme_set(my_theme). The ggthemes package provides publication-ready themes: theme_fivethirtyeight(), theme_economist(), theme_wsj(). The hrbrthemes package adds typography-focused themes with Inter and Roboto Condensed fonts.
Faceting for comparison
Facets divide a plot into panels, one per group value, with the same scales and coordinate system. This enables direct visual comparison across groups. facet_wrap(~ category) creates panels arranged in a grid. facet_grid(row_var ~ col_var) creates a strict matrix with row variable on the y-axis and column variable on the x-axis.
Free scales let each panel optimize its axis range. facet_wrap(~ var, scales = "free_y") frees only the y-axis, useful when different groups have very different value ranges but should be compared on the same x-axis (e.g., time series at different magnitudes). scales = "free" frees both axes, appropriate when panels show independent variables.
nrow and ncol in facet_wrap() control the layout: facet_wrap(~ category, nrow = 2) creates two rows of panels. strip.position = "left" moves the panel label strip to the left side, useful for comparing left-to-right.
Customizing facet labels
labeller = labeller(category = c(a = "Group A", b = "Group B")) renames panel labels. as_labeller(function(x) paste("Category:", x)) applies a transformation function. For combining variable name and value: labeller = label_both produces “category: A” style labels.
strip.text = element_text(size = 12, face = "bold") in theme() styles all strip labels. strip.background = element_rect(fill = "#e0e0e0", color = NA) styles the strip background.
ggplot2 theme hierarchy
ggplot2 themes apply in layers: complete themes (like theme_minimal()) set all elements, then theme() modifies specific elements on top. Understanding the inheritance prevents surprises when a theme setting does not take effect.
theme_update() sets theme defaults for the R session, all subsequent plots use the modified theme without specifying it per plot. theme_set(theme_minimal(base_size = 11)) applies a base theme globally.
For consistent ggplot2 output across a project, put theme_set() calls in a setup script or R Markdown setup chunk. This ensures all plots use the same style without repeating theme code.
element_blank() removes any theme element. element_line(), element_rect(), element_text() each take properties. rel(1.2) sets size relative to the base size — element_text(size = rel(1.2)) makes text 20% larger than the base size.
Facet scales and space
By default, all facets share the same axis scales. When variables have very different ranges across facets — one panel shows values in the hundreds while another shows values in the millions — free scales improve readability. Setting scales = "free_y" allows each facet’s y-axis to scale independently. This makes patterns within each facet visible but makes direct comparison of values across facets harder. Choose between fixed and free scales based on whether you want to emphasize within-facet patterns or cross-facet comparisons.
The space = "free" argument scales the size of each panel proportional to its data range. A panel covering a larger range gets more space. This is useful for categorical axes where some panels have more categories than others — each category gets equal space rather than being compressed to fit a fixed panel size.
Summary
Facets, scales, and themes are essential tools for creating professional ggplot2 visualizations:
- Facets (
facet_wrap(),facet_grid()) split data into multiple panels for comparison - Scales control how data maps to visual properties, including axis customization and color schemes
- Themes customize non-data plot elements for consistent, publication-ready styling
Master these three components, and you’ll be able to create sophisticated, polished visualizations that communicate your data effectively.
Next steps
Now that you understand facets, scales, and themes in ggplot2, explore these related topics to deepen your knowledge and apply these techniques in more complex scenarios.