Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

The Multivariate Gaussian: density, sampling, conditioning

The multivariate normal (MVN) distribution is the workhorse of Gaussian processes, Kalman filtering, and most of variational inference. This notebook is the pedagogical entry point for the GP tutorial sequence: by the end you should have a working mental model of N(μ,Σ)\mathcal{N}(\boldsymbol{\mu}, \boldsymbol{\Sigma}) and a feel for the three operations every later notebook will lean on — evaluating the density, drawing samples, and conditioning on a sub-vector.

What you’ll learn

  1. The MVN density, its level sets, and why Σ\boldsymbol{\Sigma} must be positive definite.
  2. Three equivalent ways to sample from an MVN, all driven by the same standard-normal noise.
  3. How marginalization and conditioning act on (μ,Σ)(\boldsymbol{\mu}, \boldsymbol{\Sigma}) — the Schur complement.
  4. Why naive inv(Sigma) is a bad idea, and what gaussx gives you instead.

1. The density

A random vector x∈Rd\mathbf{x}\in\mathbb{R}^d is multivariate normal with mean μ∈Rd\boldsymbol{\mu}\in\mathbb{R}^d and covariance Σ∈Rd×d\boldsymbol{\Sigma}\in\mathbb{R}^{d\times d} when its density is

p(x)=(2π)−d/2 ∣Σ∣−1/2exp⁡ ⁣(−12(x−μ)⊤Σ−1(x−μ)).p(\mathbf{x}) = (2\pi)^{-d/2}\,|\boldsymbol{\Sigma}|^{-1/2} \exp\!\left(-\tfrac{1}{2}(\mathbf{x}-\boldsymbol{\mu})^\top \boldsymbol{\Sigma}^{-1}(\mathbf{x}-\boldsymbol{\mu})\right).

Three things are doing real work in equation ((1)):

  • (x−μ)⊤Σ−1(x−μ)(\mathbf{x}-\boldsymbol{\mu})^\top \boldsymbol{\Sigma}^{-1}(\mathbf{x}-\boldsymbol{\mu}) is a squared Mahalanobis distance — the Euclidean norm rescaled by the geometry of Σ\boldsymbol{\Sigma}.
  • ∣Σ∣−1/2|\boldsymbol{\Sigma}|^{-1/2} is a normalization that makes the density integrate to 1; it grows when Σ\boldsymbol{\Sigma} shrinks.
  • Σ\boldsymbol{\Sigma} must be symmetric positive-definite (SPD) so that the quadratic form is non-negative, the inverse exists, and the density is well defined.

The log density is the version we actually compute, because log⁡∣Σ∣\log|\boldsymbol{\Sigma}| is much more stable than ∣Σ∣|\boldsymbol{\Sigma}|:

log⁡p(x)=−d2log⁡(2π)−12log⁡∣Σ∣−12(x−μ)⊤Σ−1(x−μ).\log p(\mathbf{x}) = -\tfrac{d}{2}\log(2\pi) - \tfrac{1}{2}\log|\boldsymbol{\Sigma}| - \tfrac{1}{2}(\mathbf{x}-\boldsymbol{\mu})^\top \boldsymbol{\Sigma}^{-1}(\mathbf{x}-\boldsymbol{\mu}).
event_shape : (2,)
mean        : [ 0.5  -0.25]
variance    : [1.  1.5]
log p(mu)   : -1.8428522318359293
<Figure size 550x495 with 2 Axes>

2. The geometry of the covariance

The level sets of equation ((1)) are ellipsoids defined by

{x:(x−μ)⊤Σ−1(x−μ)=c2},\{\mathbf{x} : (\mathbf{x}-\boldsymbol{\mu})^\top \boldsymbol{\Sigma}^{-1}(\mathbf{x}-\boldsymbol{\mu}) = c^2 \},

and the eigendecomposition

Σ=Q Λ Q⊤,Λ=diag(λ1,…,λd),λi>0,\boldsymbol{\Sigma} = \mathbf{Q}\,\boldsymbol{\Lambda}\,\mathbf{Q}^\top, \qquad \boldsymbol{\Lambda} = \mathrm{diag}(\lambda_1, \dots, \lambda_d), \quad \lambda_i > 0,

reads off those ellipsoids directly: the columns of Q\mathbf{Q} are the principal axes, and λi\sqrt{\lambda_i} is the radius along axis ii. So Σ\boldsymbol{\Sigma} is geometry — orientation plus scale — and SPD-ness is exactly the requirement that all principal radii be real and positive.

The figure below draws 1-, 2-, and 3-σ Mahalanobis ellipses on top of samples from pp and overlays the principal axes. The two eigenvalues differ by ∼3.5×{\sim}3.5\times, which is why the cloud is visibly elongated.

eigenvalues : [0.50669656 1.99330344]
eigenvectors (columns):
 [[-0.81741556  0.57604844]
 [ 0.57604844  0.81741556]]
<Figure size 550x495 with 1 Axes>

3. Three ways to sample

Sampling an MVN reduces to whitening in reverse: take standard normal noise ε∼N(0,I)\boldsymbol{\varepsilon}\sim\mathcal{N}(\mathbf{0},\mathbf{I}) and multiply by any matrix square root of Σ\boldsymbol{\Sigma}. Concretely, if RR⊤=Σ\mathbf{R}\mathbf{R}^\top = \boldsymbol{\Sigma} then

x=μ+R ε  ⟹  E[x]=μ,Cov[x]=R I R⊤=Σ.\mathbf{x} = \boldsymbol{\mu} + \mathbf{R}\,\boldsymbol{\varepsilon} \;\Longrightarrow\; \mathbb{E}[\mathbf{x}] = \boldsymbol{\mu}, \quad \mathrm{Cov}[\mathbf{x}] = \mathbf{R}\,\mathbb{I}\,\mathbf{R}^\top = \boldsymbol{\Sigma}.

Three common choices for R\mathbf{R}:

  1. Cholesky factor — R=L\mathbf{R} = \mathbf{L}, the unique lower-triangular L\mathbf{L} with positive diagonal such that LL⊤=Σ\mathbf{L}\mathbf{L}^\top = \boldsymbol{\Sigma}. Cheapest, most stable, the default in gaussx.
  2. Eigendecomposition — R=Q Λ1/2\mathbf{R} = \mathbf{Q}\,\boldsymbol{\Lambda}^{1/2} from equation ((4)). Useful when you need the principal axes anyway, or when Σ\boldsymbol{\Sigma} has known structure.
  3. Symmetric square root — R=Σ1/2\mathbf{R} = \boldsymbol{\Sigma}^{1/2}, the unique SPD matrix with RR=Σ\mathbf{R}\mathbf{R} = \boldsymbol{\Sigma}. Has the nice property of being symmetric, occasionally needed for re-symmetrising updates (e.g. ensemble Kalman square-root filters).

All three give the same distribution. Below we drive each route with the same noise and check that the empirical covariance matches Σ\boldsymbol{\Sigma}.

  Cholesky         -> ||emp_cov - Sigma||_F = 0.0179
  Eigendecomp      -> ||emp_cov - Sigma||_F = 0.0409
  Symmetric sqrt   -> ||emp_cov - Sigma||_F = 0.0235

symmetric?            True
R_sym @ R_sym ≈ Sigma: True
<Figure size 1210x396 with 3 Axes>

4. Marginalization

Partition x=(xa,xb)\mathbf{x} = (\mathbf{x}_a, \mathbf{x}_b) with xa∈Rda\mathbf{x}_a\in\mathbb{R}^{d_a} and xb∈Rdb\mathbf{x}_b\in\mathbb{R}^{d_b}, and write the joint as

μ=[μaμb],Σ=[ΣaaΣabΣbaΣbb].\boldsymbol{\mu} = \begin{bmatrix}\boldsymbol{\mu}_a\\\boldsymbol{\mu}_b\end{bmatrix}, \qquad \boldsymbol{\Sigma} = \begin{bmatrix} \boldsymbol{\Sigma}_{aa} & \boldsymbol{\Sigma}_{ab}\\ \boldsymbol{\Sigma}_{ba} & \boldsymbol{\Sigma}_{bb} \end{bmatrix}.

The marginal of xa\mathbf{x}_a is obtained simply by dropping the bb rows and columns:

xa∼N(μa, Σaa).\mathbf{x}_a \sim \mathcal{N}(\boldsymbol{\mu}_a,\, \boldsymbol{\Sigma}_{aa}).

This is striking: marginalization is “look up the right block”. Compare the cost of integrating out xb\mathbf{x}_b from the joint density by hand — which involves completing the square — to the act of indexing into a matrix. The integral is automatic for Gaussians.

marginal mean    : [0.5]
marginal variance: 1.0
<Figure size 605x352 with 1 Axes>

5. Conditioning — the Schur complement

The conditional p(xa∣xb=b)p(\mathbf{x}_a \mid \mathbf{x}_b = \mathbf{b}) is also Gaussian, with mean and covariance

μa∣b=μa+Σab Σbb−1 (b−μb),\boldsymbol{\mu}_{a\mid b} = \boldsymbol{\mu}_a + \boldsymbol{\Sigma}_{ab}\,\boldsymbol{\Sigma}_{bb}^{-1}\,(\mathbf{b} - \boldsymbol{\mu}_b),
Σa∣b=Σaa−Σab Σbb−1 Σba.\boldsymbol{\Sigma}_{a\mid b} = \boldsymbol{\Sigma}_{aa} - \boldsymbol{\Sigma}_{ab}\,\boldsymbol{\Sigma}_{bb}^{-1}\,\boldsymbol{\Sigma}_{ba}.

The matrix Σaa−ΣabΣbb−1Σba\boldsymbol{\Sigma}_{aa} - \boldsymbol{\Sigma}_{ab}\boldsymbol{\Sigma}_{bb}^{-1}\boldsymbol{\Sigma}_{ba} is called the Schur complement of Σbb\boldsymbol{\Sigma}_{bb} in Σ\boldsymbol{\Sigma}. Two useful intuitions:

  • Mean shift. Equation ((8)) is a linear regression: we subtract the prior on xb\mathbf{x}_b, regress the residual onto xa\mathbf{x}_a via the cross-covariance, and add it to the prior mean of xa\mathbf{x}_a. The regression coefficient is ΣabΣbb−1\boldsymbol{\Sigma}_{ab}\boldsymbol{\Sigma}_{bb}^{-1}.
  • Variance reduction. Equation ((9)) shows Σa∣b⪯Σaa\boldsymbol{\Sigma}_{a\mid b} \preceq \boldsymbol{\Sigma}_{aa} — observing xb\mathbf{x}_b never increases uncertainty in xa\mathbf{x}_a. The reduction is exactly the variance “explained” by xb\mathbf{x}_b.

gaussx provides conditional(loc, cov, obs_idx, obs_values) and schur_complement(K_XX, K_XZ, K_ZZ) — equation ((9)). We use the high-level conditional here.

  x_2 = -2.00  ->  x_1 | x_2 ~ N(-0.317, 0.673)
  x_2 = -0.50  ->  x_1 | x_2 ~ N(+0.383, 0.673)
  x_2 = +0.00  ->  x_1 | x_2 ~ N(+0.617, 0.673)
  x_2 = +1.50  ->  x_1 | x_2 ~ N(+1.317, 0.673)
<Figure size 660x440 with 1 Axes>

6. Numerical mechanics: don’t inv(Sigma)

Equations ((1)), ((2)), ((8)), and ((9)) all contain Σ−1\boldsymbol{\Sigma}^{-1}. You never form that inverse. Two reasons:

  • Cost. Forming Σ−1\boldsymbol{\Sigma}^{-1} explicitly is O(d3)O(d^3) and then every subsequent multiply is O(d2)O(d^2). Cholesky-factoring once and doing two triangular solves is also O(d3)O(d^3) — but with a smaller constant — and reuses the factor for the log-determinant.
  • Stability. Triangular solves preserve more digits than explicit inversion. The condition number of L\mathbf{L} is κ(Σ)\sqrt{\kappa(\boldsymbol{\Sigma})}, which is much friendlier than κ(Σ)\kappa(\boldsymbol{\Sigma}) itself.

The recipe gaussx uses internally is

Σ=LL⊤,z=L−1(x−μ),log⁡p=−d2log⁡(2π)−∑ilog⁡Lii−12∥z∥2,\boldsymbol{\Sigma}=\mathbf{L}\mathbf{L}^\top,\quad \mathbf{z} = \mathbf{L}^{-1}(\mathbf{x}-\boldsymbol{\mu}),\quad \log p = -\tfrac{d}{2}\log(2\pi) - \sum_i \log L_{ii} - \tfrac{1}{2}\|\mathbf{z}\|^2,

and when Σ\boldsymbol{\Sigma} is borderline-PD (e.g. a kernel matrix on nearly-coincident inputs), we add a tiny multiple of the identity — jitter — until the Cholesky succeeds.

cond(Sigma) ~ 2.30e+12
naive log p  : +50.251326  (sign|det| = +1)
gaussx log p : +31.830449
<Figure size 605x374 with 1 Axes>

7. Recap & where to go next

You now have the operational core of the multivariate Gaussian:

OperationEquationgaussx entry point
Density((1))MultivariateNormal.log_prob / gaussian_log_prob
Sampling((5))MultivariateNormal.sample
Marginal((7))block selection
Conditional((8)), ((9))conditional, schur_complement
Stable log-prob((10))cholesky + solve

Next up.

For the linear-algebra toolbox under the hood, jump to 0.9 — Cholesky, log-det, and trace primitives and Part 1.D — Solvers.