import pymc as pm
import numpy as np
import arviz as az
rng = np.random.default_rng(6420)
5. Meta-analysis via Hierarchical Models*#
Adapted from Unit 7: rats_nocentering.odc.
This example is taken from Gelfand et al. (1990) and concerns 30 young rats whose weights were measured weekly for five weeks. Measurements were taken at 8, 15, 22, 29, and 36 days.
The observed weights (in grams) for each rat are arranged in the matrix \(\mathbf{Y}\), where \(y_{i,j}\) is the weight of the \(i\)-th rat at age \(x_j\). The first two and last rows of \(\mathbf{Y}\) are shown below:
Rat |
\(y_{i,8} \) |
\( y_{i,15} \) |
\( y_{i,22} \) |
\( y_{i,29} \) |
\( y_{i,36} \) |
|---|---|---|---|---|---|
1 |
151 |
199 |
246 |
283 |
320 |
2 |
145 |
199 |
249 |
293 |
354 |
… |
… |
… |
… |
… |
… |
30 |
153 |
200 |
244 |
286 |
324 |
Each row corresponds to a different rat (\( i = 1, \ldots, 30 \)), and each column corresponds to one of the five measurement times.
Data for \(x\) can be found here, and \(\mathbf{Y}\) here.
A hierarchical model lets us model each rat’s growth curve as a linear function of age, while also allowing the intercept (baseline weight) and slope (growth rate) to vary across rats. This structure is essentially “borrowing strength” across units in a meta-analysis. It allows us to estimate both group-level (population) and individual-level effects.
Note
The term meta-analysis was carried over from lectures here, but it’s kind of an odd choice of words. Typically meta-analysis refers to combining multiple studies.
The model is defined as:
Line 1-2 are the likelihood
Lines 3 and 4 are the random effects
Lines 7-9 are hyperpriors
We will use a graphviz below to verify that we’ve specified our model correctly. This is particularly useful in models with complex hierarchical structures.
y = np.loadtxt("../data/rats_nocenteringY.txt")
x = np.loadtxt("../data/rats_nocenteringx.txt")
x.shape, y.shape
((5,), (30, 5))
with pm.Model() as m:
mu_alpha = pm.Normal("mu_alpha", 0, tau=1e-6)
tau_alpha = pm.Gamma("tau_alpha", 0.001, 0.001)
mu_beta = pm.Normal("mu_beta", 0, tau=1e-6)
tau_beta = pm.Gamma("tau_beta", 0.001, 0.001)
tau = pm.Gamma("tau", 0.001, 0.001)
sigma = pm.Deterministic("sigma", 1 / tau**0.5)
alpha = pm.Normal("alpha", mu_alpha, tau=tau_alpha, shape=(30, 1))
beta = pm.Normal("beta", mu_beta, tau=tau_beta, shape=(30, 1))
mu = alpha + beta * x
pm.Normal("y", mu, tau=tau, observed=y)
trace = pm.sample(3000, random_seed=rng)
az.summary(trace, var_names=["~alpha", "~beta"])
| mean | sd | eti89_lb | eti89_ub | ess_bulk | ess_tail | r_hat | mcse_mean | mcse_sd | |
|---|---|---|---|---|---|---|---|---|---|
| mu_alpha | 106.58 | 2.3 | 100 | 110 | 12567 | 9452 | 1.00 | 0.021 | 0.015 |
| mu_beta | 6.186 | 0.107 | 6 | 6.4 | 13138 | 9636 | 1.00 | 0.00093 | 0.00069 |
| tau_alpha | 0.01017 | 0.004 | 0.0053 | 0.017 | 9026 | 8851 | 1.00 | 4.5e-05 | 6.8e-05 |
| tau_beta | 4.34 | 1.5 | 2.4 | 6.9 | 11327 | 10206 | 1.00 | 0.014 | 0.016 |
| tau | 0.02689 | 0.00403 | 0.021 | 0.034 | 7971 | 8157 | 1.00 | 4.5e-05 | 3.3e-05 |
| sigma | 6.15 | 0.464 | 5.5 | 6.9 | 7971 | 8157 | 1.00 | 0.0052 | 0.0038 |
If you have not had any courses in Regression or Design and Analysis of Experiments, take a moment and research how to interpret these coefficients: especially mu’s and tau’s.
pm.model_to_graphviz(model=m)
%load_ext watermark
%watermark -n -u -v -iv -p pytensor
Last updated: Sun, 18 Oct 2026
Python implementation: CPython
Python version : 3.14.7
IPython version : 9.16.1
pytensor: 3.3.0
arviz: 1.3.0
numpy: 2.4.6
pymc : 6.3.1