from pathlib import Path
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import lapylace as lp
root = Path("../../ROS-Examples")
def coef_medians(fit):
return pd.Series(
[np.median(fit.alpha_draws()), *np.median(fit.beta_draws(), axis=0)],
index=["Intercept", *fit.columns],
)
def fit_glm(formula, data, family=None, seed=1, prior_scale=2.5, intercept_scale=5, aux_scale=10, **kwargs):
return lp.stan_glm(
formula,
data=data,
family=family or lp.gaussian(),
prior=lp.normal(0, prior_scale),
prior_intercept=lp.normal(0, intercept_scale),
prior_aux=lp.exponential(aux_scale),
chains=2,
parallel_chains=2,
iter_warmup=300,
iter_sampling=500,
seed=seed,
refresh=100,
**kwargs,
)KidIQ: k-fold cross-validation
Source: KidIQ/kidiq_kcv.Rmd
The R page compares predictive performance for several stan_glm formulas. This Python translation uses the same formulas with lapylace for the Bayesian model and scikit-learn only for deterministic fold splitting.
Setup
from sklearn.model_selection import KFold
from sklearn.metrics import mean_squared_error
kidiq = pd.read_csv(root / "KidIQ/data/kidiq.csv")
kidiq.head()| kid_score | mom_hs | mom_iq | mom_work | mom_age | |
|---|---|---|---|---|---|
| 0 | 65 | 1 | 121.117529 | 4 | 27 |
| 1 | 98 | 1 | 89.361882 | 4 | 25 |
| 2 | 85 | 1 | 115.443165 | 4 | 27 |
| 3 | 83 | 1 | 99.449639 | 3 | 25 |
| 4 | 115 | 1 | 92.745710 | 4 | 27 |
Baseline Bayesian fit
fit = fit_glm("kid_score ~ mom_hs + mom_iq", kidiq, seed=17101, prior_scale=10, intercept_scale=30, aux_scale=30)
fit.summary(["alpha", "beta", "sigma"])
| Mean | MCSE | StdDev | 5% | 50% | 95% | N_Eff | N_Eff/s | R_hat | |
|---|---|---|---|---|---|---|---|---|---|
| alpha | 24.744100 | 0.332486 | 6.018050 | 15.354300 | 24.882700 | 34.03400 | 327.61500 | 1502.82000 | 1.005890 |
| beta[1] | 5.601030 | 0.078803 | 2.124470 | 1.990300 | 5.609650 | 9.01148 | 726.80100 | 3333.95000 | 0.999462 |
| beta[2] | 0.576259 | 0.003331 | 0.060353 | 0.479869 | 0.575611 | 0.67201 | 328.18400 | 1505.43000 | 1.004930 |
| sigma | 18.172930 | 0.026440 | 0.667380 | 17.122700 | 18.132300 | 19.27370 | 636.89239 | 2921.52472 | 0.998800 |
K-fold predictive comparison
For speed in the rendered book, each fold uses posterior predictive means from a short Stan run. The comparison is deliberately small but follows the same target as the R example: out-of-fold prediction error.
formulas = [
"kid_score ~ mom_hs",
"kid_score ~ mom_hs + mom_iq",
"kid_score ~ mom_hs * mom_iq",
]
rows = []
kf = KFold(n_splits=5, shuffle=True, random_state=17102)
for j, formula in enumerate(formulas, start=1):
fold_rmse = []
for fold, (train_idx, test_idx) in enumerate(kf.split(kidiq), start=1):
train = kidiq.iloc[train_idx]
test = kidiq.iloc[test_idx]
m = fit_glm(formula, train, seed=17110 + 10*j + fold, prior_scale=10, intercept_scale=30, aux_scale=30)
pred = m.posterior_epred(test).mean(axis=0)
fold_rmse.append(np.sqrt(mean_squared_error(test["kid_score"], pred)))
rows.append({"formula": formula, "rmse": np.mean(fold_rmse), "fold_sd": np.std(fold_rmse)})
pd.DataFrame(rows)
| formula | rmse | fold_sd | |
|---|---|---|---|
| 0 | kid_score ~ mom_hs | 19.852398 | 1.481193 |
| 1 | kid_score ~ mom_hs + mom_iq | 18.134966 | 1.367551 |
| 2 | kid_score ~ mom_hs * mom_iq | 18.089493 | 1.401176 |