Imports¶
Install packages¶
!sudo /bin/bash -c "(source /venv/bin/activate; pip install --quiet jupyterlab-vim)"
!jupyter labextension enable!sudo /bin/bash -c "(source /venv/bin/activate; pip install --quiet graphviz)"!sudo /bin/bash -c "(source /venv/bin/activate; pip install --quiet dataframe_image)"Import modules¶
%load_ext autoreload
%autoreload 2
import arviz as az
import pandas as pd
import pymc as pm
import numpy as np
import seaborn as snsWARNING (pytensor.tensor.blas): Using NumPy C-API based implementation for BLAS functions.
dir_name = "./L07_data"
!ls $dir_nameimport msml610.tutorials.msml610_utils as ut
ut.config_notebook()# Setting notebook style
# Notebook signature
Python 3.12.3
Linux ec1347f4acf5 6.10.14-linuxkit #1 SMP Tue Apr 15 16:00:54 UTC 2025 aarch64 aarch64 aarch64 GNU/Linux
numpy version=1.26.4
pymc version=5.18.2
matplotlib version=3.10.3
arviz version=0.21.0
preliz version=0.19.0
Group comparison¶
tips = pd.read_csv(dir_name + "/tips.csv")
tipsLoading...
sns.boxplot(x="day", y="tip", data=tips)
# Extract the tips.
tip = tips["tip"].values
print(tip[:10])
# Create a vector going from day to group idx.
idx = pd.Categorical(tips["day"]).codes
print("idx=", idx)
# Count the groups.
groups = np.unique(idx)
n_groups = len(groups)
print("groups=", n_groups, groups)[1.01 1.66 3.5 3.31 3.61 4.71 2. 3.12 1.96 3.23]
idx= [2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 3 3 3 3 3 3 3 3 3 3 3 3 3 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1
1 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0
0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3]
groups= 4 [0 1 2 3]
# The model is the same as before but it can be easily vectorized.
# There is no need to write a for-loop.
with pm.Model() as comparing_groups:
# mu is a vector of 4 elems.
mu = pm.Normal("mu", mu=0, sigma=10, shape=n_groups)
# sigma is a vector of 4 elems.
sigma = pm.HalfNormal("sigma", sigma=10, shape=n_groups)
# y is a vector of 4 normals each with mean and sigma for the group.
y = pm.Normal("y", mu=mu[idx], sigma=sigma[idx], observed=tip)
idata_cg = pm.sample(5000)Auto-assigning NUTS sampler...
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [mu, sigma]
Loading...
Loading...
Sampling 4 chains for 1_000 tune and 5_000 draw iterations (4_000 + 20_000 draws total) took 3 seconds.
Hierarchical models¶
cs_data = pd.read_csv(dir_name + "/chemical_shifts_theo_exp.csv")
cs_data["diff"] = cs_data["theo"] - cs_data["exp"]
display(cs_data)Loading...
diff = cs_data.theo.values - cs_data.exp.values
print("diff=", diff)
# Array of categorical values.
cat_encode = pd.Categorical(cs_data["aa"])
print("cat_encode=", cat_encode)
idx = cat_encode.codes
print("idx=", len(idx), idx)
coords = {"aa": cat_encode.categories}
print("coords=", coords)diff= [ 2.91 0.77 -0.49 ... 0.57 1.12 -2.48]
cat_encode= ['ILE', 'TYR', 'SER', 'ALA', 'ARG', ..., 'LYS', 'ARG', 'LYS', 'GLU', 'SER']
Length: 1776
Categories (19, object): ['ALA', 'ARG', 'ASN', 'ASP', ..., 'THR', 'TRP', 'TYR', 'VAL']
idx= 1776 [ 8 17 14 ... 10 5 14]
coords= {'aa': Index(['ALA', 'ARG', 'ASN', 'ASP', 'GLN', 'GLU', 'GLY', 'HIS', 'ILE', 'LEU',
'LYS', 'MET', 'PHE', 'PRO', 'SER', 'THR', 'TRP', 'TYR', 'VAL'],
dtype='object')}
# Non-hierarchical model.
with pm.Model(coords=coords) as cs_nh:
# One separate prior for each group.
mu = pm.Normal("mu", mu=0, sigma=10, dims="aa")
sigma = pm.HalfNormal("sigma", sigma=10, dims="aa")
# Likelihood.
y = pm.Normal("y", mu=mu[idx], sigma=sigma[idx], observed=diff)
idata_cs_nh = pm.sample()Auto-assigning NUTS sampler...
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [mu, sigma]
Loading...
Loading...
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 2 seconds.
pm.model_to_graphviz(cs_nh)Loading...
with pm.Model(coords=coords) as cs_h:
# Hyper-priors.
mu_mu = pm.Normal("mu_mu", mu=0, sigma=10)
mu_sigma = pm.HalfNormal("mu_sigma", sigma=10)
# Priors.
mu = pm.Normal("mu", mu=mu_mu, sigma=mu_sigma, dims="aa")
sigma = pm.HalfNormal("sigma", sigma=10, dims="aa")
# Likelihood (same as before).
y = pm.Normal("y", mu=mu[idx], sigma=sigma[idx], observed=diff)
idata_cs_h = pm.sample()Auto-assigning NUTS sampler...
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [mu_mu, mu_sigma, mu, sigma]
Loading...
Loading...
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 2 seconds.
pm.model_to_graphviz(cs_h)Loading...
# We have two models and we want to compare the estimates.
# - There are 20 groups and each model has 4 estimates.
# - We plot the 94% credible intervals.
# - The vertical line is the global mean according to the hierarchical model.
# - The blue (hierarchical) means are pulled towards the mean, wrt the orange (non-hierarchical) ones.
axes = az.plot_forest(
[idata_cs_h, idata_cs_nh],
model_names=["h", "n_h"],
var_names="mu",
combined=True,
colors="cycle",
)
y_lims = axes[0].get_ylim()
axes[0].vlines(idata_cs_h.posterior["mu_mu"].mean(), *y_lims, color="navy")
axes[0].vlines(idata_cs_nh.posterior["mu"].mean(), *y_lims, color="orange")