Skip to content

A/B Testing

Complete A/B test analysis: frequentist, Bayesian, sequential, power, multiple testing.

Quick Start

from causal_toolkit.analysis import ABTestAnalyzer, ABTestData, TestType

analyzer = ABTestAnalyzer(confidence_level=0.95)

# From counts (conversion test)
data = ABTestData(
    n_a=10000, successes_a=1200,
    n_b=10000, successes_b=1350
)
result = analyzer.proportion_ztest(data)
print(result)

# From DataFrame
data = analyzer.from_dataframe(df, variant_col="group", outcome_col="converted",
                                variant_a="control", variant_b="treatment",
                                test_type=TestType.PROPORTION)
result = analyzer.analyze(data, TestType.PROPORTION, method="bayesian")

Frequentist Tests

# Proportion Z-test (conversions)
result = analyzer.proportion_ztest(data, Alternative.TWO_SIDED)

# t-test (continuous metrics: revenue, time, etc.)
data = ABTestData(
    n_a=100, sum_a=50000, sum_sq_a=300000000,
    n_b=100, sum_b=55000, sum_sq_b=350000000
)
result = analyzer.ttest(data)

# Mann-Whitney (non-parametric)
result = analyzer.mann_whitney(control_outcomes, treatment_outcomes)

Bayesian Tests

# Beta-Binomial (proportions)
result = analyzer.bayesian_proportion(
    data,
    prior_alpha=1.0,      # Prior pseudo-counts
    prior_beta=1.0,
    rope_width=0.01       # Region of Practical Equivalence (±1%)
)

# Interpreting Bayesian output:
print(f"P(B > A) = {result.prob_b_better:.2%}")      # Probability B is better
print(f"ROPE = {result.rope_probability:.2%}")       # Probability difference is negligible
print(f"Expected loss if choose A = {result.expected_loss_a:.4f}")
print(f"Expected loss if choose B = {result.expected_loss_b:.4f}")

Decision Rules: - prob_b_better > 0.95 → Ship B - rope_probability > 0.9 → No meaningful difference - expected_loss_b < 0.001 → Safe to ship B

Sequential Testing

# SPRT (Wald's Sequential Probability Ratio Test)
sprt = analyzer.sprt(
    data,
    mde=0.05,      # 5% relative MDE
    alpha=0.05,
    beta=0.2       # 80% power
)
# Decision: "accept_h1" / "accept_h0" / "continue"

# mSPRT (Mixture SPRT - Bayesian, optional stopping)
msprt = analyzer.msprt(
    data,
    mde=0.05,
    alpha=0.05,
    prior_strength=1.0
)
# Decision: "accept_h1" / "accept_h0" / "continue"
# Bayes factor threshold = (1-α)/α

Power Analysis

# Sample size for given MDE
power = analyzer.power_analysis(
    baseline_rate=0.10,    # 10% baseline conversion
    mde=0.05,              # 5% relative lift (→ 10.5%)
    alpha=0.05,
    power=0.8,
    ratio=1.0              # Equal allocation
)
# Returns: sample_size_per_variant, total_sample_size, effect_size, achieved_power

# MDE for given sample size
mde = analyzer.mde_calculation(
    baseline_rate=0.10,
    n_per_variant=10000,
    alpha=0.05,
    power=0.8
)

Multiple Testing Correction

p_values = [0.01, 0.03, 0.001, 0.5, 0.02]

# Bonferroni (FWER control)
bonf = analyzer.bonferroni_correction(p_values, alpha=0.05)
# adjusted_alpha = 0.01
# rejected: indices where p < 0.01

# Benjamini-Hochberg (FDR control)
bh = analyzer.benjamini_hochberg(p_values, alpha=0.05)
# More powerful than Bonferroni

End-to-End Analysis

result = analyzer.analyze(
    data=data,
    test_type=TestType.PROPORTION,
    method="bayesian",      # "frequentist", "bayesian", "sequential"
    rope_width=0.01         # For Bayesian
)

# Unified output in ABTestResult
print(f"p-value: {result.p_value}")
print(f"Prob B better: {result.prob_b_better}")
print(f"CI: [{result.ci_lower:.4f}, {result.ci_upper:.4f}]")

Uplift Evaluation

from causal_toolkit.analysis import evaluate_uplift

# Predicted individual uplift (CATE)
uplift = model.predict_uplift(X_test)

metrics = evaluate_uplift(uplift, T_test, Y_test)
# {
#   "qini": 0.142,                 # Qini coefficient
#   "auuc": 0.098,                 # Area Under Uplift Curve
#   "gain_at_10pct": 0.18,         # Gain in top 10%
#   "gain_at_50pct": 0.12,
#   "uplift_mean": 0.05,
#   "uplift_std": 0.03
# }

CLI

# Proportion test
causal-toolkit ab_test --data exp.csv --variant group --outcome converted \
    --control A --treatment B --method bayesian

# Continuous outcome
causal-toolkit ab_test --data exp.csv --variant group --outcome revenue \
    --control A --treatment B --type mean --method frequentist

# Power calculation
causal-toolkit power --baseline 0.1 --mde 0.05 --alpha 0.05 --power 0.8