library(tidyverse)
library(tidytext)Storytelling with {ggplot2}: Bar chart sequence
1. Load data
dat <- read_csv("fs_outcome_data.csv") 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(
metric == "CAR" & rate > 0 ~"a",
metric == "Retention Rate" & rate > 0 ~"b",
metric == "Graduation Rate" & rate > 0 ~"c",
T ~ "d"),
negative = case_when(
metric == "CAR" & rate < 0 ~"a",
metric == "Retention Rate" & rate < 0 ~"b",
metric == "Graduation Rate" & rate < 0 ~"c",
T ~ "d"),
goal = case_when(
metric == "CAR" & rate >= 1 ~"a",
metric == "Retention Rate" & rate >= 1 ~"b",
metric == "Graduation Rate" & rate >= 1 ~"c",
T~"d"),
case = case_when(
metric == "CAR" & college %in%
c("GGG","MMM","FFF")~"a",
metric == "Retention Rate" & college %in%
c("GGG","MMM","FFF")~"b",
metric == "Graduation Rate"& college %in%
c("GGG","MMM","FFF")~"c",
T~"d"),
positive_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)
college_count <- function(x) {
dat |>
filter(metric == x,
rate >= 1) |>
count() |> pull(n)
}
bracket_floor <- function(y) {
26.5 - college_count(y)
}
bracket_floors <- c(bracket_floor("CAR"),
bracket_floor("Retention Rate"),
bracket_floor("Graduation Rate"))
brackets <- data.frame(
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
annotations <- data.frame(metric = factor(c("CAR",
"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
build_chart <- function(fill_var, color_var, annotation_color) {
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)