Create Exploded Donut Plots in Geographic Map Layout with ggplot2 to Visualize the Presidential Election Results

In this article, we’ll use the geofacet package developed by Ryan Hafen to visualize the voting results of the 2016 Presidential Election between Hillary Clinton and Donald Trump for U.S. states arranged in a map layout. The dataset we use is built in this package.

Major techniques used in this visualization include:


Packages and data cleanup 🌻

library(ggplot2)library(dplyr)# install.packages("geofacet")library(geofacet)

R has two related built-in named vectors, state.name which records the name of 50 states in the United States, and state.abb which records the state name abbreviations in the same order. “District of Columbia” (“DC”) however is not included. Here we add it to these two vectors.

state.abb.2  <- c(state.abb, "DC")state.name.2 <- c(state.name, "District of Columbia")

election is a dataset from the geofacet package. It records the 2016 U.S. presidential election results from each state (including DC). Here we add to this dataset a new column of state name abbreviations to annotate the donut plots later.

e <- election %>% as_tibble() %>%   mutate(state.abbreviate = state.abb.2[match(state, state.name.2)] )
head(e, n = 3)

Output:

# A tibble: 3 × 5
state candidate votes pct state.abbreviate
<chr> <fct> <int> <dbl> <chr>
1 Alabama Clinton 729547 34.4 AL
2 Alabama Trump 1318255 62.1 AL
3 Alabama Other 75570 3.56 AL

Visualization 🦩

Step 1. Create “geographically” faceted bar plots showing the voting results for each state. facet_geo from the geofacet package works similarly to facet_wrap, but positions the subplots of each state in a manner reminiscent of their actual geographical locations.

p1 <- e %>%   ggplot(aes(x = 1, y = pct, fill = candidate)) +  geom_col(alpha = 0.8, color = "white") +  # create subplots reflecting the geographical locations of each state  facet_geo(~ state, grid = "us_state_grid1")  
p1

In facet_geo, the grid option us_state_grid2 puts Alaska at top left, and Hawaii bottom left, better mimicking the geographical position, but creates more white space. In comparison, us_state_grid1 (as used in this work) puts both states at the bottom left corner, and is better at utilizing the available plotting space. Grids for many other geographical regions in the world are also available, and can be printed out by get_grid_names().

Step 2. Convert the bars into pies by applying the polar coordinate. Also use theme_void to create a blank canvas.

p2 <- p1+   coord_polar(theta = "y") +  theme_void() p2

Step 3. Convert the pies into donuts. This can be achieved simply by adjusting the x-axis scale range. A donut thinner than the displayed one can be produced by further reducing the first value in the limits argument, e.g., by limits = c( -1, 1.5). If you don’t understand this technique, check this very useful summary to learn the one-to-one relationship between the linear Cartesian coordinate and the polar coordinate.

p3 <- p2 +   scale_x_continuous(limits = c( -.2, 1.5)) p3

Step 4. Add state name abbreviations at the donut center to replace subplot titles Here each abbreviated name created by geom_text is actually an overlap of three labels, as each state corresponds to three rows in the dataset. Also note that the coordinate x value -.2 is the same as the left edge limit specified in scale_x_continuous in step 3, so that the state abbreviations are positioned at the center of donuts circles.

p4 <- p3 +   # add abbreviated names  geom_text(aes(label = state.abbreviate, x = -.2, y = 0),            size = 3) +  # remove the full names (subplots titles)  theme(strip.text = element_blank())p4

Step 5. Reposition the legend and enlarge the donuts. Here we move the legend from right side of the plot to the top left corner. This frees up the space on the right side that the donuts can expand into. In addition, we reduce the margin between the subplots, and this further enlarges the donuts and renders a more compact outlook.

p5 <- p4 +   guides(fill = guide_legend(direction = "horizontal")) +  theme(legend.position = c(.2, .95),        legend.title = element_blank(),        # reduce distance between panels to expand the donuts        panel.spacing = unit(-10, "pt")) 
p5

Step 6. A final polish-up. Here we update the donut colors, and add a plot title.

p6 <- p5 +   # adjust color scale  scale_fill_manual(values = c("#4e79a7", "#e15759", "#59a14f")) +  # add  plot title  ggtitle("2016 Election Results") +  theme(plot.title = element_text(size = 16, face = "bold", hjust = .08)) 
p6
library(ggplot2)library(dplyr)# install.packages("geofacet")library(geofacet)
# Create two vectors of US state names, one for full name, one for abbreviated namesstate.abb.2 <- c(state.abb, "DC")state.name.2 <- c(state.name, "District of Columbia")
# Here we add a new column of abbreviations of the state names.e <- election %>% as_tibble() %>% mutate(state.abbreviate = state.abb.2[match(state, state.name.2)] )
# ready for visualization!head(e, n = 3)

# Create geographically faceted bar plots showing the voting results for each state.p1 <- e %>% ggplot(aes(x = 1, y = pct, fill = candidate)) + geom_col(alpha = 0.8, color = "white") + # create subplots reflecting the geographical locations of each state facet_geo(~ state, grid = "us_state_grid1") p1

# Convert the bars into piesp2 <- p1+ coord_polar(theta = "y") + theme_void() p2

# Step 3. Convert the pies into donuts.p3 <- p2 + scale_x_continuous(limits = c( -.2, 1.5)) p3

# Add state name abbreviations at the donut center to replace full names.p4 <- p3 + # add abbreviated names geom_text(aes(label = state.abbreviate, x = -.2, y = 0), size = 3) + # remove the full names, i.e., the title of subplots theme(strip.text = element_blank())p4

# Reposition the legend and enlarge the donuts.p5 <- p4 + guides(fill = guide_legend(direction = "horizontal")) + theme(legend.position = c(.2, .95), legend.title = element_blank(), # reduce distance between panels to expand the donuts panel.spacing = unit(-10, "pt")) p5

# Update the donut colors, and add a plot title.p6 <- p5 + # adjust color scale scale_fill_manual(values = c("#4e79a7", "#e15759", "#59a14f")) + # add plot title ggtitle("2016 Election Results") + theme(plot.title = element_text(size = 16, face = "bold", hjust = .08)) p6




Continue Exploring — 🚀 one level up!


Check these awesome exploded donut plots in faceted panels that visualize the GDP contribution of the top 4 countries separately in each continent.



The pie and donut charts demonstrated above are created by mapping the y aesthetic from a linear coordinate to angles in a polar coordinate (coord_polar(theta = "y")). Alternatively, the x aesthetic can be mapped to angles (with (theta = "x")) to create a drastically different appearance. While less used, it opens opportunities to creative visual effects. In the following article, we use this new technique to create a spinning barplot to echo with the theme of the visualization - the power of car engines.

And in a follow-up article, we further tweak the spinning barplot into a spinning lollipop plot.