Catalog PyPI
PACKAGE EXPLORERView in registry

abtk

PyPI

A/B Testing Toolkit - Statistical analysis library for A/B tests

ab-testinganalyticsexperimentationhypothesis-testingstatistics
Versions collected1
Direct dependencies10From release 0.4.0
Last observed20 Oct, 17:45 UTC

README

Version 0.4.0 documentation

ABTK - A/B Testing Toolkit

PyPI version Python 3.8+ License: MIT

ABTK is a comprehensive Python library for statistical analysis of A/B tests. It provides a unified interface for parametric and nonparametric hypothesis tests, variance reduction techniques, and multiple comparisons correction.

Key Features

  • 12 Statistical Tests - Parametric (T-Test, Z-Test, CUPED, ANCOVA) and Nonparametric (Bootstrap)
  • Cluster-Randomized Experiments - Geo experiments, store tests, market-level randomization
  • Simulation-Based Power Analysis - PowerAnalyzer for experiment planning (NEW in v0.4.0)
  • Variance Reduction - CUPED, ANCOVA with multiple covariates
  • Multiple Comparisons - Bonferroni, Holm, Benjamini-Hochberg, and more
  • Quantile Analysis - Analyze treatment effects across the distribution
  • Unified Interface - All tests return standardized TestResult objects
  • Automatic Diagnostics - ICC, design effect, VIF, and more
  • Flexible - Support for relative and absolute effects

Quick Start

Installation

pip install abtk

For visualization support:

pip install abtk[viz]

Basic Example

from core.data_types import SampleData
from tests.parametric import TTest

# Prepare your data
control = SampleData(
    data=[100, 110, 95, 105, 98, 102],
    name="Control"
)

treatment = SampleData(
    data=[105, 115, 100, 110, 103, 108],
    name="Treatment"
)

# Run test
test = TTest(alpha=0.05, test_type="relative")
results = test.compare([control, treatment])

# Get results
result = results[0]
print(f"Effect: {result.effect:.2%}")           # e.g., "Effect: 5.5%"
print(f"P-value: {result.pvalue:.4f}")
print(f"Significant: {result.reject}")          # True/False
print(f"95% CI: [{result.left_bound:.2%}, {result.right_bound:.2%}]")

Using pandas DataFrames

For most analysts, data starts in a pandas DataFrame. Use the helper utilities for easy conversion:

import pandas as pd
from utils.dataframe_helpers import sample_data_from_dataframe
from tests.parametric import TTest

# Your experiment data as DataFrame
df = pd.DataFrame({
    'variant': ['control', 'control', 'treatment', 'treatment'],
    'revenue': [100, 110, 105, 115],
    'baseline_revenue': [90, 100, 92, 102]  # Optional: for CUPED
})

# Convert to SampleData objects automatically
samples = sample_data_from_dataframe(
    df,
    group_col='variant',
    metric_col='revenue',
    covariate_cols='baseline_revenue'  # Optional
)

# Run test
test = TTest(alpha=0.05, test_type="relative")
results = test.compare(samples)

See the DataFrame Usage Examples for more.

Available Tests

Parametric Tests

TestUse CaseSpecial Features
TTestStandard A/B testFast, well-understood
PairedTTestMatched pairs A/B testRemoves between-subject variability
CupedTTestA/B test with 1 covariateVariance reduction
ZTestProportions (CTR, CVR)Designed for binary outcomes
AncovaTest (or OLSTest)A/B test with multiple covariatesMaximum variance reduction, diagnostics

Nonparametric Tests

TestUse CaseSpecial Features
BootstrapTestNon-normal data, outliersNo assumptions, robust
PairedBootstrapTestMatched pairs, non-normalCombines pairing + nonparametric
PostNormedBootstrapTestNon-normal with covariateBootstrap + variance reduction

Cluster-Randomized Tests (NEW in v0.3.0)

For experiments where randomization occurs at group level (cities, stores, schools, etc.) rather than individual level.

TestUse CaseSpecial Features
ClusteredTTestGeo experiments, store testsCluster-robust SE, ICC diagnostics
ClusteredAncovaTestCluster experiments with covariatesMultiple covariates + cluster SE
ClusteredZTestProportions in geo experimentsCTR/CVR by city with cluster SE
ClusteredBootstrapTestNon-normal cluster dataResamples clusters, not individuals

Key Features:

  • Accounts for within-cluster correlation (ICC)
  • Provides design effect and effective sample size
  • Validates cluster structure (minimum clusters, balance)
  • All tests return comprehensive cluster diagnostics

See Cluster Experiments Guide for details.

Documentation

Example: Variance Reduction with CUPED

from tests.parametric import CupedTTest

# Include historical data for variance reduction
control = SampleData(
    data=[100, 110, 95, 105],           # Current metric
    covariates=[90, 100, 85, 95],       # Historical baseline
    name="Control"
)

treatment = SampleData(
    data=[105, 115, 100, 110],
    covariates=[92, 102, 87, 97],
    name="Treatment"
)

test = CupedTTest(alpha=0.05)
results = test.compare([control, treatment])

# CUPED typically gives narrower CI and lower p-value!

Example: Cluster-Randomized Experiment (NEW in v0.3.0)

For geo experiments where cities/regions are randomized:

from tests.parametric import ClusteredTTest

# Geo experiment: cities randomized to control/treatment
control = SampleData(
    data=[100, 110, 95, 105, 98, 102, 108, 92],  # User metrics
    clusters=[1, 1, 2, 2, 3, 3, 4, 4],            # City IDs (2 users per city)
    name="Control Cities"
)

treatment = SampleData(
    data=[105, 115, 100, 110, 103, 108, 113, 97],
    clusters=[5, 5, 6, 6, 7, 7, 8, 8],
    name="Treatment Cities"
)

test = ClusteredTTest(alpha=0.05, min_clusters=4)
results = test.compare([control, treatment])

result = results[0]
# Access cluster diagnostics
print(f"ICC: {result.method_params['icc_control']:.3f}")
print(f"Design Effect: {result.method_params['design_effect_control']:.2f}")
print(f"Effective N: {result.method_params['effective_n_control']:.0f}")

See Cluster Experiments Guide for complete workflows.

Example: Multiple Comparisons Correction

from tests.parametric import TTest
from utils.corrections import adjust_pvalues

# Test multiple variants
test = TTest(alpha=0.05)
results = test.compare([control, treatment_a, treatment_b, treatment_c])

# Apply Bonferroni correction
adjusted = adjust_pvalues(results, method="bonferroni")

for r in adjusted:
    print(f"{r.name_1} vs {r.name_2}: p={r.pvalue:.4f}, significant={r.reject}")

Example: Quantile Analysis

from tests.nonparametric import BootstrapTest
from utils.quantile_analysis import QuantileAnalyzer

# Analyze treatment effects at different quantiles
bootstrap = BootstrapTest(alpha=0.05, n_samples=10000)
analyzer = QuantileAnalyzer(
    test=bootstrap,
    quantiles=[0.25, 0.5, 0.75, 0.9, 0.95]
)

results = analyzer.compare([control, treatment])
result = results[0]

# View results as table
print(result.to_dataframe())

# Find where effects are significant
sig_quantiles = result.significant_quantiles()
print(f"Effects significant at: {sig_quantiles}")

Experiment Planning

Analytical Approach (Fast)

For quick TTest/ZTest/CUPED planning, use analytical formulas:

from utils.sample_size_calculator import calculate_mde_ttest, calculate_sample_size_ttest

# Question 1: What can I detect with 1000 users?
mde = calculate_mde_ttest(mean=100, std=20, n=1000)
print(f"Can detect {mde:.2%} effect")  # 3.5%

# Question 2: How many users to detect 5% effect?
n = calculate_sample_size_ttest(mean=100, std=20, mde=0.05)
print(f"Need {n:,} users per group")  # 1,571

Simulation-Based Power Analysis (NEW in v0.4.0)

For Bootstrap tests and cluster experiments, use PowerAnalyzer - the ONLY way to get MDE when no analytical formula exists:

from core.data_types import SampleData
from tests.nonparametric import BootstrapTest
from utils.power_analysis import PowerAnalyzer
import numpy as np

# Historical data (NOT split)
historical = SampleData(data=last_month_revenue)

# Configure test
test = BootstrapTest(alpha=0.05, n_bootstrap=500, test_type="relative")

# Create power analyzer
analyzer = PowerAnalyzer(test=test, n_simulations=200, seed=42)

# Calculate MDE for 80% power (ONLY option for Bootstrap!)
mde = analyzer.minimum_detectable_effect(
    sample=historical,
    target_power=0.8,
    effect_type="multiplicative"
)
print(f"MDE: {mde:.1%}")  # e.g., "MDE: 8.5%"

# Or estimate power for planned effect
power = analyzer.power_analysis(
    sample=historical,
    effect=0.05,  # 5% increase
    effect_type="multiplicative"
)
print(f"Power: {power:.1%}")  # e.g., "Power: 65.3%"

Key advantages of PowerAnalyzer:

  • ✅ Works with ALL 12 tests (including Bootstrap, ClusteredBootstrap)
  • ✅ ONLY way to get MDE for Bootstrap tests (no analytical formula exists)
  • ✅ Automatic data splitting (handles simple/cluster/paired designs)
  • ✅ Empirical power estimates (no distributional assumptions)

When to use:

Test TypeUse AnalyticalUse Simulation (PowerAnalyzer)
TTest, ZTest✅ Fast (~instant)⚠️ Slower (~10 sec)
BootstrapTest❌ No formula✅ ONLY option
ClusteredBootstrapTest❌ No formula✅ ONLY option
Any cluster test⚠️ Need ICC✅ Easier, automatic

See Experiment Planning Guide for complete details.

Test Selection Decision Tree

Is randomization at GROUP level (cities, stores)?
├─ Yes (Cluster-Randomized)
│   ├─ Proportions (CTR, CVR)? → ClusteredZTest
│   └─ Continuous metric
│       ├─ Have covariates? → ClusteredAncovaTest
│       ├─ Assume normality? → ClusteredTTest
│       └─ No assumptions → ClusteredBootstrapTest
└─ No (Individual-Level Randomization)
    ├─ Do you have proportions (CTR, CVR)?
    │   └─ Yes → ZTest
    └─ No (continuous metric)
        └─ Do you have paired data?
            ├─ Yes
            │   ├─ Assume normality? → PairedTTest
            │   └─ No assumptions → PairedBootstrapTest
            └─ No (independent samples)
                └─ Do you have covariates?
                    ├─ Yes
                    │   ├─ Multiple covariates? → AncovaTest (or OLSTest)
                    │   ├─ One covariate, normal → CupedTTest
                    │   └─ One covariate, non-parametric → PostNormedBootstrapTest
                    └─ No covariates
                        ├─ Assume normality? → TTest
                        └─ No assumptions → BootstrapTest

See Test Selection Guide for detailed recommendations.

Requirements

  • Python >= 3.8
  • numpy >= 1.20.0
  • scipy >= 1.7.0
  • pandas >= 1.3.0
  • statsmodels >= 0.13.0

Optional:

  • matplotlib >= 3.5.0 (for visualization)

Installation Options

For Users

# Basic installation from PyPI
pip install abtk

# With visualization support
pip install abtk[viz]

For Developers

If you want to contribute or modify the code:

# Clone the repository
git clone https://github.com/alexeiveselov92/abtk.git
cd abtk

# Install in editable mode with development dependencies
pip install -e ".[dev]"

# Or with visualization support
pip install -e ".[dev,viz]"

Running Tests

# Run all tests
pytest unit_tests/

# Run with coverage
pytest --cov=. unit_tests/

# Run specific test file
pytest unit_tests/test_ancova.py

Project Structure

abtk/
├── core/                    # Core data structures
│   ├── data_types.py        # SampleData, ProportionData
│   ├── test_result.py       # TestResult
│   └── quantile_test_result.py
├── tests/
│   ├── parametric/          # Parametric tests
│   └── nonparametric/       # Nonparametric tests
├── utils/                   # Utilities
│   ├── corrections.py       # Multiple comparisons
│   ├── quantile_analysis.py # Quantile treatment effects
│   └── visualization.py     # Plotting (optional)
├── unit_tests/              # Unit tests
├── examples/                # Runnable examples
└── docs/                    # Documentation

Contributing

We welcome contributions! See CONTRIBUTING.md for guidelines.

License

MIT License - see LICENSE for details.

Citation

If you use ABTK in your research, please cite:

@software{abtk2025,
  title={ABTK: A/B Testing Toolkit},
  author={Alexei Veselov},
  year={2025},
  url={https://github.com/alexeiveselov92/abtk}
}

Support

Acknowledgments

ABTK implements methods from:

  • Deng et al. (2013) - CUPED methodology
  • Benjamini & Hochberg (1995) - FDR control
  • And many other contributions to A/B testing literature

Ready to get started? → Check out the Getting Started Guide

Downloads & popularity

2downloads
1-day window · source-reported counts19 Oct 2026
54270
2026-07-23: 2 · observed 2026-10-20T20:12:50.7227438Z · period 2026-07-23–2026-07-232026-07-24: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-07-24–2026-07-242026-07-27: 2 · observed 2026-10-20T20:12:50.7227438Z · period 2026-07-27–2026-07-272026-07-29: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-07-29–2026-07-292026-08-01: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-01–2026-08-012026-08-02: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-02–2026-08-022026-08-03: 12 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-03–2026-08-032026-08-04: 8 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-04–2026-08-042026-08-06: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-06–2026-08-062026-08-09: 8 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-09–2026-08-092026-08-13: 2 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-13–2026-08-132026-08-14: 3 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-14–2026-08-142026-08-17: 9 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-17–2026-08-172026-08-18: 54 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-18–2026-08-182026-08-19: 2 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-19–2026-08-192026-08-20: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-20–2026-08-202026-08-23: 4 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-23–2026-08-232026-08-24: 13 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-24–2026-08-242026-08-26: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-26–2026-08-262026-08-28: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-28–2026-08-282026-08-30: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-30–2026-08-302026-08-31: 2 · observed 2026-10-20T20:12:50.7227438Z · period 2026-08-31–2026-08-312026-09-01: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-01–2026-09-012026-09-03: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-03–2026-09-032026-09-05: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-05–2026-09-052026-09-06: 6 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-06–2026-09-062026-09-07: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-07–2026-09-072026-09-08: 2 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-08–2026-09-082026-09-09: 2 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-09–2026-09-092026-09-10: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-10–2026-09-102026-09-12: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-12–2026-09-122026-09-18: 12 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-18–2026-09-182026-09-20: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-20–2026-09-202026-09-23: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-23–2026-09-232026-09-24: 9 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-24–2026-09-242026-09-25: 2 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-25–2026-09-252026-09-26: 3 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-26–2026-09-262026-09-27: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-27–2026-09-272026-09-28: 3 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-28–2026-09-282026-09-29: 6 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-29–2026-09-292026-09-30: 12 · observed 2026-10-20T20:12:50.7227438Z · period 2026-09-30–2026-09-302026-10-01: 3 · observed 2026-10-20T20:12:50.7227438Z · period 2026-10-01–2026-10-012026-10-04: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-10-04–2026-10-042026-10-06: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-10-06–2026-10-062026-10-07: 1 · observed 2026-10-20T20:12:50.7227438Z · period 2026-10-07–2026-10-072026-10-08: 14 · observed 2026-10-20T20:12:50.7227438Z · period 2026-10-08–2026-10-082026-10-09: 9 · observed 2026-10-20T20:12:50.7227438Z · period 2026-10-09–2026-10-092026-10-10: 2 · observed 2026-10-20T20:12:50.7227438Z · period 2026-10-10–2026-10-102026-10-11: 4 · observed 2026-10-20T20:12:50.7227438Z · period 2026-10-11–2026-10-112026-10-12: 2 · observed 2026-10-20T20:12:50.7227438Z · period 2026-10-12–2026-10-122026-10-13: 3 · observed 2026-10-20T20:12:50.7227438Z · period 2026-10-13–2026-10-132026-10-14: 2 · observed 2026-10-20T20:12:50.7227438Z · period 2026-10-14–2026-10-142026-10-15: 3 · observed 2026-10-20T20:12:50.7227438Z · period 2026-10-15–2026-10-152026-10-16: 2 · observed 2026-10-20T20:12:50.7227438Z · period 2026-10-16–2026-10-162026-10-17: 6 · observed 2026-10-20T20:12:50.7227438Z · period 2026-10-17–2026-10-172026-10-19: 2 · observed 2026-10-20T20:12:50.7227438Z · period 2026-10-19–2026-10-19
23 Jul19 Oct

pypistats · 56 observations shown. Missing days are unknown. Source category: overall: without mirrors.

View observed values
Date / source periodValueObserved at
2026-10-19 – 2026-10-1922026-10-20T20:12:50.7227438Z
2026-10-17 – 2026-10-1762026-10-20T20:12:50.7227438Z
2026-10-16 – 2026-10-1622026-10-20T20:12:50.7227438Z
2026-10-15 – 2026-10-1532026-10-20T20:12:50.7227438Z
2026-10-14 – 2026-10-1422026-10-20T20:12:50.7227438Z
2026-10-13 – 2026-10-1332026-10-20T20:12:50.7227438Z
2026-10-12 – 2026-10-1222026-10-20T20:12:50.7227438Z
2026-10-11 – 2026-10-1142026-10-20T20:12:50.7227438Z
2026-10-10 – 2026-10-1022026-10-20T20:12:50.7227438Z
2026-10-09 – 2026-10-0992026-10-20T20:12:50.7227438Z
2026-10-08 – 2026-10-08142026-10-20T20:12:50.7227438Z
2026-10-07 – 2026-10-0712026-10-20T20:12:50.7227438Z
2026-10-06 – 2026-10-0612026-10-20T20:12:50.7227438Z
2026-10-04 – 2026-10-0412026-10-20T20:12:50.7227438Z
2026-10-01 – 2026-10-0132026-10-20T20:12:50.7227438Z
2026-09-30 – 2026-09-30122026-10-20T20:12:50.7227438Z
2026-09-29 – 2026-09-2962026-10-20T20:12:50.7227438Z
2026-09-28 – 2026-09-2832026-10-20T20:12:50.7227438Z
2026-09-27 – 2026-09-2712026-10-20T20:12:50.7227438Z
2026-09-26 – 2026-09-2632026-10-20T20:12:50.7227438Z
2026-09-25 – 2026-09-2522026-10-20T20:12:50.7227438Z
2026-09-24 – 2026-09-2492026-10-20T20:12:50.7227438Z
2026-09-23 – 2026-09-2312026-10-20T20:12:50.7227438Z
2026-09-20 – 2026-09-2012026-10-20T20:12:50.7227438Z
2026-09-18 – 2026-09-18122026-10-20T20:12:50.7227438Z
2026-09-12 – 2026-09-1212026-10-20T20:12:50.7227438Z
2026-09-10 – 2026-09-1012026-10-20T20:12:50.7227438Z
2026-09-09 – 2026-09-0922026-10-20T20:12:50.7227438Z
2026-09-08 – 2026-09-0822026-10-20T20:12:50.7227438Z
2026-09-07 – 2026-09-0712026-10-20T20:12:50.7227438Z
2026-09-06 – 2026-09-0662026-10-20T20:12:50.7227438Z
2026-09-05 – 2026-09-0512026-10-20T20:12:50.7227438Z
2026-09-03 – 2026-09-0312026-10-20T20:12:50.7227438Z
2026-09-01 – 2026-09-0112026-10-20T20:12:50.7227438Z
2026-08-31 – 2026-08-3122026-10-20T20:12:50.7227438Z
2026-08-30 – 2026-08-3012026-10-20T20:12:50.7227438Z
2026-08-28 – 2026-08-2812026-10-20T20:12:50.7227438Z
2026-08-26 – 2026-08-2612026-10-20T20:12:50.7227438Z
2026-08-24 – 2026-08-24132026-10-20T20:12:50.7227438Z
2026-08-23 – 2026-08-2342026-10-20T20:12:50.7227438Z
2026-08-20 – 2026-08-2012026-10-20T20:12:50.7227438Z
2026-08-19 – 2026-08-1922026-10-20T20:12:50.7227438Z
2026-08-18 – 2026-08-18542026-10-20T20:12:50.7227438Z
2026-08-17 – 2026-08-1792026-10-20T20:12:50.7227438Z
2026-08-14 – 2026-08-1432026-10-20T20:12:50.7227438Z
2026-08-13 – 2026-08-1322026-10-20T20:12:50.7227438Z
2026-08-09 – 2026-08-0982026-10-20T20:12:50.7227438Z
2026-08-06 – 2026-08-0612026-10-20T20:12:50.7227438Z
2026-08-04 – 2026-08-0482026-10-20T20:12:50.7227438Z
2026-08-03 – 2026-08-03122026-10-20T20:12:50.7227438Z
2026-08-02 – 2026-08-0212026-10-20T20:12:50.7227438Z
2026-08-01 – 2026-08-0112026-10-20T20:12:50.7227438Z
2026-07-29 – 2026-07-2912026-10-20T20:12:50.7227438Z
2026-07-27 – 2026-07-2722026-10-20T20:12:50.7227438Z
2026-07-24 – 2026-07-2412026-10-20T20:12:50.7227438Z
2026-07-23 – 2026-07-2322026-10-20T20:12:50.7227438Z

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.4.024 Oct 202510

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
alexeiveselov92/abtk

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 17:45 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:22bd6936cb41587341c5e83ebd4c7a9a2c59e6db0561e4e42562f24e21399512