null_df <- data.frame(
diff = null_dist,
extreme = abs(null_dist) >= abs(obs_diff)
)
q025 <- quantile(null_dist, 0.025)
q975 <- quantile(null_dist, 0.975)
# Shared break-points so both histogram layers align perfectly
null_breaks <- seq(
floor(min(null_dist) * 20) / 20 - 0.025,
ceiling(max(null_dist) * 20) / 20 + 0.025,
by = 0.05
)
ggplot(null_df, aes(x = diff, fill = extreme)) +
geom_histogram(aes(y = after_stat(density)),
breaks = null_breaks, color = "white", linewidth = 0.15) +
geom_density(aes(x = diff), data = null_df, inherit.aes = FALSE,
color = "#08519c", linewidth = 1.0) +
geom_vline(xintercept = obs_diff, color = "#d7301f", linewidth = 1.6) +
geom_vline(xintercept = -obs_diff, color = "#d7301f", linewidth = 1.0,
linetype = "dashed") +
geom_vline(xintercept = q025, color = "#525252", linewidth = 0.7,
linetype = "dotted") +
geom_vline(xintercept = q975, color = "#525252", linewidth = 0.7,
linetype = "dotted") +
annotate("label", x = 0, y = max(density(null_dist)$y) * 0.82,
label = sprintf("p = %.3f", p_val),
fill = "#deebf7", color = "#08306b", fontface = "bold", size = 5.5,
label.size = 0.4, label.padding = unit(0.35, "lines")) +
annotate("text",
x = obs_diff + sign(obs_diff) * 0.08,
y = max(density(null_dist)$y) * 0.52,
label = sprintf("Observed\n\u0394 = $%.2f", obs_diff),
color = "#d7301f", fontface = "bold", size = 3.5,
hjust = ifelse(obs_diff >= 0, 0, 1)) +
annotate("text", x = q025 - 0.03, y = 0.16,
label = "2.5th\npercentile", color = "#525252", size = 2.9, hjust = 1) +
annotate("text", x = q975 + 0.03, y = 0.16,
label = "97.5th\npercentile", color = "#525252", size = 2.9, hjust = 0) +
scale_fill_manual(
values = c("FALSE" = "#9ecae1", "TRUE" = "#de2d26"),
labels = c(expression(paste("Consistent with ", H[0])),
"As or more extreme than observed"),
name = NULL
) +
scale_x_continuous(breaks = seq(-1.5, 1.5, 0.5),
labels = function(x) sprintf("$%.1f", x)) +
labs(
x = "Mean difference (Eco-label \u2212 Control) across 10,000 shuffles",
y = "Density",
title = "Null Distribution: What Chance Alone Produces",
subtitle = sprintf(
"Blue = consistent with H0 \u00b7 Red = as or more extreme (p = %.3f) \u00b7 Dotted = 95%% CI of null",
p_val)
) +
theme_minimal(base_size = 13) +
theme(
panel.grid.minor = element_blank(),
panel.grid.major = element_line(color = "#eeeeee"),
legend.position = "bottom",
plot.title = element_text(face = "bold"),
plot.subtitle = element_text(color = "#555555", size = 10.5)
)