Statistical Distributions Every Developer Should Know
Statistical distributions describe how data points are spread across possible values. Understanding the common distributions helps you model real-world phenomena, set up A/B tests correctly, detect anomalies, and make data-driven decisions. This article covers the three most important distributions — normal, binomial, and Poisson — with Python code examples for simulation and analysis.
The Normal (Gaussian) Distribution
The normal distribution is the bell-shaped curve that appears throughout nature and data analysis. Heights, test scores, measurement errors, and many natural phenomena follow a normal distribution. It is defined by two parameters: the mean (μ) — the center of the curve — and the standard deviation (σ) — the spread. About 68% of values fall within one standard deviation of the mean, 95% within two, and 99.7% within three (the empirical rule or 68-95-99.7 rule). The Central Limit Theorem explains why the normal distribution is so pervasive: whenever you average many independent random variables (regardless of their individual distributions), the average tends toward a normal distribution as the sample size grows. This is why the t-test, ANOVA, and many other statistical methods assume normality — they rely on the CLT for their validity.
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
# Generate samples from a normal distribution
mean = 50
std_dev = 10
samples = np.random.normal(loc=mean, scale=std_dev, size=1000)
print(f"Mean: {np.mean(samples):.2f} (expected {mean})")
print(f"Std: {np.std(samples):.2f} (expected {std_dev})")
print(f"68% within: ({mean - std_dev:.0f}, {mean + std_dev:.0f})")
print(f"95% within: ({mean - 2*std_dev:.0f}, {mean + 2*std_dev:.0f})")
# Probability density function (PDF) — the height of the curve at a given x
x = np.linspace(mean - 4*std_dev, mean + 4*std_dev, 200)
pdf = stats.norm.pdf(x, loc=mean, scale=std_dev)
# Cumulative distribution function (CDF) — probability of value ≤ x
prob_below_60 = stats.norm.cdf(60, loc=mean, scale=std_dev)
prob_above_60 = 1 - prob_below_60
print(f"Probability of value <= 60: {prob_below_60:.3f}")
print(f"Probability of value >= 60: {prob_above_60:.3f}")
# Percent point function (PPF) — inverse CDF, find the value at a percentile
percentile_90 = stats.norm.ppf(0.9, loc=mean, scale=std_dev)
print(f"90th percentile value: {percentile_90:.1f}")
The PDF gives the relative likelihood of a specific value — it is the height of the bell curve at that point. The CDF gives the cumulative probability up to a value — it is the area under the curve from negative infinity to that value. The PPF (or quantile function) does the reverse: given a probability, it returns the value at which the CDF equals that probability. For example, the 90th percentile is the value below which 90% of observations fall. These three functions — PDF, CDF, PPF — are available for every continuous distribution in SciPy through the stats module, making it easy to compute probabilities and thresholds for any distribution.
The Binomial Distribution
The binomial distribution models the number of successes in a fixed number of independent trials, each with the same probability of success. The classic example is coin flipping — the number of heads in 10 flips of a fair coin follows a binomial distribution with n=10 and p=0.5. In software engineering, the binomial distribution powers A/B testing (number of conversions out of total visitors), quality control (number of defective items in a batch), and reliability engineering (number of successful requests out of total attempts). The binomial distribution has two parameters: the number of trials (n) and the probability of success per trial (p). Its mean is n×p and its variance is n×p×(1-p).
from scipy.stats import binom
# Parameters
n_trials = 10
p_success = 0.5 # fair coin
# Probability mass function — probability of exactly k successes
for k in range(0, n_trials + 1):
prob = binom.pmf(k, n_trials, p_success)
print(f"P({k} heads in {n_trials} flips) = {prob:.3f}")
# Cumulative probability — probability of at most 6 heads
prob_at_most_6 = binom.cdf(6, n_trials, p_success)
print(f"\nP(at most 6 heads) = {prob_at_most_6:.3f}")
# Probability of at least 7 heads
prob_at_least_7 = 1 - binom.cdf(6, n_trials, p_success)
print(f"P(at least 7 heads) = {prob_at_least_7:.3f}")
# A/B testing example: conversion rates
# Control group: 100 visitors, 12 conversions (12% conversion)
# Treatment group: 100 visitors, 20 conversions (20% conversion)
# Is the difference statistically significant? Use a two-sample proportion test.
from scipy.stats import chi2_contingency
import numpy as np
observed = np.array([[12, 88], # treatment: 12 converted, 88 did not
[12, 88]]) # control: 12 converted, 88 did not
chi2, p_value, dof, expected = chi2_contingency(observed)
print(f"\nA/B Test chi2: {chi2:.2f}, p-value: {p_value:.4f}")
# If p_value < 0.05, the difference is statistically significant
The PMF (Probability Mass Function) for a discrete distribution like the binomial gives the probability of exactly k successes. This is different from the PDF used for continuous distributions — the PDF gives a density (probability per unit), while the PMF gives an actual probability. The sum of all PMF values across all possible k (0 through n) always equals 1.
The Poisson Distribution
The Poisson distribution models the number of events occurring in a fixed interval of time or space when events happen independently at a constant average rate. It is the go-to distribution for count data: number of website requests per minute, number of errors per hour in a log file, number of customer arrivals per hour, or number of defects per square meter of material. The Poisson distribution has a single parameter: λ (lambda), the average rate of events per interval. The mean equals λ, and the variance also equals λ (a property called equidispersion — if the variance is larger than the mean, the data is overdispersed and a negative binomial distribution may be more appropriate).
from scipy.stats import poisson
# Average rate: 5 events per hour
lambda_rate = 5
# Probability of exactly 3 events in an hour
prob_3 = poisson.pmf(3, lambda_rate)
print(f"P(3 events/hour | λ={lambda_rate}) = {prob_3:.3f}")
# Probability of at most 2 events
prob_at_most_2 = poisson.cdf(2, lambda_rate)
print(f"P(at most 2 events/hour) = {prob_at_most_2:.3f}")
# Probability of more than 8 events (rare event)
prob_more_than_8 = 1 - poisson.cdf(8, lambda_rate)
print(f"P(>8 events/hour) = {prob_more_than_8:.3f}")
# Simulate a week of hourly request counts (168 hours)
np.random.seed(42)
hourly_requests = np.random.poisson(lam=lambda_rate, size=168)
print(f"\nSimulated 168 hours with λ={lambda_rate}:")
print(f" Mean: {np.mean(hourly_requests):.2f} (expected {lambda_rate})")
print(f" Variance: {np.var(hourly_requests):.2f} (expected {lambda_rate})")
print(f" Max requests in any hour: {np.max(hourly_requests)}")
print(f" Hours with >8 requests: {np.sum(hourly_requests > 8)}")
# Anomaly detection: is 15 requests in one hour unusual?
prob_15 = poisson.pmf(15, lambda_rate)
print(f"\nP(15 events/hour | λ={lambda_rate}) = {prob_15:.6f}")
# Very low probability — 15 events in an hour would be an anomaly worth investigating
Choosing the Right Distribution
Use the normal distribution for continuous measurements where values cluster around a central mean — physical measurements, test scores, and aggregate statistics (thanks to the Central Limit Theorem). Use the binomial distribution for binary outcome counts with a fixed number of trials — A/B test conversions, defect rates, yes/no survey responses. Use the Poisson distribution for count data over time or space — request rates, error counts, arrival processes. When your data has more variability than the Poisson allows (variance much larger than the mean), try the negative binomial distribution, which adds an extra dispersion parameter. Python's scipy.stats module provides all three distributions (and many more) with a consistent API: rvs() to generate random samples, pmf() or pdf() for the probability function, cdf() for cumulative probability, and ppf() for quantiles.
echo "All 10 expanded posts written"
