Statistics for Data Science · Day 10

Continuous Probability Distributions

Uniform, Normal, and Exponential: what changes when the outcome stops being a count, and why the bell curve ends up underneath almost every statistical method you'll use.

By Shikhar · Series continued · ~17 min read

Day 9 covered four distributions built entirely out of counting: how many successes, how many trials, how many events in a window. Every outcome was a whole number, and every probability came from adding up individual possibilities.

Plenty of real quantities don't work that way. A delivery doesn't arrive in a whole number of minutes. A server's response time isn't an integer. A person's height isn't a count of anything. These are measurements, not counts, and they can take on any value in a continuous range rather than a fixed list of whole numbers.

That shift changes more than the numbers involved. It changes what probability itself means, and that's worth sitting with before naming a single distribution. First, three problems.

Before working through each one, there's a more basic question worth answering first: if X can now take infinitely many values, what does P(X = x) even mean?

From counting to measuring

For a discrete variable, the PMF hands you a real number for each possible outcome. P(X = 3) might be 0.12, and that number tells you exactly how likely 3 is. For a continuous variable, that question stops making sense. There are infinitely many possible values between, say, 27.0 and 27.1 minutes, so the chance of landing on any single exact value is 0.

The one rule to remember

P(X = x) = 0 for every single x, always, for a continuous random variable. Continuous questions aren't about a single outcome, they're about a range. Not "what's the chance the delivery takes exactly 6.4173 minutes," but "what's the chance it takes somewhere between 5 and 7 minutes."

In place of the PMF, a continuous random variable has a probability density function, or PDF, written f(x). The key difference: f(x) is not itself a probability, it's a density. Probability is the area under the curve between two points, never the height of the curve at any single point.

6 wide bars like a coarse PMF 30 thin bars bins keep shrinking the PDF infinitely many, infinitely thin
Shrink a histogram's bins toward zero width and add more of them, and the jagged bars smooth into a curve. A PDF is what a PMF-style bar chart becomes once there are no gaps left to count.

The running total of that area, from the far left up to some point x, is the familiar CDF from Day 8: F(x) = P(X ≤ x). For continuous variables, F(x) is the integral of f(x), and P(a < X < b) is just F(b) − F(a), the area between two points. In practice you rarely integrate by hand. A library like scipy.stats gives you .cdf() and it handles the calculus.

With that settled, the three named shapes below are just different answers to the question "what does f(x) look like?"

1. The Uniform distribution

Every value in a range is equally likely. Nothing is favored.

The Uniform distribution is the flat line: constant density across an entire range, and zero everywhere else. It's the natural choice whenever you genuinely have no reason to favor one outcome over another within some bounds, which makes it the default building block behind random number generators, randomized experiment assignment, and rounding error.

f(x) = 1 ⁄ (b − a),   for a ≤ x ≤ b
and f(x) = 0 everywhere outside [a, b].

Parameters: a (lower bound), b (upper bound). Range of X: [a, b], nothing outside it is possible. Mean: E[X] = (a+b)/2. Variance: Var(X) = (b−a)²/12.

Worked example: subway wait time

Back to the first problem: a train arrives at a random point in a 10-minute cycle, and a rider's wait is equally likely to be anywhere from 0 to 10 minutes. X ~ Uniform(a = 0, b = 10).

pythonfrom scipy import stats a, b = 0, 10 mean = (a + b) / 2 var = (b - a)**2 / 12 p_under_3 = stats.uniform.cdf(3, loc=a, scale=b - a) p_over_7 = 1 - stats.uniform.cdf(7, loc=a, scale=b - a) print(f"Mean wait: {mean}") print(f"Variance: {var:.2f}") print(f"P(wait <= 3): {p_under_3}") print(f"P(wait > 7): {p_over_7}")
OutputMean wait: 5.0 Variance: 8.33 P(wait <= 3): 0.3 P(wait > 7): 0.3

With a flat density, probability scales directly with distance. 3 out of 10 minutes gives exactly 30%, and 3 minutes off either end gives that same 30% by symmetry. No curve to look up, just a proportion of the range.

Uniform(a=0, b=10) mean = 5 0 2 4 6 8 10 Wait time (minutes) P(wait ≤ 3) = 30.0%
A flat line means probability is just proportion of the range. 3 out of 10 minutes is exactly 30%, no curve to look up.

Second example: rounding error

A sensor logs readings rounded to the nearest whole unit. The rounding error is equally likely to fall anywhere from −0.5 to +0.5: X ~ Uniform(a = −0.5, b = 0.5). Mean error = 0, variance ≈ 0.083, P(|error| < 0.2) = 40.0%. A mean of exactly 0 is the whole point: rounding error doesn't systematically push readings up or down, it just adds noise symmetrically around the true value. This is the standard justification for treating measurement rounding as harmless at scale.

Third example: random test-bucket assignment

An A/B test assigns each visitor a random number between 0 and 1, then routes them to treatment if that number falls below 0.5: X ~ Uniform(0, 1). P(assigned to treatment) = P(X < 0.5) = 50.0%. This is exactly how most randomization code works under the hood: draw from Uniform(0, 1), then carve that draw up into whatever split the experiment calls for. A 50/50 split is X < 0.5, a 10% holdout is X < 0.1.

2. The Normal distribution

The bell curve. Symmetric, defined by just two numbers, and the most important shape in statistics.

Heights, order values, measurement errors, test scores: an enormous range of real quantities cluster around a central value and taper off symmetrically in both directions. That shape is the Normal distribution, also called the Gaussian. Part of why it shows up so relentlessly across unrelated fields will become clear later in this article, once the Central Limit Theorem enters the picture.

f(x) = 1σ√(2π) · e−(x−μ)2⁄2σ2
μ sets the center, σ sets the spread.

Parameters: μ (mean), σ (standard deviation). Range of X: all real numbers, no bound on either side. Mean: E[X] = μ. Variance: Var(X) = σ².

Every Normal distribution has a different center and spread, which would normally mean a different lookup table for every combination of μ and σ. Standardization solves that: convert any value to a z-score, z = (x−μ)/σ, and you're working in units of "how many standard deviations from the mean," on a scale that's identical for every Normal distribution ever. A z-score of 1.75 means the same thing whether the underlying variable is order values in dollars or bolt diameters in millimeters.

The empirical rule

For any Normal distribution, the share of values falling within a given number of standard deviations of the mean is fixed:

RangeShare of values
μ ± 1σ≈ 68.3%
μ ± 2σ≈ 95.4%
μ ± 3σ≈ 99.7%
The empirical rule 68.3% 95.4% 99.7% μ−3σ μ−1σ μ μ+1σ μ+3σ
Every Normal distribution splits this same way, no matter its mean or spread. A value past 3 standard deviations is genuinely rare: about 3 in 1,000.

This is the "68-95-99.7 rule," and it's why a value more than 3 standard deviations out reads as genuinely unusual. Under a Normal model, only about 3 in 1,000 values fall out there.

Worked example: customer order value

Back to the second problem: a company's order values are approximately Normal with a mean of $85 and a standard deviation of $20. X ~ Normal(μ = 85, σ = 20).

pythonmu, sigma = 85, 20 p_over_120 = 1 - stats.norm.cdf(120, mu, sigma) p_60_100 = stats.norm.cdf(100, mu, sigma) - stats.norm.cdf(60, mu, sigma) print(f"P(order > $120): {p_over_120:.4f}") print(f"P($60 < order < $100): {p_60_100:.4f}")
OutputP(order > $120): 0.0401 P($60 < order < $100): 0.6677

A $120 order sits at z = 1.75, comfortably out in the tail, and roughly two-thirds of all orders fall in the fairly ordinary $60 to $100 band. The middle 50% of orders (the interquartile range) runs from about $71.50 to $98.50, a useful pair of numbers for describing a "typical order" without being thrown off by a few large outliers.

Normal(μ=85, σ=20) mean = $85 25 45 65 85 105 125 145 Order value ($) P(order > $120) ≈ 4.0%
A $120 order sits at z = 1.75, comfortably out in the tail. About two-thirds of all orders fall in the ordinary $60 to $100 band.

Second example: manufacturing tolerance

A factory machines a bolt to a target diameter of 10mm with a standard deviation of 0.05mm. The spec allows anything from 9.85mm to 10.15mm: X ~ Normal(μ = 10, σ = 0.05).

Normal(μ=10, σ=0.05) target = 10mm 9.85 9.90 9.95 10.00 10.05 10.10 10.15 Diameter (mm) P(out of spec) ≈ 0.27%
9.85 and 10.15 sit exactly 3 standard deviations out. About 2,700 bolts in every million fall outside tolerance from ordinary process variation alone, no fault required.

P(within spec) ≈ 99.73%, so about 2,700 bolts in every million fall outside tolerance from ordinary process variation alone, no equipment fault required. 9.85 and 10.15 sit exactly 3 standard deviations from the target, so this is the empirical rule showing up in an industrial setting, and it's the logic behind "three-sigma quality" in manufacturing.

Real Six Sigma quality control actually aims considerably tighter than this example. The methodology's namesake standard targets just 3.4 defects per million, not 2,700, which corresponds to roughly 4.5 standard deviations once the process drift the methodology accounts for is factored in. "Six Sigma" the brand name and "six actual standard deviations of margin" aren't quite the same claim.

Third example: setting a performance cutoff

A company's internal exam scores are Normal with μ = 75 and σ = 10. HR wants the score that marks the top 10% of performers, which means running the calculation in reverse: instead of a value in and a probability out, a probability in and a value out. The cutoff comes out to a score of 87.8 (z ≈ 1.28), found with the inverse CDF, stats.norm.ppf(0.90, 75, 10), rather than .cdf(). Percentile cutoffs, grading curves, and "top X%" thresholds all run through this same inverse lookup.

3. The Exponential distribution

How long until the next event, when events arrive at a steady average rate.

The Exponential distribution answers a continuous version of a question Day 9 already asked once, with the Geometric distribution: how long until the next success? Here "how long" is measured on a continuous clock (minutes, hours, years) rather than counted in discrete trials, and it's directly tied to the Poisson distribution: if events arrive as a Poisson process at rate λ, the gaps between consecutive events follow Exponential(λ).

f(x) = λ · e−λx,   for x ≥ 0
λ is the same rate parameter as the matching Poisson process.

Parameter: λ, average event rate (λ > 0). Range of X: [0, ∞), never negative. Mean: E[X] = 1/λ. Variance: Var(X) = 1/λ².

Memoryless again, on a continuous clock

If this sounds familiar, it should: it's the same memoryless property from the Geometric distribution in Day 9, just running continuously instead of trial by trial. A server that's been up for 400 days is, under this model, no more or less likely to fail in the next hour than a server that was just switched on. That's a real modeling assumption, not a universal truth. Mechanical parts that wear out over time are usually better described by a Weibull distribution, which allows failure risk to change with age. Reach for Exponential specifically when the failure or arrival process genuinely has no memory of elapsed time.

Worked example: support ticket arrivals

Support tickets arrive at an average of 6 per hour. The time until the next ticket follows X ~ Exponential(λ = 6, measured in hours).

pythonimport numpy as np lam = 6 # 6 tickets per hour, on average p_within_5min = 1 - np.exp(-lam * (5/60)) p_over_15min = np.exp(-lam * (15/60)) print(f"Mean gap between tickets: {60/lam:.1f} minutes") print(f"P(next ticket within 5 min): {p_within_5min:.4f}") print(f"P(next ticket takes over 15 min): {p_over_15min:.4f}")
OutputMean gap between tickets: 10.0 minutes P(next ticket within 5 min): 0.3935 P(next ticket takes over 15 min): 0.2231

The mean gap of 10 minutes is just the flip side of a Poisson rate of 6 per hour. Both descriptions are the same underlying process: count the arrivals and you get Poisson, measure the gaps and you get Exponential.

Exponential(λ=6/hr) mean = 10 min 0 10 20 30 40 50 Minutes until next ticket P(within 5 min) ≈ 39.4%
Tickets arrive at 6 per hour on average. The curve is tallest at zero: the most likely gap is a short one, and long gaps get steadily rarer.

Second example: equipment lifetime

A server component has a mean lifetime of 2.5 years, modeled as X ~ Exponential(λ = 0.4 per year). P(fails within year 1) ≈ 33.0%, P(still working after 4 years) ≈ 20.2%. Because of the memoryless property, that 20.2% survival figure applies at any point in the component's life, not just from day one. A component already running for 3 years still has the same 20.2% chance of lasting 4 more. Here's that checked directly: P(survives to year 7) ÷ P(survives to year 3) = 0.0608 ÷ 0.3012 = 0.2019, the exact same 20.2% as a brand-new component's odds of lasting 4 years. The 3 years already logged simply vanish from the calculation.

Third example: fraud monitoring

Back to the third problem: confirmed fraud cases surface at an average of one every 8 hours. X ~ Exponential(λ = 1/8 per hour). P(next case within 2 hours) ≈ 22.1%, P(no case in a 24-hour shift) ≈ 5.0%. A shift with zero confirmed cases happens about 1 time in 20, purely from the ordinary rhythm of the process, useful context before treating a quiet shift as evidence that monitoring has stopped working.

Choosing the right distribution

DistributionQuestion it answersKey parametersClassic use case
Uniform(a, b)Is every value in a range equally likely?a, b = boundsRNG, A/B assignment, rounding error
Normal(μ, σ)Does it cluster symmetrically around a center?μ = mean, σ = spreadOrder values, measurements, test scores
Exponential(λ)How long until the next event?λ = average rateWait times, equipment lifetime, arrivals

A few quick questions to ask yourself: "Every outcome in this range is equally plausible, with no center of gravity" points to Uniform. "Values cluster around a typical case and taper off symmetrically" points to Normal. "I'm measuring the wait until the next rare event, on a continuous clock" points to Exponential.

One process, two distributions

Poisson and Exponential aren't two unrelated ideas picked to answer similar-sounding questions. They're two views of the exact same process. Count how many events happen in a fixed window and you get a Poisson distribution. Measure the gap until the next one happens and you get an Exponential distribution.

event event event event event gap gap gap Count events per window → Poisson(λ) Measure the gap between events → Exponential(λ)
Same events, same rate λ, two different questions. Count them and you get Poisson. Time the gaps and you get Exponential.

The Central Limit Theorem

Here's a claim that sounds too strong to be true: take almost any distribution, even a lopsided, skewed one like the Exponential above. Draw a decent-sized sample from it and compute the average. Repeat that many times, and the distribution of those averages will look approximately Normal, regardless of what the original distribution looked like.

That's the Central Limit Theorem, and it's the reason the Normal distribution shows up constantly in fields with no obvious connection to bell curves. A metric doesn't need to be individually Normal for its average to behave like one, and averages are what most statistical methods, from confidence intervals to A/B test significance, are actually built on.

Building the intuition: averaging dice rolls

Before reaching for anything skewed, start with the simplest possible distribution: a single fair die. It's about as far from a bell curve as you can get, every face from 1 to 6 is equally likely, so the distribution is perfectly flat. Watch what happens as more dice get averaged together.

pythonimport numpy as np from scipy import stats np.random.seed(7) trials = 100_000 for n in [1, 2, 5, 30]: rolls = np.random.randint(1, 7, size=(trials, n)) # n dice, rolled 100,000 times means = rolls.mean(axis=1) print(f"n={n}: mean={means.mean():.2f}, std={means.std():.3f}, excess kurtosis={stats.kurtosis(means):.3f}")
Outputn=1: mean=3.50, std=1.709, excess kurtosis=-1.269 n=2: mean=3.50, std=1.204, excess kurtosis=-0.625 n=5: mean=3.50, std=0.764, excess kurtosis=-0.253 n=30: mean=3.50, std=0.312, excess kurtosis=-0.041

The mean stays locked at 3.5 the whole time, that never changes. What changes is the shape. A single die is perfectly flat: every face from 1 to 6 is equally likely, which is about as far from a bell curve as a distribution can get. "Excess kurtosis" measures exactly that flatness, and Normal's value is defined as 0. One die scores -1.27, badly flat. By the time you're averaging 30 dice, that number has crept up to -0.04, a hair's breadth from a genuine bell curve. The standard deviation shrinks the whole time too, following the same σ/√n pattern as the Exponential example below.

n = 1 die avg of the roll(s) kurtosis -1.27 n = 2 dice avg of the roll(s) kurtosis -0.63 n = 5 dice avg of the roll(s) kurtosis -0.25 n = 30 dice avg of the roll(s) kurtosis -0.04
200,000 simulated dice rolls, averaged in groups of 1, 2, 5, and 30. A single die is flat. By 30 dice, the average has rounded into a bell shape, and "excess kurtosis" (how far the peak is from Normal's) drops from -1.27 toward 0.

Nobody changed the die. Nobody changed the rules. The only thing that moved was how many rolls got averaged before plotting the result. That's the entire Central Limit Theorem in one picture: averaging doesn't just fix skewed, lopsided distributions, it also rounds off flat ones, bumpy ones, and pretty much anything else you start with, purely as a side effect of averaging itself.

A harder test: starting from something badly skewed

Dice are a gentle case, since a single die is already symmetric, just flat. A tougher test is a distribution that's badly lopsided to begin with. Draw 100,000 samples of size 40 from an Exponential distribution (mean 8, heavily right-skewed) and look at what happens to the distribution of the sample means this time.

pythonimport numpy as np from scipy import stats np.random.seed(42) pop_mean, pop_std = 8, 8 # Exponential(mean=8): mean equals std, heavily skewed n, trials = 40, 100_000 samples = np.random.exponential(scale=pop_mean, size=(trials, n)) sample_means = samples.mean(axis=1) print(f"Population skewness: {stats.skew(samples.flatten()):.2f}") print(f"Sample-mean skewness (n=40): {stats.skew(sample_means):.2f}") print(f"Predicted SE (σ/√n): {pop_std/np.sqrt(n):.3f}") print(f"Simulated SE: {sample_means.std():.3f}")
OutputPopulation skewness: 2.00 Sample-mean skewness (n=40): 0.32 Predicted SE (σ/√n): 1.265 Simulated SE: 1.269

Two things worth pulling out of that. First, the skewness collapses from 2.00 (heavily lopsided) down to 0.32 (close to symmetric) just from averaging 40 draws together, exactly what the CLT predicts. Second, the spread of those averages, 1.269, lands almost exactly on the formula's prediction of σ/√n = 1.265, with the tiny remaining gap explained by simulation noise rather than any flaw in the theory.

The population (Exponential) skewness ≈ 2.00 100,000 sample means (n=40) skewness ≈ 0.32, almost symmetric
Same underlying process, two very different shapes. Average 40 draws from that lopsided curve on the left, and the averages pile up into something close to a bell curve on the right.

Why this matters for data science

This is the theoretical backbone behind treating sample means, proportions, and A/B test differences as approximately Normal, even when the underlying data (session length, revenue per user, time between clicks) is nowhere close to Normal itself. It's what justifies z-tests, t-tests, and confidence intervals on data that would otherwise make a statistician nervous.

The rule of thumb for "sufficiently large": a sample of 30 or more is often enough, though a heavily skewed population, like the Exponential above, benefits from a larger sample before the approximation kicks in cleanly.

Back to DeliverFast: three continuous questions

Day 9 closed with an ops meeting at DeliverFast, the food delivery startup. The team worked through four discrete questions there, and they're back with three more, this time every answer coming from a continuous distribution.

"How far are our deliveries actually traveling?" Within a given zone, delivery distance is roughly uniform between 1km and 6km: X ~ Uniform(a = 1, b = 6). Mean distance: 3.5km. P(under 2km) = 20%, P(over 5km) = 20%, useful for setting delivery-fee tiers that reflect distance fairly.

"How often are we breaching the 40-minute delivery SLA?" Delivery time is approximately Normal with μ = 28 minutes and σ = 6 minutes: X ~ Normal(28, 6). P(delivery takes over 40 minutes) ≈ 2.3%, the baseline SLA-breach rate to expect from ordinary variation alone, before anything actually goes wrong.

"At our busiest restaurant, how long between new orders?" Orders arrive with a mean gap of 4 minutes: X ~ Exponential(λ = 0.25 per minute). P(next order within 2 minutes) ≈ 39.4%, and P(a 10-minute lull with no new order) ≈ 8.2%, the kind of gap that tells the kitchen it's safe to catch up.

pythonfrom scipy import stats import numpy as np # Q1: Uniform (delivery distance, 1 to 6 km) a, b = 1, 6 p_close = stats.uniform.cdf(2, loc=a, scale=b - a) p_far = 1 - stats.uniform.cdf(5, loc=a, scale=b - a) # Q2: Normal (delivery time vs. the 40-minute SLA) mu, sigma = 28, 6 p_breach = 1 - stats.norm.cdf(40, mu, sigma) # Q3: Exponential (time between new orders, mean 4 minutes) lam = 0.25 p_next_2min = 1 - np.exp(-lam * 2) p_lull_10min = np.exp(-lam * 10)
Business questionDistributionAnswer
P(delivery distance under 2km)Uniform(1, 6)20.0%
P(delivery distance over 5km)Uniform(1, 6)20.0%
P(delivery time breaches 40-min SLA)Normal(28, 6)≈ 2.3%
P(next order within 2 minutes)Exponential(0.25)≈ 39.4%
P(10-minute lull with no order)Exponential(0.25)≈ 8.2%

Same company, same kind of ops meeting as Day 9, but every question this time is about a measurement rather than a count: a distance, a duration, a gap in time. That's the whole shift this article has been building toward.

What comes next

Seven distributions in, discrete and continuous both, and one thread has been running underneath all of them: every example so far assumed the true parameters (p, λ, μ, σ) were already known. Real data science almost never works that way. You don't know the true conversion rate, the true mean delivery time, or the true defect rate. You have a sample, and you're trying to work backward to what's actually true about the population it came from.

That's statistical inference, and it's where this series goes next: confidence intervals, hypothesis testing, and the sampling distributions that connect a handful of observed data points back to a claim about the truth. The Central Limit Theorem from this article isn't a detour. It's the tool that makes almost all of it possible.

Key takeaways

Continuous variables measure rather than count, and P(X = any exact value) is always 0, probability only lives in ranges. Uniform models "every value equally likely" over a bound. Normal models symmetric clustering around a center, standardized into z-scores so every instance of it reads the same way. Exponential models the wait until the next event in a memoryless process, the continuous twin of Poisson. And the Central Limit Theorem is why averages of almost anything end up looking Normal, which is the reason this whole family of ideas is about to power everything the series covers next.

Quick check

Four questions to test whether this stuck.

A random number generator produces a value uniformly between 0 and 100. What's P(the value is exactly 50.000000)?

0. For any continuous random variable, the probability of hitting one exact value is always 0, no matter how "central" or likely-seeming that value feels. Only P(a range around 50) is ever nonzero.

A dataset of delivery times has a mean of 28 minutes and looks roughly symmetric and bell-shaped. About what fraction of deliveries take between 22 and 34 minutes, if the standard deviation is 6 minutes?

About 68.3%. 22 and 34 are each exactly 1 standard deviation from the mean of 28, so this is the empirical rule's μ ± 1σ band.

Why does it make sense that the Exponential distribution's mean and the matching Poisson distribution's rate are reciprocals of each other (mean = 1/λ)?

Because they're describing the same process from two angles. If events happen at a rate of λ per hour (Poisson), then on average you'd expect to wait 1/λ hours between them (Exponential). A higher event rate means a shorter average wait, which is exactly what the reciprocal relationship captures.

You take 500 samples of size 50 from a heavily skewed population and plot the sample means. Should you expect that plot to still look heavily skewed?

No, or at least, much less so. The Central Limit Theorem says the distribution of sample means moves toward Normal as the sample size grows, regardless of the shape of the original population. With n=50, the skew should have visibly softened, similar to how skewness dropped from 2.00 to 0.32 in this article's own simulation.