Today, we’ll cover two topics to help you prepare your plots for publication. Specifically, we’ll have figures for scientific publications in mind, and will cover:
Formatting plots – theme presets and customizing plot appearance with ggplot’s theme()
Multi-panel layouts – with the patchwork package
Saving plots to file – with ggplot’s ggsave()
1.2 Setup
Loading packages
Like in the previous ggplot2 sessions, we’ll load two packages: the tidyverse, which includes ggplot2, and palmerpenguins, which contains the penguins dataset we’ve been working with:
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.2.0 ✔ readr 2.2.0
✔ forcats 1.0.1 ✔ stringr 1.6.0
✔ ggplot2 4.0.2 ✔ tibble 3.3.1
✔ lubridate 1.9.5 ✔ tidyr 1.3.2
✔ purrr 1.2.1
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(palmerpenguins)
Attaching package: 'palmerpenguins'
The following objects are masked from 'package:datasets':
penguins, penguins_raw
We’ll need an additional packages later on, but we’ll install and load that one along the way.
A base plot
We’ll use a scatterplot of the penguins dataset as our running example throughout – assigning the plot to object p, so we can keep layering onto it without retyping everything:
p <- penguins |>drop_na() |># Get rid of rows with NAsggplot(aes(x = flipper_length_mm, y = body_mass_g, fill = species)) +geom_point(shape =21, size =2) +scale_fill_brewer(palette ="Dark2") +scale_y_continuous(labels = scales::label_comma()) +labs(x ="Flipper length (mm)", y ="Body mass (g)")p
A couple of notes about the plot above:
I did not give the plot a title, because these are not typically present in figures in scientific papers
I used shape = 21 to get points with a fill color and black border, which I like for scatterplots
A trick we didn’t covered in the last few weeks is scales::label_comma() to add thousand-separator commas for readability. Very similar scales:: functions are available for formatting axes in other ways, e.g. label_percent() for percentages, label_dollar() for prices, etc.
2 Formatting plots
The default ggplot2 look is fine for quick exploration, but can be improved on for publication. For example, we likely don’t want a gray background and may need larger text. There are two main tools for formatting your plots:
Theme presets — functions like theme_bw() that swap out the whole look at once
theme() — fine-grained control over individual elements
2.1 Theme preset options
ggplot2 ships with several built-in theme functions. Here are some of the most useful alternatives to the default (which is called theme_gray()):
Function
Description
theme_bw()
White background, grey gridlines, black border
theme_classic()
White background, no gridlines, axis lines only
theme_minimal()
White background, minimal gridlines, no border
theme_void()
Completely empty — good for maps or diagrams
Let’s compare a few:
p +theme_bw()
p +theme_classic()
p +theme_minimal()
2.2 Scaling all text sizes with base_size()
All theme preset functions accept a base_size argument that scales the size of text and points in the plot – the larger the value, the larger the text (the default value is 11):
p +theme_classic(base_size =10)
p +theme_classic(base_size =15)
Note that this doesn’t mean all text will have the same size: instead, it is a scaling factor. So, axis titles will still remain similarly larger than axis text at tick labels, regardless of the base_size().
2.3 Customizing with theme()
Preset themes get you most of the way there, but theme() lets you adjust individual elements. You layer it on top of a preset:
p +theme_classic() +theme(...)
If you use both a theme_ preset and theme(), the theme() call must go after the preset, otherwise the preset will override your customizations.
A first example of a theme() call – to make the legend title bold:
theme(legend.title =element_text(face ="bold"))
The theme() function has dozens of arguments to adjust elements such as legend.title, axis.text, panel.background, and so on. What makes its usage perhaps a bit overwhelming at first is that you will also need to know the type of element you want to adjust, which is specified with element_*() functions:
Element type
What it controls
element_text()
Applies to text and adjusts font size, face, color, angle, alignment
element_line()
Applies to lines and adjusts Line color, size, linetype
element_rect()
Applies to rectangles (panels, legend box, etc.) and adjusts their fill and border
element_blank()
This is used to removes an element entirely
Below, we’ll see some examples of several of these.
Text size and style
You may want to emphasize the axis titles and legend title by making them bold:
p +theme_classic(base_size =13) +theme(axis.title =element_text(size =14, face ="bold.italic"),legend.title =element_text(face ="bold") )
Text options
face accepts "plain", "bold", "italic", or "bold.italic"
axis.title controls both axes; use axis.title.x or axis.title.y to target one
Legend position
By default, the legend is to the right of the plot. That’s fine in many cases, but if you for example have a wide plot, you may want to place it at the top:
p +theme_classic(base_size =13) +theme(legend.position ="top")
Want to place the legend inside the plot? (Click to expand)
You can also place the legend inside the plot by passing coordinates as a fraction of the plot area (0 = left/bottom, 1 = right/top):
I like theme_bw() because of the box it draws around the plot, but think the gridlines are a bit excessive and distracting. You can remove gridlines selectively rather than switching themes wholesale:
p +theme_bw() +theme(panel.grid.minor =element_blank(),panel.grid.major.x =element_blank(),panel.grid.major.y =element_line(linetype ="longdash", linewidth =0.5) )
Above:
We turned off all “minor” gridlines (which are in between axis tickmarks),
Turned off “major” gridlines (which are at the tickmarks) for the x-axis
For the sake of showing element_line() in action, changed the formatting major gridlines on the y-axis
Setting a default theme for the whole session
If you make a bunch of figures with a single script, and want a consistent look across all of them, it can be tedious to add the same theme_*() call to every plot.
Instead, at the top of the script, you can set a default with theme_set():
theme_set(theme_classic(base_size =13))
After this, every new plot will use theme_classic() automatically. You can further customize with theme_update() to set specific elements globally:
Let’s see this in action: make sure you have run the code above, and our next few plots should have the look defined above.
Exercise: Format according to your preferences
You may have your own ideas about how you’d like to format your plots, and that is fine! There is usually room for a personal touch, even in scientific publications.
Play around with scatterplot p, adding a theme_ preset and several theme() options, to create a plot styled the way you like.
If you are wondering how to change elements in ways we haven’t covered, feel free to ask us or the internet/genAI.
3 Multi-panel figures
The patchwork package allows you to combine multiple plots into a single multi-panel figure. This is something you might be used to doing with programs like Powerpoint or Illustrator. However, if you’re already making all the individual plots that should make up a figure in R, it is beneficial to also use R to combine them.
That way, you can easily rerun your code to recreate plots with some modifications. Otherwise, after any change, you’d have to put the plots back together in another program.
First, let’s install and load the package:
install.packages("patchwork")
library(patchwork)
Patchwork assumes that you have created and saved the individual plots as separate R objects. Let’s start by making three plots with the penguins dataset that we’ll combine later on:
To tell patchwork how to arrange these plots, the syntax is based on common mathematical operators. For example:
plot1 | plot2 puts two plots side-by-side
plot1 / plot2 stacks two plots vertically
(plot1 | plot2) / plot3) puts plot1 and plot2 on the top row, and plot3 on the bottom row
For example, to combine the three plots we just made, with the first (faceted) plot on top, and the other two side-by-side below it:
p_scatter / (p_bar | p_box)
Patchwork has quite a lot more functionality, and this is well-explained in various vignettes/tutorials on its website.
Here, we’ll just try one more feature, adding tags for the individual plots — where we tell patchwork about the type of numbering we would like (e.g. A-B-C vs. 1-2-3) by specifying the first character: