Skip to main content

Section 1.8 Plotting data with ggplot2

Now that we have this sf object, how can we map it? We mentioned before about the graphical package ggplot2. We can use this, and its syntax, in order to map spatial data using the geom_sf() geometry.
First, a quick refresher on ggplot2 and the grammar of graphics. The grammar of graphics upon which this package is based defines various components of a graphic. Some of the most important are:
  • The data: For using ggplot2 the data has to be stored as a data-frame or tibble (sf objects are of class tibble).
  • The geoms: They describe the objects that represent the data (e.g., points, lines, polygons, etc.). This is what gets drawn. And you can have various different types layered over each other in the same visualisation.
  • The aesthetics: They describe the visual characteristics that represent data (e.g., position, size, colour, shape, transparency).
  • Facets: They describe how data is split into subsets and displayed as multiple small graphs.
  • Stats: They describe statistical transformations that typically summarise data.
Let’s take it one step at time. Essentially, the philosophy behind this is that all graphics are made up of layers. You can build every graph from the same few components: a dataset, a set of geoms visual marks that represent data points, and a coordinate system. Take this example from our crimes dataframe. You have a table such as:
library(dplyr)

df <- crimes %>%
  filter(crime_type %in% c("Robbery", "Shoplifting", "Theft from the person")) %>%
  group_by(crime_type) %>%
  count()

knitr::kable(df, caption = 'Example data table')
crime_type n
Robbery 463
Shoplifting 1479
Theft from the person 718
You then want to plot this. To do so, you want to create a plot that combines the following layers:
p1 <- ggplot(df, aes(x = crime_type, y = n)) +
  geom_bar(stat = "identity") +
  theme_void()

p2 <- ggplot(df, aes(x = gsub(" from the person", "",crime_type), y = n)) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 15, hjust = 1)) +
  xlab("Crime type") +
  ylab("Number of crimes")

p3 <- ggplot(df, aes(x = crime_type, y = n)) +
  theme_void() +
  labs(title = "Freq. of crime types") +
  theme(panel.border = element_rect(colour = "black", fill=NA, size=1))

gridExtra::grid.arrange(p1, p2, p3, nrow = 1)
Three panes showing different components of a plot. The first pane shows the column geometry, the second pane shows the axes, values along them, and their labels "Crime type" versus "Number of Crimes", and the third pane shows the title "Frequency of crime types", and a border.
Figure 1.8.1. Layers of ggplot
This will result in a final plot:
ggplot(df, aes(x = gsub(" from the person", "",crime_type), y = n)) +
  geom_bar(stat = "identity") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 15, hjust = 1),
        panel.border = element_rect(colour = "black", fill=NA, size=1)) +
  labs(title = "Frequency of crime types") +
  xlab("Crime type") +
  ylab("Number of crimes")
The completed bar plot "Frequency of crime types", combining the previous three layers into a single image.
Figure 1.8.2. Final plot
Taking our crime data as an example, we would build up our plot as follows. First let’s create a small illustrative dataset of only the crimes of "Robbery", "Shoplifting", and "Theft from the person".
library(dplyr)

df <- crimes %>%
  filter(crime_type %in% c("Robbery","Shoplifting", "Theft from the person")) %>%
  group_by(crime_type) %>%
  count()

df
## # A tibble: 3 × 2
## # Groups:   crime_type [3]
##   crime_type                n
##   <chr>                 <int>
## 1 Robbery                 463
## 2 Shoplifting            1479
## 3 Theft from the person   718
Now let us add the layers to our ggplot object. First, the data layer:
library(ggplot2)

ggplot(df, aes(x = crime_type, y = n))
A grid background, with horizontal axis "crime_type" with notches denoted "Robbery", "Shoplifting", and "Theft from the person", and vertical axis "n", ranging from five hundred to fifteen hundred.
Figure 1.8.3. Data layer
Then add the geometry (in this case, geom_col()):
ggplot(df, aes(x = crime_type, y = n)) +
  geom_col()
The previous grid, now containing rectangular bars denoting the values corresponding to the crime types. The height of the bars corresponds to just under five hundred for "Robbery", fifteen hundred for "Shoplifting", and seven hundred and fifty for "Theft from the person".
Figure 1.8.4. Geometry layer
Then our annotations:
ggplot(df, aes(x = crime_type, y = n)) +
  geom_col() +
  labs(title = "Frequency of crime types") +
  xlab("Crime type") +
  ylab("Number of crimes")
The previous plot, now with the title "Frequency of crime types", and the axes labeled as "Crime type" and "Number of crimes".
Figure 1.8.5. Annotations layer
We can also specify our themes, by using a custom theme such as theme_minimal(), and by adding our own specifications within an additional theme() function:
ggplot(df, aes(x = crime_type, y = n)) +
  geom_col() +
  labs(title = "Frequency of crime types") +
  xlab("Crime type") +
  ylab("Number of crimes") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 15, hjust = 1),
        panel.border = element_rect(colour = "black", fill=NA, size=1))
The previous plot, with the background shading altered, and a black border.
Figure 1.8.6. Customise themes
We can add further specifications within the aes() (aesthetics) function, where we add additional layers from our data, to represent even more information in our charts, for example the outcomes for each crime.
# create new dataframe (df) including last_outcome_category variable

df <- crimes %>%
  filter(crime_type %in% c("Robbery", "Shoplifting", "Theft from the person")
         ) %>%
  group_by(crime_type, last_outcome_category) %>%
  count()

# plot including new variable with fill= parameter in aes() function

ggplot(df, aes(x = crime_type, y = n, fill = last_outcome_category)) +
  geom_col() +
  labs(title = "Frequency of crime types") +
  xlab("Crime type") +
  ylab("Number of crimes") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 15, hjust = 1),
        panel.border = element_rect(colour = "black", fill=NA,  size=1))
The previous graph has bars now divided into colours vertically. The right side contains a legend for the colours, the most prominent of which are "Investigation complete; no suspect identified", "Status update unavailable", and "Unable to prosecute suspect".
Figure 1.8.7. Additional layers
We can be explicit about the colours we want to use with the function scale_fill_brewer(), which we can also use to rename our legend. Don’t worry too much at this point about where the palette comes from; in the chapter 5 on cartography we will discuss colour in more detail.
ggplot(df, aes(x = crime_type,  y = n, fill = last_outcome_category)) +
  geom_col() +
  labs(title = "Frequency of crime types") +
  xlab("Crime type") +
  ylab("Number of crimes") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 15, hjust = 1),
        panel.border = element_rect(colour = "black", fill=NA, size=1)) +
  scale_fill_brewer(type = "qual", palette = 3, name = "Outcome")
The previous graph with colours now has more easily distinguishable colours, and the legend has the title "Outcome".
Figure 1.8.8. Customise colour
There are many more options. While this here is not the greatest graph you’ll ever see, it illustrates the process of building up your graphics the ggplot way. Do read up on ggplot2 for example in [216]. In later chapters, we will talk more about visualisation, colour choice, and more!