Chapter 71 · Solutions
Proportions & Differences — Solutions ✅
Five challenges, each verified in code.
⚙️ Setup¶
In [1]:
import numpy as np, pandas as pd
from scipy import stats
z = stats.norm.ppf(0.975); rng = np.random.default_rng(710)
BASE="https://raw.githubusercontent.com/johnfisher-ai/Statistics-Data-Science-AI-Visual-Book/main/data/"
CHALLENGE 1
CI for a proportion
220 of 1000 surveyed customers churned. Build a 95% CI for the churn rate.
In [2]:
succ, n = 220, 1000; p=succ/n; se=np.sqrt(p*(1-p)/n)
print(f"p-hat={p:.3f}, 95% CI [{p-z*se:.3f}, {p+z*se:.3f}]")
p-hat=0.220, 95% CI [0.194, 0.246]
CHALLENGE 2
Success-failure condition
For 6 successes out of 40, check whether the normal-approx CI is appropriate.
In [3]:
succ,n=6,40
print(f"successes={succ}, failures={n-succ}; both >= 10? {succ>=10 and n-succ>=10}")
print("-> normal approx is shaky here; prefer the Wilson/exact interval")
successes=6, failures=34; both >= 10? False -> normal approx is shaky here; prefer the Wilson/exact interval
CHALLENGE 3
Difference of two proportions
Group A: 90/600 converted. Group B: 120/620. Build a 95% CI for pB - pA.
In [4]:
pA,nA=90/600,600; pB,nB=120/620,620; diff=pB-pA
se=np.sqrt(pA*(1-pA)/nA + pB*(1-pB)/nB)
print(f"diff={diff:+.3f}, 95% CI [{diff-z*se:+.3f}, {diff+z*se:+.3f}]")
print("excludes 0?", (diff-z*se>0) or (diff+z*se<0))
diff=+0.044, 95% CI [+0.001, +0.086] excludes 0? True
CHALLENGE 4
Difference of two means
Two groups spend Normal(40,12) n=200 and Normal(44,13) n=210. Build a 95% CI for the difference of means.
In [5]:
a=rng.normal(40,12,200); b=rng.normal(44,13,210); diff=b.mean()-a.mean()
se=np.sqrt(a.var(ddof=1)/len(a)+b.var(ddof=1)/len(b))
dfw=se**4/((a.var(ddof=1)/len(a))**2/(len(a)-1)+(b.var(ddof=1)/len(b))**2/(len(b)-1))
t=stats.t.ppf(0.975,dfw)
print(f"diff={diff:+.2f}, 95% CI [{diff-t*se:+.2f}, {diff+t*se:+.2f}]")
diff=+7.35, 95% CI [+4.90, +9.79]
CHALLENGE 5
Real data: A/B lift
Load confidence-intervals-for-proportions-and-differences--ab_test.xlsx and report each variant's conversion CI and the CI for the lift (B - A).
In [6]:
try: ab = pd.read_excel("../../data/confidence-intervals-for-proportions-and-differences--ab_test.xlsx", sheet_name="Visitors")
except FileNotFoundError: ab = pd.read_excel(BASE+"confidence-intervals-for-proportions-and-differences--ab_test.xlsx", sheet_name="Visitors")
g=ab.groupby("variant").converted.agg(["size","sum"])
pA,nA=g.loc["A","sum"]/g.loc["A","size"],g.loc["A","size"]; pB,nB=g.loc["B","sum"]/g.loc["B","size"],g.loc["B","size"]
seA=np.sqrt(pA*(1-pA)/nA); seB=np.sqrt(pB*(1-pB)/nB)
print(f"A: {pA*100:.2f}% [{(pA-z*seA)*100:.2f}, {(pA+z*seA)*100:.2f}]")
print(f"B: {pB*100:.2f}% [{(pB-z*seB)*100:.2f}, {(pB+z*seB)*100:.2f}]")
diff=pB-pA; se=np.sqrt(pA*(1-pA)/nA+pB*(1-pB)/nB)
print(f"lift {diff*100:+.2f} pts, 95% CI [{(diff-z*se)*100:+.2f}, {(diff+z*se)*100:+.2f}] pts; excludes 0? {diff-z*se>0}")
A: 10.95% [9.01, 12.90] B: 14.63% [12.44, 16.81] lift +3.67 pts, 95% CI [+0.75, +6.59] pts; excludes 0? True
Statistics, Data Science and AI: A Visual Handbook · © 2026 John Fisher