Themes in ggplot2 allow you to control the overall look
of your plots. They can change the background color, gridlines, fonts,
and many other elements. Using themes can help make your visualizations
more readable and aesthetically pleasing. By default,
ggplot2 uses the theme_grey(), but there are
many other built-in themes you can use.
We’re going to work in the ggplot2 way: first, we set up a plot, then we add a different theme as a layer on top of the plot.
## Set up plot (monochrome base)
p.gap <- ggplot(data = gapminder %>% filter(year==1997), mapping = aes(x = gdpPercap, y=lifeExp)) +
geom_point(alpha = 0.6) +
scale_y_continuous(breaks=seq(20, 100, by = 10)) +
geom_smooth(method = "lm", formula = "y ~ x", color = "black") +
scale_x_log10(labels = scales::dollar_format(accuracy = 1)) +
labs(x = "GDP Per Capita", y = "Life Expectancy in Years",
title = "Economic Growth and Life Expectancy (1997)",
subtitle = "Data points are country-years",
caption = "Source: Gapminder.")
Default theme. You can see the classic ggplot2 default look that many/most use. It’s not bad, but we can do better.
p.gap + theme_gray()
In what ways does it fall short? Perhaps the grey background isn’t always the most visually appealing, or the gridlines might be too prominent for some tastes. Maybe you prefer different fonts or want to remove the box around the plot. These are the kinds of elements themes can help you control!
ggplot2 comes with several built-in themes that you can
use to quickly change the appearance of your plots. Some commonly used
themes include:
theme_bw(): A classic black-and-white theme. Note
that the plot elements are black and white; color is still possible for
geoms. I frequently use this.
theme_minimal(): A minimalistic theme with no
background annotations.
theme_classic(): A theme with a white background and
black gridlines.
Let’s try a black and white theme with theme_bw() and
theme_minimal on our plot.
p.gap + theme_bw()
p.gap + theme_minimal()
p.gap + theme_classic()
The ggthemes package provides a variety of additional
themes that you can use to further customize your plots. These themes
are inspired by various sources such as Tufte’s books, The Economist,
and Excel.
The FiveThirtyEight theme is similar to that used by the FiveThirtyEight website. It’s a good theme for data journalism.
p.gap + theme_fivethirtyeight()
And one with the Economist theme.
p.gap + theme_economist() + scale_x_log10(labels = scales::dollar_format(accuracy = 1))
## Scale for x is already present.
## Adding another scale for x, which will replace the existing scale.
Here, we emulate that other statistics package.
p.gap + theme_stata()
The hrbrthemes (https://github.com/hrbrmstr/hrbrthemes)
package offers modern and clean themes that are particularly good for
publication and presentations. These themes are designed to be visually
appealing and can help make your plots look more professional.
I used theme_ipsum in a recently published paper.
## hrbr theme examples
# Displaying multiple hrbrthemes in one chunk
p.gap + theme_ipsum()
p.gap + theme_ipsum_rc()
p.gap + theme_modern_rc()
theme()While pre-built themes are great starting points, you often want to
adjust individual parts of your plot’s appearance. The
theme() function is your command center for this, allowing
you to control almost every non-data element of your plot. Think of it
as giving specific instructions to different parts of the plot like the
title, axis labels, or legend.
Here’s how you can make some common adjustments using
p.gap as our base. We’ll focus on a few key changes to keep
it simple:
p.gap +
theme_minimal() +
labs(title = "Customized Plot Title (1997)", # We can still set labs
x = "GDP Per Capita (log scale)",
y = "Life Expectancy (Years)") +
theme(
# Modify plot title: make it larger and centered
plot.title = element_text(size = 18, hjust = 0.5),
# Modify axis titles: change their size
axis.title.x = element_text(size = 14),
axis.title.y = element_text(size = 14),
# Modify axis text (the numbers/labels on the axes): make y-axis text smaller
axis.text.y = element_text(size = 9)
# Note: legend.position was removed as p.gap doesn't have a legend here.
)
Each argument inside
theme() targets a specific plot
element. You use functions like element_text() for text
components (to control font size, face, color, alignment etc.). For
example, hjust = 0.5 centers text.
ggtextSometimes you want more than just plain text in your titles or
labels. The ggtext package extends ggplot2 to
allow you to use Markdown (and some HTML) for richer text formatting.
This means you can easily add bold, italics,
or even colors directly within your
text strings.
First, make sure ggtext is loaded (it’s in the setup
chunk).
Let’s define p.gap_color which will have a legend,
making it a good candidate to show off ggtext on legend
titles too.
# Define p.gap_color to include color aesthetic mapped to continent
p.gap_color <- ggplot(data = gapminder %>% filter(year==1997),
mapping = aes(x = gdpPercap, y=lifeExp, color = continent)) +
geom_point(alpha = 0.8, size = 2.5) +
scale_y_continuous(breaks=seq(20, 100, by = 10)) +
geom_smooth(method = "lm", formula = "y ~ x", se = FALSE, color = "gray20", linetype = "dashed", linewidth = 0.7) +
scale_x_log10(labels = scales::dollar_format(accuracy = 1)) +
labs(x = "GDP Per Capita", y = "Life Expectancy in Years",
title = "Economic Growth and Life Expectancy (1997)",
subtitle = "Data points are countries, now colored by continent",
caption = "Source: Gapminder.",
color = "Continent") # Legend title for color
Now, let’s use p.gap_color to see ggtext in
action with a simple example:
p.gap_color +
theme_minimal(base_size = 11) +
labs(
title = "Economic Growth and **Life Expectancy** (<span style='color:darkorange;'>1997</span>)",
x = "GDP Per Capita (*log scale*)",
color = "**Continent** Group" # Legend title with Markdown
) +
theme(
plot.title = element_markdown(size = 16), # Apply Markdown rendering to title
axis.title.x = element_markdown(), # And X-axis title
legend.title = element_markdown() # And legend title
)
To make
ggtext work, you generally need to: 1. Write your
text using Markdown (e.g., **bold**,
*italics*) or simple HTML tags (e.g.,
<span style='color:blue;'>blue text</span>). 2.
Tell ggplot2 to render that text element as Markdown/HTML
by using element_markdown() (from ggtext)
within the theme() function for the corresponding text
element (like plot.title, axis.title.x,
etc.).
This allows for more expressive and visually appealing text in your plots!
The ggsci package provides scientific journal and sci-fi
themed color palettes and themes, which can be very useful for creating
visually appealing and publication-ready figures. These color palettes
are inspired by the color schemes of various scientific journals,
science fiction movies, TV shows, and even the Solarized color palette.
They are designed to be visually striking and effective for
distinguishing between different data groups.
The ggsci package provides several functions to apply
its palettes to your ggplot2 visualizations. These
functions include scale_color_* and
scale_fill_* for different types of palettes. Here are some
examples of how to use these palettes.
The D3 palette provides a set of colors inspired by the D3.js library, which is widely used for data visualization on the web.
# Applying D3 palette
p.gap <- ggplot(data = gapminder %>% filter(year == 2007), mapping = aes(x = gdpPercap, y = lifeExp, color = continent)) +
geom_point(alpha = 0.8) +
scale_y_continuous(breaks = seq(20, 100, by = 10)) +
scale_x_log10(labels = scales::dollar) +
labs(x = "GDP Per Capita", y = "Life Expectancy in Years",
title = "Gapminder dataset (2007) with D3 palette")
p.gap + scale_color_d3()
Creating a custom theme allows you to define a consistent style for
your plots that can be reused across different visualizations. This is
particularly useful for maintaining a cohesive (“House Style”) look in
papers or presentations. You can start with a base theme (like
theme_minimal()) and then modify specific elements to suit
your preferences.
# Define our custom theme by starting with a base theme and adding our tweaks.
# We can wrap this in a function for easy use, though saving it as an object also works.
theme_boris_special <- function(base_size = 12) {
theme_minimal(base_size = base_size) +
theme(
# Customize plot title and subtitle
plot.title = element_text(face = "bold", size = rel(1.2), hjust = 0.5),
plot.subtitle = element_text(size = rel(1.0), hjust = 0.5, color = "gray40"),
# Customize axis titles and text
axis.title = element_text(face = "bold"),
axis.text = element_text(color = "gray50"),
# Customize legend
legend.position = "bottom",
legend.title = element_text(face = "bold"),
# Customize panel and grid lines
panel.grid.major = element_line(color = "grey90", linewidth = 0.5),
panel.grid.minor = element_blank() # No minor grid lines
)
}
# Now, let's apply our new custom theme to the plot with colored continents.
# Remember the `p.gap_color` object we created earlier.
p.gap_color
p.gap_color +
theme_boris_special() + # Apply our theme with one simple command!
labs(
title = "Plot Styled with a Custom Reusable Theme",
subtitle = "This style can be applied to any ggplot object"
)
Beyond themes that change the overall plot appearance, you often need
to control the specific colors used for your data points, lines, and
areas. This is crucial for clarity, aesthetics, and accessibility (e.g.,
ensuring colorblind-friendly palettes). The RColorBrewer
package is an excellent resource for this, offering a curated set of
color palettes designed by Cynthia Brewer, a cartographer and color
expert.
RColorBrewer palettes are colorblind-safe.RColorBrewer palettes are divided into three types:
seq): For ordered
data where values range from low to high (e.g., shades of blue from
light to dark). Best for continuous variables or ordered categorical
variables.div): For data
with a meaningful midpoint, where values diverge in two directions
(e.g., temperature differences from a mean, with reds for hot and blues
for cold).qual): For
nominal or categorical data where there’s no inherent order, and the
goal is to distinguish discrete categories (e.g., different countries,
types of products). Our continent variable is a good
candidate for this.You can explore all available palettes visually with
display.brewer.all().
display.brewer.all()
To use RColorBrewer palettes in ggplot2,
you can use the scale_color_brewer() (for point/line
colors) and scale_fill_brewer() (for area fills) functions.
You specify the desired palette using the palette
argument.
Let’s apply a qualitative palette to our plot, coloring points by
continent. We will use the p.gap_color object
defined earlier (it maps color to
continent).
p.gap_color + theme_minimal() + labs(title="Plot with Continent Colors (1997, Default Palette)")
Now that our points are colored by continent, we can explore
different ways to customize these colors using
p.gap_color.
Let’s apply a qualitative palette. The “Set2” palette is a good choice for distinct categories and is colorblind-friendly.
p.gap_color +
scale_color_brewer(palette = "Set2") +
theme_minimal() +
labs(title = "Using RColorBrewer 'Set2' Palette (1997)",
subtitle = "A colorblind-friendly qualitative palette")
The “Accent” palette is another good qualitative option.
p.gap_color +
scale_color_brewer(palette = "Accent") +
theme_bw() +
labs(title = "Using RColorBrewer 'Accent' Palette (1997)")
If you have more categories than a specific palette supports,
ggplot2 might try to interpolate, or it might recycle
colors, which is usually not ideal. It’s best to choose a palette
designed for the number of categories you have. You can check the
maximum number of colors for each qualitative palette using
brewer.pal.info. For example, “Set2” has 8 colors, “Accent”
has 8. “Paired” has 12, making it useful if you have more
categories.
brewer.pal.info %>% dplyr::filter(category == "qual")
## maxcolors category colorblind
## Accent 8 qual FALSE
## Dark2 8 qual TRUE
## Paired 12 qual TRUE
## Pastel1 9 qual FALSE
## Pastel2 8 qual FALSE
## Set1 9 qual FALSE
## Set2 8 qual TRUE
## Set3 12 qual FALSE
Our continent variable has 5 categories (5). Both “Set2”
and “Accent” (and many others) will work well.
Let’s combine theme_ipsum_rc() from
hrbrthemes with a RColorBrewer palette.
p.gap_color +
theme_ipsum_rc() +
scale_color_brewer(palette = "Dark2") +
labs(title = "ipsum_rc with RColorBrewer 'Dark2' (1997)")
To illustrate scale_fill_brewer() with a sequential
palette, we need an ordered categorical variable. Let’s use the
continent_counts data and categorize the
number_of_countries into ordered groups.
# Count countries per continent in 1997
continent_counts <- gapminder %>%
filter(year == 1997) %>%
count(continent, name = "number_of_countries")
# Create ordered categories for the number of countries
continent_counts <- continent_counts %>%
mutate(num_countries_category = cut(number_of_countries,
breaks = c(0, 20, 40, Inf),
labels = c("Few (0-20)", "Some (21-40)", "Many (>40)"),
ordered_result = TRUE))
# View the new data with categories
print(continent_counts)
## # A tibble: 5 × 3
## continent number_of_countries num_countries_category
## <fct> <int> <ord>
## 1 Africa 52 Many (>40)
## 2 Americas 25 Some (21-40)
## 3 Asia 33 Some (21-40)
## 4 Europe 30 Some (21-40)
## 5 Oceania 2 Few (0-20)
ggplot(continent_counts,
aes(x = reorder(continent, -number_of_countries),
y = number_of_countries,
fill = num_countries_category)) +
geom_col(color = "black") +
#scale_fill_brewer(palette = "Blues", name = "Country Count") +
scale_fill_ipsum() +
labs(x = "Continent", y = "Number of Countries (1997)",
title = "Number of Countries per Continent (1997)",
subtitle = "Bar fill by country count category") +
theme_ipsum_rc(grid = "Y")
Note: In this example, we created discrete, ordered categories
from a continuous variable to demonstrate
scale_fill_brewer(). This function is designed for such
categorical data, applying sequential (or diverging/qualitative)
palettes appropriately.
p.gap_color + theme_ipsum_rc() + scale_color_ipsum()
p.gap_color + theme_ipsum_rc() + scale_color_ft()
Or fill.
ggplot(continent_counts,
aes(x = reorder(continent, -number_of_countries),
y = number_of_countries,
fill = num_countries_category)) +
geom_col(color = "black") +
scale_fill_ipsum(name = "Country Count") +
labs(x = "Continent", y = "Number of Countries (1997)",
title = "Number of Countries per Continent (1997)",
subtitle = "Bar fill by country count category") +
theme_ipsum_rc(grid = "Y")
Sometimes, you might want to specify colors manually, perhaps to
match specific branding, because RColorBrewer doesn’t have
exactly what you need, or you simply want full control. You can do this
using scale_color_manual() or
scale_fill_manual().
You can use named R colors (e.g., "red",
"skyblue"; type colors() in your R console for
a full list) or hexadecimal color codes (e.g., "#FF0000"
for red). For this lesson, we’ll stick to R’s named colors as they are
often easier to remember and use.
# First, let's see the order of continents to assign colors correctly.
# This helps ensure your manual colors map to the intended continents.
# levels(factor(gapminder$continent)) # "Africa" "Americas" "Asia" "Europe" "Oceania"
# Define a custom vector of R named colors.
# It's good practice to name the vector elements if you want to ensure specific colors go to specific categories.
my_custom_colors <- c("Africa" = "firebrick",
"Americas" = "steelblue",
"Asia" = "forestgreen",
"Europe" = "darkorchid",
"Oceania" = "darkorange")
p.gap_color +
scale_color_manual(values = my_custom_colors) +
theme_ipsum_rc() +
labs(title = "Manually Specified Colors for Continents (1997)",
subtitle = "Using scale_color_manual() with R named colors")
This approach gives you precise control over your plot’s color scheme.