import pymc as pm
import pandas as pd
import numpy as np
import arviz as az
from pymc.math import dot
import pytensor.tensor as pt
rng = np.random.default_rng(6420)
11. Brozek index prediction*#
This example goes over linear regression and Bayesian \(R^2\).
Adapted from Unit 7: fat1.odc, fat2d.odc, and fatmulti.odc.
Data can be found here.
Percentage of body fat, age, weight, height, and ten body circumference measurements (e.g., abdomen) were recorded for 252 men. Percentage of body fat is estimated through an underwater weighing technique.
The data set has 252 observations and 15 variables. Brozek index (Brozek et al., 1963) is obtained by the underwater weighing while other 14 anthropometric variables are obtained using scales and a measuring tape.
y = Brozek index
X1 = 1 (intercept)
X2 = age
X3 = weight
X4 = height
X5 = adipose
X6 = neck
X7 = chest
X8 = abdomen
X9 = hip
X10 = thigh
X11 = knee
X12 = ankle
X13 = bicep
X14 = forearm
X15 = wrist
These anthropometric variables are less intrusive but also less reliable in assessing the body fat index.
Set a linear regression to predict the Brozek index from these body measurements.
Single predictor (X8)#
This is a recreation of fat1.odc.
data = pd.read_csv("../data/fat.tsv", sep="\t")
y = data["y"].to_numpy(copy=True)
X = data["X8"].to_numpy(copy=True)
# p will be the number of predictors + intercept (1 + 1 in this case)
n, p = X.shape[0], 2
with pm.Model() as m:
tau = pm.Gamma("tau", 0.001, 0.001)
beta0 = pm.Normal("beta0_intercept", 0, tau=0.001)
beta1 = pm.Normal("beta1_abdomen", 0, tau=0.001)
variance = pm.Deterministic("variance", 1 / tau)
mu = pm.Deterministic("mu", beta0 + beta1 * X)
pm.Normal("likelihood", mu=mu, tau=tau, observed=y)
# Bayesian R2 from fat1.odc
# no need to use this anymore, use az.bayesian_r2 instead
sse = (n - p) * variance
cy = y - y.mean()
sst = dot(cy, cy)
br2 = pm.Deterministic("br2", 1 - sse / sst)
trace = pm.sample(2000, random_seed=rng)
az.summary(trace, var_names="~mu", ci_prob=0.95)
| mean | sd | eti95_lb | eti95_ub | ess_bulk | ess_tail | r_hat | mcse_mean | mcse_sd | |
|---|---|---|---|---|---|---|---|---|---|
| beta0_intercept | -35 | 2.49 | -40 | -30 | 1266 | 1258 | 1.00 | 0.07 | 0.049 |
| beta1_abdomen | 0.583 | 0.027 | 0.53 | 0.64 | 1268 | 1303 | 1.00 | 0.00075 | 0.00052 |
| tau | 0.049 | 0.0044 | 0.041 | 0.058 | 3995 | 3516 | 1.00 | 7e-05 | 5.1e-05 |
| variance | 20.57 | 1.88 | 17 | 25 | 3995 | 3516 | 1.00 | 0.03 | 0.024 |
| br2 | 0.659 | 0.0312 | 0.59 | 0.71 | 3995 | 3516 | 1.00 | 0.0005 | 0.00039 |
az.bayesian_r2(
trace,
pred_mean="mu",
scale="variance",
scale_kind="var",
ci_prob=0.95,
ci_kind="hdi",
)
bayesian_R2(mean=0.66, hdi_lb=0.6, hdi_ub=0.71)
All predictors#
This is not in the lecture, just adding an extra PyMC example with all predictors.
y = data["y"].to_numpy()
X = data.drop(columns="y")
with pm.Model(coords={"predictor": X.columns}) as m_multiple:
tau = pm.Gamma("tau", 0.001, 0.001)
alpha = pm.Normal("intercept", 0, tau=0.001)
beta = pm.Normal("beta", 0, tau=0.001, dims="predictor")
variance = pm.Deterministic("variance", 1 / tau)
mu = pm.Deterministic("mu", alpha + pm.math.dot(X, beta))
pm.Normal("likelihood", mu=mu, tau=tau, observed=y)
trace = pm.sample(3000, random_seed=6420)
NUTS[nutpie]: [tau, intercept, beta]
az.summary(trace, var_names="~mu", ci_prob=0.95)
| mean | sd | eti95_lb | eti95_ub | ess_bulk | ess_tail | r_hat | mcse_mean | mcse_sd | |
|---|---|---|---|---|---|---|---|---|---|
| intercept | -12.2 | 14.6 | -41 | 16 | 3700 | 4990 | 1.00 | 0.24 | 0.18 |
| beta[X2] | 0.0568 | 0.0304 | -0.0021 | 0.12 | 10942 | 10301 | 1.00 | 0.00029 | 0.00021 |
| beta[X3] | -0.073 | 0.046 | -0.17 | 0.016 | 4108 | 5302 | 1.00 | 0.00072 | 0.00053 |
| beta[X4] | -0.064 | 0.102 | -0.26 | 0.14 | 7290 | 8004 | 1.00 | 0.0012 | 0.00086 |
| beta[X5] | 0.064 | 0.28 | -0.48 | 0.62 | 7969 | 8146 | 1.00 | 0.0031 | 0.0022 |
| beta[X6] | -0.456 | 0.221 | -0.89 | -0.02 | 7706 | 8078 | 1.00 | 0.0025 | 0.0019 |
| beta[X7] | -0.039 | 0.097 | -0.23 | 0.16 | 6869 | 7952 | 1.00 | 0.0012 | 0.00084 |
| beta[X8] | 0.879 | 0.086 | 0.71 | 1 | 8102 | 8096 | 1.00 | 0.00095 | 0.00068 |
| beta[X9] | -0.217 | 0.132 | -0.47 | 0.048 | 4763 | 6044 | 1.00 | 0.0019 | 0.0014 |
| beta[X10] | 0.224 | 0.138 | -0.047 | 0.5 | 7669 | 7633 | 1.00 | 0.0016 | 0.0011 |
| beta[X11] | -0.01 | 0.228 | -0.45 | 0.44 | 8501 | 8341 | 1.00 | 0.0025 | 0.0018 |
| beta[X12] | 0.15 | 0.209 | -0.26 | 0.56 | 13769 | 9745 | 1.00 | 0.0018 | 0.0012 |
| beta[X13] | 0.146 | 0.163 | -0.17 | 0.47 | 13077 | 9531 | 1.00 | 0.0014 | 0.001 |
| beta[X14] | 0.428 | 0.189 | 0.05 | 0.8 | 13739 | 9203 | 1.00 | 0.0016 | 0.0012 |
| beta[X15 ] | -1.49 | 0.494 | -2.5 | -0.53 | 7642 | 7930 | 1.00 | 0.0056 | 0.004 |
| tau | 0.0627 | 0.00577 | 0.052 | 0.074 | 12074 | 7100 | 1.00 | 5.2e-05 | 3.7e-05 |
| variance | 16.09 | 1.5 | 13 | 19 | 12074 | 7100 | 1.00 | 0.014 | 0.01 |
az.bayesian_r2(
trace,
pred_mean="mu",
scale="variance",
scale_kind="var",
ci_prob=0.95,
ci_kind="hdi",
)
bayesian_R2(mean=0.74, hdi_lb=0.69, hdi_ub=0.78)
%load_ext watermark
%watermark -n -u -v -iv
Last updated: Sun, 18 Oct 2026
Python implementation: CPython
Python version : 3.14.7
IPython version : 9.16.1
arviz : 1.3.0
numpy : 2.4.6
pandas : 3.0.5
pymc : 6.3.1
pytensor: 3.3.0