In the spirit of Emily Riederer’s ugliest
ggplot ever, we’ll play around with ggplot
code in
order to learn how it works. The goal: make the ugliest plot
possible.
We’ll load in our packages below:
# general use
library(tidyverse) # general tidying and visualization: ggplot is loaded by default with tidyverse
library(lterdatasampler) # data we're using comes from this package
library(lubridate) # working with dates
library(here) # folder organization
# extras
library(patchwork) # arranging plots
library(magick) # putting images into ggplots
Note: lterdatasampler
has to be installed from the
GitHub repo using the code below (copy, paste, and run in the
console):
remotes::install_github("lter/lterdatasampler")
Today, we’ll use the Plum
Island fiddler crab data from lterdatasampler
to
visualize relationships between latitude and crab size. Read the linked
vignette to learn about Bergmann’s rule!
The Plum Island LTER has a data set of crab sizes (column
size
) from Summer 2016 at 13 different marshes spanning 12
degrees latitude. A sample is below, but try View(pie_crab)
in the console to see the whole data frame.
pie_crab %>%
slice_sample(n = 5)
## # A tibble: 5 × 9
## date latitude site size air_temp air_temp_sd water_temp water_…¹ name
## <date> <dbl> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>
## 1 2016-07-28 41.6 NB 17.0 12.2 9.48 17.5 7.86 Narr…
## 2 2016-08-12 42.2 BC 11.4 11.6 9.53 14.0 6.9 Bare…
## 3 2016-08-09 37.2 VCR 14.0 15.0 8.41 17.6 8.43 Virg…
## 4 2016-08-13 42.7 PIE 17.7 10.3 9.45 14.3 4.84 Plum…
## 5 2016-08-01 34.7 RC 17.4 18.6 8.40 20.5 7.00 Rach…
## # … with abbreviated variable name ¹water_temp_sd
Just to make things a little more interesting, I’m going to split up
the dates into years, months, and days and save that as a new data
frame, crab_data
.
crab_data <- pie_crab %>%
# extracting month from the date column using lubridate::month()
# also making this a factor (instead of numeric) using as.factor()
mutate(month = as.factor(month(date)))
ggplot
grammarggplot
works in layers. The code to make a plot can
vary, but always includes:
1. the ggplot()
call: this tells R that you want to use the
function ggplot()
in ggplot
to plot
things.
2. data
and aesthetics
within that
ggplot()
call: tells ggplot
to use a specific
data frame any variables in that data frame that should be represented
in the plot (for example, x- and y- axes, colors, shapes)
3. a geom_()
: short for “geometry”, geom_()
calls tell ggplot
what kind of plot you want to make. Try
?geom_
in the console to see the different options.
# step 1: call ggplot
ggplot(
# step 2: specify the data and the aesthetics
# plotting latitude on the x-axis and crab size on the y-axis
data = crab_data, aes(x = latitude, y = size)) +
# step 3: specify a geom - in this case, we're creating a scatter plot
geom_point()
Note that when you’re adding on layers in ggplot
, you’ll
use the +
instead of the %>%
operator. This
is because ggplot()
is the function call, but everything
else you add on is a modifier of the ggplot()
plotting
function (instead of a new function doing something different).
So we’ve just made this plot. But how can we make it worse?
ggplot()
takes aesthetics from the data frame, so I’m
going to color the points by site and make the shapes represent month.
I’m also going to make a jitter plot, which is a scatter plot with the
points “jittered”, or randomly shaken up so that it’s easier to see the
overlap (or be chaotic). I’m also going to facet the plot by month using
facet_wrap()
, which is a useful function when you’re trying
to see differences between variables in different panels (or you can use
facet_grid()
, which does essentially the same thing).
ggplot(data = crab_data, aes(x = latitude, y = size)) +
# putting the aesthetics in here: color points by site, shape points by month
geom_jitter(aes(color = site, shape = month),
# anything that doesn't have to do with variables (like point size or transparency) goes outside the aesthetics
size = 3, alpha = 0.6) +
# facet by month
facet_wrap(~ month)
Wow! This plot is getting worse to look at. But we can do better.
ggplot
defaultsYou can adjust the colors, shapes, axis limits, etc. using one of the
scale_
functions (try ?scale_
to see the many
options).
A lot of customization comes from ggplot
themes. Theme
elements deal with everything else in the plot that doesn’t
have to do with the data (e.g. the plot background, grid, etc.). There
are a lot of arguments that you can stack up in theme()
(try ?theme()
) to see all of them, but they generally fall
into a few categories: 1. axes, 2. legends, 3. panels, 4, plots, 5,
strips. There are built-in themes in ggplot
that can make
your plot look pretty good right away, but playing around with theme
elements yourself is a great way of getting the exact plot you want.
Additionally, every argument takes a function modifying the
elements: 1. lines, 2. rectangles (more broadly, shapes), 3.
text. See the help pages for these functions
(e.g. ?element_text()
) to figure out what you can alter
about each element - there’s a lot!
Annotations are useful when you want to point something out on a
plot. The function is annotate()
, which adds an annotation
layer onto your plot.
Lastly, you can adjust any text on labels using the
labs()
function.
Note: all the code in the theme below comes from Emily Riederer’s gist, with some modifications!
crab_plot <- ggplot(data = crab_data, aes(x = latitude, y = size)) +
geom_jitter(aes(color = site, shape = month), size = 3, alpha = 0.6) +
facet_wrap(~ month) +
# scaling
# changing point colors + assigning each to a site
scale_color_manual(values = c("BC" = "#687f47", "CC" = "#268e86", "CT" = "#373f1f",
"DB" = "#03010a", "GTM" = "#447c49", "JC" = "#2e2747",
"NB" = "#7f696a", "NIB" = "#777a76", "PIE" = "#a87445",
"RC" = "#aef23a", "SI" = "#e05357", "VCR" = "#087f0c",
"ZI" = "#010201")) +
# changing the point shapes
scale_shape_manual(values = c("7" = 21, "8" = 18)) +
# changing x and y axis limits and breaks
scale_x_continuous(limits = c(29, 45)) +
scale_y_continuous(n.breaks = 10) +
# theme() call
theme(
# panel: anything having to do with the main area
panel.background = element_rect(fill = '#B5C7F4', color = '#F761F9', linewidth = 5),
panel.border = element_rect(fill = NA, color = "#87Bf18", linewidth = 2),
panel.grid.major.x = element_line(color = "#FF21E1", linetype = 6),
panel.grid.minor.x = element_line(color = "#2D65BF", linetype = 4),
panel.grid.minor.y = element_blank()
) +
# another theme() call: you don't have to split things up like this
# just doing it for ease of demonstration
theme(
# plot: anything having to do with the area around the panel
plot.background = element_rect(fill = "#98FC6A"),
plot.title = element_text(size = 30, hjust = 0.25, family = "Helvetica"),
plot.subtitle = element_text(size = 20, hjust = 0.75, color = "#2A23A3", family = "Times New Roman"),
plot.caption = element_text(size = 10, angle = 25, family = "Comic Sans MS"),
plot.margin = unit(c(1, 4, 1, 3), "cm")
) +
theme(
# axes: anything having to do with the x- and y- axes
axis.title.x = element_text(face = "bold.italic", color = "#9254D3"),
axis.title.y = element_text(family = "Arial", face = "bold", size = 20, hjust = 0.25),
axis.text = element_text(face = "italic", size = 15),
# note that axis.text options from above are inherited
axis.text.x.bottom = element_text(angle = 180)
) +
theme(
# strips: anything having to do with the facet titles
strip.background = element_rect(fill = "#C9E886"),
strip.text = element_text(color = "#854EED", family = "Garamond", face = "bold")
) +
theme(
# legend: anything having to do with the legend
legend.background = element_rect(fill = "#DC86E8"),
legend.key = element_rect(fill = "#C2F774"),
legend.direction = "horizontal",
legend.position = "top",
legend.justification = "left",
legend.title = element_text(family = "serif", color = "#B452F9"),
legend.text = element_text(color = "#30F92C", family = "mono")
) +
# annotate: big gap
annotate(geom = "text", x = 32, y = 20, label = "big gap", color = "#CC107D") +
# labs: anything having to do with labels
labs(title = "Crabs!",
subtitle = "I love crabs",
x = "Latitude (low to high)",
y = "Size (small to big)",
caption = "Do you love crabs?",
col = "I love colors!")
crab_plot
The last thing we’ll do is save the plot using ggsave()
.
The function needs you to tell it what the file name is in the first
argument filename
, but I’ll show you a little trick I use
to 1) save the figure in a folder and 2) “version” the figure based on
date (i.e. the file name for the figure has the date it was made in it).
Both are extraneous, but things I like to do to keep my figures
organized.
ggsave(
# specify a file name, but here we'll specify a file path
# choose the folder named "figures"
filename = here::here("figures",
# create a file name that includes the date using lubridate::today()
# you could also use Sys.Date() - works the same way!
paste("crab_plot_ugly_", today(), ".jpg", sep = "")),
# name the plot object you're wanting to save
plot = crab_plot,
# specify the dimensions and units
width = 10, height = 5, units = "in",
# specify resolution
dpi = 300
)
There are many ways of adding images to plots. The package I’ll use
is magick
. This is a round-about way of getting an image
in, but allows you to be flexible with your image types (gifs, etc.).
I’ll just save this as an object called crab_image
to use
later.
# use magick::image_read() to read in the jpeg
crab_image <- image_read(here::here("code", "pwhittle-fiddler-crab.jpeg")) %>%
# turn this into a raster
as.raster()
There are a couple packages you can use to put plots together like
panels in a figure. The big ones are patchwork
and
cowplot
. Try each of them out and see what you like, but
we’ll be using patchwork
today.
Just to demonstrate how this works, I’m going to make up a histogram of crab sizes. The code is annotated if you’re interested in the customized aesthetics, but they’re not important for this demonstration.
# for a histogram, you don't need y in your aesthetic call
crab_hist <- ggplot(crab_data, aes(x = size)) +
# telling ggplot that you want to plot a histogram
geom_histogram(binwidth = 1, aes(fill = site)) +
# some scaling
scale_x_continuous(breaks = seq(from = 6.5, to = 24.5, by = 1)) +
scale_y_continuous(limits = c(0, 45), expand = c(0, 0)) +
scale_fill_manual(values = c("BC" = "#7D9084", "CC" = "#8B9FD9", "CT" = "#DDD5D5",
"DB" = "#CADDA5", "GTM" = "#DF697F", "JC" = "#E09E5F",
"NB" = "#7F56DB", "NIB" = "#D8DF5D", "PIE" = "#DC53D3",
"RC" = "#84D9DF", "SI" = "#D798D1", "VCR" = "#77E2A8",
"ZI" = "#76E762")) +
# some theme-ing
theme(
panel.background = element_rect(fill = '#B6E45F', color = '#DABA84', linewidth = 5),
panel.grid.minor.x = element_blank(),
panel.grid.major.y = element_line(color = "#E16B86", linetype = 2),
panel.grid.minor.y = element_line(color = "#B49CD2", linetype = 3),
legend.background = element_rect(fill = "#A0C8E8"),
plot.background = element_rect(fill = "#8ADDA8"),
plot.title = element_text(size = 20),
plot.margin = unit(c(1, 4, 1, 3), "cm")
) +
# some labeling
labs(x = "Crab sizes (small to big)",
y = "Number of crabs (few to lots)",
title = "This is a crab histogram",
fill = "Places") +
# adding the crab image
annotation_raster(crab_image, xmin = 8, xmax = 10, ymin = 30, ymax = 35) +
# annotating the plot with the photographer credit
annotate(geom = "text", x = 9, y = 29, label = "PWhittle, iNaturalist", size = 3)
crab_hist
patchwork
thinks similarly to ggplot
in
that it takes things in layers with +
signs, but also takes
&
. To be honest, I can’t remember the rules most of the
time and I have to look them up.
# this automatically recognizes that you're putting plots together (no function call!)
crab_plots_together <- crab_plot + crab_hist +
# plot_layout: anything having to do with the way plots are arranged
# widths takes a ratio: the panel ratio will be 2:1 left:right
plot_layout(widths = c(2, 1)) +
# plot_annotation: anything having to do with annotations
plot_annotation(tag_levels = "A", tag_suffix = ")") & # annotating them with panel letters
# patchwork has its own theme options!
theme(plot.tag = element_text(size = 40)) # changing the size of the annotations
# display our beautiful plot
crab_plots_together
# saving the plot
ggsave(
filename = here::here("figures",
paste("crab_plot_and_hist_", today(), ".jpg", sep = "")),
plot = crab_plots_together,
width = 18, height = 8, units = "in",
dpi = 150
)
Now to create an ugly plot of your own!