**Approach (brief)** Use the O'Brien–Fleming alpha-spending rule which allocates cumulative alpha at look i as 2*(1 - Phi( z_alpha/ sqrt(t_i) )) inverted; equivalently compute nominal two-sided alpha_i for each look so that the overall two-sided Type I error is alpha across k looks with equally spaced information times t_i = i/k. We'll compute boundaries (two-sided) by solving for z_i such that cumulative spent equals the OBF spending function: alpha_spent(t) = 2*(1 - Phi( z_alpha / sqrt(t) )). Then nominal alpha_i = 2*(1 - Phi(z_i)), where z_i is boundary at look i. Implementation uses root finding per look.python
import math
from scipy.stats import norm
from scipy.optimize import brentq
def obrien_fleming_bounds(alpha, k):
"""
Returns list of two-sided nominal alphas for k interim looks (including final).
Assumes equally spaced information times t_i = i/k and overall two-sided alpha.
"""
if k < 1 or alpha <= 0 or alpha >= 1:
raise ValueError("k must be >=1 and 0<alpha<1")
t = [(i+1)/k for i in range(k)]
z_alpha = norm.ppf(1 - alpha/2)
def spent_at_t(z, tt):
# given boundary z at information fraction tt, OBF cumulative spent:
return 2 * (1 - norm.cdf(z / math.sqrt(tt)))
bounds = []
# For each look solve for z_i such that spent_at_t(z_i, t_i) == target_spent_i
# target_spent_i is the OBF cumulative alpha at t_i; but OBF defines target via z_alpha
for tt in t:
target = spent_at_t(z_alpha, tt)
# z decreases as target increases; search z in (0, 10)
z_i = brentq(lambda z: spent_at_t(z, tt) - target, 1e-8, 10.0)
alpha_i = 2 * (1 - norm.cdf(z_i)) # nominal two-sided alpha at this look
bounds.append(alpha_i)
return bounds
**Explanation & usage**- Each alpha_i is the nominal two-sided significance threshold to apply at look i; compare two-sided p-value <= alpha_i to stop. Internally this corresponds to z boundary z_i = norm.ppf(1 - alpha_i/2).- Complexity: O(k * root_iters), negligible for typical k.**Assumptions & limitations**- Assumes equally spaced information fractions. For unequal spacing, replace t accordingly.- Uses asymptotic normality; valid for large-sample test statistics (z-statistics).- Nominal alphas are two-sided. For one-sided testing adapt alpha allocations.- Numerical root-finding may need bounds tuning for extreme alphas.**Automated monitoring recommendations**- Pre-compute bounds and store z thresholds. At each look compute test z (or p); if |z| >= z_i (or p <= alpha_i) trigger stop. Log look number, information fraction, test statistic, and decision. Include guardrails for late looks and adjustments if info fractions deviate from plan.