# Create dot-based visualization of null distributions
# Fixed x-axis range for all plots
x_range <- c(-2.25, 2.25)
plot_null_dots <- function(result, label, obs_effect, p_val) {
null_df <- result$null_distribution %>%
arrange(stat) %>%
mutate(
# Use fixed breaks based on common x-axis range
bin = cut(stat, breaks = seq(x_range[1], x_range[2], length.out = 41), labels = FALSE)
) %>%
filter(!is.na(bin)) %>%
group_by(bin) %>%
mutate(
y_pos = row_number(),
bin_center = mean(stat)
) %>%
ungroup()
# Determine if result crosses threshold
p_display <- ifelse(p_val < 0.001, "< 0.001", round(p_val, 3))
sig_label <- paste0("p = ", p_display)
sig_color <- ifelse(p_val < 0.05, treatment_color, "#666666")
ggplot(null_df, aes(x = bin_center, y = y_pos)) +
geom_point(color = null_color, size = 1.5, alpha = 0.7) +
geom_vline(xintercept = obs_effect, linetype = "dashed",
color = treatment_color, linewidth = 1.2) +
geom_vline(xintercept = 0, color = "black", linewidth = 0.5) +
annotate("text", x = obs_effect, y = max(null_df$y_pos) * 0.95,
label = paste0("Point estimate\n= ", round(obs_effect, 2)),
hjust = -0.1,
color = treatment_color, fontface = "bold", size = 3.5) +
annotate("label", x = x_range[1] + 0.1, y = max(null_df$y_pos) * 0.9,
label = sig_label, hjust = 0, fill = "white",
color = sig_color, fontface = "bold", size = 4) +
scale_x_continuous(limits = x_range) +
labs(
title = label,
x = "Effect size (Treatment - Control)",
y = "Count"
) +
theme(
plot.title = element_text(face = "bold"),
axis.text.y = element_blank(),
axis.ticks.y = element_blank()
)
}
# Create individual plots
p1 <- plot_null_dots(results[["n = 50"]], "n = 50",
results[["n = 50"]]$observed,
results[["n = 50"]]$p_value)
p2 <- plot_null_dots(results[["n = 150"]], "n = 150",
results[["n = 150"]]$observed,
results[["n = 150"]]$p_value)
p3 <- plot_null_dots(results[["n = 500"]], "n = 500",
results[["n = 500"]]$observed,
results[["n = 500"]]$p_value)
p4 <- plot_null_dots(results[["n = 1,000"]], "n = 1,000",
results[["n = 1,000"]]$observed,
results[["n = 1,000"]]$p_value)
(p1 + p2) / (p3 + p4) +
plot_annotation(
title = "Null Distributions from Permutation Tests",
subtitle = "Each dot = one shuffled result. Dashed line = observed point estimate. Same effect, different p-values.",
theme = theme(
plot.title = element_text(face = "bold", size = 16),
plot.subtitle = element_text(size = 12, color = "gray40")
)
)