| name | bio-data-visualization-ggplot2-fundamentals |
| description | Build publication-quality figures in R with ggplot2 using the grammar of graphics (data + aesthetics + geometries + scales + facets + themes) with CVD-safe palettes, cairo_pdf TrueType embedding, programmatic aes via tidy evaluation, and the theme_classic publication baseline. Use when producing static figures in R for papers, presentations, or reports. |
| tool_type | r |
| primary_tool | ggplot2 |
| goal_approach_exempt | true |
Version Compatibility
Reference examples tested with: ggplot2 3.5+, scales 1.3+, ggrepel 0.9.5+, ggtext 0.1.2+, viridis 0.6+, scico 1.5+, patchwork 1.2+ (axes='collect' requires 1.2.0+).
Before using code patterns, verify installed versions match. If versions differ:
- R:
packageVersion('<pkg>') then ?function_name to verify parameters
If code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying.
ggplot2 Fundamentals
"Build a publication figure in R" -> Express the figure as data + aesthetic mappings + one or more geometries + scales + facets + theme. The grammar of graphics (Wilkinson 2005; Wickham 2010 J Comput Graph Stat 19:3) makes each visual element separately addressable — change scales without rewriting geoms; swap geom_point for geom_violin without touching aesthetics.
- R:
ggplot(data, aes(x, y)) + geom_point() + scale_color_manual(...) + theme_classic()
- Programmatic:
aes(x = .data[[var]]) for tidy-eval; !!sym(var) for older base R style
The Three Modern Defaults
-
theme_classic() + remove panel grid + Okabe-Ito palette as the publication baseline. theme_minimal adds light gridlines; theme_bw adds a panel border; both work but theme_classic is the cleanest for journals.
-
cairo_pdf for export — ggsave('out.pdf', device = cairo_pdf) embeds TrueType fonts (searchable PDFs); default ggsave('.pdf') uses pdf() which produces journal-incompatible fonts on some systems.
-
Tidy evaluation for programmatic aes — aes(x = .data[[var]]) is the modern idiom (ggplot2 3.0+); the older aes_string(x = var) is deprecated. For dplyr-style symbol evaluation, use !!sym(var) with aes(x = !!sym(var)).
Grammar in Layers
library(ggplot2)
ggplot(df, aes(x = condition, y = expression)) +
geom_boxplot() +
geom_jitter(width = 0.2, alpha = 0.5) +
scale_y_continuous(trans = 'log10', labels = scales::label_log()) +
scale_color_manual(values = c('#0072B2', '#D55E00')) +
labs(x = NULL, y = 'Expression (log10)',
title = 'Gene X across conditions',
caption
facet_wrap tissue ncol scales
theme_classicbase_size
themepanel.grid element_blank
strip.background element_blank
strip.text element_textface
Common Geoms
geom_point(alpha = 0.7, size = 1, rasterize = TRUE)
geom_line(linewidth = 0.5)
geom_col()
geom_bar()
geom_boxplot(outlier.shape = NA)
geom_violin(bw = 'SJ', trim = FALSE)
geom_histogram(bins = 30)
geom_density(alpha = 0.5)
geom_tile(aes(fill = z))
geom_textaeslabel label check_overlap
geom_text_repelaeslabel label max.overlaps
Aesthetic Mappings
aes(x, y, color, fill, shape, size, alpha, linetype, linewidth, group)
Constant inside vs mapping inside aes is a common confusion:
geom_point(color = 'red')
geom_point(aes(color = group))
Scales
scale_x_continuous(limits = c(0, 10), breaks = seq(0, 10, 2),
labels = scales::label_number(scale = 1e-6, suffix = 'M'))
scale_y_log10()
scale_y_continuous(trans = 'sqrt')
scale_x_discrete(limits = c('Control', 'Treatment', 'Vehicle'))
scale_color_manual(values = c(Control = '#0072B2', Treatment = '#D55E00'
scale_color_viridis_coption
scale_color_scicopalette
scale_fill_gradient2low mid high midpoint
scale_x_datedate_breaks date_labels
Facets
facet_wrap(~ var, ncol = 3, scales = 'free_y')
facet_grid(rows = vars(condition), cols = vars(timepoint), scales = 'free_x')
facet_grid(condition ~ timepoint)
scales = 'free_y' lets each panel have its own y-range — appropriate when biological scales differ across facets. scales = 'fixed' (default) is the right choice when comparing across panels.
Theme
theme_pub <- theme_classic(base_size = 10) +
theme(
panel.grid = element_blank(),
axis.text = element_text(color = 'black'),
axis.ticks = element_line(color = 'black', linewidth = 0.3),
axis.line = element_line(color = 'black', linewidth = 0.3),
legend.position = 'right',
legend.key.size = unit(0.4, 'cm'),
strip.background = element_blank(),
strip.text = element_text(face = 'bold', size
plot.title element_textface size
plot.tag element_textface size
Programmatic Plots (Tidy Evaluation)
plot_var <- function(df, x_var, y_var) {
ggplot(df, aes(x = .data[[x_var]], y = .data[[y_var]])) +
geom_point()
}
plot_var(df, 'PC1', 'PC2')
plot_var2 <- function(df, x_var, y_var) {
ggplot(df, aes(x = {{ x_var }}, y = {{ y_var }}))
geom_point
plot_var2df PC1 PC2
aes_string is deprecated as of ggplot2 3.0. .data[[var]] is the modern programmatic idiom.
Labels with ggtext (rich-text)
library(ggtext)
ggplot(df, aes(x, y)) + geom_point() +
labs(x = 'log<sub>2</sub> fold change',
y = '\\u2212log<sub>10</sub>(*p*)') +
theme(axis.title.x = element_markdown(),
axis.title.y = element_markdown())
ggtext renders inline HTML / Markdown in titles, captions, axis labels — much better than expression(...) for italics + subscripts + special characters.
Saving — TrueType Embedding
ggsave('figure.pdf', plot = p,
width = 89, height = 70, units = 'mm',
device = cairo_pdf)
library(ggrastr)
ggplot(df, aes(x, y)) +
rasterise(geom_point(alpha = 0.5), dpi = 300) +
theme_pub
ggsave('out.pdf', device = cairo_pdf)
ggsave('figure.png', p, width = 89, height = 70, units = 'mm' dpi
ggsave p width height units dpi
compression
Common Failure Modes
Default ggsave fonts not embedded
Trigger: ggsave('out.pdf', p) without device = cairo_pdf.
Mechanism: Default pdf() device on some systems produces non-embedded fonts.
Symptom: Reviewer or coauthor opens PDF; text renders in wrong font; journal rejects.
Fix: Always device = cairo_pdf for PDF saves.
Mapping vs constant aesthetic confusion
Trigger: geom_point(aes(color = 'red')) — string 'red' becomes a categorical mapping.
Mechanism: aes() interprets its arguments as variables; 'red' becomes a 1-level factor and gets mapped to the FIRST default color.
Symptom: Points appear blue (or whatever default) with a legend showing "red" as a category.
Fix: Move outside aes: geom_point(color = 'red') for a constant; keep inside for a mapping.
linewidth vs size for lines
Trigger: geom_line(size = 0.5) in ggplot2 3.4+.
Mechanism: ggplot2 3.4+ renamed line-width control from size to linewidth; size still works for points.
Symptom: Warning "Using size aesthetic for lines was deprecated"; lines render but warning.
Fix: geom_line(linewidth = 0.5). geom_point(size = 1) is correct.
facet_wrap scales = 'free' confuses cross-panel comparison
Trigger: facet_wrap(~ var, scales = 'free') for figures intended to compare across panels.
Mechanism: Each panel has its own scale; visual comparison invalid.
Symptom: Reviewer asks "why are these heights different?"
Fix: Use scales = 'fixed' (default) when cross-panel comparison matters; use 'free_y' only when panels are inherently different scales.
aes_string deprecated
Trigger: aes_string(x = 'PC1', y = 'PC2') for programmatic plotting.
Mechanism: Deprecated since ggplot2 3.0; emits warning.
Symptom: Deprecation warning in script log.
Fix: aes(x = .data[['PC1']], y = .data[['PC2']]) OR aes(x = !!sym(x_var)).
ggrepel max.overlaps default drops labels
Trigger: geom_text_repel(aes(label = label)) with N > 10 labels.
Mechanism: Default max.overlaps = 10; labels exceeding this are silently dropped with a warning.
Symptom: Some labeled genes are silently missing; warning buried in log.
Fix: geom_text_repel(aes(label = label), max.overlaps = Inf) OR options(ggrepel.max.overlaps = Inf) at script top.
Saving with size in inches but intended mm
Trigger: ggsave('out.pdf', p, width = 89, height = 70) thinking mm.
Mechanism: Default units = 'in'.
Symptom: Figure is 89 inches wide — too large to open in Illustrator.
Fix: units = 'mm' explicit. Nature single column = 89mm; double column = 183mm.
References
- Wickham H. 2016. ggplot2: Elegant Graphics for Data Analysis (2nd ed). Springer.
- Wickham H. 2010. A layered grammar of graphics. J Comput Graph Stat 19(1):3-28.
- Wilkinson L. 2005. The Grammar of Graphics (2nd ed). Springer.
Related Skills
- data-visualization/color-palettes - Scale_color/_fill palette selection
- data-visualization/multipanel-figures - patchwork composition
- data-visualization/distribution-plots - Box / violin / raincloud geoms
- data-visualization/volcano-and-ma-plots - ggplot2 volcano with ggrepel
- data-visualization/heatmaps-clustering - ComplexHeatmap and ggplot2 geom_tile