import numpy as np
from scipy.stats import gaussian_kde
import matplotlib.pyplot as plt
import preliz as pz
pz.style.use('preliz-doc')Chapter 3, demos 1-4
Bayesian Data Analysis, 3rd ed
Examples and illustrations for a normal model with unknown mean and variance (BDA3 section 3.2 on p. 64).
# data
y = np.array([93, 112, 122, 135, 122, 150, 118, 90, 124, 114])
# sufficient statistics
n = len(y)
s2 = np.var(y, ddof=1) # here ddof=1 is used to get the sample estimate
my = np.mean(y)# set random number generator with seed
rng = np.random.default_rng(0)# factorize the joint posterior p(mu,sigma2|y) to p(sigma2|y)p(mu|sigma2,y)
# sample from the joint posterior using this factorization
# sample from p(sigma2|y)
sigma2 = pz.ScaledInverseChiSquared(n-1, s2).rvs(size=1000, random_state=rng)
# sample from p(mu|sigma2,y)
mu = my + np.sqrt(sigma2/n)* pz.Normal(0, 1).rvs(sigma2.shape)
# display sigma instead of sigma2
sigma = np.sqrt(sigma2)
# sample from the predictive distribution p(ynew|y)
# for each sample of (mu, sigma)
ynew = pz.Normal(mu, sigma).rvs(*mu.shape)# for mu compute the density in these points
tl1 = [90, 150]
t1 = np.linspace(tl1[0], tl1[1], 1000)
# for sigma compute the density in these points
tl2 = [10, 60]
t2 = np.linspace(tl2[0], tl2[1], 1000)
# for ynew compute the density in these points
tlynew = [50, 185]
xynew = np.linspace(tlynew[0], tlynew[1], 1000)
# evaluate the joint density in a grid
# note that the following is not normalized, but for plotting
# contours it does not matter
Z = pz.Normal(my, t2[:,np.newaxis]/np.sqrt(n)).pdf(t1)
Z *= (pz.ScaledInverseChiSquared(n-1, s2).pdf(t2**2)*2*t2)[:,np.newaxis]
# compute the exact marginal density for mu
pm_mu = pz.StudentT(n-1, my, np.sqrt(s2/n)).pdf(t1)
# estimate the marginal density for mu using samples and an ad hoc Gaussian
# kernel approximation
pk_mu = gaussian_kde(mu).evaluate(t1)
# compute the exact marginal density for sigma
# multiplication by 2*t2 is due to the transformation of variable
# z=t2^2, see BDA3 p. 21
pm_sigma = pz.ScaledInverseChiSquared(n-1, s2).pdf(t2**2)*2*t2
# N.B. this was already calculated in the joint distribution case
# estimate the marginal density for sigma using samples and an ad hoc Gaussian
# kernel approximation
pk_sigma = gaussian_kde(sigma).evaluate(t2)
# compute the exact predictive density
p_new = pz.StudentT(n-1, my, np.sqrt(s2*(1+1/n))).pdf(xynew)Visualise the joint density and marginal densities of the posterior of normal distribution with unknown mean and variance.
# Create figure
fig, ((ax_mu, ax_legend), (ax_joint, ax_sigma)) = plt.subplots(
2, 2, figsize=(9, 9),
gridspec_kw=dict(width_ratios=[2, 1], height_ratios=[1, 2])
)
fig.suptitle("Joint and marginal posterior distributions", fontsize=16)
# Joint posterior
samps = ax_joint.scatter(mu, sigma, s=5, color="C2")
c_levels = np.linspace(1e-5, Z.max(), 6)[:-1]
ax_joint.contour(t1, t2, Z, c_levels, colors="C0")
ax_joint.set(xlim=tl1, ylim=tl2, xlabel=r"$\mu$", ylabel=r"$\sigma$")
# Marginal of μ
ax_mu.plot(t1, pm_mu, color="C1", lw=5, label="exact marginal")
ax_mu.plot(t1, pk_mu, "k--", lw=1.5, label="empirical marginal")
ax_mu.set(xlim=tl1, yticks=[], xticklabels=[])
# Marginal of σ
exact_line, = ax_sigma.plot(pm_sigma, t2, color="C1", lw=5)
empirical_line, = ax_sigma.plot(pk_sigma, t2, "k--", lw=1.5)
ax_sigma.set(ylim=tl2, xticks=[], yticklabels=[])
# Legend
ax_legend.axis("off")
ax_legend.legend(
[samps, plt.Line2D([], [], color="C0"), exact_line, empirical_line],
["samples", "exact joint contour", "exact marginal", "empirical marginal"],
loc="center"
);
Visualise factored sampling and the corresponding marginal and conditional densities.
# Create figure
fig, (ax_joint, ax_sigma) = plt.subplots(
1, 2, figsize=(9, 6),
gridspec_kw=dict(width_ratios=[2, 1])
)
# Joint posterior
c_levels = np.linspace(1e-5, Z.max(), 6)[:-1]
contour = ax_joint.contour(t1, t2, Z, c_levels, colors="C0")
line1 = ax_joint.axhline(sigma[0], color="k")
line2, = ax_joint.plot(
t1,
sigma[0] + pz.Normal(my, np.sqrt(sigma2[0] / n)).pdf(t1) * 100,
"C2--", lw=2
)
scat = ax_joint.scatter(mu[0], sigma[0], s=40, color="k", zorder=3)
ax_joint.set(
xlim=tl1, ylim=tl2,
xlabel=r"$\mu$", ylabel=r"$\sigma$",
title="joint posterior"
)
ax_joint.legend(
[plt.Line2D([], [], color="C0"), line1, line2, scat],
[
"exact contour plot",
r"sample from the marginal of $\sigma$",
r"conditional distribution of $\mu$",
r"sample from joint posterior",
],
loc="upper center"
)
# Marginal of σ
ax_sigma.plot(pm_sigma, t2)
ax_sigma.set(ylim=tl2, title=r"marginal of $\sigma$", xticks=[], yticklabels=[]);
Visualise the marginal distribution of mu as a mixture of normals.
# Compute conditional PDFs
condpdfs = pz.Normal(my, np.sqrt(sigma2 / n)[:, None]).pdf(t1)
fig, (ax_cond, ax_mean) = plt.subplots(2, 1, figsize=(9, 6), sharex=True)
# First 25 conditional distributions
ax_cond.plot(t1, condpdfs[:25].T, "C2", alpha=0.25)
ax_cond.set(
xlim=tl1,
title=r"conditional distribution of $\mu$ for first 25 samples",
yticks=[]
)
ylims = ax_cond.get_ylim()
# average of conditionals vs exact marginal
ax_mean.plot(t1, condpdfs.mean(axis=0), "C1", lw=5, label="average of sampled conditionals")
ax_mean.plot(t1, pm_mu, "k--", lw=1.5, label=r"exact marginal of $\mu$")
ax_mean.set(xlim=tl1, ylim=ylims, yticks=[])
ax_mean.legend(loc="upper center");
Visualise sampling from the posterior predictive distribution.
# Predictive PDF for first posterior sample
ynewdists = pz.Normal(mu[0], sigma[0]).pdf(xynew)
fig, (ax_joint, ax_first, ax_all) = plt.subplots(3, 1, figsize=(12, 8))
# Joint posterior
c_levels = np.linspace(1e-5, Z.max(), 6)[:-1]
ax_joint.contour(t1, t2, Z, c_levels, colors="C0")
samps = ax_joint.scatter(mu, sigma, s=5, color="C2")
ax_joint.scatter(mu[0], sigma[0], s=40, color="r")
ax_joint.set(
xlim=tl1, ylim=tl2,
xlabel=r"$\mu$", ylabel=r"$\sigma$"
)
ax_joint.legend(
[plt.Line2D([], [], color="C0"), samps],
["exact joint posterior contour", "joint posterior samples"],
loc="center left", bbox_to_anchor=(1, 0.5)
)
# Predictive distribution for first sample
line1, = ax_first.plot(xynew, ynewdists)
dot = ax_first.scatter(ynew[0], 0.02 * ynewdists.max(), s=40, color="r")
ylims = ax_first.get_ylim()
ax_first.set(
xlim=tlynew, ylim=ylims,
xlabel=r"$\widetilde{y}$", yticks=[]
)
ax_first.legend(
[line1, dot],
["pred. dist. given the posterior sample", "sample from predictive dist."],
loc="center left", bbox_to_anchor=(1, 0.5)
)
# All predictive samples
ax_all.scatter(
ynew,
(0.02 + 0.05 * pz.Uniform(0, 1).rvs(*ynew.shape)) * ynewdists.max(),
s=10, color="C2", alpha=0.3, edgecolor="None",
label="samples from predictive distribution"
)
ax_all.plot(xynew, p_new, lw=1.5, label="exact predictive distribution")
ax_all.set(
xlim=tlynew, ylim=ylims,
xlabel=r"$\widetilde{y}$", yticks=[]
)
ax_all.legend(loc="center left", bbox_to_anchor=(1, 0.5));