Lecture 6: Continuous Random Variables, PDFs, CDFs
Author
Jamie Haddock
Why do we need a new kind of random variable?
Every random variable so far has been discrete: a finite or countably infinite list of possible values, each with its own probability mass. But plenty of quantities we’d like to model — a waiting time, a position, a measurement error — can take any real value in an interval. There’s no way to list them out one at a time.
Definition: Continuous random variable, PDF
A random variable \(X\) is continuous if there is a function \(f_X(x)\ge 0\), called its probability density function (PDF), such that for every interval \([a,b]\), \[
P(a \le X \le b) = \int_a^b f_X(x)\, dx, \qquad \int_{-\infty}^{\infty} f_X(x)\,dx = 1.
\]
Instead of bars of probability sitting on individual points (a PMF), probability is now area under a curve.
Every value in \([a,b]\) is “equally likely,” in the sense that the density is flat — probability of a sub-interval depends only on its length, not its location.
Code
import numpy as npimport matplotlib.pyplot as pltfrom scipy.integrate import quada_u, b_u =2, 5def f_uniform(x): x = np.asarray(x, dtype=float)return np.where((x >= a_u) & (x <= b_u), 1/ (b_u - a_u), 0.0)xs = np.linspace(a_u -1, b_u +1, 400)fig, ax = plt.subplots(figsize=(6, 3))ax.plot(xs, f_uniform(xs), color='black')ax.set_ylim(0, 1/ (b_u - a_u) *1.3)ax.set_xlabel('$x$')ax.set_ylabel('$f_X(x)$')ax.set_title(f'Uniform({a_u},{b_u}) density')plt.tight_layout()plt.show()total, _ = quad(f_uniform, a_u -1, b_u +1)p_3_4, _ = quad(f_uniform, 3, 4)print(f"total area: {total:.4f}")print(f"P(3 <= X <= 4) = {p_3_4:.4f} (should be 1/3 = {1/3:.4f}, since [3,4] is 1/3 of [2,5])")
total area: 1.0000
P(3 <= X <= 4) = 0.3333 (should be 1/3 = 0.3333, since [3,4] is 1/3 of [2,5])
Example: a PDF can take arbitrarily large values
A density is not a probability
Nothing in the definition requires \(f_X(x)\le 1\) — only that the total area equals \(1\). Squeeze the same unit of probability into a short enough interval and the height of the density can be as large as you like.
Fact: \(P(X=x_0)=0\) for continuous \(X\)
For any single point \(x_0\), \[
P(X=x_0) = \int_{x_0}^{x_0} f_X(x)\,dx = 0,
\] since we’re integrating over an interval of zero width. Continuous random variables put no probability mass on individual points — only on intervals of positive length.
The rules we’ve seen previously generalize once we replace summations with integrals!
Theorem: LOTUS for continuous random variables
For any function \(g\), \[
E[g(X)] = \int_{-\infty}^{\infty} g(x)\, f_X(x)\, dx.
\]
Definitions: moments and variance
The \(k\)-th moment of \(X\) is \(E[X^k]\). Variance is defined exactly as in the discrete case, \[
\mathrm{Var}(X) = E\big[(X-E[X])^2\big] = E[X^2]-(E[X])^2,
\] and the shortcut formula’s proof used only linearity — nothing about summing versus integrating — so it carries over unchanged.
In-class exercise
Prove that for constants \(a,b\) and a continuous random variable \(X\): \[
E[aX+b] = aE[X]+b \qquad\text{and}\qquad \mathrm{Var}(aX+b) = a^2\,\mathrm{Var}(X).
\]
Every fact about expectation and variance built on linearity in L4 carries over to continuous RVs with zero extra work.
The Exponential random variable
The continuous “cousin” of the Geometric — instead of counting trials until an event, it measures the waiting time until an event that can happen at any instant.
\[
E[X] = \frac1\lambda, \qquad \mathrm{Var}(X) = \frac1{\lambda^2}
\] (both derivable by integration by parts) — the same shape of answer as the Geometric(\(p\)) mean \(1/p\), with \(\lambda\) playing the role of a continuous rate in place of \(p\).
Example: customers arrive at a coffee shop at an average rate of \(\lambda=3\) per hour, memorylessly (the time since the last arrival tells you nothing about when the next one comes). The waiting time \(X\) until the next customer is Exponential(\(3\)), measured in hours, so \(E[X]=\tfrac13\) hour \(=20\) minutes.
For any random variable \(X\) — discrete or continuous — \[
F_X(x) = P(X \le x).
\]
Unlike the PMF or PDF, the CDF is defined the same way regardless of type. And each of PMF, PDF, or CDF — whichever applies to \(X\) — unambiguously pins down the entire distribution: from any one of them, you can recover the others.
\(F_X(x) \to 0\) as \(x\to-\infty\), and \(F_X(x)\to 1\) as \(x\to\infty\),
for discrete\(X\): \(F_X\) is a piecewise-constant step function — flat except for a jump of size \(p_X(x)\) at every \(x\) in the range of \(X\),
for continuous\(X\): \(F_X\) is continuous everywhere (no jumps at all — consistent with \(P(X=x_0)=0\) for every \(x_0\)).
Relation between PDF and CDF, for \(X\) continuous:\[
F_X(x) = \int_{-\infty}^x f_X(t)\,dt, \qquad\text{equivalently}\qquad f_X(x) = F_X'(x)
\] wherever \(F_X\) is differentiable — the Fundamental Theorem of Calculus!
Code
from scipy.stats import binomfig, axes = plt.subplots(1, 2, figsize=(9, 3.5))# discrete: Binomial(5, 0.4) CDF -- a step functionn_b, p_b =5, 0.4xs_d = np.linspace(-1, 6, 700)cdf_d = binom.cdf(np.floor(xs_d), n_b, p_b)axes[0].step(xs_d, cdf_d, where='post', color='black')axes[0].set_title('discrete CDF\n(Binomial(5,0.4)) -- a step function')axes[0].set_xlabel('$x$')axes[0].set_ylabel('$F_X(x)$')# continuous: Uniform(2,5) CDF -- continuous, and (in this special case) piecewise linearxs_c = np.linspace(0, 7, 400)cdf_c = np.clip((xs_c - a_u) / (b_u - a_u), 0, 1)axes[1].plot(xs_c, cdf_c, color='black')axes[1].set_title('continuous CDF\n(Uniform(2,5)) -- continuous, no jumps')axes[1].set_xlabel('$x$')plt.tight_layout()plt.show()
Same shape of formula — because the Exponential really is the continuous limit of the Geometric. Chop a unit of time into \(n\) tiny slices, let each slice independently “succeed” with probability \(p=\lambda/n\), and count time (not trials) until the first success. As \(n\to\infty\), the Geometric CDF — rescaled so trial number \(k\) becomes time \(k/n\) — converges to the Exponential(\(\lambda\)) CDF.
Aside
Both distributions are memoryless — the property we used to derive the Geometric’s mean carries over exactly to the Exponential, and we’ll put it to work again later this course.
Code
lam =2.0t = np.linspace(0, 3, 400)exp_cdf =1- np.exp(-lam * t)fig, ax = plt.subplots(figsize=(6, 3.5))ax.plot(t, exp_cdf, 'k--', lw=2, label=f'Exponential($\\lambda={lam}$) CDF')for n in [2, 5, 20, 200]: p = lam / n k_vals = np.arange(0, int(3* n) +1) geo_cdf =1- (1- p) ** k_vals ax.step(k_vals / n, geo_cdf, where='post', alpha=0.7, label=f'Geometric($\\lambda/n$), n={n}')ax.set_xlabel('time $t = k/n$')ax.set_ylabel('CDF')ax.legend(fontsize=8)plt.tight_layout()plt.show()# max pointwise CDF gap, shrinking as n growsn_range = np.unique(np.geomspace(2, 5000, 25).astype(int))max_gaps = []for n in n_range: p = lam / n k_vals = np.arange(0, int(5* n) +1) geo_cdf =1- (1- p) ** k_vals t_vals = k_vals / n exp_at_t =1- np.exp(-lam * t_vals) max_gaps.append(np.max(np.abs(geo_cdf - exp_at_t)))fig2, ax2 = plt.subplots(figsize=(5, 3))ax2.loglog(n_range, max_gaps, 'o-')ax2.set_xlabel('$n$')ax2.set_ylabel('max pointwise CDF gap')plt.tight_layout()plt.show()
Example: continuous RV in randomized trace estimation
Recall the randomized trace estimator: for \(A\in\mathbb R^{n\times n}\), draw \(z\in\mathbb R^n\) with i.i.d. mean-\(0\), variance-\(1\), pairwise independent entries and set \(\tau = z^\top A z\). The unbiasedness proof only ever used \[
E[z_i^2] = 1 \qquad\text{and}\qquad E[z_iz_j] = 0 \ (i\ne j),
\] which followed from mean \(0\), variance \(1\), and pairwise independence — nothing about \(z_i\) being discrete. So a continuous \(z_i\) works exactly as well.
A continuous alternative
Instead of Rademacher entries \(z_i=\pm1\), draw \(z_i\) i.i.d. from a continuous distribution with mean \(0\) and variance \(1\) — for concreteness, the (bell-shaped) standard Gaussian, whose PDF we’ll meet formally in a later lecture. The same proof works exactly, just a different distribution!
Code
rng = np.random.default_rng(151)d =50A = rng.standard_normal((d, d)) +5* np.eye(d)true_trace = np.trace(A)num_samples =20_000Z_rad = rng.choice([-1.0, 1.0], size=(num_samples, d)) # discrete: RademacherZ_gauss = rng.standard_normal((num_samples, d)) # continuous: standard Gaussiantau_rad = np.einsum('si,ij,sj->s', Z_rad, A, Z_rad)tau_gauss = np.einsum('si,ij,sj->s', Z_gauss, A, Z_gauss)print(f"true trace(A) = {true_trace:.4f}")print(f"mean of tau, Rademacher z (discrete) = {tau_rad.mean():.4f}")print(f"mean of tau, Gaussian z (continuous) = {tau_gauss.mean():.4f}")
true trace(A) = 250.0783
mean of tau, Rademacher z (discrete) = 249.9342
mean of tau, Gaussian z (continuous) = 249.5031