A custom scatterplot with an overlayed regression fit and
auto-positioned labels to explore the relationship between the
Corruption Perceptions Index and Human Development Index made with
R
and the tidyverse
. This post includes a
variety of custom colors, markers, and layout adjustments. The
library ggrepel
is used to automatically adjust the
position of labels in the plots.
This page showcases the work of Claus O. Wilke in his R package practicalgg that contains step-by-step examples demonstrating how to get the most out of ggplot2. You can find the original code for this example here.
Thanks to him for accepting sharing his work here! Thanks also to Tomás Capretto who split the original code into this step-by-step guide! 🙏🙏
As a teaser, here is the plot we’re building today:
As usual, it is first necessary to load some packages before building
the figure.
ggrepel
provides geoms for ggplot2
to repel overlapping text
labels. Text labels repel away from each other, away from data points,
and away from edges of the plotting area in an automatic fashion.
Also,
colorspace
is loaded to use its function darken()
, and
cowplot
contributes its theme_minimal_hgrid()
built-in theme.
library(tidyverse)
library(cowplot)
library(colorspace)
library(ggrepel)
Today’s chart uses the
corruption
dataset in the
practicalgg
package. This data contains information about Corruption Perceptions
Index (CPI) and Human Development Index (HDI) for 176 countries, from
2012 to 2015.
The original source are the
Corruption Perceptions Index 2016
released by
Transparency International and
the
Human Development Index
made available in the
Human Development Reports by the
United Nations Development Programme. These datasets were merged and made available by
Claus O. Wilke as the
corruption
dataset in his
practicalgg
package. Thanks to Claus for all the work and
making this possible!
The following chunk loads the data, keeps only observations for the 2015 year, and drops any row that contains a missing value.
<- practicalgg::corruption %>%
corrupt filter(year == 2015) %>%
na.omit()
Next, longer region names are split into multiple lines so they fit better in the legend that goes on the top region of the plot.
<- corrupt %>%
corrupt mutate(
region = case_when(
== "Middle East and North Africa" ~ "Middle East\nand North Africa",
region == "Europe and Central Asia" ~ "Europe and\nCentral Asia",
region == "Sub Saharan Africa" ~ "Sub-Saharan\nAfrica",
region TRUE ~ region # All the other remain the same
) )
And finally, we add a new variable, label
, that contains
the name of some selected countries. These countries are going to be
added to the plot with the geom_text_repel()
function
from the ggrepel
package that is going to automatically
adjust their positions to avoid overlap.
<- c(
country_highlight "Germany", "Norway", "United States", "Greece", "Singapore", "Rwanda",
"Russia", "Venezuela", "Sudan", "Iraq", "Ghana", "Niger", "Chad", "Kuwait",
"Qatar", "Myanmar", "Nepal", "Chile", "Argentina", "Japan", "China"
)
<- corrupt %>%
corrupt mutate(
label = ifelse(country %in% country_highlight, country, "")
)
That’s it for the data preparation step! Let’s build the chart now!
Unlike other guides in this series, this post goes straight to the point and builds the chart in a single chunk of code. The original vignette is already an excellent step-by-step guide on the construction of this plot. Some comments are still added within the code to explain what is going on in certain lines.
# Okabe Ito colors
# The last color is used for the regression fit.
<- c("#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#999999")
region_cols
ggplot(corrupt, aes(cpi, hdi)) +
# Adding the regression fit before the points make sure the line stays behind the points.
geom_smooth(
aes(color = "y ~ log(x)", fill = "y ~ log(x)"),
method = "lm",
formula = y~log(x),
se = FALSE, # Plot the line only (without confidence bands)
fullrange = TRUE # The fit spans the full range of the horizontal axis
+
) geom_point(
aes(color = region, fill = region),
size = 2.5, alpha = 0.5,
shape = 21 # This is a dot with both border (color) and fill.
+
) # Add auto-positioned text
geom_text_repel(
aes(label = label),
color = "black",
size = 9/.pt, # font size 9 pt
point.padding = 0.1,
box.padding = 0.6,
min.segment.length = 0,
max.overlaps = 1000,
seed = 7654 # For reproducibility reasons
+
) scale_color_manual(
name = NULL, # it's one way to omit the legend title
values = darken(region_cols, 0.3) # dot borders are a darker than the fill
+
) scale_fill_manual(
name = NULL,
values = region_cols
+
) # Add labels and customize axes
scale_x_continuous(
name = "Corruption Perceptions Index, 2015 (100 = least corrupt)",
limits = c(10, 95),
breaks = c(20, 40, 60, 80, 100),
expand = c(0, 0) # This removes the default padding on the ends of the axis
+
) scale_y_continuous(
name = "Human Development Index, 2015\n(1.0 = most developed)",
limits = c(0.3, 1.05),
breaks = c(0.2, 0.4, 0.6, 0.8, 1.0), # Manually set axis breaks
expand = c(0, 0)
+
) # Override default legend appearance
guides(
color = guide_legend(
# All keys go in the same row.
nrow = 1,
override.aes = list(
# 0 means no line, 1 is a solid line
# The result is 5 keys with no line and 1 with a line
linetype = c(rep(0, 5), 1),
# Now, 5 keys with the marker number 21 (the one used in the plot)
# and 1 key without this marker.
shape = c(rep(21, 5), NA)
)
)+
) # Minimal grid theme that only draws horizontal lines
theme_minimal_hgrid(12, rel_small = 1) +
# Customize aspects of the legend
theme(
legend.position = "top",
legend.justification = "right",
legend.text = element_text(size = 9),
legend.box.spacing = unit(0, "pt")
)