FWER, FDR, Positive and Negative effects(R)
False Discovery Rate and Positive vs. Negative effects
The false discovery rate (FDR) is the (expected) proportion of false positives out of true and false positives:
\[ FDR = \frac{\color{red}{FalsePositives}}{\color{red}{FalsePositives} + \color{green}{TruePositives}} \]
Estimating this consistently is tricky because \(\color{red}{FalsePositives}\) should be negative effects (hence written in red), as they reflect negative effects in the population that have incorrectly been identified as positive.
The fact that all false positives are negatives can be visualised as follows:
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.3 ✔ readr 2.1.4
✔ forcats 1.0.0 ✔ stringr 1.5.0
✔ ggplot2 3.4.4 ✔ tibble 3.2.1
✔ lubridate 1.9.3 ✔ tidyr 1.3.0
✔ purrr 1.0.2
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(eulerr)
set.seed(6)
<- list(
neg_vd "Positive" = 301:1000,
"False Negative" = 301:335,
"Negative" = 1:300,
"False Positive" = 1:15
)
plot(euler(neg_vd, shape = "ellipse"),
fills = list(
fill = c(
"dark green", # Positive
"purple", # False Negative
"light blue", # Negative
"red" # False positive
),alpha = 0.5
) )
The above illustration also illustrates that the ratio between positive and negative effects in the population should change your false discovery rate. To illustrate this, let’s create a table of how many false positives and true positives we would expect based on different proportions of positive effects in the population, and what the impact is on the FDR.
<- data.frame(
fdr_df pop_pos = c(700,500,300),
pop_neg = c(300,500,700),
power = c(.95,.95,.95),
alpha = c(.05,.05,.05)
)$true_pos = fdr_df$pop_pos * fdr_df$power
fdr_df$true_neg = fdr_df$pop_neg * (1- fdr_df$alpha)
fdr_df$false_pos = fdr_df$pop_neg * fdr_df$alpha
fdr_df$false_neg = fdr_df$pop_pos * (1-fdr_df$power)
fdr_df$FDR = fdr_df$false_pos/(fdr_df$false_pos + fdr_df$true_pos)
fdr_df::kable(fdr_df) knitr
pop_pos | pop_neg | power | alpha | true_pos | true_neg | false_pos | false_neg | FDR |
---|---|---|---|---|---|---|---|---|
700 | 300 | 0.95 | 0.05 | 665 | 285 | 15 | 35 | 0.0220588 |
500 | 500 | 0.95 | 0.05 | 475 | 475 | 25 | 25 | 0.0500000 |
300 | 700 | 0.95 | 0.05 | 285 | 665 | 35 | 15 | 0.1093750 |
The false discovery rate is highest when only 20% of the effects in the population are positive, and lowest when 70% of the effects in the population are positive. Let’s visualise this with some Venn Diagrams:
library(ggplot2)
library(ggpubr)
ggarrange(
pos_neg_1_plot,
fdr_1_plot,
pos_neg_2_plot,
fdr_2_plot,
pos_neg_3_plot,
fdr_3_plot,ncol = 2,
nrow = 3,
labels = c("Sample","FDR")
)
In the above figure green represents positives (bright green represents true positives), blue represents negatives, purple reflects false negatives and red reflects false positives. Consistent with the table above, the ratio between false positives and true positives shifts depending on how many positives and negatives there are in the population, thus changing the FDR.
Family-Wise Error Rate and Positive vs. Negative effects
The family-wise error rate (FWER) is the likelihood that at least one of your negative effects has been incorrectly accepted and thus is false-positive. Unlike FDR, there is no comparison between effects that are positive or negative in the population, and so the ratio between positive and negative effects in the population should have no impact on the FWER. The simulations below will help assess if this is true.
Simulations of FDR with different positive rates in the population
We will now run some simulations to generate expected FDR outputs using one-sample t-tests. The parameters will be as follows:
effect size is 1 (Cohen’s d)
\(\alpha\) is .05
Power (\(1-\beta\)) is .95
A Benjamini-Hochberg procedure will be applied to try to attain the FDR required for each simulation:
ggplot(fdr_sim_df, aes(x = q, y = fdr,color=as.factor(positive_prop))) +
geom_smooth(method = "lm", formula = "y ~ x") +
xlab("Researcher estimated FDR") +
ylab("Actual FDR") +
geom_segment(aes(x = 0, y = 0, xend = 1, yend = 1, color="Expected FDR")) +
geom_jitter(width = .0025, height = 0.001) +
coord_cartesian(
xlim = (c(0,.1)),
ylim = (c(0,.4))
+
) labs(color="Proportion Positive")
The above simulations show an interaction between the false discovery rate a researcher sets (known as \(q\)) and the actual false discovery rate depending on the proportion of positive and negative findings in the population. Whilst it might seem problematic that the underlying number of positives and negatives in the population impacts the actual false discovery rate, the good news is that even if only 10% of your tests should be positive (Proportion Positive = .1) based on the population, the Benjamini-Hochberg will on average keep the FDR below the researcher specified FDR.
Simulations of FWER with different positive rates in the population
We will now run some simulations to generate expected FWER outputs. The parameters will be the same as for FDR above:
effect size is 1
\(\alpha\) is .05
Power (\(1-\beta\)) is .95
A Holm-Šidák procedure will be applied to try to keep the FWER to .05:
# identifying if any simulation had at least one false positive to identify FWER as 1 or 0
$hb_fwer = ifelse(fwer_sim_df$hb_fp > 0, 1 , 0)
fwer_sim_df
# allocating simulations into sets of 100
$sim_group = rep(1:25, each = 100)
fwer_sim_df
<- fwer_sim_df %>%
fwer_summary group_by(positive_prop, sim_group) %>%
summarise(
.groups = "keep",
fwer = mean(hb_fwer)
)
<- fwer_summary %>%
fwer_summary_mean_se group_by(positive_prop) %>%
summarise(
fwer_mean = mean(fwer),
fwer_sd = sd(fwer)
)
ggplot(
data = fwer_summary_mean_se,
aes(
x=positive_prop,
y=fwer_mean,
fill=as.factor(positive_prop))
+
) geom_col() +
geom_errorbar(aes(ymin = fwer_mean - fwer_sd, ymax = fwer_mean + fwer_sd)) +
xlab("Proportion of the population that is positive") +
ylab("FWER per 100 simulations")
The above figure (error bars reflect SD) visualises how the FWER doesn’t change as a function of the proportion of the population effects are positive (as expected).