Use Faceted Doughnut Charts with ggplot2 to Visualize Continent-Wise GDP

In this article, we’ll leverage the doughnut chart to visualize the GDP contribution proportion of the top 4 countries separately in each continent. Major techniques covered in this tutorial include:

  • Specify the stacking order of bars and doughnut slices.
  • Transform a linear coordinate into a polar coordinate.
  • Create a doughnut chart with text annotation.
  • Combine two plots together using the cowplot package.

Packages and data cleanup

We’ll use the gapminder dataset from the famous gapminder package.

library(ggplot2)library(gapminder) # use its datasetlibrary(dplyr)     # data wrangling  library(stringr)   # string manipulationlibrary(scico)     # color paletteslibrary(cowplot)   # combine plots together
head(gapminder, 3)

Output:

# A tibble: 3 × 6
country continent year lifeExp pop gdpPercap
<fct> <fct> <int> <dbl> <int> <dbl>
1 Afghanistan Asia 1952 28.8 8425333 779.
2 Afghanistan Asia 1957 30.3 9240934 821.
3 Afghanistan Asia 1962 32.0 10267083 853.

We’ll use the subset of year 2007 for demonstration. First, we calculate the total GDP (GDP per capita multiplied by the population) in each country in 2007.

g <- gapminder %>%   filter(year %in% c(2007)) %>%   # calculate total GDP in billions in each country  mutate(gdp.Total = pop * gdpPercap / 10^9)

Prepare a dataset containing the GDP of the top 4 countries, as well as the sum of the rest of the countries (named as “others”), separately in each continent. In particular, we create a factor variable gdp.rank which ranks countries from 1 to 5. This variable specifies the stacking order of the countries in the bars / doughnut plot. (Check here for a complete guide on graphic reordering)

# separately for each continent, rank countries' GDP in descending orderg <- g %>%   group_by(continent) %>%   mutate(gdp.rank = rank(desc(gdp.Total))) %>%    mutate(    # in each continent, rename all countries ranked below 4th as "others"     country  = ifelse(gdp.rank <= 4, as.character(country), "others"),        # rank the "others" as 5th, and convert the rank to a factor variable    # the factor level determines the stacking order of the bar     gdp.rank = ifelse(gdp.rank <= 4, gdp.rank, 5) %>% factor(levels = 1:5)) %>%     # calculate the GDP of the 5 countries (including "others") in each continent  group_by(continent, country, gdp.rank) %>%   summarise(gdp.Total = sum(gdp.Total))
head(g, 3) # ready for visualization!

Output:

# A tibble: 3 × 4
# Groups: continent, country [3]
continent country gdp.rank gdp.Total
<fct> <chr> <fct> <dbl>
1 Africa Algeria 4 207.
2 Africa Egypt 1 448.
3 Africa Nigeria 3 272.

Visualization

We’ll first create a stacked bar plot in the linear Cartesian coordinate (the default system), and then convert the bars into a doughnut plot by transforming the linear coordinate into a polar coordinate. Check this very useful summary to develop an intuitive insight into the relationship between the linear and polar coordinate.

1. Create a stacked bar plot for each continent. One critical edit here is the use of the fill position to normalize the bars to the same total height of 1 unit. This normalization ensures a complete 360-degree in the doughnut circles that the bars will be transformed into; otherwise, in faceted panels, bars of varying heights will become fractional circles when converted in a polar coordinate – check here to learn an illustrated example.

p1 <- g  %>%   ggplot(aes(x = 1, y = gdp.Total,              # color based on rank             fill = gdp.rank, color = gdp.rank)) +   geom_col(    # bars stacked and normalized to the range of [0, 1]     position = position_fill(),     # the bar outline creates the explosive effect in doughnut chart    # we'll later merge the outline into the background color    color = "floralwhite",     linewidth = 2, alpha = .7) +   facet_wrap( ~ continent, nrow = 2) p1

2. Add country names to the right side of the bar. Once transformed to a polar coordinate, the texts will be positioned on the peripheral side of the doughnut. (Don’t worry that only half of the texts are displayed by this step. We’ll fix it later)

p2 <- p1 +   geom_text(    aes(x = 2.3, label = str_wrap(country, width = 1)),     position = position_fill(vjust = .5),    fontface = "bold", size = 4)p2

3. Add names of the continents to the left edge of the plot (with an x-coordinate value of -1). The empty space created on the left side of the bar will become a central hole of the doughnut plot when transformed into a polar coordinate. A smaller x-coordinate value will create more space on the left side of the bar, and corresponds to a thinner doughnut.

p3 <- p2 +   geom_text(    aes(x = -1, y = .5, label = continent),    fontface = "bold", size = 5, color = "grey50")p3

4. Transform the linear Cartesian coordinate into the polar coordinate system.

p4 <- p3 + coord_polar(  # map the y-aesthetic to angles    theta = "y",   # show texts that extend beyond the panel range  clip = "off") p4

5. Clean up the theme. Now it can be seen clearly that in the doughnut chart of each continent, in a counterclockwise direction and going from blue to pink, each doughnut piece corresponds to the 1st to the 4th largest GDP countries, followed by the sum of the rest of the countries. This consistency is achieved by specifying the bar stacking order via the gdp.rank variable (review this earlier step).

p5 <- p4 +   # color scale, using the "scico" package  scale_fill_scico_d() +  scale_color_scico_d() +    theme_void() + # a commonly used theme for pie/doughnut charts  theme(legend.position = "none",         strip.text = element_blank(),                # reduce the spacing between panels        panel.spacing = unit(-20, "pt"),                 plot.background = element_rect(fill = "floralwhite", color = NA),        # increase space to the left and right side of the plot        plot.margin = margin(l = 30, r = 20))  p5

6. Create a plot title at the bottom right corner. Since it’s not straightforward to directly annotate in one of the faceted panels in a polar coordinate, we’ll instead first create a separate ggplot2 object of the plot title. Then, we’ll combine the doughnut plot and the title plot in the next step.

p.title <- ggplot() +  annotate(    geom = "text",     label = "2007 GDP of\nthe TOP 4 countries\nin each continent",    x = 0, y = 0, size = 5, fontface = "bold",    color = "steelblue4") +  theme_void()
p.title

7. Combine the doughnut plot and the title plot using the cowplot package.

artwork <- ggdraw() +  draw_plot(p5) +  draw_plot(p.title, x = .3, y = -.25,            width = 1)
artwork

8 Save the plot.

ggsave(filename = "2007 GDP doughnut.pdf",        path = "graphics", # a relative path       width = 8, height = 5)
library(ggplot2)library(gapminder) # use its datasetlibrary(dplyr)     # data wrangling  library(stringr)   # string manipulationlibrary(scico)     # color paletteslibrary(cowplot)   # combine plots together
# Calculate the total GDP (GDP per capita times population) in each country in 2007. g <- gapminder %>% filter(year %in% c(2007)) %>% # calculate total GDP in billions in each country mutate(gdp.Total = pop * gdpPercap / 10^9)

# Prepare a dataset containing the GDP of the top 4 countries, # and the sum of the rest of the countries, separately in each continent.
g <- g %>% # separately for each continent: group_by(continent) %>% # rank countries' GDP in descending order mutate(gdp.rank = rank(desc(gdp.Total))) %>% mutate( # in each continent, rename all countries ranked below 4th as "others" country = ifelse(gdp.rank <= 4, as.character(country), "others"), # rank the "others" as 5th, and convert the rank to a factor variable # the factor level determines the stacking order of the bar gdp.rank = ifelse(gdp.rank <= 4, gdp.rank, 5) %>% factor(levels = 1:5)) %>% # calculate the GDP of the 5 countries (including "others") in each continent group_by(continent, country, gdp.rank) %>% summarise(gdp.Total = sum(gdp.Total))
head(g, 3) # ready for visualization!

# Create a stacked bar plot for each continent. p1 <- g %>% ggplot(aes(x = 1, y = gdp.Total, # color based on rank fill = gdp.rank, color = gdp.rank)) + geom_col( # bars stacked and normalized to the range of [0, 1] position = position_fill(), # the bar outline creates the explosive effect in doughnut chart # we'll later merge the outline into the background color color = "floralwhite", linewidth = 2, alpha = .7) + facet_wrap( ~ continent, nrow = 2) p1

# Add country names to the right side of the bar. p2 <- p1 + geom_text( aes(x = 2.3, label = str_wrap(country, width = 1)), position = position_fill(vjust = .5), fontface = "bold", size = 4)p2

# Add names of the continents to the left edge of the plot.p3 <- p2 + geom_text( aes(x = -1, y = .5, label = continent), fontface = "bold", size = 5, color = "grey50")p3

# Transform the linear Cartesian coordinate into the polar coordinate system. p4 <- p3 + coord_polar( # map the y-aesthetic to angles theta = "y", # show texts that extend beyond the panel range clip = "off") p4

# Clean up the theme. p5 <- p4 + # color scale, using the "scico" package scale_fill_scico_d() + scale_color_scico_d() + theme_void() + # a commonly used theme for pie/doughnut charts theme(legend.position = "none", strip.text = element_blank(), # reduce the spacing between panels panel.spacing = unit(-20, "pt"), plot.background = element_rect(fill = "floralwhite", color = NA), # increase space to the left and right side of the plot plot.margin = margin(l = 30, r = 20)) p5

# Create the plot title. p.title <- ggplot() + annotate( geom = "text", label = "2007 GDP of\nthe TOP 4 countries\nin each continent", x = 0, y = 0, size = 5, fontface = "bold", color = "steelblue4") + theme_void()
p.title

# Combine the doughnut plot and the title plot using the `cowplot` package. artwork <- ggdraw() + draw_plot(p5) + draw_plot(p.title, x = .3, y = -.25, width = 1)
artwork

# Save the plot. ggsave(filename = "2007 GDP doughnut.pdf", path = "graphics", width = 8, height = 5)




Continue Exploring — 🚀 one level up!


Check these exploded donut plots in a U.S. map layout that visualizes the state-wise voting results of the 2016 Presidential Election between Hillary Clinton and Donald Trump .



In the above continent-wise GDP visualization, reordering is a key edit that renders consistent position and color design across the faceted donut plots. The following stacked area / alluvial plot leverages a similar technique to cluster ribbons (countries) of the same continent, and to use color gradients to distinguish countries and continents.