Getting started with theme()

[This article was first published on The Jumping Rivers Blog, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

The theme() function in {ggplot2} is awesome. Although it’s only one function, it gives you so much control over your final plot. theme() allows us to generate a consistent, in-house style for our graphics, modify the text within our plots and more. Getting comfortable with theme() will really take your {ggplot2} skills up a notch.

Normally, when people want help with an R function I tell them to use the built-in documentation about the function. This is normally done by typing ?function_name into the console. It’s usually pretty informative and often enough to help people understand a new function.

So let’s try this with theme()


You probably feel a bit like this now:

The same scatter plot as above (theme minimal, legend at bottom, grey axes) but grid lines have been removed, so that the points on the scatter plot are on a plain, white background.

If you’re coding along with me you’ll be able to see there’s loads of arguments to theme(). If you’re patient and like counting, you’ll find that there are ninety-nine arguments to the theme() function.

Do you need to know all of these arguments? No.

Do I know all of these arguments? Also no.

What to we want to achieve?

By the end of this blog post, you are going to:

  • become familiar with a handful of theme() arguments
  • be able to understand how to modify theme elements
  • have built the confidence to try modifying aspects of a theme on your own

What I’m not aiming to do:

  • construct a the world’s most elegant {ggplot2} theme
  • show you every single thing that can be modified via theme()

Basically, I want to give you the tools to make your plots look the way you want them to.

It’s also worth mentioning that this post is peppered with personal opinion; I want you to absorb how I’ve managed to implement my stylistic choices, not take these choices as the “truth”.

Whether you want to start from scratch, or improve your skills, Jumping Rivers has a training course for you.

Building a basic plot

In order to modify a plot theme, we’re going to need a plot to start with. We’re going to work with a simple scatter plot derived from the Palmer Penguins data set. The data are freely available via the {palmerpenguins} package.

library("RColorBrewer") # pkg for nice colours
penguins = palmerpenguins::penguins
palette = brewer.pal(3, "Set2") # pick some nice colours

base_plot = penguins %>%
 drop_na() %>% # remove missing values
 ggplot(aes(x = body_mass_g, y = bill_length_mm, colour = species)) +
 geom_point() +
 scale_colour_manual(values = palette) +
 ggtitle("Do heavier penguins have longer bills?") +
 labs(colour = "Species") +
 xlab("Body mass (g)") +
 ylab("Bill length (mm)")

A scatter plot showing bill length (mm) vs body mass (g) for three penguin species which are seperated by colour; Adelie, Chinstrap and Gentoo. The plot is styled using standard basic ggplot2 theme; the plot background is grey with white gridlines. Title (Do heavier penguins have longer bills?) is left justified and the legend is to the right of the plot.

We’ve created a basic scatter plot here. It’s perhaps one you’ve seen before. Perfectly functional, but lacks personality.

Using built in {ggplot2} themes

A good starting point for modifying your plot theme is actually to side-step theme() and use one of the themes provided by {ggplot2}. The themes all have similar names: theme_*(). I personally tend to start with theme_minimal(), but feel free to try some others. For example, theme_classic() or theme_light(). The usage of these themes is super simple, just add it to your plot a bit like a geom_*():

plot = base_plot +
The same scatter plot as before but with the ggplot theme theme_minimal() applied. The background is now white and the grid lines are a light grey colour.

We’ve now got a plot which looks cleaner. There’s still some things that I don’t like. For example, I don’t like having my legend at the side of the plot and I don’t like the grid lines. We can modify these with theme().

Our first theme() modification

The first thing I like to do is move the legend to the bottom of the plot. This is where we start to use the theme() function to modify our plot appearance. This one isn’t too tricky, we just specify (as a string) where we want our legend to sit.

plot +
 legend.position = "bottom"

And here’s the result:

The same scatter plot as before but the legend has been moved from the right of the plot to below the plot.

Okay, that wasn’t too bad. The other options here would be "top", "left" or "right" to modify the position, or "none" to remove the legend entirely.

I’d like to you meet my friends: the element_*() functions

Most arguments of the theme() function don’t take simple values like character or numeric values as arguments. A large number of the arguments take in a list of a specific class, where the list elements describe what the plot looks like. We can generate this list via an element_*() function. The functions are element_blank(), element_rect(), element_line(), element_text(). Each of these functions has arguments which modify a given feature of our plot. I’ll run you through how each of the element_*() functions might be used, but remember just like any other function ?element_*() will show you more

Modifying line elements

Now our plot doesn’t have any lines on to indicate where the axes are. Suppose I want to make it clear where the axis lines are. Because these are, well, lines, I can use element_line() to include them as so:

plot +
 legend.position = "bottom",
 axis.line = element_line(colour = "grey50")

obviously we all have different favourite colours, use whatever you like. Neutral colours (probably just shades of grey) are probably best for anything professional or for publication.

The same scatter plot as above (theme minimal, legend at bottom) but grey lines to make the x and y axes clear have been added.

So here we’ve specified the colour of the line element which corresponds to axis.line. We can change other characteristics here like linewidth or linetype, but I’ll leave that for you to experiment with later.

Removing plot elements

The next thing I want to modify is the grid lines. I personally don’t like them so I’m going to remove them. This could be done with element_line() by matching the grid line colour to the background colour, but off the top of my head I don’t know what the background colour is, and there’s a much simpler solution: element_blank(). This removes aspects of our plot by generating an empty list entry for that plot component.

plot +
 legend.position = "bottom",
 axis.line = element_line(colour = "grey50"),
 panel.grid = element_blank()
The same scatter plot as above (theme minimal, legend at bottom, grey axes) but grid lines have been removed, so that the points on the scatter plot are on a plain, white background.

assigning element_blank() to panel.grid() removes all grid lines. If we only wanted to remove the minor ones, we would set panel.grid.minor = element_blank(). If we wanted to remove only the vertical lines (for whatever reason), we can set panel.grid.minor.x = element_blank() and panel.grid.major.x = element_blank(). Removing only the horizontal ones is the same, but swap x for y. This shows that although theme() might have 99 arguments, there is structure to argument names which reduces how many you really need to remember.

Modifying text features

Text features are the next thing I’m going to change. We’re going to do two things at once here:

  • change the font for all text features with the text argument
  • change the positioning and size of the plot title with the plot.title argument

we’ll adjust both of these via the element_text() function

plot +
 legend.position = "bottom",
 axis.line = element_line(colour = "grey50"),
 panel.grid = element_blank(),
 text = element_text(family = "lato"), ## modify font
 plot.title = element_text(hjust = 0.5, size = 18) ## modify positioning and size
The same scatter plot as above (theme minimal, legend at bottom, grey axes, no grid lines) but all text now uses the lato font, and the main plot title is centered and larger.

the family argument lets us specify the font: we’re using the lato font here. I won’t delve too much into fonts, but {showtext} is a package that makes using a wide variety of fonts straightforward. I’d definitely recommend playing with fonts if you’re looking to develop a theme for a corporate identity, or simply add some personality to a plot.

The hjust argument controls the horizontal justification, essentially, the positioning of the text. hjust = 0 is left justification (the text is moved to the left), hjust = 1 is right justification (text moved to the right), and numbers between 0 and 1 will position the text somewhere between the far right and far left. Setting hjust = 0.5 centers the text for us. I’ve not used vjust, but this argument adjusts the vertical justification. Note that on the y axis, hjust moves the text along the axis, and vjust moves the text closer to/further away from the y axis. The size argument is just the font size in pts, something we will all be familiar with. The result is that the text, especially the title, shines through a little more.

Borders and backgrounds

Borders and backgrounds are next on our list of things to modify. We use element_rect() (rect as in rectangle) to change the styling of things such as the background around our legend or the entire plot background. By default, the legend background will be the same as the plot background, so it isn’t actually obvious that the legend even has a background! We’re going to modify the plot background here. The plot is currently sitting on a white background, and the web page you’re viewing also has a white background. This means that the plot melts into the webpage. This isn’t necessarily a bad thing, but you might want to frame your plot a bit by putting in on a coloured background. Alternatively, you might have a corporate slide deck with a coloured background, and want the plot to melt into this background.

plot +
 legend.position = "bottom",
 axis.line = element_line(colour = "grey50"),
 panel.grid = element_blank(),
 text = element_text(family = "lato"),
 plot.title = element_text(hjust = 0.5, size = 18),
 plot.background = element_rect(fill = "#dffffc", colour = "#dffffc")

The fill argument here changes the actual plot background. The colour argument (color will also work if you’re u-averse) controls the colour of a thin border all the way around the plot. The colour #dffffc is a very pale blue, which ensures that there is sufficient contract between the points and background of the plot. This is an important accessibility feature, so do think very carefully about how changing the background colour of your plot may impact the ability for other people to properly absorb the message you are trying to communicate. I’d generally avoid changing the background colour, but it’s useful here for demonstration purposes. To reiterate: if you do change the background colour, take care to ensure that accessibility is not compromised.

The same scatter plot as above (theme minimal, legend at bottom, grey axes, no grid lines, modified text) but entire plot background has been changed to a pale blue colour.

That brings to a close our introduction to each of the element_*() functions. I know it was a bit traumatic before, but if you type ?theme into your R console, you’ll notice that the help page tells you which element_*() function you need to use for each theme() argument.

Creating space

The last function that we use to modify aspects of a theme is margin(). Margin is a little bit different to the element_*() functions, instead of controlling colours, fonts and line types, margin() lets us create or remove space around certain aspects of our theme by modify distances. If an argument needs to be modified with margin(), it’s likely that the argument name looks like something.margin. You may also notice that element_text() has a margin argument – use margin() here to create space around the text aspects of your plot.

There are 5 arguments to margin(): t, r, b, l and unit. t, r, b and l are short for top, right, bottom and left – to remember the order, it’s just clockwise from the top. You should assign a number to these arguments, then unit is simply the units of these values. unit defaults to pt, which scales well with text, but you can choose something else if that makes more sense to you.

Let’s now modify the space between (a) the plot title and the scatterplot itself and also (b) the legend and the x axis.

plot +
 legend.position = "bottom",
 axis.line = element_line(colour = "grey50"),
 panel.grid = element_blank(),
 text = element_text(family = "lato"),
 plot.title = element_text(
 hjust = 0.5, size = 18, margin = margin(b = 30) # modify title-plot spacing
 plot.background = element_rect(fill = "#dffffc", colour = "#dffffc"),
 legend.margin = margin(t = 15) # modify x axis-legend spacing
The same scatter plot as above (theme minimal, legend at bottom, grey axes, no grid lines, modified text, pale blue background) but there is now more space between the plot title and main plot, and the legend and the main plot.

Now this is our final plot! Each individual step made a minor adjustment to the plot, but added together, we have a plot with a much improved appearance.

Bringing it all together

An important programming concept is don’t repeat yourself (DRY). This applies to constructing graphics as well. We don’t want to copy and paste our theme for every plot we make. The great thing about {ggplot2} themes is that they can be effortlessly applied to basically any plot. All we have to do it turn out theme into a function, and then it can be used just like any of the “built in” {ggplot2} themes. For example:

my_theme = function(){
 theme_minimal() +
 legend.position = "bottom",
 axis.line = element_line(colour = "grey50"),
 panel.grid = element_blank(),
 text = element_text(family = "lato"),
 plot.title = element_text(
 hjust = 0.5, size = 18, margin = margin(b = 30)
 plot.background = element_rect(fill = "#dffffc", colour = "#dffffc"),
 legend.margin = margin(t = 15)

notice that the first line of the function is theme_minimal(), we used this theme as a starting point for our custom theme.

Then we can apply this to any other plot as we would apply a standard {ggplot2} theme:

my_boxplot = penguins %>%
 drop_na() %>%
 ggplot(aes(x = species, y = flipper_length_mm)) +
 geom_boxplot(fill = "#ff9300") +
 xlab("Species") +
 ylab("Flipper length (mm)") +
 ggtitle("Which type of penguin has the longest flippers?")

my_boxplot + my_theme()
A boxplot (vertical arrangement) with penguin flipper length (mm) on the y axis and species on x axis. Adelie penguins have median 190 and (LQ, UQ) = (185, 195). Chinstrap penguins have median 195 and (LQ, UQ) = (190, 200). Adelie penguins have median 215 and (LQ, UQ) = (210, 2205).

That was easy! We can just add my_theme() to all of our plots to ensure consistent styling. If we wanted to make minor adjustments to the theme for a specific plot, we can just add on the theme() command again and make the required adjustments. If you want to apply the style to all plots in a single script or report, theme_set() is a really handy way to do this.

What next?

The theme() function has a lot of arguments, and it can feel overwhelming if you’re wanting to start modifying your own theme. We’ve managed to gain a little bit of experience with the tools that modify a {ggplot2} theme, and now you should have the confidence to modify other elements on your own, and try out the different arguments in element_*() functions.

If you’re wanting to explore what makes a really good chart, I’d recommend the RSS style guide for practical, actionable advice on constructing publication-ready graphics with examples in both R and Python. Cara Thompson gave the keynote talk at our 2023 edition of Shiny in Production; her talk walked us though 10 important considerations for making text shine within a data visualisation, and her slides are packed with with ways to make your plots awesome.

If you feel like going back to basics would really help you out, booking onto one of our upcoming Data Visualisation with {ggplot2} is a great way to get to grips with a wide variety of {ggplot2} features.

For updates and revisions to this article, see the original post

To leave a comment for the author, please follow the link and comment on their blog: The Jumping Rivers Blog. offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)