ggplot2 Cheat Sheet: Quick Reference for Geoms, Scales, Themes and More
This cheat sheet is your one-page reference for ggplot2 — covering the layer grammar, every major geom, aesthetic mappings, scale functions, facets, and theme customization, each with a minimal working example you can run and modify.
Introduction
ggplot2 follows a layered grammar: start with data and a coordinate system, add geometric layers (geoms), control how data maps to visual properties (aesthetics and scales), and polish with themes. Every chart in ggplot2 is built by composing these layers using +.
The basic template is always:
Each section below shows the key functions in that category with quick examples. All code shares a single session — mtcars and mpg are always available.
What Are the Essential Geoms in ggplot2?
A geom defines the visual mark drawn for each data point. Add multiple geoms to the same chart by layering them with +.
| Geom | Use For | Key Arguments |
|---|---|---|
geom_point() |
Scatter plots | size, shape, alpha |
geom_line() |
Line charts (sorted by x) | linewidth, linetype |
geom_bar() |
Count frequency from raw data | fill, position |
geom_col() |
Bar heights from pre-computed values | fill, width |
geom_histogram() |
Distribution of one continuous variable | binwidth, bins, fill |
geom_density() |
Smooth density curve | adjust, fill, alpha |
geom_boxplot() |
Median, quartiles, outliers | fill, notch |
geom_violin() |
Full distribution shape per group | fill, draw_quantiles |
geom_smooth() |
Trend / regression line | method, se |
geom_text() |
Text labels at data positions | label, hjust, vjust |
geom_hline() |
Horizontal reference line | yintercept, linetype |
geom_vline() |
Vertical reference line | xintercept, linetype |
geom_abline() |
Line by slope and intercept | slope, intercept |
geom_area() |
Filled area under a line | fill, alpha |
geom_tile() |
Heatmap tiles | fill |
geom_bin2d() |
2D density bins | bins, fill |
geom_jitter() |
Scatter with random noise (reduces overlap) | width, height |
geom_errorbar() |
Error bars | ymin, ymax, width |
geom_ribbon() |
Shaded band between y bounds | ymin, ymax, fill |
geom_segment() |
Arbitrary line segments | x, xend, y, yend |
TIP: Stack multiple geoms to build richer charts. A common pattern:
geom_boxplot() + geom_jitter()shows both the summary statistics and the raw data distribution simultaneously.
Try it: Replace geom_boxplot() with geom_violin() in p_box. How does the shape of the distribution change compared to the boxplot?
How Do Aesthetics and aes() Work?
Aesthetics map data columns to visual properties. Mappings inside aes() are data-driven. Values set outside aes() are fixed for all data points.
| Aesthetic | Controls | Variable Types | Example |
|---|---|---|---|
x, y |
Position | Continuous, discrete, date | aes(x = date, y = value) |
color |
Point/line/text color | Continuous → gradient; Discrete → palette | aes(color = species) |
fill |
Interior fill color | Continuous → gradient; Discrete → palette | aes(fill = category) |
size |
Point size, line thickness | Continuous numeric | aes(size = population) |
shape |
Point shape (16 = circle, 17 = triangle…) | Discrete (≤ 6 levels) | aes(shape = group) |
alpha |
Transparency (0 = invisible, 1 = opaque) | Continuous numeric | aes(alpha = confidence) |
linetype |
Line style | Discrete | aes(linetype = type) |
label |
Text content | Character | aes(label = name) |
group |
Grouping without visual change | Discrete | aes(group = id) |
KEY INSIGHT:
color = "blue"(outsideaes()) sets all points to blue.aes(color = drv)maps thedrvcolumn to color — one color per unique value. Putting a column name outsideaes()(e.g.,color = drv) is a common error that maps the literal string, not the column.
Try it: Move size = cyl from inside aes() to outside as size = 3. How does the chart change?
How Do You Customize Scales for Axes and Colors?
Scale functions control how data values map to visual properties — axis range, breaks, labels, color palettes, size ranges. Every aesthetic has a corresponding scale_*() family.
Axis scales:
| Function | Controls | Key Args |
|---|---|---|
scale_x_continuous() |
Continuous x-axis | limits, breaks, labels, trans |
scale_y_continuous() |
Continuous y-axis | limits, breaks, labels, expand |
scale_x_discrete() |
Categorical x-axis | limits (reorder), labels |
scale_x_date() |
Date x-axis | date_breaks, date_labels |
scale_x_log10() |
Log10 x-axis | — |
Color/fill scales:
| Function | For | When to Use |
|---|---|---|
scale_color_brewer() |
Discrete color | ColorBrewer qualitative/sequential |
scale_fill_brewer() |
Discrete fill | Same, for bar/tile interiors |
scale_color_distiller() |
Continuous color | ColorBrewer interpolated |
scale_fill_distiller() |
Continuous fill | Same, for fill |
scale_color_viridis_c() |
Continuous color | Perceptually uniform, colorblind-safe |
scale_color_viridis_d() |
Discrete color | Same but for categories |
scale_color_manual() |
Discrete color | Specify exact hex/named colors |
scale_color_gradient() |
Continuous, 2-color | Simple low-to-high gradient |
scale_color_gradient2() |
Continuous, diverging | Low-mid-high with midpoint |
TIP:
labels = scales::commaformats numeric axis labels with thousand separators.labels = scales::percentadds a % sign.labels = scales::dollaradds a $ prefix. Load thescalespackage once and use these as shorthand throughout your project.
Try it: Add scale_x_log10() to any scatter plot. How does a log-transformed axis change the appearance of the data distribution?
How Do You Use Facets to Create Multi-Panel Charts?
Facets split your data by a variable and draw one panel per group — all with the same axes and geoms, making comparison effortless.
| Function | What it Does | Key Args |
|---|---|---|
facet_wrap(~ var) |
Wrap panels into rows and columns automatically | nrow, ncol, scales, labeller |
facet_wrap(~ var1 + var2) |
Facet by two variables, wrapped | Same |
facet_grid(rows ~ cols) |
Grid layout: one variable per axis | scales, space, labeller |
facet_grid(. ~ cols) |
Facet columns only | — |
facet_grid(rows ~ .) |
Facet rows only | — |
The scales argument controls axis behavior across panels:
scales = |
Behavior |
|---|---|
"fixed" (default) |
All panels share the same x and y range |
"free_x" |
Each panel has its own x-axis range |
"free_y" |
Each panel has its own y-axis range |
"free" |
Each panel has independent x and y ranges |
KEY INSIGHT:
scales = "free_y"is essential when comparing metrics with very different units or magnitudes (e.g., count vs. percentage). Without it, a metric with small values appears as a flat line at the bottom of the panel.
Try it: Add scales = "free" to facet_wrap() in p_facet. How does this change the axes across the three panels?
How Do You Apply Themes and Polish Your Chart?
Themes control all non-data visual elements: background, grid lines, axis text, legend position, font sizes, and more.
Built-in themes:
| Theme | Style |
|---|---|
theme_gray() |
Default — grey background, white grid |
theme_bw() |
White background, black grid frame |
theme_minimal() |
White background, light grey grid, no border |
theme_classic() |
White background, no grid, clean axes |
theme_void() |
No axes, no background — for maps and diagrams |
theme_dark() |
Dark background — good for viridis color scales |
theme_light() |
Light grey lines, white background |
**Customizing with theme():**
**Key theme() element functions:**
| Function | Controls |
|---|---|
element_text(face, size, color, angle, hjust, vjust) |
Text elements |
element_line(color, linewidth, linetype) |
Line elements |
element_rect(fill, color, linewidth) |
Rectangle/border elements |
element_blank() |
Remove an element entirely |
**Common theme() targets:**
| Target | What It Affects |
|---|---|
plot.title |
Chart title |
plot.subtitle |
Subtitle text |
plot.caption |
Caption at bottom-right |
axis.title.x / y |
Axis label text |
axis.text.x / y |
Axis tick labels |
panel.grid.major / minor |
Grid lines |
panel.background |
Plot area background |
legend.position |
"top", "bottom", "left", "right", "none" |
strip.text |
Facet panel labels |
TIP: Set a theme globally for all charts in a session with
theme_set(theme_minimal()). Every subsequentggplot()call inherits that theme without repeating it. You can still override individual elements with+ theme(...)on specific charts.
Try it: Replace theme_minimal() with theme_classic(). How does the chart's overall feel change?
Common Mistakes and How to Fix Them
Mistake 1: Mapping a variable name outside aes()
❌ geom_point(color = drv) passes the string "drv" as color, coloring all points one color:
✅ Move data mappings inside aes():
Mistake 2: Using geom_bar() with pre-computed data
❌ geom_bar() counts rows. Adding y = value causes an error unless you add stat = "identity".
✅ Use geom_col() when you have pre-computed heights.
Mistake 3: Scales applied after geoms that ignore them
❌ scale_x_log10() applied to a histogram changes the axis labels but not the bin boundaries — the histogram was already computed on the original scale.
✅ Log-transform the variable before plotting, or use scale_x_continuous(trans = "log10") which correctly affects stat_bin().
Mistake 4: Forgetting to load ggplot2 or a required extension
❌ patchwork::wrap_plots() fails silently or with a confusing error if patchwork isn't loaded.
✅ library(patchwork) before use. Extensions like ggrepel, patchwork, and ggridges must be explicitly loaded even when ggplot2 is already loaded.
Mistake 5: theme() overriding theme_*() — order matters
❌ Placing theme_minimal() after theme() resets all the custom theme() changes:
✅ Always put the base theme function before your theme() customizations:
Practice Exercises
Exercise 1: Build a chart from scratch
Using only this cheat sheet as a reference, create a scatter plot of mpg's cty (x) vs hwy (y) with:
- Points colored by
classusingscale_color_brewer(palette = "Set2") - A regression line per class (
geom_smooth(method="lm", se=FALSE)) - Facets by
drvusingfacet_wrap() theme_minimal()+ a bold title
Exercise 2: Recreate a specific chart style
Build a horizontal bar chart of average hwy per manufacturer (top 10 only) that:
- Sorts bars by descending average mpg (
fct_reorder+coord_flip()) - Uses
scale_fill_viridis_d()for fill color - Adds value labels with
geom_text(hjust = -0.2) - Removes grid lines with
theme(panel.grid.major.y = element_blank())
Complete Example
This end-to-end example assembles all five sections — geoms, aesthetics, scales, facets, and themes — into a single publication-quality chart:
Summary
ggplot2 grammar in one line: ggplot(data, aes()) + geom_*() + scale_*() + facet_*() + theme_*()
Quick decision guide:
| Want to… | Use… |
|---|---|
| Plot points | geom_point() |
| Connect points over time | geom_line() |
| Show frequency counts | geom_bar() or geom_histogram() |
| Show pre-computed values | geom_col() |
| Show distribution | geom_boxplot() or geom_violin() |
| Add trend line | geom_smooth(method="lm") |
| Add labels | geom_text() or ggrepel::geom_label_repel() |
| Color by category | aes(color=var) + scale_color_brewer() |
| Color continuous | aes(color=var) + scale_color_viridis_c() |
| Custom colors | scale_color_manual(values=c(...)) |
| Split into panels | facet_wrap(~var) |
| Clean white background | theme_minimal() or theme_classic() |
| Remove an element | theme(element = element_blank()) |
| Move legend | theme(legend.position = "top") |
FAQ
What is the difference between color and fill in ggplot2?
color controls the outline or stroke color — of points, lines, and text. fill controls the interior fill of shapes — bars, polygons, and filled circles (shape 21+). For geom_point() with solid shapes (the default shape 16), only color applies. For geom_col() and geom_histogram(), only fill applies to the bar interior.
How do I save a ggplot2 chart to a file?
Use ggsave(): ggsave("chart.png", plot = p_final, width = 8, height = 5, dpi = 300). ggsave() saves the most recently printed plot if you omit the plot argument. Supported formats: PNG, PDF, SVG, JPEG, TIFF.
How do I combine multiple ggplot2 charts into one figure?
Use the patchwork package: p1 + p2 places charts side-by-side; p1 / p2 stacks them vertically; (p1 | p2) / p3 creates a 2-over-1 layout. Annotate the combined figure with plot_annotation(title = "...").
How do I add a second y-axis in ggplot2?
ggplot2 discourages dual y-axes for good design reasons (they are easily misleading). The supported approach uses scale_y_continuous(sec.axis = sec_axis(~ . / 100, name = "Percent")). The secondary axis must be a mathematical transformation of the primary — you cannot independently scale two different variables.
How do I reorder a categorical axis?
Convert the variable to a factor with levels in the desired order before plotting, or use fct_reorder() from the forcats package inside aes(): aes(x = fct_reorder(category, value)). For frequency ordering, use fct_infreq(category).
References
- ggplot2 official documentation. https://ggplot2.tidyverse.org/reference/
- Wickham, H. (2016). ggplot2: Elegant Graphics for Data Analysis. Springer. https://ggplot2-book.org/
- Wickham, H. & Grolemund, G. (2017). R for Data Science, Chapter 3. https://r4ds.had.co.nz/data-visualisation.html
- RStudio/Posit ggplot2 cheat sheet (PDF). https://rstudio.github.io/cheatsheets/html/data-visualization.html
- Wilke, C. O. (2019). Fundamentals of Data Visualization. O'Reilly. https://clauswilke.com/dataviz/
What's Next?
- ggplot2 Getting Started — the full introductory tutorial covering the grammar of graphics from scratch with step-by-step examples.
- ggplot2 Scatter Plots — deep dive into
geom_point()with color, size, and shape mappings and overplotting solutions. - ggplot2 Themes in R — comprehensive coverage of
theme()customization to produce publication-quality charts.