Learn one API, get every distribution free
Most people learn distributions as a pile of unrelated formulas. scipy takes a better view: a distribution is an object, and every object has the same methods. Ask any of them for its density, its cumulative probability, its quantiles, some random samples, or a fit to your data - the method names never change. That means the hard part is no longer the math, it is picking the right distribution for your data. That choice is what this session drills.
.pdf/.pmf, .cdf, .ppf (the inverse cdf / quantile), .rvs (sample), and .fit (estimate params) - across normal, binomial, Poisson, uniform, exponential, and lognormal. You will model Lumen conversions as binomial and daily orders as Poisson, prove order value is lognormal not normal with a Q-Q plot, and use .ppf and .cdf to answer real business questions about order size.
The five verbs, and which distribution to reach for 7 min live
Two things to hold in your head. First, the five methods every scipy distribution shares. Second, a quick decision for which distribution fits your data - is it a continuous symmetric measurement, a count of yes/no trials, a count of events per period, or right-skewed money? The map below is the whole decision.
LiveThe five verbs every distribution shares4 min▶
.pdf(x)/.pmf(k): the height. pdf for continuous (a density), pmf for discrete (a real probability)..cdf(x): P(X <= x). "What fraction sits at or below here." Answers "P(order < $100)"..ppf(q): the inverse cdf, a quantile. "What value has q of the mass below it." Answers "the 95th-percentile order"..rvs(size): draw random samples. Your simulation engine from b2 and b3, built in..fit(data): estimate the distribution's parameters from data by maximum likelihood. Turns raw numbers into a fitted model.
cdf takes a value and returns a probability; ppf takes a probability and returns a value. So rv.ppf(rv.cdf(100)) gives you 100 back. Whenever a question says "what value" you want ppf; whenever it says "what fraction" or "what probability" you want cdf.
Model conversions, then prove order value is lognormal ★ 22 min · everyone builds
First the easy fits: conversions as binomial, daily orders as Poisson. Then the honest one - hold order value up against both a normal and a lognormal, and let the Q-Q plot decide. Finally use ppf and cdf to answer questions the business actually asks.
Conversions and daily orders. Binomial for "successes out of 4,000 sessions", Poisson for "orders per day". They center on the same ~128.
import numpy as np, scipy.stats as st, matplotlib.pyplot as plt
orders = st.binom(4000, 0.032) # conversions among 4,000 daily sessions
daily = st.poisson(128) # daily orders, lambda = 4000 * 0.032 = 128
print("binomial mean:", orders.mean(), " Poisson mean:", daily.mean()) # ~128 both
k = np.arange(90, 170)
fig, ax = plt.subplots(1, 2, figsize=(11, 4))
ax[0].bar(k, orders.pmf(k), color="#7FD1DE"); ax[0].set_title("Binomial(4000, 0.032)")
ax[1].bar(k, daily.pmf(k), color="#7FD1DE"); ax[1].set_title("Poisson(128)")
plt.tight_layout(); plt.show()
# Poisson is the binomial's large-n, small-p limit - the two pmfs nearly overlay
The honest fit. Order value is right-skewed money, so fit lognormal AND normal, overlay both, then let a Q-Q plot expose which one fails.
import pandas as pd, numpy as np, scipy.stats as st, matplotlib.pyplot as plt
df = pd.read_parquet("lumen_orders.parquet")
ov = df["order_value"].values
shape, loc, scale = st.lognorm.fit(ov, floc=0) # fit lognormal (fix loc=0)
mu, sigma = st.norm.fit(ov) # fit normal
x = np.linspace(ov.min(), ov.max(), 300)
fig, ax = plt.subplots(1, 2, figsize=(11, 4))
ax[0].hist(ov, bins=60, density=True, color="#7FD1DE", edgecolor="white")
ax[0].plot(x, st.lognorm.pdf(x, shape, loc, scale), color="#0E7C90", lw=2, label="lognormal")
ax[0].plot(x, st.norm.pdf(x, mu, sigma), color="#F97362", lw=2, label="normal")
ax[0].legend(); ax[0].set_title("Order value - lognormal hugs it, normal does not")
st.probplot(ov, dist="norm", plot=ax[1]) # normal Q-Q plot
ax[1].set_title("Normal Q-Q - points peel off in the right tail")
plt.tight_layout(); plt.show()
A Lumen analyst once built a "flag orders more than 2 standard deviations above the mean" rule, assuming normality. On lognormal data that rule flags almost nothing on the low side and a flood of ordinary large orders on the high side, because the normal model puts the mean and SD in the wrong place. The Q-Q plot above is the 10-second check that would have caught it: when the tail points bend away from the line, stop assuming normal.
Answer business questions with ppf and cdf. How big is a "big" order, and how many orders come in under $100?
import scipy.stats as st
# reuse the fitted lognormal params: shape, loc, scale
p95 = st.lognorm.ppf(0.95, shape, loc, scale) # ppf = quantile / inverse cdf
print("95th-percentile order:", round(p95, 2)) # the size of a top-5% order
p_under_100 = st.lognorm.cdf(100, shape, loc, scale)
print("P(order < $100):", round(p_under_100, 3))
# sanity: cdf and ppf are inverses
print(round(st.lognorm.cdf(p95, shape, loc, scale), 2)) # 0.95
The sampler - watch samples fill the curve interactive
This is the code you just ran, made draggable. Flip between Normal, Binomial, and Poisson, drag the parameters, and watch the random samples pile up into the theoretical pmf / pdf. More samples, tighter fit - the law of large numbers from b2 and the distributions from today, in one widget.
This week ◐ 40 min total
- Fit and sample. Use
st.lognorm.rvs(shape, loc, scale, size=50_000)to draw synthetic order values from your fitted model, then compare their mean and median to the real Lumen numbers (~$74 and ~$63). - Poisson for support. Freeze
st.poisson(5)for support tickets per hour. Use.cdfto find P(more than 8 tickets in an hour) as1 - cdf(8). - ppf for SLAs. If order value is lognormal, what order size is the 99th percentile? One
.ppf(0.99)call. Write one sentence on how a warehouse would use it. - Optional: add an exponential fit for time-between-orders and an
st.uniformbaseline, and note which real Lumen columns each does or does not suit.
Three questions before you go 🎯 ◐ 90 seconds
1 · You want the order value below which 95% of orders fall. Which scipy method?
ppf maps a probability to a value (the quantile), which is exactly "the value with 95% of the mass below it." cdf goes the other way (value to probability); pdf gives a density height.
2 · A normal Q-Q plot of Lumen order value shows the points bending away from the line in the upper tail. What does that tell you?
Q-Q points that peel off the line in the tail mean the data departs from the reference distribution there. For right-skewed money the upper tail is heavier than normal, which is exactly why lognormal is the honest choice.
3 · Why can Lumen's daily orders be modeled as Poisson(128) when there are ~4,000 sessions at 3.2%?
When n is large and p is small, the binomial(n, p) is very well approximated by a Poisson with lambda = n x p. Here 4,000 x 0.032 = 128, so both pmfs center and overlay on ~128 orders/day.
What this session covers
This builder session covers ~80% of the named-distributions content in the sources below, using the shared scipy API on Lumen data. Sampling distributions and the central limit theorem - why sample means go normal even when the data does not - come next in b5.
Builder Session 4 cheat sheet · pin this
.pdf/.pmf, .cdf, .ppf, .rvs, .fit. Learn once, use everywhere.st.probplot(data, dist="norm", plot=ax). Tail bends off the line = not that distribution.