Skill Tracer
Skills › Scientific Agent Skills › statistical-power › simulation_based_power.md

simulation_based_power.md

144 lines · skills/statistical-power/references/simulation_based_power.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Simulation-Based (Monte Carlo) Power
2
3Simulation estimates a specified analysis procedure under an assumed
4data-generating process. It is useful when analytical methods omit important
5design features; it is only as credible as those assumptions. Several GLM,
6cluster, repeated-measures, and survival designs do have analytical approximations.
7Use one when it matches the estimand and planned analysis; otherwise simulate.
8

Simulation-Based (Monte Carlo) Power

Simulation estimates a specified analysis procedure under an assumed data-generating process. It is useful when analytical methods omit important design features; it is only as credible as those assumptions. Several GLM, cluster, repeated-measures, and survival designs do have analytical approximations. Use one when it matches the estimand and planned analysis; otherwise simulate.

Raw lines 1 to 8
9## The recipe (always the same)
111. **Simulate** a dataset of size *n* from your assumed truth: the effect you want
12 to detect, plus realistic structure (baseline rates, residual SD, cluster random
13 effects, dropout, covariate distributions).
142. **Analyze** it with the *exact* model/test planned for the real study.
153. **Repeat** R times. Power = fraction of replicates where the test is significant.
16 Use R ≥ 1,000; use 5,000–10,000 for a stable estimate near the 80% decision point.
18Always report the **Monte Carlo confidence interval** on the estimate (the
19`scripts/simulate_power.py` harness returns a Wilson interval). With R = 1,000 the
20±2 SE width near p = 0.8 is roughly ±0.025, so don't over-interpret 0.81 vs 0.79.

The recipe (always the same)

  1. Simulate a dataset of size n from your assumed truth: the effect you want to detect, plus realistic structure (baseline rates, residual SD, cluster random effects, dropout, covariate distributions).
  2. Analyze it with the exact model/test planned for the real study.
  3. Repeat R times. Power = fraction of replicates where the test is significant. Use R ≥ 1,000; use 5,000–10,000 for a stable estimate near the 80% decision point.

Always report the Monte Carlo confidence interval on the estimate (the scripts/simulate_power.py harness returns a Wilson interval). With R = 1,000 the ±2 SE width near p = 0.8 is roughly ±0.025, so don't over-interpret 0.81 vs 0.79.

Raw lines 9 to 21
Runs code scripts/simulate_power.py :19
22## Using the harness
24`scripts/simulate_power.py` gives you `simulate_power()` and `find_sample_size()`.
25You supply a function `gen_and_test(n, rng) -> bool` that builds one dataset, runs
26the analysis, and returns whether it was significant. The `rng` is a seeded
27`numpy.random.Generator` so each replicate consumes fresh pseudorandom draws. Reproduction also requires
28recording software versions; seeded generator streams need not be identical
29across future NumPy versions.
31```python
32from simulate_power import (simulate_power, find_sample_size,
33 example_two_group_difference)
35alpha = 0.05
36# n is per group; use this as a runnable template and replace the generator/test.
37gen_and_test = example_two_group_difference(effect=0.5, sd=1.0, alpha=alpha)
38est = simulate_power(gen_and_test, n=64, n_sims=2000, alpha=alpha, seed=0)
39print(est)
41# A candidate, not a guarantee of globally minimal n under Monte Carlo noise.
42n, est = find_sample_size(gen_and_test, target_power=0.80, n_sims=1000,
43 alpha=alpha, lo=40, hi=100, seed=0, verbose=False)
44# Check neighboring sizes with more replicates and an independent seed.
45checks = [simulate_power(gen_and_test, n=j, n_sims=5000,
46 alpha=alpha, seed=2026) for j in (n - 1, n, n + 1)]
47```
49The file ships four adaptable examples: two-group difference (a sanity check
50against the closed-form t-test), logistic regression, a cluster-randomized trial
51with an ICC, and a repeated-measures linear mixed model. Copy the closest one and
52edit the data-generating block.

Using the harness

scripts/simulate_power.py gives you simulate_power() and find_sample_size(). You supply a function gen_and_test(n, rng) -> bool that builds one dataset, runs the analysis, and returns whether it was significant. The rng is a seeded numpy.random.Generator so each replicate consumes fresh pseudorandom draws. Reproduction also requires recording software versions; seeded generator streams need not be identical across future NumPy versions.

from simulate_power import (simulate_power, find_sample_size,
                            example_two_group_difference)

alpha = 0.05
# n is per group; use this as a runnable template and replace the generator/test.
gen_and_test = example_two_group_difference(effect=0.5, sd=1.0, alpha=alpha)
est = simulate_power(gen_and_test, n=64, n_sims=2000, alpha=alpha, seed=0)
print(est)

# A candidate, not a guarantee of globally minimal n under Monte Carlo noise.
n, est = find_sample_size(gen_and_test, target_power=0.80, n_sims=1000,
                          alpha=alpha, lo=40, hi=100, seed=0, verbose=False)
# Check neighboring sizes with more replicates and an independent seed.
checks = [simulate_power(gen_and_test, n=j, n_sims=5000,
                         alpha=alpha, seed=2026) for j in (n - 1, n, n + 1)]

The file ships four adaptable examples: two-group difference (a sanity check against the closed-form t-test), logistic regression, a cluster-randomized trial with an ICC, and a repeated-measures linear mixed model. Copy the closest one and edit the data-generating block.

Raw lines 22 to 53
Runs code scripts/simulate_power.py :24
54## When simulation is useful
56| Design / analysis | Features to include | What to simulate |
57|-------------------|----------------|------------------|
58| Logistic / Poisson regression | Power depends on the full covariate distribution | Generate predictors, compute the linear predictor, draw the outcome, fit the GLM |
59| Mixed-effects / repeated measures | Random effects + within-subject correlation | Draw subject/cluster random effects, then observations; fit `mixedlm` |
60| Cluster-randomized trial | ICC inflates variance; clusters are the unit | Cluster random intercepts via ICC; fit the planned cluster-aware analysis; check small-cluster inference |
61| Survival (Cox / log-rank) | Censoring and event-time distribution | Draw event and censoring times; fit `lifelines` CoxPH or run a log-rank test |
62| Interaction terms | The target is the interaction contrast, not either main effect | Generate the factorial structure and the interaction effect; test that coefficient |
63| Mediation | Product-of-coefficients null is non-normal | Simulate the path model; bootstrap or test the indirect effect |
64| Non-standard / custom test | Analytical assumptions do not match the design | Whatever your analysis script does |

When simulation is useful

Design / analysisFeatures to includeWhat to simulate
Logistic / Poisson regressionPower depends on the full covariate distributionGenerate predictors, compute the linear predictor, draw the outcome, fit the GLM
Mixed-effects / repeated measuresRandom effects + within-subject correlationDraw subject/cluster random effects, then observations; fit mixedlm
Cluster-randomized trialICC inflates variance; clusters are the unitCluster random intercepts via ICC; fit the planned cluster-aware analysis; check small-cluster inference
Survival (Cox / log-rank)Censoring and event-time distributionDraw event and censoring times; fit lifelines CoxPH or run a log-rank test
Interaction termsThe target is the interaction contrast, not either main effectGenerate the factorial structure and the interaction effect; test that coefficient
MediationProduct-of-coefficients null is non-normalSimulate the path model; bootstrap or test the indirect effect
Non-standard / custom testAnalytical assumptions do not match the designWhatever your analysis script does
Raw lines 54 to 65
66## Key correctness points
68- **Analyze exactly as planned.** If the real analysis adjusts for covariates,
69 include them in the simulation. If it uses a robust SE or a specific correction,
70 apply it in `gen_and_test`. The whole value of simulation is fidelity to the plan.
71- **Handle estimation failures explicitly.** The bundled examples check convergence,
72 separation/Hessian failures, and finite p-values. They raise `SimulationFitError`
73 for anticipated failures; the harness retains those runs in the denominator as
74 non-rejections and reports `n_failures` plus `failure_reasons`. Unexpected
75 exceptions propagate. Counting failures this way defines an unconditional
76 operational rejection rate; it does not prove valid statistical inference.
77 Frequent failures require revising the planned analysis/design, not silently
78 dropping replicates. Warnings are summarized in `n_warned` and `warning_counts`;
79 inspect their categories even when a fit reports convergence.
80- **Watch Type I error too.** As a check, simulate under the *null* (effect = 0)
81 and confirm the rejection rate ≈ α. If it's inflated (common with small-cluster
82 mixed models or naive cluster SEs), your planned analysis is anticonservative and
83 the power number is meaningless until you fix the analysis.
84- **Separate search from confirmation.** A fixed seed makes results reproducible,
85 but does not remove noise or make power monotone in n. Bisection is only a
86 heuristic for a design with increasing power. Confirm the selected n and nearby
87 feasible allocations with larger runs and fresh seeds. The ordinary Wilson CI
88 at an adaptively selected n is not selection-adjusted. If discrete tests,
89 allocation rounding, or failures cause nonmonotonicity, inspect a grid instead.
90- **Keep alpha in the callback.** The harness validates its `alpha` argument but
91 does not forward it or threshold p-values. The callback must return bool, and
92 must use the same planned threshold/procedure; raw p-values raise `TypeError`.
93- **Distinguish conditional assumptions.** Logistic `beta` is per raw predictor
94 unit; `base_rate` is risk at x=0. `n` is total observations for that example,
95 clusters per arm for the cluster example, and subjects for repeated measures.
96 The mixed-model examples use REML random intercepts and asymptotic Wald tests;
97 they do not implement small-sample df corrections or random slopes.

Key correctness points

  • Analyze exactly as planned. If the real analysis adjusts for covariates, include them in the simulation. If it uses a robust SE or a specific correction, apply it in gen_and_test. The whole value of simulation is fidelity to the plan.
  • Handle estimation failures explicitly. The bundled examples check convergence, separation/Hessian failures, and finite p-values. They raise SimulationFitError for anticipated failures; the harness retains those runs in the denominator as non-rejections and reports n_failures plus failure_reasons. Unexpected exceptions propagate. Counting failures this way defines an unconditional operational rejection rate; it does not prove valid statistical inference. Frequent failures require revising the planned analysis/design, not silently dropping replicates. Warnings are summarized in n_warned and warning_counts; inspect their categories even when a fit reports convergence.
  • Watch Type I error too. As a check, simulate under the null (effect = 0) and confirm the rejection rate ≈ α. If it's inflated (common with small-cluster mixed models or naive cluster SEs), your planned analysis is anticonservative and the power number is meaningless until you fix the analysis.
  • Separate search from confirmation. A fixed seed makes results reproducible, but does not remove noise or make power monotone in n. Bisection is only a heuristic for a design with increasing power. Confirm the selected n and nearby feasible allocations with larger runs and fresh seeds. The ordinary Wilson CI at an adaptively selected n is not selection-adjusted. If discrete tests, allocation rounding, or failures cause nonmonotonicity, inspect a grid instead.
  • Keep alpha in the callback. The harness validates its alpha argument but does not forward it or threshold p-values. The callback must return bool, and must use the same planned threshold/procedure; raw p-values raise TypeError.
  • Distinguish conditional assumptions. Logistic beta is per raw predictor unit; base_rate is risk at x=0. n is total observations for that example, clusters per arm for the cluster example, and subjects for repeated measures. The mixed-model examples use REML random intercepts and asymptotic Wald tests; they do not implement small-sample df corrections or random slopes.
Raw lines 66 to 98
99## Modeling realistic complications
101- **Dropout.** Either simulate missingness directly (drop rows / occasions under
102 the assumed mechanism, then apply the planned missing-data analysis — estimates rejection rates
103 under that assumed mechanism, but does not itself estimate bias or validate MAR/MNAR assumptions), or compute the analyzed n and inflate the enrolled n by
104 `1/(1−dropout)`.
105- **Clustering / ICC.** Split total variance into between-cluster (τ²) and residual
106 (σ²) with `ICC = τ²/(τ²+σ²)`, draw a cluster random effect ~ N(0, τ), add it to
107 every member of the cluster. See `example_cluster_randomized`.
108- **Unequal allocation / stratification.** Generate the exact group sizes and strata
109 the design will produce; don't assume balance the design won't deliver.
110- **Repeated measures.** Subject random intercept (and slope, if relevant) plus a
111 within-subject residual; the correlation is `τ²/(τ²+σ²)` only for the random-intercept, independent-homoskedastic-residual example. Time-dependent residual correlation/random slopes require a richer generator and fit.

Modeling realistic complications

  • Dropout. Either simulate missingness directly (drop rows / occasions under the assumed mechanism, then apply the planned missing-data analysis — estimates rejection rates under that assumed mechanism, but does not itself estimate bias or validate MAR/MNAR assumptions), or compute the analyzed n and inflate the enrolled n by 1/(1−dropout).
  • Clustering / ICC. Split total variance into between-cluster (τ²) and residual (σ²) with ICC = τ²/(τ²+σ²), draw a cluster random effect ~ N(0, τ), add it to every member of the cluster. See example_cluster_randomized.
  • Unequal allocation / stratification. Generate the exact group sizes and strata the design will produce; don't assume balance the design won't deliver.
  • Repeated measures. Subject random intercept (and slope, if relevant) plus a within-subject residual; the correlation is τ²/(τ²+σ²) only for the random-intercept, independent-homoskedastic-residual example. Time-dependent residual correlation/random slopes require a richer generator and fit.
Raw lines 99 to 112
113## Reporting a simulation-based power analysis
115State enough that someone could rerun it:
117```text
118Power was estimated at [n and its unit] with [R] independent replicates at each
119prespecified sample size. The generator assumed [effect in raw units], [covariate
120distribution], [conditional baseline risk / residual variance], [ICC, cluster sizes],
121and [dropout/censoring]. The analysis used [model, estimand, test, multiplicity rule],
122matching the protocol. At alpha [value], [hits]/[all R] runs rejected the null
123([power], 95% Wilson Monte Carlo CI [low, high]); [failures] fits failed and were
124retained as non-rejections. Warning categories were [counts]. Null simulations
125at the same design gave [Type I error and MC CI]. Candidate n was confirmed with
126[fresh seed and R]. Assumption sensitivity covered [ranges]. Code/versions: [link].
127```
129Replace every bracket with executed results; this template contains no invented
130study outcome. Monte Carlo CIs quantify sampling noise at fixed assumptions,
131not uncertainty in effects, nuisance parameters, or population transportability.

Reporting a simulation-based power analysis

State enough that someone could rerun it:

Power was estimated at [n and its unit] with [R] independent replicates at each
prespecified sample size. The generator assumed [effect in raw units], [covariate
distribution], [conditional baseline risk / residual variance], [ICC, cluster sizes],
and [dropout/censoring]. The analysis used [model, estimand, test, multiplicity rule],
matching the protocol. At alpha [value], [hits]/[all R] runs rejected the null
([power], 95% Wilson Monte Carlo CI [low, high]); [failures] fits failed and were
retained as non-rejections. Warning categories were [counts]. Null simulations
at the same design gave [Type I error and MC CI]. Candidate n was confirmed with
[fresh seed and R]. Assumption sensitivity covered [ranges]. Code/versions: [link].

Replace every bracket with executed results; this template contains no invented study outcome. Monte Carlo CIs quantify sampling noise at fixed assumptions, not uncertainty in effects, nuisance parameters, or population transportability.

Raw lines 113 to 132
133## Current primary sources and execution scope
135Reviewed 2026-10-01; all four bundled generators ran on small synthetic data with
136statsmodels 0.15.0 / SciPy 1.18.1 / NumPy 2.5.3 / pandas 2.3.3. The pooled t-test
137has a native null/power calibration check; small smoke runs of other models verify
138software mechanics only, not study-specific Type I error or sample-size adequacy.
140- [SciPy ttest_ind](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.ttest_ind.html): pooled variance is `equal_var=True`; Welch is a different analysis.
141- [statsmodels Logit.fit](https://www.statsmodels.org/stable/generated/statsmodels.discrete.discrete_model.Logit.fit.html), [MixedLM.fit](https://www.statsmodels.org/stable/generated/statsmodels.regression.mixed_linear_model.MixedLM.fit.html), and [fit pitfalls](https://www.statsmodels.org/stable/pitfalls.html).
142- [NumPy Generator](https://numpy.org/doc/stable/reference/random/generator.html): RNG methods and version compatibility.
143- [lifelines statistics](https://lifelines.readthedocs.io/en/latest/lifelines.statistics.html): logrank tests and `sample_size_necessary_under_cph` / `power_under_cph` offer specific proportional-hazards approximations. [CoxPHFitter](https://lifelines.readthedocs.io/en/latest/fitters/regression/CoxPHFitter.html) supports right-censored time/event fitting. These survival extensions are guidance, not a bundled calibrated survival generator; informative censoring and nonproportional hazards require additional modeling.
144- [Arnold et al. (2011)](https://doi.org/10.1186/1471-2288-11-94): simulation design and reporting.

Current primary sources and execution scope

Reviewed 2026-10-01; all four bundled generators ran on small synthetic data with statsmodels 0.15.0 / SciPy 1.18.1 / NumPy 2.5.3 / pandas 2.3.3. The pooled t-test has a native null/power calibration check; small smoke runs of other models verify software mechanics only, not study-specific Type I error or sample-size adequacy.

Raw lines 133 to 144