Reference: Probability Distributions
A probability distribution is a function that assigns probabilities to the possible outcomes of a random process. For a discrete distribution, those probabilities sum to 1 over the countable outcomes; for a continuous distribution, the area under the density curve integrates to 1. Every simulation you write with np.random.*, every confidence interval you compute, and every Bayesian prior you place is ultimately a choice of distribution — so the roster below is the vocabulary you need to read the rest of the corpus cleanly.
Roster of core distributions
| Distribution | Formula / density | Support (range) | Mean | Variance | Natural generative story |
|---|---|---|---|---|---|
| Bernoulli | A single yes/no trial with success probability | ||||
| Binomial | Count of successes in independent Bernoulli trials | ||||
| Poisson | Count of events in a fixed window when events are rare and independent | ||||
| Uniform | Pure ignorance / maximum-entropy over a bounded interval | ||||
| Normal (Gaussian) | Sum (or average) of many independent additive effects — Central Limit Theorem | ||||
| Exponential | Waiting time until the next event in a Poisson process | ||||
| Gamma | Waiting time until the -th event; sum of exponentials; conjugate prior for Poisson rates | ||||
| Beta | A probability about a probability; conjugate prior for Bernoulli/Binomial |
The two parameters of the Gamma distribution go by many names depending on the textbook — here use the shape/rate convention; the shape/scale convention writes the scale as . NumPy and SciPy both expose scale, so when you port these formulas to code, mind which convention you’re in.
Which distribution for which generative story?
Use this tree when you can describe how the data is produced but aren’t sure which distribution matches that story.
- Is the outcome a count?
- Is each trial a single yes/no? → Bernoulli (one coin flip) or Binomial ( flips, count the heads).
- Is the count over a fixed interval with no natural upper bound, and events are rare? → Poisson ( = expected count per interval).
- Is the count bounded but you want maximum ignorance? → Discrete Uniform on .
- Is the outcome a continuous quantity on a bounded range?
- On with no preferred value? → Uniform.
- On and you want to model an unknown probability with prior beliefs? → Beta.
- Is the outcome a continuous quantity on the full real line?
- Does it arise from averaging or summing many small independent effects? → Normal (Central Limit Theorem).
- Is it strictly positive and roughly symmetric on the log scale? → Log-Normal (a Normal on ; common for prices, incomes, sizes).
- Is the outcome a waiting time or time-to-event?
- Time until the first event with constant hazard? → Exponential.
- Time until the -th event? → Gamma.
- Heavy-tailed waiting times (e.g., extreme events)? → consider Weibull (beyond this roster).
The single most important branch is the Normal one: it is the default distribution for measurement noise, regression residuals, and aggregate effects, and it is the limiting distribution that the Central Limit Theorem hands you for free. The corpus article The Central Limit Theorem: Why Your Data Doesn’t Need to Be Normal builds the intuition for why averaging washes out almost any original shape into Normal.
Worked Python example: sampling and plotting each distribution
The block below draws 50,000 samples from each distribution, computes the empirical mean and variance, and overlays the theoretical density. Run it as-is in any modern Python environment with NumPy, SciPy, and Matplotlib installed. The comments after each print are the actual output of this exact run (NumPy 2.x, seed=42) — re-running it reproduces these numbers exactly, since np.random.default_rng is stable across NumPy versions for these distributions.
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
rng = np.random.default_rng(seed=42)
N = 50_000
# Bernoulli(p=0.3)
bern = rng.binomial(1, 0.3, size=N)
print(bern.mean(), bern.var())
# 0.3023 0.2109 (theoretical: 0.30, 0.21)
# Binomial(n=10, p=0.3)
binom = rng.binomial(10, 0.3, size=N)
print(binom.mean(), binom.var())
# 3.0018 2.1007 (theoretical: 3.0, 2.1)
# Poisson(lambda=4)
pois = rng.poisson(4.0, size=N)
print(pois.mean(), pois.var())
# 4.0014 3.9704 (theoretical: 4, 4)
# Uniform(a=0, b=10) -- note NumPy's high is exclusive
uni = rng.uniform(0, 10, size=N)
print(uni.mean(), uni.var())
# 4.9892 8.3094 (theoretical: 5, 8.333)
# Normal(mu=2, sigma=3) -- sigma is the standard deviation, not variance
norm = rng.normal(2, 3, size=N)
print(norm.mean(), norm.var())
# 1.9800 9.0412 (theoretical: 2, 9)
# Exponential(rate=0.5) -> scale = 1/rate = 2
expo = rng.exponential(scale=2.0, size=N)
print(expo.mean(), expo.var())
# 1.9866 3.9654 (theoretical: 2, 4)
# Gamma(shape=3, scale=2) -> mean = shape*scale, var = shape*scale^2
gam = rng.gamma(3, 2, size=N)
print(gam.mean(), gam.var())
# 5.9814 11.8007 (theoretical: 6, 12)
# Beta(alpha=2, beta=5)
beta = rng.beta(2, 5, size=N)
print(beta.mean(), beta.var())
# 0.2855 0.0255 (theoretical: 2/7 ~ 0.2857, 10/(49*8) ~ 0.0255)
# Quick visual check for one of them
fig, ax = plt.subplots(1, 1, figsize=(7, 3))
ax.hist(binom, bins=np.arange(-0.5, 11.5, 1), density=True, alpha=0.4, label="empirical")
xs = np.arange(0, 11)
ax.plot(xs, stats.binom.pmf(xs, 10, 0.3), "o-", label="theoretical")
ax.set_xlabel("k"); ax.set_ylabel("P(X=k)"); ax.legend(); ax.set_title("Binomial(10, 0.3)")
plt.tight_layout(); plt.show()
The unifying object is the cumulative distribution function (CDF), . Every distribution above has one; the density/mass function is its derivative (continuous case) or its discrete jump structure.
| Plain English | Statistical symbol | Python equivalent |
|---|---|---|
| Probability density at | scipy.stats.<dist>.pdf(x, ...) (continuous) or .pmf(...) (discrete) | |
| Cumulative probability up to | scipy.stats.<dist>.cdf(x, ...) | |
| Inverse CDF (quantile function) | scipy.stats.<dist>.ppf(u, ...) | |
| Expectation (mean) | np.mean(samples) on a large draw | |
| Variance | np.var(samples, ddof=1) for unbiased sample estimate | |
| -th central moment | ((samples - samples.mean())**n).mean() | |
| Skew (standardized 3rd moment) | scipy.stats.skew(samples) | |
| Excess kurtosis | scipy.stats.kurtosis(samples) |
The Bernoulli, Binomial, Poisson, and discrete-Uniform distributions are discrete — they have a probability mass function that you can read directly. The (continuous) Uniform, Normal, Exponential, Gamma, and Beta distributions are continuous — they have a density , and can exceed 1 (it just has to integrate to 1). A common student confusion is treating a Normal density of as a “probability”; it is not. Only the integral over a region is a probability.
The moment-generating function for a Normal is , and the fact that sums of independent Normals are Normal falls out immediately because MGFs multiply under independence. That same multiplicative property is the algebraic core of the Central Limit Theorem: the sum of i.i.d. variables, standardized, has an MGF that converges to — the MGF of a standard Normal.
Line by line:
rng = np.random.default_rng(seed=42)— the modern NumPyGenerator. Prefer it over the legacynp.random.*functions because it is faster, statistically better-tested, and reproducible per-instance. The seed pins the stream so the numbers above match what you get on your machine.N = 50_000— large enough that the empirical mean and variance land within a percent or two of theory for these distributions. For heavy-tailed distributions (Gamma with small shape, or anything heavier) you would need far more samples for the variance to stabilize.rng.binomial(1, 0.3, size=N)—n=1makes this Bernoulli;n=10makes it Binomial(10, 0.3). NumPy does not expose a separaterng.bernoulli; that’s a common gotcha.rng.poisson(4.0, ...)— the single argument is the rate , which is both the mean and the variance. The Poisson’s variance equals its mean; if your count data has variance >> mean, you have overdispersion and Poisson is the wrong model.rng.uniform(0, 10, ...)— note thathighis exclusive in NumPy’suniform. The continuous Uniform on has mean 5 and variance .rng.normal(2, 3, ...)— the second argument is the standard deviation , not the variance . This is the single most common bug in distribution code. If you want variance 9, pass3, not9.rng.exponential(scale=2.0, ...)—scaleis , the mean of the distribution. A rate of per unit time means an average wait of time units.rng.gamma(3, 2, ...)— NumPy/SciPy use the shape/scale convention; mean , variance . Verify withscipy.stats.gamma.mean(3, scale=2)andscipy.stats.gamma.var(3, scale=2).rng.beta(2, 5, ...)— the Beta’s mean makes it interpretable as “your best-guess probability” in a Beta-Binomial model. With the mean is — i.e., you lean toward “this Bernoulli succeeds about 29% of the time.”ax.hist(..., density=True)vsax.plot(xs, stats.binom.pmf(...))— for a discrete distribution, overlay the PMF as points; for a continuous one, overlay the PDF as a curve. Usingdensity=Trueon the histogram normalizes the bars to integrate to 1, so they’re comparable to a PDF.
The “natural home” map: real-world process → distribution
Each distribution earns its place by being the limiting or exact answer to a recognizable generative process. Memorize the process, and the distribution comes with it.
| Generative process | Distribution | Why |
|---|---|---|
| Flip a biased coin once | Bernoulli | Definition of the distribution |
| Flip a biased coin times, count heads | Binomial | Sum of i.i.d. Bernoullis |
| Independent rare events occur over a fixed window | Poisson | Limit of Binomial as , , |
| Time until the first rare event | Exponential | Continuous analogue of geometric; memoryless |
| Time until the -th rare event | Gamma | Sum of i.i.d. Exponentials |
| Average of many small independent additive effects | Normal | Central Limit Theorem |
| Pick a value with no information beyond its bounds | Uniform | Maximum-entropy distribution on |
| Express a prior belief about an unknown probability | Beta | Conjugate prior for Bernoulli/Binomial; flexible shape on |
The Beta-Binomial and Gamma-Poisson pairings show up constantly in Bayesian workflows because of conjugacy: if your likelihood is Binomial and your prior is Beta, your posterior is also Beta (with updated parameters), and similarly for Gamma-Poisson. The corpus article The Bootstrap: Estimating Uncertainty Without Assumptions is the frequentist counterpart — it sidesteps the conjugacy story entirely by resampling, which is why “the bootstrap” is the no-assumptions route when you’d rather not commit to a parametric prior.
Edge cases and common mistakes
1. Confusing sigma and sigma^2 in np.random.normal. The second positional argument is the standard deviation. Passing np.random.normal(0, 4) produces a distribution with variance 16, not 4. If your code’s residual variance looks four times too big, this is the first thing to check.
2. Treating a density as a probability. A Normal with has peak density , which is greater than 1. That’s fine — densities integrate to probability, they aren’t probabilities themselves. This matters when you threshold on density values (“outlier if ”) — that threshold is unitless and depends on .
3. Using Poisson for count data with overdispersion. Poisson forces . Real count data — clicks per session, claims per policyholder, faults per batch — almost always has variance larger than the mean. Check the ratio first; if , reach for Negative Binomial instead. This is directly relevant to drift monitoring: see How to Detect and Handle Data Drift in Production), where assuming Poisson when the data is overdispersed will manufacture false-positive drift alerts.
4. Sampling a Binomial with and calling it Bernoulli. This is correct but obscures the modeling intent. Use rng.binomial(1, p) only when you genuinely have one trial; if your data is per-row yes/no, the Bernoulli framing keeps the likelihood story honest. Statistically they are the same distribution, but the story you tell about your data differs.
5. Forgetting that rng.uniform’s upper bound is exclusive. rng.uniform(0, 1) never returns exactly 1.0. For simulation this rarely matters, but if you index into a list with the result (categories[int(rng.uniform(0, len(cats)))]) the exclusivity is what keeps you from going out of bounds — exploit it deliberately.
6. Using the Normal as a default for positive-valued data. A Normal assigns nonzero probability to negative values. For strictly positive measurements (prices, durations, sizes) the Exponential, Gamma, or Log-Normal are safer generative models; for a Normal you at least need so that is negligible.
7. Mis-specifying Gamma parameterization when porting formulas. Three conventions are in active use: shape-rate , shape-scale , and mean-shape . Wikipedia’s primary form is shape-rate; NumPy and SciPy expose shape-scale; Stan exposes both. Always read the docstring before copying a formula across libraries.
8. Picking a Beta prior with or without thinking. Those parameters put infinite density at 0 or 1 respectively — the prior says “the probability is almost certainly 0 or 1, but I don’t know which.” For a weakly-informative prior centered on a value, use with at your prior mean. The corpus uses this pattern implicitly when the modeling articles reach for priors — keep the edges of the Beta in mind.
9. Assuming the Central Limit Theorem rescues small samples. It doesn’t, or not quickly. The CLT is an asymptotic result; for from a heavily skewed parent distribution the sample mean is still noticeably skewed. The corpus article Gradient Boosting for Time Series Using LightGBM) is relevant here: tree-based models don’t lean on Normality assumptions for the target, which is part of why they tolerate the heavy-tailed, small-effective-sample regime common in time series.
10. Reusing the global np.random state across parallel workers. If you fork a process and each worker calls np.random.normal, they inherit the same seed and produce identical streams. Always construct a np.random.default_rng() (or a SeedSequence-spawned child) inside each worker. The corpus article on the bootstrap depends on this being done correctly — duplicated resamples silently corrupt your uncertainty estimates.
Cross-references
- The Central Limit Theorem: Why Your Data Doesn’t Need to Be Normal — builds the intuition for why sums and averages converge to Normal, and when the convergence is fast enough to lean on.
- The Bootstrap: Estimating Uncertainty Without Assumptions — resampling-based alternative to assuming a parametric distribution; explains why you sometimes don’t need to commit to one from this roster.
- How to Detect and Handle Data Drift in Production) — uses distributional comparisons (KS tests, PSI) to spot when production data has shifted away from the training distribution; the choice of reference distribution matters.
- Gradient Boosting for Time Series Using LightGBM) — tree-based models don’t assume a target distribution, which is why they thrive on the heavy-tailed, non-Normal data that the roster’s other branches describe.
Further reading
- Casella, G. & Berger, R. L. Statistical Inference (2nd ed., Duxbury, 2002), Chapters 3–4 — the canonical graduate-level treatment of the distributions above, including derivations of the Poisson-from-Binomial and Gamma-from-Exponential limits. The reference text most working statisticians reach for first.
- Wikipedia — Conjugate prior. A clean table of which prior is conjugate to which likelihood (Beta-Binomial, Gamma-Poisson, Normal-Normal, Dirichlet-Multinomial), with the posterior-update formulas. The natural next step for readers coming to this roster from the Bayesian side.
- NumPy
Generatordocs —numpy.org/doc/stable/reference/random/generator.html— the canonical reference for the sampling API used in the worked example above, including theSeedSequence-based parallel-stream pattern. - SciPy
statsmodule docs —docs.scipy.org/doc/scipy/reference/stats.html—pdf,cdf,ppf,fit, andrvsfor every distribution in the roster; the reference for when you need exact quantiles or maximum-likelihood parameter estimates rather than samples.
Related articles
- Statistics Under review
Reference: Significance Tests
A reference catalog of significance tests — z-tests, t-tests, chi-square, KS, and permutation tests — covering what each tests, when to use it, and common pitfalls.
- Statistics Under review
Reference: Hypothesis Testing
A complete reference on hypothesis testing: p-values, error types, power, multiple comparison corrections, and choosing the right test with Python examples.
- Statistics Under review
Type I vs. Type II Errors: How to Actually Manage the Tradeoff Without a Math Degree
Learn the Type I vs. Type II error tradeoff with smoke-alarm analogies and Python code, and discover how to set thresholds based on real business costs.
- Statistics Under review
Think Like a Bayesian: A Guide for Frequentists Who Hate Formulas
Learn how Bayesian priors, posteriors, and base rates produce clearer, more actionable insights than Frequentist methods — with intuitive Python examples.
Looking for something else?
Search every article by title, summary or topic.