Load useful packages
library(pbapply)
require(pbapply) # Adds progress bars to simulation runs
require(ggplot2) # Helps generate plots
Function running a single simulated sample, and testing to see if it “succeeded”
single_test <- function(n, beta_treat, sd, CI_width){
# Generate sample from our "universe" set by formulas
treat <- sample(0:1, n, replace = TRUE)
y <- treat*beta_treat + rnorm(n, 0, sd)
df <- data.frame(treat, y)
# Runs our statistical test of choice. In this case it's OLS...
test <- lm(df$y~df$treat) # ... where we run our analyis test of choice ...
CI <- confint(test)[2, ] # ... where we pull the 95% CI of the variable of interest ...
CI_width_test <- CI[2]-CI[1] # ... and (in the example case) checks how wide that CI is
# Return whether the test "succeeded" as a 1 if yes, 0 if no
return(as.integer(CI_width_test< = CI_width))
# Note: Slightly more common case where your "success" is p< = 0.05
# In that case you might have the following:
# test <- summary(lm(df$y~df$treat))
# p_value <- test$coefficients[2, 4]
# p_value_test <- p_value< = 0.05
# return(as.integer(p_value_test))
}
Obtain power if sample size is 150, true beta = 2, sd = 3, and CI width threshold of interest = 2
iters <- 10000
mean(replicate(iters, single_test(n = 150, beta_treat = 2, sd = 3, CI_width = 2)))
Same as above, but wrapped in a function
power_test <- function(iters, n, beta_treat, sd, CI_width){
# Repeat/replicate single_test function (consider using pbreplicate)
simulated_tests <- replicate(iters, single_test(n, beta_treat, sd, CI_width))
# The power is the proportion of "successes" (i.e. the mean of 1's and 0's)
power <- mean(simulated_tests)
# Output a one-line data frame with the parameters and results of this test
data.frame(n, beta_treat, sd, CI_width, iters, power)
}
Check power at different sample sizes
# Generate a vector of every 10th n from 100:200
ns <- seq(100, 200, 10)
# Run our power test at each of those ns
tests <- pblapply(ns, function(x) power_test(iters = 1000, n = x, beta_treat = 2, sd = 3, CI_width = 2))
# Put them all together int a dataset
df <- do.call("rbind", tests)
# Chart it!
ggplot(data = df, aes(x = n, y = power)) +
geom_point() +
geom_line(color = "blue") +
theme_light()
PBMAPPLY FUNCTION
Check power for many combinations of parameters!

LS0tDQp0aXRsZTogIlNhbXBsZSBzaXplIGV4YW1wbGVzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyBMb2FkIHVzZWZ1bCBwYWNrYWdlcw0KDQpgYGB7cn0NCmxpYnJhcnkocGJhcHBseSkNCiAgcmVxdWlyZShwYmFwcGx5KSAjIEFkZHMgcHJvZ3Jlc3MgYmFycyB0byBzaW11bGF0aW9uIHJ1bnMNCiAgcmVxdWlyZShnZ3Bsb3QyKSAjIEhlbHBzIGdlbmVyYXRlIHBsb3RzDQpgYGANCg0KDQojIEZ1bmN0aW9uIHJ1bm5pbmcgYSBzaW5nbGUgc2ltdWxhdGVkIHNhbXBsZSwgIGFuZCB0ZXN0aW5nIHRvIHNlZSBpZiBpdCAic3VjY2VlZGVkIg0KDQpgYGB7cn0NCnNpbmdsZV90ZXN0IDwtIGZ1bmN0aW9uKG4sIGJldGFfdHJlYXQsIHNkLCBDSV93aWR0aCl7DQogICMgR2VuZXJhdGUgc2FtcGxlIGZyb20gb3VyICJ1bml2ZXJzZSIgc2V0IGJ5IGZvcm11bGFzDQogICAgdHJlYXQgPC0gc2FtcGxlKDA6MSwgbiwgcmVwbGFjZSA9IFRSVUUpDQogICAgeSA8LSB0cmVhdCpiZXRhX3RyZWF0ICsgcm5vcm0obiwgMCwgc2QpDQogICAgZGYgPC0gZGF0YS5mcmFtZSh0cmVhdCwgeSkNCiAgIyBSdW5zIG91ciBzdGF0aXN0aWNhbCB0ZXN0IG9mIGNob2ljZS4gSW4gdGhpcyBjYXNlIGl0J3MgT0xTLi4uDQogICAgdGVzdCA8LSBsbShkZiR5fmRmJHRyZWF0KSAjIC4uLiB3aGVyZSB3ZSBydW4gb3VyIGFuYWx5aXMgdGVzdCBvZiBjaG9pY2UgLi4uDQogICAgQ0kgPC0gY29uZmludCh0ZXN0KVsyLCBdICMgLi4uIHdoZXJlIHdlIHB1bGwgdGhlIDk1JSBDSSBvZiB0aGUgdmFyaWFibGUgb2YgaW50ZXJlc3QgLi4uDQogICAgQ0lfd2lkdGhfdGVzdCA8LSBDSVsyXS1DSVsxXSAjIC4uLiBhbmQgKGluIHRoZSBleGFtcGxlIGNhc2UpIGNoZWNrcyBob3cgd2lkZSB0aGF0IENJIGlzDQogICMgUmV0dXJuIHdoZXRoZXIgdGhlIHRlc3QgInN1Y2NlZWRlZCIgYXMgYSAxIGlmIHllcywgIDAgaWYgbm8NCiAgICByZXR1cm4oYXMuaW50ZWdlcihDSV93aWR0aF90ZXN0PCA9IENJX3dpZHRoKSkNCiAgICANCiAgIyBOb3RlOiBTbGlnaHRseSBtb3JlIGNvbW1vbiBjYXNlIHdoZXJlIHlvdXIgInN1Y2Nlc3MiIGlzIHA8ID0gMC4wNQ0KICAjIEluIHRoYXQgY2FzZSB5b3UgbWlnaHQgaGF2ZSB0aGUgZm9sbG93aW5nOg0KICAgICMgdGVzdCA8LSBzdW1tYXJ5KGxtKGRmJHl+ZGYkdHJlYXQpKQ0KICAgICMgcF92YWx1ZSA8LSB0ZXN0JGNvZWZmaWNpZW50c1syLCA0XQ0KICAgICMgcF92YWx1ZV90ZXN0IDwtIHBfdmFsdWU8ID0gMC4wNQ0KICAgICMgcmV0dXJuKGFzLmludGVnZXIocF92YWx1ZV90ZXN0KSkNCn0NCmBgYA0KDQoNCg0KIyBPYnRhaW4gcG93ZXIgaWYgc2FtcGxlIHNpemUgaXMgMTUwLCAgdHJ1ZSBiZXRhICA9ICAyLCAgc2QgID0gIDMsICBhbmQgQ0kgd2lkdGggdGhyZXNob2xkIG9mIGludGVyZXN0ICA9ICAyDQogIA0KYGBge3J9DQoNCiAgaXRlcnMgPC0gMTAwMDANCiAgbWVhbihyZXBsaWNhdGUoaXRlcnMsIHNpbmdsZV90ZXN0KG4gPSAxNTAsIGJldGFfdHJlYXQgPSAyLCBzZCA9IDMsIENJX3dpZHRoID0gMikpKQ0KDQpgYGANCg0KDQojIFNhbWUgYXMgYWJvdmUsICBidXQgd3JhcHBlZCBpbiBhIGZ1bmN0aW9uDQpgYGB7cn0NCnBvd2VyX3Rlc3QgPC0gZnVuY3Rpb24oaXRlcnMsIG4sIGJldGFfdHJlYXQsIHNkLCBDSV93aWR0aCl7DQogICMgUmVwZWF0L3JlcGxpY2F0ZSBzaW5nbGVfdGVzdCBmdW5jdGlvbiAoY29uc2lkZXIgdXNpbmcgcGJyZXBsaWNhdGUpDQogICAgc2ltdWxhdGVkX3Rlc3RzIDwtIHJlcGxpY2F0ZShpdGVycywgc2luZ2xlX3Rlc3QobiwgYmV0YV90cmVhdCwgc2QsIENJX3dpZHRoKSkNCiAgIyBUaGUgcG93ZXIgaXMgdGhlIHByb3BvcnRpb24gb2YgInN1Y2Nlc3NlcyIgKGkuZS4gdGhlIG1lYW4gb2YgMSdzIGFuZCAwJ3MpDQogICAgcG93ZXIgPC0gbWVhbihzaW11bGF0ZWRfdGVzdHMpDQogICMgT3V0cHV0IGEgb25lLWxpbmUgZGF0YSBmcmFtZSB3aXRoIHRoZSBwYXJhbWV0ZXJzIGFuZCByZXN1bHRzIG9mIHRoaXMgdGVzdA0KICAgIGRhdGEuZnJhbWUobiwgYmV0YV90cmVhdCwgc2QsIENJX3dpZHRoLCBpdGVycywgcG93ZXIpDQp9DQpgYGANCg0KIyBDaGVjayBwb3dlciBhdCBkaWZmZXJlbnQgc2FtcGxlIHNpemVzDQpgYGB7cn0NCiAgIyBHZW5lcmF0ZSBhIHZlY3RvciBvZiBldmVyeSAxMHRoIG4gZnJvbSAxMDA6MjAwDQogICAgbnMgPC0gc2VxKDEwMCwgMjAwLCAxMCkgDQogICMgUnVuIG91ciBwb3dlciB0ZXN0IGF0IGVhY2ggb2YgdGhvc2UgbnMNCiAgICB0ZXN0cyA8LSBwYmxhcHBseShucywgZnVuY3Rpb24oeCkgcG93ZXJfdGVzdChpdGVycyA9IDEwMDAsIG4gPSB4LCBiZXRhX3RyZWF0ID0gMiwgc2QgPSAzLCBDSV93aWR0aCA9IDIpKQ0KICAjIFB1dCB0aGVtIGFsbCB0b2dldGhlciBpbnQgYSBkYXRhc2V0DQogICAgZGYgPC0gZG8uY2FsbCgicmJpbmQiLCB0ZXN0cykNCiAgIyBDaGFydCBpdCENCiAgICBnZ3Bsb3QoZGF0YSA9IGRmLCBhZXMoeCA9IG4sIHkgPSBwb3dlcikpICsNCiAgICAgIGdlb21fcG9pbnQoKSArDQogICAgICBnZW9tX2xpbmUoY29sb3IgPSAiYmx1ZSIpICsNCiAgICAgIHRoZW1lX2xpZ2h0KCkNCmBgYA0KDQoNCiMgUEJNQVBQTFkgRlVOQ1RJT04NCmBgYHtyfQ0KcGJtYXBwbHkgPC0gZnVuY3Rpb24oRlVOLCAuLi4sIE1vcmVBcmdzID0gTlVMTCkgew0KICBhcmdzIDwtIGxpc3QoLi4uKQ0KICByZXN1bHQgPC0gbGlzdCgpDQogIA0KICBmb3IoaSBpbiBzZXFfYWxvbmcoYXJnc1tbMV1dKSkgew0KICAgIGFyZ0xpc3QgPC0gbGlzdCgpDQogICAgZm9yKGogaW4gMTpsZW5ndGgoYXJncykpIHsNCiAgICAgIGFyZ0xpc3RbW2pdXSA8LSBhcmdzW1tqXV1bW2ldXQ0KICAgIH0NCiAgICByZXN1bHRbW2ldXSA8LSBkby5jYWxsKEZVTiwgYyhhcmdMaXN0LCBNb3JlQXJncykpDQogIH0NCiAgcmVzdWx0DQp9DQpgYGANCg0KDQojIENoZWNrIHBvd2VyIGZvciBtYW55IGNvbWJpbmF0aW9ucyBvZiBwYXJhbWV0ZXJzIQ0KYGBge3J9DQogICMgR2VuZXJhdGUgYSBkYXRhIGZyYW1lIHdpdGggZXZlcnkgY29tYmluYXRpb24gb2YgcGFyYW1ldGVycyBvZiBpbnRlcmVzdA0KICAgIHBhcmFtcyA8LSBleHBhbmQuZ3JpZChpdGVycyA9IDEwMCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIG4gPSBzZXEoMTAwLCAyMDAsIDUpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgYmV0YV90cmVhdCA9IDIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBzZCA9IHNlcSgxLCA1LCAuMSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBDSV93aWR0aCA9IDIpDQogICMgUnVuIHRoZW0gYWxsIHVzaW5nIG1hcHBseSAocGJtYXBwbHkgdG8gZ2V0IHByb2dyZXNzIGJhciBhbmQgdGltZSBlc3RpbWF0ZXMpDQogICAgdGVzdHMgPC0gcGJtYXBwbHkocG93ZXJfdGVzdCwgDQogICAgICAgICAgICAgICAgICAgICAgcGFyYW1zJGl0ZXJzLCANCiAgICAgICAgICAgICAgICAgICAgICBuID0gcGFyYW1zJG4sIA0KICAgICAgICAgICAgICAgICAgICAgIGJldGFfdHJlYXQgPSBwYXJhbXMkYmV0YV90cmVhdCwgDQogICAgICAgICAgICAgICAgICAgICAgc2QgPSBwYXJhbXMkc2QsIA0KICAgICAgICAgICAgICAgICAgICAgIENJX3dpZHRoID0gcGFyYW1zJENJX3dpZHRoLCANCiAgICAgICAgICAgICAgICAgICAgICBTSU1QTElGWSAgPSAgRkFMU0UpICMgRW5zdXJlcyB0aGF0IHdlIGdldCBhIGxpc3Qgb3V0cHV0LCAganVzdCBsaWtlIGxhcHBseQ0KICAjIENvbWJpbmUgdGhlbQ0KICAgIGRmIDwtIGRvLmNhbGwoInJiaW5kIiwgdGVzdHMpDQogICMgUGxvdCB0aGVtDQogICAgZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHggPSBuLCB5ID0gc2QsIHogPSBwb3dlcikpICsNCiAgICAgIGdlb21fcmFzdGVyKGFlcyhmaWxsID0gcG93ZXIpLCBpbnRlcnBvbGF0ZSAgPSAgVFJVRSkgKw0KICAgICAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3VycyA9IGMoIiNGRjAwMDBGRiIsICIjRkZGRkZGRkYiLCAiIzAwMDBGRkZGIikpKw0KICAgICAgdGhlbWVfbGlnaHQoKSArDQogICAgICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKw0KICAgICAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpDQpgYGANCg0K