ppad-eproc-0.1.0: Anytime-valid sequential testing via e-processes.
Copyright(c) 2026 Jared Tobin
LicenseMIT
MaintainerJared Tobin <jared@ppad.tech>
Safe HaskellNone
LanguageHaskell2010

Numeric.Eproc.Paired

Description

Paired two-sample anytime-valid mean-equality test.

For paired observations (a_t, b_t) where both samples lie in [lo, hi], tests H_0: E[a] = E[b] against H_1: E[a] /= E[b].

The reduction is straightforward: under the null, the differences d_t = a_t - b_t have mean zero, and differences of [lo, hi] values lie in [lo - hi, hi - lo]. So the paired test is just the bounded-mean test (Numeric.Eproc.Bounded) on d_t with null mean 0 and sample bounds [lo - hi, hi - lo].

Pairing is required: independent two-sample testing without alignment would need to bet against a richer alternative (the joint distribution rather than the marginal difference) and is beyond the scope of this module.

Example

Test H_0: E[a] = E[b] for samples in [0, 1] at level alpha = 1e-3 against a stream of paired observations where a runs systematically higher than b:

>>> let cfg = config 0.0 1.0 1.0e-3 Newton
>>> let ps  = take 1000 (cycle [(1, 0), (1, 0), (0, 0), (1, 1)])
>>> decide cfg (foldl' (update cfg) (initial cfg) ps)
Reject
Synopsis

Test configuration and state

data Config Source #

Paired two-sample test configuration. Build with config. Wraps a Config for the underlying difference test.

data State Source #

Streaming paired two-sample test state. Construct with initial and fold paired observations through update.

data Verdict Source #

Test outcome at the current sample count.

Reject means the wealth process has crossed the rejection threshold, so H_0 is rejected at level alpha. Continue means there is not yet enough evidence; collect more samples (or stop and report no rejection -- the type-I error guarantee holds for any stopping rule).

Constructors

Reject 
Continue 

Instances

Instances details
Show Verdict Source # 
Instance details

Defined in Numeric.Eproc.Common

Eq Verdict Source # 
Instance details

Defined in Numeric.Eproc.Common

Methods

(==) :: Verdict -> Verdict -> Bool #

(/=) :: Verdict -> Verdict -> Bool #

Bettor strategies

data Bettor Source #

A predictable bettor.

A bettor describes how, given the history of centred observations z_t (each test module specifies its own centring; see the per-module documentation), the next predictable bet lambda_t is chosen. Predictability -- that is, lambda_t depends only on data observed strictly before step t -- is what makes the resulting wealth process a nonnegative supermartingale under H_0.

For Adaptive and Newton, a safe-bet ceiling lambda_max derived from the test's admissible-observation range is enforced by clipping lambda to [0, lambda_max], so the wealth factor stays nonnegative.

  • Fixed always bets the supplied constant lambda. The wager does not respond to observed data; this strategy is useful only as a baseline.
  • Adaptive is the aGRAPA (approximate growth-rate adaptive predictable plug-in) bettor of Waudby-Smith & Ramdas (2024). It tracks the empirical mean mu and variance sigma^2 of centred observations and bets the Kelly-optimal plug-in lambda* = mu / (sigma^2 + mu^2) clipped to [0, lambda_max]. Fast to compute and competitive in practice.
  • Newton is the online Newton step (ONS) bettor. The per-step log-wealth loss -log(1 + lambda * z) is convex in lambda; ONS performs one Newton step per observation, accumulating squared gradients to scale the update. Achieves logarithmic regret against the best constant bet in hindsight and is in practice the strongest of the three bettors under most signal regimes.

Constructors

Fixed !Double 
Adaptive 
Newton 

Instances

Instances details
Show Bettor Source # 
Instance details

Defined in Numeric.Eproc.Common

Eq Bettor Source # 
Instance details

Defined in Numeric.Eproc.Common

Methods

(==) :: Bettor -> Bettor -> Bool #

(/=) :: Bettor -> Bettor -> Bool #

Construction

config Source #

Arguments

:: Double

sample lower bound lo

-> Double

sample upper bound hi

-> Double

significance level alpha

-> Bettor

bettor strategy

-> Config 

Build a Config for the paired two-sample test.

Bounds lo and hi are the (shared) bounds on the individual a and b samples; the underlying mean test is then configured on the differences, which lie in [lo - hi, hi - lo] with null mean 0.

>>> let cfg = config 0.0 1.0 1.0e-3 Newton

initial :: Config -> State Source #

The initial State for a fresh streaming test.

>>> let s0 = initial cfg

Streaming

update :: Config -> State -> (Double, Double) -> State Source #

Fold one paired observation (a, b) into the running State.

Equivalent to feeding the difference a - b into the underlying bounded-mean test.

>>> let s1 = update cfg s0 (0.3, 0.7)

decide :: Config -> State -> Verdict Source #

Compute the current Verdict from the running State.

Reject iff either directional log-wealth of the underlying bounded-mean test on the differences has crossed log(2 / alpha).

>>> decide cfg s0
Continue

Inspection

log_wealth :: State -> Double Source #

The current log-wealth of the underlying bounded-mean test on the differences.

>>> log_wealth s0
0.0

samples :: State -> Int Source #

The number of paired observations consumed so far.

>>> samples s0
0