agentleFS
sergeeeysergeeey/Claude-cod-top-2026/skills/extensions/ab-test/SKILL.md

ab-test

Skill9 starsChanged 6 months agoMITView on GitHub

USE when you have A/B test results and need rigorous statistical analysis. Validates experiment integrity (SRM at 3 levels, novelty effect, peeking), computes two-proportion z-test with CI (+ Fisher exact for low counts), checks MCID (practical significance ≠ statistical), multiple comparisons correction, and gives a Ship/Extend/Stop/Investigate verdict with evidence markers. Integrates with rules/estimand-ops.md — MCID required before analysis. Triggers: /ab-test, a/b test, проанализируй эксперимент, результаты теста, split test, experiment results, A/B анализ, significance, конверсия. [STATUS: review] [CONFIDENCE: high] [REVIEWED: 2026-08-13]

6 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use email-sequence.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • AnthropicSkill

    email-sequence

    Design and draft multi-email sequences with full copy, timing, branching logic, exit conditions, and performance benchmarks. Use when building onboarding, lead nurture, re-engagement, win-back, or product launch flows, when you need a complete drip campaign with A/B test suggestions, or when mapping a sequence end-to-end with a flow diagram.

    Also in 3 projects that use this

The file

Files
SKILL.md
<!-- BSV — Brief Skill View | поиск: BSV Скил : ab-test TL;DR : Статистически строгий анализ A/B теста с MCID, peeking guard и evidence markers Вызов : `/ab-test`, 'проанализируй эксперимент', 'результаты A/B теста' НЕ для : Дизайна теста до запуска (→ /experiment-design), causality без рандомизации -->

/ab-test — A/B Test Analysis

No integrity, no inference. Если рандомизация или логирование сломаны — статистический вывод запрещён независимо от p-value.

Когда использовать

  • Есть результаты рандомизированного эксперимента (A/B, A/A, multivariate)
  • Нужно решение: пускать в прод / продлить / остановить
  • Требуется доказуемость с evidence markers ([VERIFIED-REAL])

НЕ использовать:

  • Для дизайна теста до запуска → /experiment-design
  • Для observational data без рандомизации → добавить causal layer (rules/estimand-ops.md)
  • Для последовательного анализа без pre-registration → секция "peeking guard" ниже

Step 0 — Estimand (до любых расчётов)

Из rules/estimand-ops.md — заполнить до просмотра данных:

Population:   [кто именно, inclusion/exclusion criteria]
Intervention: [variant — точное описание изменения]
Comparator:   [control]
Endpoint:     [primary metric — конкретная, с единицами]
MCID:         [минимальное практически значимое изменение, напр. +0.5% conversion]
ICE:          [intercurrent events — что делать с drop-outs, технические сбои]
Secondary metrics: [явно перечислить + пометить exploratory если не pre-specified]

Жёсткое правило: MCID должен быть задан ДО просмотра результатов. Если MCID неизвестен → остановись, договорись с командой, только потом анализируй.


Step 1 — Integrity Gate

Пройти все три уровня SRM и оба поведенческих check. Если хоть одна проверка провалена — STOP, вердикт = INVALID.

1a. Sample Ratio Mismatch (SRM) — три уровня

SRM нужно проверять на каждом уровне отдельно: баг может быть не в рандомизации, а в логировании событий.

from scipy.stats import chisquare

def check_srm(observed_c, observed_v, expected_split=0.5, label=""):
    total = observed_c + observed_v
    exp_c = total * expected_split
    exp_v = total * (1 - expected_split)
    chi2, p = chisquare([observed_c, observed_v], f_exp=[exp_c, exp_v])
    status = "⚠️ SRM DETECTED — STOP" if p < 0.01 else "OK"
    print(f"SRM [{label}]: χ²={chi2:.3f}, p={p:.4f} → {status}")
    return p

# Уровень 1: Assignment (рандомизация при попадании в тест)
p_srm_assign = check_srm(n_assigned_c, n_assigned_v, label="assignment")

# Уровень 2: Exposure (пользователи которые реально увидели вариант)
p_srm_exposure = check_srm(n_exposed_c, n_exposed_v, label="exposure")

# Уровень 3: Analysis population (после фильтров inclusion criteria)
p_srm_analysis = check_srm(n_analysis_c, n_analysis_v, label="analysis")

Интерпретация:

  • SRM на уровне assignment → баг в рандомизации или assignment логике
  • SRM на уровне exposure → баг в feature flag / rollout системе
  • SRM только на уровне analysis → баг в event logging или post-hoc фильтрации

Любой SRM p < 0.01 → STOP. Найти уровень, найти причину, починить до анализа.

1b. Novelty Effect Check

  • Тест шёл менее 2 полных business cycles (обычно 2 недели)?
  • Первые 3 дня показывают аномальный spike в variant?

Если да → разбить данные по неделям. Если week1 >> week2 → novelty effect, нужно продлить.

1c. Peeking Guard

Смотрел ли кто-то на p-value до окончания теста и принял решение остановить?

Если ДА и тест остановлен при p < 0.05:
  Uncontrolled repeated peeking inflates Type I error well above 0.05 —
  potentially toward ~0.30 depending on monitoring frequency,
  stopping behavior, and total sample size.
  (Точная инфляция зависит от числа просмотров и stopping rule.)

Решение:
  (a) применить sequential correction (mSPRT / alpha-spending function)
  (b) зафиксировать как "exploratory only", не production decision

Step 2 — Statistical Test

2a. Low-count check (сначала)

# Если ожидаемых событий мало — z-test ненадёжен
# ВАЖНО: n_c, conv_c, n_v, conv_v — введи свои данные здесь
n_c = <n control>
conv_c = <conversions control>
n_v = <n variant>
conv_v = <conversions variant>

rate_c = conv_c / n_c
rate_v = conv_v / n_v
p_pool = (conv_c + conv_v) / (n_c + n_v)  # определяем здесь — нужен и в 2b и в 2d

expected_c_pos = n_c * p_pool
expected_c_neg = n_c * (1 - p_pool)
expected_v_pos = n_v * p_pool
expected_v_neg = n_v * (1 - p_pool)

if min(expected_c_pos, expected_c_neg, expected_v_pos, expected_v_neg) < 5:
    print("⚠️ Low expected counts (<5 per cell) — use Fisher exact or Bayesian")
    USE_FISHER = True
else:
    USE_FISHER = False

2b. Two-proportion z-test (если USE_FISHER = False)

import numpy as np
from scipy import stats

# n_c, conv_c, n_v, conv_v, rate_c, rate_v, p_pool — определены в 2a
lift_rel = (rate_v - rate_c) / rate_c
lift_abs = rate_v - rate_c

# Two-proportion z-test (pooled SE для p-value)
se = np.sqrt(p_pool * (1 - p_pool) * (1/n_c + 1/n_v))
z = (rate_v - rate_c) / se
p_value = 2 * (1 - stats.norm.cdf(abs(z)))  # two-tailed

# 95% CI (unpooled SE для интервала)
se_ci = np.sqrt(rate_c*(1-rate_c)/n_c + rate_v*(1-rate_v)/n_v)
ci_low  = lift_abs - 1.96 * se_ci
ci_high = lift_abs + 1.96 * se_ci

print(f"Control:  {rate_c:.4f} ({conv_c}/{n_c})")
print(f"Variant:  {rate_v:.4f} ({conv_v}/{n_v})")
print(f"Lift:     {lift_rel:+.2%} rel ({lift_abs:+.4f} abs)")
print(f"p-value:  {p_value:.4f}")
print(f"95% CI:   [{ci_low:+.4f}, {ci_high:+.4f}]")

CI vs p-value (не баг — корректно): p-value считается на pooled SE (под H₀: p₁=p₂), а 95% CI — на unpooled SE (оценка реальной разности). У границы значимости они могут слегка расходиться (CI включает 0 при p чуть < 0.05, или наоборот). Это ожидаемо: p-value — для решения о H₀, CI — для величины эффекта. Не «подгонять» один под другой.

2c. Fisher exact (если USE_FISHER = True)

from scipy.stats import fisher_exact
from statsmodels.stats.proportion import proportion_confint

odds_ratio, p_fisher = fisher_exact([[conv_c, n_c - conv_c],
                                      [conv_v, n_v - conv_v]])
ci_c = proportion_confint(conv_c, n_c, method='wilson')
ci_v = proportion_confint(conv_v, n_v, method='wilson')
print(f"Fisher p: {p_fisher:.4f}")
print(f"Wilson CI control:  {ci_c}")
print(f"Wilson CI variant:  {ci_v}")

2d. Power Check (если p ≥ α)

from statsmodels.stats.power import NormalIndPower
from statsmodels.stats.proportion import proportion_effectsize

mcid = <твой MCID как абсолютная разница>
# Cohen's h — корректный effect size для двух пропорций
# (точнее приближения mcid/√(p(1−p)); h = 2·arcsin√p1 − 2·arcsin√p2)
effect_size = proportion_effectsize(rate_c + mcid, rate_c)
# ВАЖНО: параметр называется nobs1 (не n_obs) — иначе TypeError
power = NormalIndPower().power(effect_size, nobs1=min(n_c, n_v), alpha=0.05)
print(f"Power для MCID={mcid}: {power:.2f}")
# power < 0.80 → тест был underpowered, нельзя делать вывод о null

# Null bound (rules/estimand-ops.md, Design Detectability): numeric MDE in both directions,
# computed on the analysed n and the observed baseline; power 0.80, alpha 0.05 two-sided
import numpy as np
n_arm = min(n_c, n_v)
h_mde = NormalIndPower().solve_power(nobs1=n_arm, alpha=0.05, power=0.80, ratio=1.0)  # Cohen's h at 80% power
phi = np.arcsin(np.sqrt(rate_c))
mde_up = np.sin(phi + h_mde / 2) ** 2 - rate_c if phi + h_mde / 2 <= np.pi / 2 else None  # None = undetectable
mde_down = rate_c - np.sin(phi - h_mde / 2) ** 2 if phi - h_mde / 2 >= 0 else None       # None = undetectable

2e. Multiple Comparisons Correction

Если тестировались несколько метрик или несколько вариантов:

Проблема: при 10 метриках вероятность хотя бы одного ложно-позитивного результата ≈ 1 − 0.95¹⁰ = 40% при α=0.05.

Правило:

  • Primary metric: pre-specified, без коррекции.
  • Secondary metrics (pre-specified): применить Holm-Bonferroni.
  • Secondary metrics (post-hoc): пометить [EXPLORATORY], не для production decisions.
  • Multiple variants (k вариантов): скорректировать α = 0.05 / (k − 1).
from statsmodels.stats.multitest import multipletests

# p_values — список p-value для pre-specified secondary метрик
p_values = [p_metric1, p_metric2, p_metric3]
reject, p_corrected, _, _ = multipletests(p_values, method='holm')
for i, (r, pc) in enumerate(zip(reject, p_corrected)):
    print(f"Metric {i+1}: p_corrected={pc:.4f} → {'REJECT H0' if r else 'FAIL TO REJECT'}")

Step 3 — Guardrail Metrics

Для каждой guardrail metric (revenue, latency, retention, error rate):

MetricControlVariantΔСтатус
Primary metricXY+Z%✅/❌
Revenue per userXYΔ✅/⚠️/❌
Page load timeXYΔ✅/⚠️/❌
Error rateXYΔ✅/⚠️/❌

⚠️ = деградация ≥ MCID guardrail → флаг для Investigate


Step 4 — Verdict

УсловиеВердикт
p < α AND lift_abs ≥ MCID AND все guardrails OK✅ SHIP
p < α AND lift_abs < MCID (статистически значимо но не практически)⚠️ STOP — ниже MCID
p ≥ α AND power ≥ 0.80⛔ STOP — не обнаружено при MDE из Null bound (рост и падение, n и baseline указаны); «эффекта нет» не утверждается
p ≥ α AND power < 0.80⏳ EXTEND — underpowered, продолжить
p < α AND guardrail деградировал🔍 INVESTIGATE — трейдофф требует решения
SRM detected (любой уровень)🚫 INVALID — устранить до решения
Peeking без коррекции🚫 INVALID — пометить exploratory или применить mSPRT

Step 5 — Отчёт с Evidence Markers

## A/B Test: [название]
Период: [дата старт — дата конец]
Данные: [источник — [VERIFIED-REAL] с URL/путём к данным]

### Integrity
- SRM assignment:  p=X → OK / ⚠️ DETECTED [VERIFIED-REAL]
- SRM exposure:    p=X → OK / ⚠️ DETECTED [VERIFIED-REAL]
- SRM analysis:    p=X → OK / ⚠️ DETECTED [VERIFIED-REAL]
- Novelty effect:  [проверен / не применимо]
- Peeking:         [не было / был → скорректирован mSPRT / помечен exploratory]
- Low counts:      [N/A / Fisher exact использован]

### Results
- Control: {rate_c:.4f} (n={n_c})
- Variant: {rate_v:.4f} (n={n_v})
- Lift: {lift_rel:+.2%} | 95% CI: [{ci_low:+.4f}, {ci_high:+.4f}]
- p-value: {p_value:.4f} (α=0.05) [VERIFIED-REAL]
- MCID: {mcid} → EXCEEDED / NOT REACHED
- Null bound: [не обнаружено при MDE: рост = {mde_up}, падение = {mde_down} (абс.; None = undetectable, неинформативно при этом n); n={n_arm}, baseline={rate_c:.4f}, alpha=0.05 two-sided, power=0.80] / [N/A — результат не нулевой]
- Multiple comparisons: [N/A / Holm corrected / secondary marked exploratory]

### Guardrails
[таблица из Step 3]

### Verdict: [SHIP / EXTEND / STOP / INVESTIGATE / INVALID]
Reason: [одно предложение]

### What This Does NOT Mean
1. Не доказывает причинно-следственную связь если A/B не был полностью рандомизирован
2. Не гарантирует результат на 100% трафика (Simpson's paradox по сегментам)
3. Не применимо для пользователей вне inclusion criteria теста
4. Secondary / post-hoc метрики помечены [EXPLORATORY] — не для production decisions

Интеграция с нашим стеком

  • Estimand → rules/estimand-ops.md (MCID, ICE, population definition)
  • Evidence markers → rules/evidence-markers.md ([VERIFIED-REAL] обязателен)
  • Skeptic trigger: если lift = round number (50%, 100%) → auto-trigger rule 4
  • Causal layer: если данные не из рандомизированного эксперимента → остановись, требуется DAG + identifiability check (rules/estimand-ops.md §Causal Layer)

Принцип всего скилла:

No integrity, no inference.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.