Docs / Experiments View as Markdown

Statistics and verdicts

A sequential test that is safe to peek, SRM check, minimum sample, data-quality stars, guardrails, when a verdict is final.

SplitHub Cloud turns per-variant counts into a verdict. The same analysis answers the CLI, the MCP server, the dashboard, the reports and the emails, so every surface quotes the same numbers. A self-hosted endpoint returns the raw counts (visitors, conversions, rate, revenue per variant) with analysis: { available: false }; verdicts, stars and the rest of this page come with SplitHub Cloud.

Peeking-safe by design

Agents and people check results all the time. A classic p-value read on every check and acted on at the first significant moment inflates false positives several times over. SplitHub decides with an always-valid sequential test (a mixture sequential probability ratio test): its p-value and confidence interval stay valid however often you look, so you can check any time and stop as soon as the verdict says so. Verdicts depend on sample size, not on days.

What is computed

For each variant: unique exposed visitors, converted visitors, rate, revenue (when the goal has it), and the chance to be the best variant. For each non-control variant against the control:

Field Meaning
absoluteLift rate − control rate, as a fraction; multiply by 100 for percentage points
relativeLift (rate − control rate) / control rate
sequential.pValue, sequential.ci always-valid p-value and 95% interval of the absolute difference: what decisions use
relativeCi the always-valid interval as a relative lift
significant the always-valid p-value is below 0.05 after Holm correction across the comparisons
pValue, ci, zScore the fixed-horizon two-proportion z-test, for reference only
probabilityToBeat probability that the variant's true rate is above the control's (normal approximation)

Plus the sample-ratio-mismatch check: a chi-square test of the observed split against the configured weights, flagged when p < 0.001. When a targeting flow's conditions decide who reaches which variant, the expected split depends on who visits, so the check is skipped and verdict.notes says so.

The verdict

status When What to do
no_data no exposures yet wait, or check the setup with splithub doctor
insufficient_sample fewer than 100 visitors in a variant or fewer than 50 conversions in total, or the sequential test has not decided yet keep running; the message says about how many visitors per variant will settle it
srm_detected the split does not match the weights the setup is broken (bots, caching, a variant that errors, a redirect loop); fix it, restart as a new experiment
winner_found a variant beats the control, or the control beats a significantly worse variant read winner and message, then finish the experiment
no_significant_difference every interval rules out a change of 10% or more, or every variant reached the planned sample ship the simpler variant, finish
guardrail_breached a variant is significantly worse on a guardrail goal do not ship it; find out what broke

"No difference" must be earned like a winner: the always-valid interval of every comparison sits within ±10% of the control rate (mde, the relative effect worth detecting), or every variant passed the sample where the test has 80% power for that effect.

message is one sentence written for a person or an agent to act on, for example: Stop: "bold" beats "control" (+18.3%, always-valid p=0.0120). Ship "bold". Agents are told in the skills to quote it verbatim and never to call a winner from raw rates. Next to it, voice is a friendlier line for people, and daysToVerdict estimates when to look again at the current traffic.

When an experiment has run 14 days (or reached its planned sample) with nothing significant and a verdict is more than 30 days off or every lift is under 3%, or when the gap left is so small that proving it would take months, voice.advice is move_on: the experiment has run its course. It never changes verdict.status.

minDays (opt-in, 0–30, default 0) holds a verdict until that many days of data are in; waiting messages mention days only when it is set.

Sample quality and stars

Both are informational: they never change the verdict.

  • verdict.sample.quality is small below the minimums, good once every variant has its share of goodSample (default 5,000 visitors in total, set per experiment with goodSample or create --good-sample), and ok in between.
  • verdict.sample.stars (0–5) counts five checks in verdict.sample.checks: the minimums are met; the split matches the weights with 1,000+ visitors; the good sample is reached; the sample is big enough to spot a 10% change; it has run a full week. With a broken split the score is capped at 1.

Sample size

Rule of thumb for a classic test at 95% confidence and 80% power: visitors per variant ≈ 16 · p(1−p) / (p·L)² with baseline rate p and relative lift L. Baseline 5% and lift 20% → about 7,600 per variant; baseline 2% and lift 10% → about 78,000. Being safe to check any time costs sample: SplitHub's sequential test needs about twice that (about 16,000 and 170,000 per variant for the same two cases). Ask before you start:

npx splithub plan --baseline 0.05 --mde 0.2 [--variants 3] [<id>]

It answers visitors per variant for SplitHub's test, and with an experiment id the days at that experiment's traffic. No account needed; the same answer is GET https://go.splithub.io/v1/plan?baseline=0.05&mde=0.2&variants=2.

Several variants

With three or more variants every comparison is against the control, and significance is Holm-corrected across the comparisons (same guarantee as Bonferroni, more power). Pairwise comparisons between non-control variants are not reported; create a new experiment with the two finalists if you need that.

Secondary metrics and guardrails

Goals in metrics are read with the same sequential test as the primary goal:

  • secondary: two-sided, Holm-corrected across all secondary comparisons. Informational.
  • guardrails: one-sided (harm only, in the direction you set with worse), Holm-corrected across all guardrail comparisons, judged once both sides have the minimum 100 visitors. A variant significantly worse on a guardrail is never the winner: the verdict becomes guardrail_breached and verdict.guardrailBreached[] lists the metric, the variant and the lift. When the decided winner is the control or is clean, the call stands and the harm is added to verdict.notes.

Each family is corrected on its own, so adding metrics never weakens the primary verdict.

Multivariate experiments

An mvt experiment gets the verdict over its combinations, plus factors[]: for each factor, every level's visitors and conversions summed over the combinations that use it and compared with the factor's first level. Factor levels and combinations are Holm-corrected together, so reading both keeps the 5% false-positive rate. best names the level that beats the first one significantly with the largest lift.

Segments

Breakdowns by country, device, browser, platform, app version, source, channel, landing page and more are for hypotheses, not verdicts: slicing after the fact finds noise. Each segment carries its own comparison and a small flag when it has too few visitors. A slice (results --from --to --filter) is marked as one and carries no advice; the decision stays with the whole experiment. If a segment looks interesting, run a targeted experiment on it.

Holdout

With a holdout in the config, splithub results __holdout compares the held-out visitors with everyone who was enrolled, by the same test: the long-term effect of all your experiments together.

SplitHub
See what works. Ship what wins.