A/B test results
bar chart with Wald confidence intervals
Example from the compendium of canonical charts
Python Code
"""A/B test results — bar chart with Wald confidence intervals."""
import numpy as np
import pandas as pd
import plotly.graph_objects as go
# Kaggle A/B Testing dataset (publicly available on GitHub mirrors)
AB_URL = "https://raw.githubusercontent.com/ndleah/python-mini-project/main/AB%20Testing/ab_data.csv"
AB_URL2 = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/ab-test.csv"
def wald_ci(converted, n, z=1.96):
"""Wald confidence interval for a proportion."""
p = converted / n
se = np.sqrt(p * (1 - p) / n)
return p, p - z * se, p + z * se
def generate():
print("fetching A/B test dataset …")
df = None
for url in [AB_URL, AB_URL2]:
try:
df = fetch_csv(url)
print(f" cols: {list(df.columns)}, rows: {len(df)}")
if len(df) > 10:
break
except Exception as e:
print(f" {url} failed: {e}")
if df is None or len(df) < 10:
print(" using synthetic A/B test data")
rng = np.random.default_rng(34)
n_control, n_treat = 5000, 5000
control_conv = rng.binomial(1, 0.112, n_control)
treat_conv = rng.binomial(1, 0.125, n_treat)
df = pd.DataFrame({
"group": ["control"] * n_control + ["treatment"] * n_treat,
"converted": list(control_conv) + list(treat_conv),
})
source = "Synthetic"
else:
source = "Kaggle A/B Testing"
# Normalize column names
df.columns = [c.strip().lower() for c in df.columns]
group_col = next((c for c in df.columns if "group" in c or "variant" in c), None)
conv_col = next((c for c in df.columns if "convert" in c or "outcome" in c
or "click" in c or "purchase" in c), None)
if group_col is None:
group_col = df.columns[0]
if conv_col is None:
# Try last numeric column
conv_col = df.select_dtypes(include=[np.number]).columns[-1]
print(f" group_col={group_col}, conv_col={conv_col}")
df[conv_col] = pd.to_numeric(df[conv_col], errors="coerce").fillna(0)
groups = df[group_col].unique()[:4] # at most 4 groups for clarity
rates, lo_errs, hi_errs, labels = [], [], [], []
for g in groups:
sub = df[df[group_col] == g]
n = len(sub)
converted = sub[conv_col].sum()
p, lo, hi = wald_ci(converted, n)
rates.append(p * 100)
lo_errs.append((p - lo) * 100)
hi_errs.append((hi - p) * 100)
labels.append(str(g).replace("_", " ").title())
print(f" {g}: n={n}, converted={converted:.0f}, rate={p*100:.2f}% [{lo*100:.2f}%, {hi*100:.2f}%]")
colors = [VIOLET, GREEN, TEAL, PINK][:len(groups)]
fig = go.Figure()
fig.add_trace(go.Bar(
x=labels,
y=rates,
error_y=dict(
type="data",
symmetric=False,
array=hi_errs,
arrayminus=lo_errs,
color="#1c2024",
thickness=2,
width=6,
),
marker=dict(color=colors, opacity=0.85),
hovertemplate="%{x}<br>Conversion: %{y:.2f}%<extra></extra>",
name="",
))
fig.update_layout(
title=dict(text=f"A/B Test Conversion Rates — {source}", x=0.5),
xaxis=dict(title="Variant"),
yaxis=dict(title="Conversion rate (%)", range=[0, max(rates) * 1.35]),
margin=dict(t=60, b=50, l=70, r=40),
showlegend=False,
height=420,
)
return fig
if __name__ == "__main__":
generate()
Made with Plotly