One-Proportion Z-Test
Tests whether a population proportion π equals a hypothesized value π₀.
Conditions for Validity
The test assumes the sampling distribution of p̂ is approximately normal:
- nπ₀ ≥ 10 and n(1−π₀) ≥ 10
- Random sample from the population
- Independence (sampling without replacement: n < 10% of N)
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
def proportion_z_test(successes, n, pi_0, alternative='two-sided', alpha=0.05):
p_hat = successes / n
# Check conditions
if n * pi_0 < 10 or n * (1 - pi_0) < 10:
print("WARNING: Conditions not met (nπ₀ or n(1-π₀) < 10). Use exact binomial test.")
# Test statistic
se = np.sqrt(pi_0 * (1 - pi_0) / n)
z = (p_hat - pi_0) / se
# P-value
if alternative == 'two-sided':
p_val = 2 * stats.norm.sf(abs(z))
elif alternative == 'less':
p_val = stats.norm.cdf(z)
elif alternative == 'greater':
p_val = stats.norm.sf(z)
# Confidence interval (Wilson interval — more accurate)
z_crit = stats.norm.ppf(1 - alpha/2)
# Standard CI
se_ci = np.sqrt(p_hat * (1 - p_hat) / n)
ci_std = (p_hat - z_crit * se_ci, p_hat + z_crit * se_ci)
# Effect size (Cohen's h)
h = 2 * np.arcsin(np.sqrt(p_hat)) - 2 * np.arcsin(np.sqrt(pi_0))
print(f"=== One-Proportion Z-Test ===")
print(f"H₀: π = {pi_0}")
print(f"H₁: π {'≠' if alternative=='two-sided' else '<' if alternative=='less' else '>'} {pi_0}")
print(f"\np̂ = {successes}/{n} = {p_hat:.4f}")
print(f"z = {z:.4f}")
print(f"p-value = {p_val:.6f}")
print(f"95% CI (standard): ({ci_std[0]:.4f}, {ci_std[1]:.4f})")
print(f"Cohen's h = {h:.4f}")
print(f"Decision: {'Reject H₀' if p_val < alpha else 'Fail to reject H₀'}")
return z, p_val
# Example 1: Is a coin fair?
print("=== Example 1: Coin Fairness Test ===")
# 63 heads in 100 flips. Is coin biased?
proportion_z_test(63, 100, 0.50, alternative='two-sided')
# Example 2: Website conversion rate
print("\n=== Example 2: Website Conversion Rate ===")
# Claim: 5% conversion rate. We observe 23 conversions in 350 visitors.
proportion_z_test(23, 350, 0.05, alternative='greater')
Comparison: Z-Test vs Exact Binomial
from scipy.stats import binom_test
# Small sample: when Z approximation breaks down
successes, n, pi_0 = 8, 20, 0.5
# Z-test (may be inaccurate for small n)
se = np.sqrt(pi_0 * (1 - pi_0) / n)
z = (successes/n - pi_0) / se
p_z = 2 * stats.norm.sf(abs(z))
# Exact binomial test (always valid)
p_exact = binom_test(successes, n, pi_0, alternative='two-sided')
print(f"n={n}, x={successes}, π₀={pi_0}")
print(f"Z-test p-value: {p_z:.6f}")
print(f"Exact binomial p-value: {p_exact:.6f}")
print(f"Difference: {abs(p_z - p_exact):.6f}")
print("Use exact binomial when nπ₀ < 10 or n(1-π₀) < 10")
Key Takeaways
- Conditions: nπ₀ ≥ 10 AND n(1−π₀) ≥ 10 for normality approximation
- Standard error uses π₀ (not p̂) in the test statistic
- Confidence interval uses p̂ in the standard error
- Effect size: Cohen's h = 2arcsin(√p̂) − 2arcsin(√π₀)
- Exact binomial test is more accurate for small n — prefer it when conditions aren't met
- Real applications: A/B testing, quality control, election polling