# Preparation: Install and load all necessary packages
# install.packages(c("RcppArmadillo", "ggplot2", "patchwork", "pwr"))
library(ggplot2) # for plotting
library(RcppArmadillo) # for fast LMs
library(patchwork) # for arranging multiple ggplots
library(pwr) # for analytical power analysis
Bonus: How many Monte-Carlo iterations are necessary?
Reading/working time: ~25 min.
To find the sample size needed for a study, we have previously use, say, 1000 iterations of data simulation and analysis, and varied the sample size n from, say, 100 to 1000, every 50, to find where the 80% power threshold was crossed (see LM1.qmd#sample-size-planning-find-the-necessary-sample-size). But is 1000 iterations giving a precise enough result?
Using the optimized code, we can explore how many Monte-Carlo iterations are necessary to get stable computational results: we can re-run the same simulation with the same sample size and see how much random variation is between results!
Let’s start with 1000 iterations (at n = 100, and 10 repetitions of the same power analysis):
set.seed(0xBEEF)
<- 1000
iterations
# CHANGE: do the same sample size repeatedly, and see how much different runs deviate.
<- rep(100, 10)
ns
<- data.frame()
result
for (n in ns) {
<- cbind(
x rep(1, n),
c(rep(0, n/2), rep(1, n/2))
)
<- rep(NA, iterations)
p_values
for (i in 1:iterations) {
<- 23 - 3*x[, 2] + rnorm(n, mean=0, sd=sqrt(117))
y
<- RcppArmadillo::fastLmPure(x, y)
mdl <- 2*pt(abs(mdl$coefficients/mdl$stderr), mdl$df.residual, lower.tail=FALSE)
pval
<- pval[2]
p_values[i]
}
<- rbind(result, data.frame(n = n, power = sum(p_values < .005)/iterations))
result
}
result
n power
1 100 0.065
2 100 0.070
3 100 0.068
4 100 0.066
5 100 0.067
6 100 0.073
7 100 0.071
8 100 0.070
9 100 0.071
10 100 0.063
As you can see, the power estimates show some variance, ranging from 0.063 to 0.073. This can be formalized as the Monte-Carlo error (MCE), which is define as “the standard deviation of the Monte Carlo estimator, taken across hypothetical repetitions of the simulation” (Koehler et al., 2009). With 1000 iterations (and 10 repetitions), this is:
sd(result$power) |> round(4)
[1] 0.0031
We only computed 10 repetitions of our power estimate, hence the MCE estimate is quite unstable. In the next computation, we will compute 100 repetitions of each power estimate (all with the same simulated sample size).
How much do we have to increase the iterations
to achieve a MCE smaller than, say, 0.005 (i.e, an SD of +/- 0.5% of the power estimate)?
Let’s loop through increasing iterations (this takes a few minutes):
<- seq(1000, 6000, by=1000)
iterations
# let's have 100 repetitions to get sufficiently stable MCE estimates
<- rep(100, 100)
ns <- data.frame()
result
for (it in iterations) {
# print(it) uncomment for showing the progress
for (n in ns) {
<- cbind(
x rep(1, n),
c(rep(0, n/2), rep(1, n/2))
)
<- rep(NA, it)
p_values
for (i in 1:it) {
<- 23 - 3*x[, 2] + rnorm(n, mean=0, sd=sqrt(117))
y
<- RcppArmadillo::fastLmPure(x, y)
mdl <- 2*pt(abs(mdl$coefficients/mdl$stderr), mdl$df.residual, lower.tail=FALSE)
pval
<- pval[2]
p_values[i]
}
<- rbind(result, data.frame(iterations = it, n = n, power = sum(p_values < .005)/it))
result
}
}
# We can compute the exact power with the analytical solution:
<- pwr.t.test(d = 3 / sqrt(117), sig.level = 0.005, n = 50)
exact_power
<- ggplot(result, aes(x=iterations, y=power)) + stat_summary(fun.data=mean_cl_normal) + ggtitle("Power estimate (error bars = SD)") + geom_hline(yintercept = exact_power$power, colour = "blue", linetype = "dashed")
p1
<- ggplot(result, aes(x=iterations, y=power)) + stat_summary(fun="sd", geom="point") + ylab("MCE") + ggtitle("Monte Carlo Error")
p2
/p2 p1
As you can see, the MCE gets smaller with increasing iterations. The desired precision of MCE <= .005 can be achieved at around 3000 iterations (the dashed blue line is the exact power estimate from the analytical solution). While precision increases quickly by going from 1000 to 2000 iterations, further improvements are costly in terms of computation time. In sum, 3000 iterations seems to be a good compromise for this specific power simulation.
In the lower plot, you can also see that the MCE estimate itself is a bit wiggly - we would expect a smooth curve. It suffers from meta-MCE! We could increase the precision of the MCE estimate by increasing the number of repetitions (currently at 100).