library(tidyverse)
library(tidytext)
Storytelling with {ggplot2}: Bar chart sequence
1. Load data
<- read_csv("fs_outcome_data.csv") dat
Rows: 78 Columns: 3
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (2): college, metric
dbl (1): rate
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(dat)
# A tibble: 6 × 3
college metric rate
<chr> <chr> <dbl>
1 AAA Credit Accumulation Rate -0.05
2 BBB Credit Accumulation Rate -1
3 CCC Credit Accumulation Rate -1.3
4 DDD Credit Accumulation Rate -0.5
5 EEE Credit Accumulation Rate 1.1
6 FFF Credit Accumulation Rate 1.7
2. Build fill and color variables
<- dat |>
dat mutate(metric = factor(metric, levels = c("Credit Accumulation Rate",
"Retention Rate",
"Graduation Rate"),
labels = c("CAR","Retention Rate","Graduation Rate")),
positive = case_when(
== "CAR" & rate > 0 ~"a",
metric == "Retention Rate" & rate > 0 ~"b",
metric == "Graduation Rate" & rate > 0 ~"c",
metric ~ "d"),
T negative = case_when(
== "CAR" & rate < 0 ~"a",
metric == "Retention Rate" & rate < 0 ~"b",
metric == "Graduation Rate" & rate < 0 ~"c",
metric ~ "d"),
T goal = case_when(
== "CAR" & rate >= 1 ~"a",
metric == "Retention Rate" & rate >= 1 ~"b",
metric == "Graduation Rate" & rate >= 1 ~"c",
metric ~"d"),
Tcase = case_when(
== "CAR" & college %in%
metric c("GGG","MMM","FFF")~"a",
== "Retention Rate" & college %in%
metric c("GGG","MMM","FFF")~"b",
== "Graduation Rate"& college %in%
metric c("GGG","MMM","FFF")~"c",
~"d"),
Tpositive_color = ifelse(positive != "d", "a","b"),
negative_color = ifelse(negative != "d", "a","b"),
goal_color = ifelse(goal != "d", "a","b"),
case_color = ifelse(case != "d", "a","b"))
2. Set bracket positions
(Ugh! There has to be a better way to do this … But manually setting for now)
<- function(x) {
college_count |>
dat filter(metric == x,
>= 1) |>
rate count() |> pull(n)
}
<- function(y) {
bracket_floor 26.5 - college_count(y)
}
<- c(bracket_floor("CAR"),
bracket_floors bracket_floor("Retention Rate"),
bracket_floor("Graduation Rate"))
<- data.frame(
brackets x = c(2.05, 1.35, 2.95,
2.25, 1.55, 3.15,
2.05, 1.35, 2.95),
xend = rep(c(2.25, 1.55, 3.15),3),
y = c(rep(26.5,6),
bracket_floors),yend = c(26.5, 26.5, 26.5,
rep(bracket_floors, 2)),
metric = rep(c("CAR",
"Retention Rate",
"Graduation Rate"),3)) |>
mutate(metric = factor(metric, levels = c("CAR",
"Retention Rate",
"Graduation Rate")))
3. Set annotation labels/positions
<- data.frame(metric = factor(c("CAR",
annotations "Retention Rate",
"Graduation Rate"),
levels = c("CAR",
"Retention Rate",
"Graduation Rate")),
lab = c(paste0(college_count("CAR"), " colleges"),
paste0(college_count("Retention Rate"), " colleges"),
paste0(college_count("Graduation Rate"), " colleges")),
x = c(2.33, 1.65, 1.95),
y = c(27 - (college_count("CAR")/2),
27 - (college_count("Retention Rate")/2),
27 - (college_count("Graduation Rate")/2)))
4. Build function
<- function(fill_var, color_var, annotation_color) {
build_chart
ggplot(dat,
aes(x = rate, y = reorder_within(college, rate, metric))) +
geom_col(aes(fill = {{ fill_var }}),
width = .85, show.legend = F) +
geom_text(dat |> filter(rate < 0),
mapping = aes(x = .1, y = reorder_within(college, rate, metric),
label = college, color = {{ color_var }}),
hjust = 0, vjust = .4, size = 3.3, show.legend = F) +
geom_text(dat |> filter(rate > 0),
mapping = aes(x = -.1, y = reorder_within(college, rate, metric),
label = college, color = {{ color_var }}),
hjust = 1, vjust = .4, size = 3.3, show.legend = F) +
geom_segment(brackets,
mapping = aes(x = x, xend = xend, y = y, yend = yend),
show.legend = F, linewidth = .5,
color = annotation_color) +
geom_text(annotations,
mapping = aes(x = x, y = y, label = lab), hjust = 0,
show.legend = F, fontface = "bold", size = 3.3,
color = annotation_color) +
facet_wrap(~metric, scales = 'free_y') +
labs(x = "KPI Improvement Rate",
caption = "Note: Improvement rate is based on the slope of a regression line (percentage points per year) fitted to the annual KPI trend values.") +
scale_y_reordered() +
scale_fill_manual(values = c("#949e48","#ea9f2c","#0290c0","grey90")) +
scale_color_manual(values = c("grey40","grey90")) +
theme_minimal() +
theme(strip.text = element_text(face = "bold",size = 10, color = "#444",
margin = margin(b=10)),
panel.spacing = unit(.7,"cm"),
panel.grid = element_blank(),
axis.ticks.x = element_line(color = "#999"),
axis.line.x = element_line(color = "#999"),
axis.text.y = element_blank(),
axis.title.y = element_blank(),
axis.text.x = element_text(size = 10),
axis.title.x = element_text(face = "bold", size = 10, color = "#444",
margin = margin(t=10)),
plot.caption = element_text(size = 8, color = "darkgrey",hjust = 0,
margin = margin(t=10)),
plot.caption.position = "plot")
}
5. Run function
build_chart(positive, positive_color,"transparent")
build_chart(negative, negative_color,"transparent")
build_chart(goal, goal_color, "grey50") +
geom_vline(aes(xintercept = 1),
color = "grey70",
linetype = "dotted",
linewidth = .65)
build_chart(case, case_color, "grey90") +
geom_vline(aes(xintercept = 1),
color = "grey90",
linetype = "dotted",
linewidth = .65) +
geom_col(aes(fill = case),
width = .85, show.legend = F)