In [1]:
import numpy as np
from scipy.stats import beta
from scipy.special import gammaln
%matplotlib inline
import matplotlib.pyplot as plt
In [2]:
import os, sys
# add utilities directory to path
util_path = os.path.abspath(os.path.join(os.path.pardir, 'utilities_and_data'))
if util_path not in sys.path and os.path.exists(util_path):
sys.path.insert(0, util_path)
# import from utilities
import plot_tools
In [3]:
# edit default plot settings
plt.rc('font', size=12)
# apply custom background plotting style
plt.style.use(plot_tools.custom_styles['gray_background'])
In [4]:
# rat data (BDA3, p. 102)
y = np.array([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 5, 2,
5, 3, 2, 7, 7, 3, 3, 2, 9, 10, 4, 4, 4, 4, 4, 4, 4,
10, 4, 4, 4, 5, 11, 12, 5, 5, 6, 5, 6, 6, 6, 6, 16, 15,
15, 9, 4
])
n = np.array([
20, 20, 20, 20, 20, 20, 20, 19, 19, 19, 19, 18, 18, 17, 20, 20, 20,
20, 19, 19, 18, 18, 25, 24, 23, 20, 20, 20, 20, 20, 20, 10, 49, 19,
46, 27, 17, 49, 47, 20, 20, 13, 48, 50, 20, 20, 20, 20, 20, 20, 20,
48, 19, 19, 19, 22, 46, 49, 20, 20, 23, 19, 22, 20, 20, 20, 52, 46,
47, 24, 14
])
M = len(y)
In [5]:
# plot the separate and pooled models
fig, axes = plt.subplots(2, 1, sharex=True, figsize=(8, 8))
x = np.linspace(0, 1, 250)
# separate
ax = axes[0]
lines = ax.plot(
x,
beta.pdf(x[:,None], y[:-1] + 1, n[:-1] - y[:-1] + 1),
color='C0',
linewidth=0.5
)
# highlight the last line
lines[-1].set_linewidth(2)
lines[-1].set_color('r')
ax.legend(
(lines[0], lines[-1]),
(r'Posterior of $\theta_j$',
r'Posterior of $\theta_{71}$')
)
ax.set_ylim((0, ax.set_ylim()[1])) # set y base to zero
ax.set_yticks(())
ax.set_title('separate model')
# pooled
ax = axes[1]
ax.plot(
x,
beta.pdf(x, y.sum() + 1, n.sum() - y.sum() + 1),
linewidth=2,
label=(r'Posterior of common $\theta$')
)
ax.legend()
ax.set_ylim((0, ax.set_ylim()[1])) # set y base to zero
ax.set_yticks(())
ax.set_xlabel(r'$\theta$')
ax.set_title('pooled model')
fig.tight_layout()
In [6]:
# compute the marginal posterior of alpha and beta in the hierarchical model in a grid
A = np.linspace(0.5, 6, 100)
B = np.linspace(3, 33, 100)
# calculated in logarithms for numerical accuracy
lp = (
- 5/2 * np.log(A + B[:,None])
+ np.sum(
gammaln(A + B[:,None])
- gammaln(A)
- gammaln(B[:,None])
+ gammaln(A + y[:,None,None])
+ gammaln(B[:,None] + (n - y)[:,None,None])
- gammaln(A + B[:,None] + n[:,None,None]),
axis=0
)
)
# subtract the maximum value to avoid over/underflow in exponentation
lp -= lp.max()
p = np.exp(lp)
In [7]:
# plot the marginal posterior
plt.imshow(
p,
origin='lower',
aspect='auto',
extent=(A[0], A[-1], B[0], B[-1])
)
plt.xlabel(r'$\alpha$')
plt.ylabel(r'$\beta$')
plt.title('The marginal posterior of alpha and beta in hierarchical model')
plt.grid('off')
In [8]:
# sample from the posterior grid of alpha and beta
nsamp = 1000
samp_indices = np.unravel_index(
np.random.choice(p.size, size=nsamp, p=p.ravel()/p.sum()),
p.shape
)
samp_A = A[samp_indices[1]]
samp_B = B[samp_indices[0]]
# add random jitter, see BDA3 p. 76
samp_A += (np.random.rand(nsamp) - 0.5) * (A[1]-A[0])
samp_B += (np.random.rand(nsamp) - 0.5) * (B[1]-B[0])
In [9]:
fig, axes = plt.subplots(2, 1, sharex=True, figsize=(8, 8))
# Plot samples from the distribution of distributions Beta(alpha,beta),
# that is, plot Beta(alpha,beta) using the posterior samples of alpha and beta
ax = axes[0]
ax.plot(
x,
beta.pdf(x[:,None], samp_A[:20], samp_B[:20]),
linewidth=0.5,
color='C0'
)
ax.set_yticks(())
ax.set_title(
r'Posterior samples from the distribution of distributions '
r'Beta($\alpha$,$\beta$)'
)
ax.set_ylim((0, ax.set_ylim()[1])) # set y base to zero
# The average of above distributions, is the predictive distribution for a new
# theta, and also the prior distribution for theta_j.
# Plot this.
ax = axes[1]
plt.plot(x, np.mean(beta.pdf(x, samp_A[:,None], samp_B[:,None]), axis=0))
ax.set_yticks(())
ax.set_xlabel(r'$\theta$')
ax.set_title(
r'Predictive distribution for a new $\theta$ '
r'and prior for $\theta_j$'
)
ax.set_ylim((0, ax.set_ylim()[1])) # set y base to zero
fig.tight_layout()
In [10]:
# And finally compare the separate model and the hierarchical model
fig, axes = plt.subplots(2, 1, sharex=True, figsize=(8, 8))
x = np.linspace(0, 1, 250)
# first plot the separate model (same as above)
ax = axes[0]
# note that for clarity only every 7th distribution is plotted
lines = ax.plot(
x,
beta.pdf(x[:,None], y[7:-1:7] + 1, n[7:-1:7] - y[7:-1:7] + 1),
color='C0',
linewidth=1
)
# highlight the last line
lines[-1].set_linewidth(2)
lines[-1].set_color('r')
ax.set_yticks(())
ax.set_title('separate model')
ax.set_ylim((0, ax.set_ylim()[1])) # set y base to zero
# And the hierarchical model. Note that these marginal posteriors for theta_j are
# more narrow than in the separate model case, due to the borrowed information from
# the other theta_j's.
ax = axes[1]
# note that for clarity only every 7th distribution is plotted
lines = ax.plot(
x,
np.mean(
beta.pdf(
x[:,None],
y[7::7] + samp_A[:,None,None],
n[7::7] - y[7::7] + samp_B[:,None,None]
),
axis=0
),
color='C0',
linewidth=1,
)
# highlight the last line
lines[-1].set_linewidth(2)
lines[-1].set_color('r')
ax.set_yticks(())
ax.set_xlabel(r'$\theta$')
ax.set_title('hierarchical model')
ax.set_ylim((0, ax.set_ylim()[1])) # set y base to zero
fig.tight_layout()