scatter_data <- votes |>
filter(year(date_vote) >= 2000) |>
select(
title = titel_kurz_e,
volkja_proz,
annahme,
inserate_total,
inserate_jaanteil
) |>
filter(!is.na(inserate_jaanteil), !is.na(volkja_proz)) |>
mutate(
outcome_label = ifelse(annahme == 1, "Accepted", "Rejected"),
)
money_pits <- scatter_data |>
filter(inserate_jaanteil > 60 & volkja_proz < 45) |>
slice_max(order_by = inserate_total, n = 3)
underdogs <- scatter_data |>
filter(inserate_jaanteil < 40 & volkja_proz > 55) |>
slice_max(order_by = volkja_proz, n = 3)
behemoths <- scatter_data |>
slice_max(order_by = inserate_total, n = 3)
labels_final <- bind_rows(money_pits, underdogs, behemoths) |> distinct(title, .keep_all = TRUE)
ggplot(scatter_data, aes(x = inserate_jaanteil, y = volkja_proz)) +
annotate("rect", xmin = 50, xmax = 100, ymin = 50, ymax = 100, fill = "#27AE60", alpha = 0.05) +
annotate("rect", xmin = 50, xmax = 100, ymin = 0, ymax = 50, fill = "#E74C3C", alpha = 0.05) +
annotate("rect", xmin = 0, xmax = 50, ymin = 50, ymax = 100, fill = "#3498DB", alpha = 0.05) +
annotate("rect", xmin = 0, xmax = 50, ymin = 0, ymax = 50, fill = "grey50", alpha = 0.05) +
geom_hline(yintercept = 50, linetype = "dashed", color = "grey60") +
geom_vline(xintercept = 50, linetype = "dashed", color = "grey60") +
annotate("text", x = 98, y = 98, label = "DOMINANT WIN\n(High Ad Share / Won)", hjust = 1, vjust = 1, fontface = "bold", size = 3, color = "#27AE60") +
annotate("text", x = 98, y = 2, label = "MONEY PIT\n(High Ad Share / Lost)", hjust = 1, vjust = 0, fontface = "bold", size = 3, color = "#C0392B") +
annotate("text", x = 2, y = 98, label = "UNDERDOG\n(Low Ad Share / Won)", hjust = 0, vjust = 1, fontface = "bold", size = 3, color = "#2980B9") +
annotate("text", x = 2, y = 2, label = "LOST CAUSE\n(Low Ad Share / Lost)", hjust = 0, vjust = 0, fontface = "bold", size = 3, color = "grey60") +
geom_point(aes(size = inserate_total, fill = outcome_label), shape = 21, color = "white", stroke = 0.5, alpha = 0.8) +
geom_text_repel(
data = labels_final,
aes(label = title),
size = 2.8,
min.segment.length = 0,
box.padding = 0.6,
max.overlaps = Inf,
bg.color = "white",
bg.r = 0.15
) +
geom_smooth(method = "lm", color = "black", se = FALSE, linetype = "dotted", size = 0.5) +
scale_x_continuous(labels = percent_format(scale = 1), limits = c(0, 100)) +
scale_y_continuous(labels = percent_format(scale = 1), limits = c(0, 100)) +
scale_size_continuous(range = c(1, 14), labels = label_comma(), name = "Total Ad Volume") +
scale_fill_manual(values = outcome_colors, name = "Outcome") +
labs(
title = "Can Money Buy Votes? (2000-Present)",
subtitle = "Comparing 'Yes' Ad Share vs. 'Yes' Vote Share.",
caption = "Notes: Labeled points include the top 3 campaigns by total volume (Size), plus specific outliers:\n'Money Pits' (Ad Share > 60% but Vote < 45%) and 'Underdogs' (Ad Share < 40% but Vote > 55%).",
x = "Share of 'Yes' Advertisements (%)",
y = "Share of Popular Vote (%)"
) +
theme(
plot.caption = element_text(hjust = 0, size = 8.5, color = "grey30", margin = margin(t = 10)),
legend.box = "horizontal",
) +
guides(
size = guide_legend(
title.position = "top",
title.hjust = 0.5,
override.aes = list(fill = "grey70", color = "grey30")
),
fill = guide_legend(
title.position = "top",
title.hjust = 0.5,
override.aes = list(size = 4)
)
)