ConceptsE-values

E-values

An e-value for a null hypothesis H0H_0 is a nonnegative statistic EE with

EH0[E]≤1.\mathbb{E}_{H_0}[E] \le 1.

Large values are evidence against H0H_0: by Markov’s inequality, PH0(E≥1/δ)≤δP_{H_0}(E \ge 1/\delta) \le \delta. E-values multiply across independent pieces of evidence, stay valid under optional stopping when built as e-processes, allow the significance level to be chosen after the fact, and give false-discovery-rate control under arbitrary dependence. CLI uses them for drift monitors, batch FDR control, and label-efficient calibration. All of it is in cli_sdk.stats.evalues.

Betting martingales

A drift monitor watches a stream of 0/1 outcomes X1,X2,…X_1, X_2, \dots: a miss (the returned set did not contain the label) for a coverage monitor, or a loss for a risk monitor. The null hypothesis is that the rate has not risen above its calibrated value p0p_0:

H0:E[Xt∣X1,…,Xt−1]≤p0for every t.H_0 : \mathbb{E}[X_t \mid X_1, \dots, X_{t-1}] \le p_0 \quad \text{for every } t.

For a betting fraction λ∈[0,1/p0]\lambda \in [0, 1/p_0], define

Mt(λ)=∏i=1t(1+λ(Xi−p0)).M_t(\lambda) = \prod_{i=1}^{t} \big(1 + \lambda (X_i - p_0)\big).

Each factor is nonnegative (because Xi≥0X_i \ge 0 and λp0≤1\lambda p_0 \le 1) and has conditional expectation 1+λ(E[Xt∣past]−p0)≤11 + \lambda(\mathbb{E}[X_t \mid \text{past}] - p_0) \le 1 under H0H_0, so Mt(λ)M_t(\lambda) is a nonnegative supermartingale starting at 1. Read it as the wealth of a gambler who bets a fraction of their wealth, each round, that the next outcome will be a miss: if misses really occur at rate at most p0p_0, the bet cannot be expected to make money.

CLI does not require you to guess the size of the drift. It averages five bets, λk=ck/p0\lambda_k = c_k / p_0 with ck∈{0.10,0.25,0.45,0.65,0.85}c_k \in \{0.10, 0.25, 0.45, 0.65, 0.85\}:

Mt=15∑k=15Mt(λk).M_t = \frac{1}{5}\sum_{k=1}^{5} M_t(\lambda_k).

An average of nonnegative supermartingales is again one, so MtM_t keeps the same guarantee. This is anytime.BettingMartingale.

Ville’s inequality

For any nonnegative supermartingale MtM_t with M0=1M_0 = 1,

PH0(∃ t≥1:Mt≥1/δ)≤δ.P_{H_0}\big(\exists\, t \ge 1 : M_t \ge 1/\delta\big) \le \delta.

The bound is over the whole stream: it holds however long the monitor runs and however often you look at it. A monitor alarms the first time Mt≥1/δM_t \ge 1/\delta, where δ\delta is the false_alarm_rate (so the threshold is 20 at the default 0.05). The probability that it ever alarms while H0H_0 holds is at most δ\delta.

from cli_sdk.stats.evalues.anytime import BettingMartingale
 
m = BettingMartingale(null_rate=0.10, false_alarm_rate=0.05)
for miss in [0, 0, 1, 0, 1, 1, 0, 1]:
    m.update(miss)
print(m.n, round(m.e_value, 2), m.triggered)   # 8 27.85 True

A classical test controls its error only at one pre-committed sample size. Re-running it after every new outcome and alarming the first time it rejects inflates the false-alarm rate far above its nominal level. In a simulation with 400 in-spec streams of 3,000 outcomes each (miss rate exactly 0.10), a one-sided z-test at level 0.05, re-run after every outcome from the 30th on, alarmed on 43% of the streams. LocalMonitor at false_alarm_rate=0.05 alarmed on 4.25%.

Coverage and risk monitors

ClassFeedsTests
eprocess.CoverageMonitor(target, false_alarm_rate)update(covered: bool)miss rate ≤1−target\le 1 - \text{target}
eprocess.RiskMonitor(target, false_alarm_rate)update(is_loss: bool)loss rate ≤target\le \text{target}
cli_sdk.LocalMonitor(type, target, false_alarm_rate)update(outcome: bool)either of the above; returns an Alert once

CoverageMonitor.update and RiskMonitor.update return an alert dict whenever the e-value is at or above the threshold (and None otherwise); act on the first one. LocalMonitor wraps them and returns a typed Alert only the first time.

The null is about the miss rate given the calibration you actually deployed. Because realized coverage on a fixed calibration set scatters around the target (see profile.realized_coverage_ci), a monitor set to exactly the nominal target can eventually and correctly detect a profile that landed slightly under it. Set the monitor target at or below the lower end of the profile’s coverage interval if you only want to hear about drift.

Detection delay

The monitor tests “the rate has been at most p0p_0 since the monitor started”. It is not a change-point detector with a bounded delay: during a long in-spec period the e-value shrinks toward zero and has to be rebuilt after a change. In simulation (coverage target 0.90, miss rate doubling from 0.10 to 0.20), a freshly started monitor fired after a median of 59 labelled outcomes; one that had first watched 1,000 in-spec outcomes fired a median of about 740 outcomes after the change. The further the pre-change rate sits inside the null, the faster the e-value shrinks and the longer the delay: with a 0.88 target and 90% coverage before the change, the median delay was 1,750 outcomes. Start a new monitor with each new profile version, and see the silent model update guide for a worked example and for windowed monitoring.

Post-hoc significance levels

For any e-value EE and any level α>0\alpha > 0,

1{E≥1/α}α≤E,\frac{\mathbf{1}\{E \ge 1/\alpha\}}{\alpha} \le E,

so for a level α~\tilde\alpha chosen after looking at the data, E[1{E≥1/α~}/α~]≤1\mathbb{E}\big[\mathbf{1}\{E \ge 1/\tilde\alpha\}/\tilde\alpha\big] \le 1. The ratio of the chance of a false rejection to the level you reported stays bounded in expectation, which fixed-level p-value procedures cannot offer. In practice: report 1/E1/E as the smallest level at which the evidence rejects. An Alert carries its e_value for this purpose.

e-BH: FDR control under arbitrary dependence

Given e-values e1,…,eme_1, \dots, e_m for mm hypotheses and a target false-discovery rate qq, sort them in decreasing order e(1)≥⋯≥e(m)e_{(1)} \ge \dots \ge e_{(m)} and let

k∗=max⁡{k:e(k)≥mq k}k^* = \max\Big\{k : e_{(k)} \ge \frac{m}{q\,k}\Big\}

(zero if no kk qualifies). Rejecting the k∗k^* hypotheses with the largest e-values controls FDR≤q m0/m≤q\mathrm{FDR} \le q\, m_0 / m \le q, where m0m_0 is the number of true nulls, whatever the dependence between the e-values (Wang & Ramdas, 2022). Benjamini-Hochberg on p-values needs independence or positive dependence for the same statement. CLI uses e-BH for Gate batches in "fdr" mode when the decisions may be correlated.

import numpy as np
from cli_sdk.stats.evalues import ebh
 
e_values = np.array([45.0, 0.3, 120.0, 2.0, 18.0, 0.9, 60.0, 1.1, 0.2, 35.0])
print(ebh.select(e_values, q=0.10))   # [0 2 6 9]

Here m/q=100m/q = 100: the sorted e-values 120, 60, 45, 35 clear 100/1,100/2,100/3,100/4100/1, 100/2, 100/3, 100/4, and the fifth (18) misses 100/5=20100/5 = 20.

From p-values to e-values

A valid p-value PP (super-uniform under H0H_0) becomes a valid e-value through a calibrator:

fκ(p)=κ pκ−1,κ∈(0,1).f_\kappa(p) = \kappa\, p^{\kappa - 1}, \qquad \kappa \in (0, 1).

Since ∫01κ pκ−1 dp=1\int_0^1 \kappa\, p^{\kappa-1}\, dp = 1 and fκf_\kappa is decreasing, E[fκ(P)]≤1\mathbb{E}[f_\kappa(P)] \le 1. The naive 1/p1/p is not a valid e-value (its expectation under a uniform p-value is infinite). With the default κ=1/2\kappa = 1/2, f(p)=1/(2p)f(p) = 1/(2\sqrt{p}):

from cli_sdk.stats.evalues import ebh
 
ebh.calibrator_from_p_value(0.01)     # 5.0
ebh.calibrator_from_p_value(0.0001)   # 50.0

Prediction-powered inference

Label-efficient calibration estimates a rate (coverage, error rate, judge-human agreement) from a small human-labelled sample plus a large pool labelled by a judge model. Let YiY_i be human labels and fif_i the judge’s labels on the same nn items, and f~j\tilde f_j the judge’s labels on NN further items. The prediction-powered estimate of θ=E[Y]\theta = \mathbb{E}[Y] is

θ^λ=λ⋅1N∑j=1Nf~j  +  1n∑i=1n(Yi−λfi).\hat\theta_\lambda = \lambda \cdot \frac{1}{N}\sum_{j=1}^{N} \tilde f_j \;+\; \frac{1}{n}\sum_{i=1}^{n} \big(Y_i - \lambda f_i\big).

The second term is a rectifier: it measures the judge’s bias on the items humans labelled and subtracts it. For a fixed λ\lambda the estimate is unbiased whenever the human-labelled items are a uniformly random subset of the same population as the pool, whatever the judge’s quality. Its standard error is

SE^=λ2 Var^(f~)N+Var^(Y−λf)n,\widehat{\mathrm{SE}} = \sqrt{\frac{\lambda^2\, \widehat{\mathrm{Var}}(\tilde f)}{N} + \frac{\widehat{\mathrm{Var}}(Y - \lambda f)}{n}},

and the reported 1−α1-\alpha interval is θ^λ±z1−α/2 SE^\hat\theta_\lambda \pm z_{1-\alpha/2}\, \widehat{\mathrm{SE}}. With tune_lambda=True (PPI++), CLI sets λ^=clip(Cov^(f,Y)/Var^(f), 0, 1)\hat\lambda = \mathrm{clip}\big(\widehat{\mathrm{Cov}}(f, Y) / \widehat{\mathrm{Var}}(f),\ 0,\ 1\big), which is never worse than the human-only estimate (λ=0\lambda = 0) up to estimation error; tune_lambda=False uses classical PPI (λ=1\lambda = 1).

from cli_sdk.stats.evalues import ppi
 
result = ppi.estimate_mean(
    labelled_predictions=judge_on_human_sample,   # (n,) judge labels on the human-labelled items
    labelled_labels=human_labels,                 # (n,) human labels on the same items
    unlabelled_predictions=judge_on_pool,         # (N,) judge labels on the pool
    alpha=0.05,
)
print(result.estimate, result.ci_lower, result.ci_upper, result.lam, result.standard_error)

The PPI interval is a large-sample (central limit theorem) interval, not a finite-sample or anytime-valid one. It needs the human-labelled items to be a random sample of the pool. A poor judge makes the interval wider, never invalid; it just saves fewer labels. With the optimal λ\lambda and a large pool, each human label is worth about 1/(1−ρ2)1/(1-\rho^2) human-only labels, where ρ\rho is the judge-human correlation (calibration.judge_quality reports this as effective_label_multiplier).

See the label-efficient calibration guide for an end-to-end run.