Posterior predictive checking: Stochastic learning in dogs

Author

Andrew Gelman

Published

2022-07-16

Modified

2026-07-10

This notebook includes the CmdStanPy code for the Bayesian Workflow book Chapter 21 Posterior predictive checking: Stochastic learning in dogs.

1 Introduction

We analyse stochastic learning in dogs data by Bush and Mosteller (1955).

import sys
import warnings

sys.path.insert(0, "..")

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from cmdstanpy import CmdStanModel, disable_logging
from scipy.special import expit

import arviz as az

from utils import print_stan

warnings.filterwarnings("ignore")
disable_logging()

az.style.use("arviz-variat")
az.rcParams["stats.ci_prob"] = 0.95
plt.rcParams["figure.dpi"] = 100

SEED = 123
rng = np.random.default_rng(SEED)

shock_cmap = ListedColormap(["#fac364", "#7c2695"])
/home/osvaldo/anaconda3/envs/bwb/lib/python3.14/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm

2 Data

dogs = pd.read_csv("data/dogs.dat", skiprows=2, sep=r"\s+", header=None)
shock = (dogs.iloc[:, 1:26].values == "S").astype(int)
dogs_data = {"y": shock, "J": shock.shape[0], "T": shock.shape[1]}

3 Models

dogs_0 = CmdStanModel(stan_file="dogs_0.stan")
print_stan(dogs_0)
data {
  int<lower=0> J;
  int<lower=0> T;
  array[J, T] int<lower=0, upper=1> y;
}
parameters {
  real alpha;
  real beta;
}
transformed parameters {
  matrix<lower=0, upper=1>[J, T] p;
  for (j in 1:J) {
    for (t in 1:T) {
      p[j, t] = inv_logit(alpha + beta * t);
    }
  }
}
model {
  for (j in 1:J) {
    for (t in 1:T) {
      y[j, t] ~ bernoulli(p[j, t]);
    }
  }
}
generated quantities {
  array[J, T] int<lower=0, upper=1> y_rep;
  for (j in 1:J) {
    for (t in 1:T) {
      y_rep[j, t] = bernoulli_rng(p[j, t]);
    }
  }
}
fit_0 = dogs_0.sample(data=dogs_data, seed=SEED, show_progress=False)
dt_0 = az.from_cmdstanpy(fit_0, posterior_predictive=["y_rep"])
az.summary(dt_0)
mean sd eti95_lb eti95_ub ess_bulk ess_tail r_hat mcse_mean mcse_sd
alpha 2.35 0.23 1.9 2.8 734 875 1.00 0.0086 0.0062
beta -0.296 0.023 -0.34 -0.25 750 881 1.00 0.00083 0.00059
p[0, 0] 0.885 0.022 0.84 0.92 747 911 1.00 0.0008 0.00064
p[0, 1] 0.852 0.024 0.8 0.9 769 990 1.00 0.00089 0.00068
p[0, 2] 0.811 0.027 0.76 0.86 802 1053 1.00 0.00095 0.00071
... ... ... ... ... ... ... ... ... ...
p[29, 20] 0.0215 0.006 0.012 0.035 1037 1489 1.00 0.00018 0.00015
p[29, 21] 0.0161 0.0049 0.0084 0.027 1004 1456 1.00 0.00015 0.00013
p[29, 22] 0.0121 0.0039 0.006 0.021 977 1445 1.00 0.00013 0.00011
p[29, 23] 0.0091 0.0032 0.0043 0.016 955 1377 1.00 0.0001 9.5e-05
p[29, 24] 0.0069 0.0025 0.003 0.013 937 1292 1.00 8.3e-05 8.1e-05

752 rows × 9 columns

dogs_1 = CmdStanModel(stan_file="dogs_1.stan")
print_stan(dogs_1)
data {
  int<lower=0> J;
  int<lower=0> T;
  array[J, T] int<lower=0, upper=1> y;
}
parameters {
  real<lower=0, upper=1> a;
}
transformed parameters {
  matrix<lower=0, upper=1>[J, T] p;
  for (j in 1:J) {
    for (t in 1:T) {
      p[j, t] = a^(t-1);
    }
  }
}
model {
  for (j in 1:J) {
    for (t in 1:T) {
      y[j, t] ~ bernoulli(p[j, t]);
    }
  }
}
generated quantities {
  array[J, T] int<lower=0, upper=1> y_rep;
  for (j in 1:J) {
    for (t in 1:T) {
      y_rep[j, t] = bernoulli_rng(p[j, t]);
    }
  }
}
fit_1 = dogs_1.sample(data=dogs_data, seed=SEED, show_progress=False)
dt_1 = az.from_cmdstanpy(fit_1, posterior_predictive=["y_rep"])
az.summary(dt_1)
mean sd eti95_lb eti95_ub ess_bulk ess_tail r_hat mcse_mean mcse_sd
a 0.8698 0.0064 0.86 0.88 1286 1236 1.00 0.00018 0.00012
p[0, 0] 1 0 1 1 4000 4000 NaN 0 NaN
p[0, 1] 0.8698 0.0064 0.86 0.88 1286 1236 1.00 0.00018 0.00012
p[0, 2] 0.7566 0.0111 0.73 0.78 1286 1236 1.00 0.00031 0.00021
p[0, 3] 0.6581 0.0144 0.63 0.69 1286 1236 1.00 0.0004 0.00028
... ... ... ... ... ... ... ... ... ...
p[29, 20] 0.062 0.0091 0.046 0.081 1286 1236 1.00 0.00025 0.00018
p[29, 21] 0.054 0.0083 0.039 0.072 1286 1236 1.00 0.00023 0.00016
p[29, 22] 0.047 0.0076 0.034 0.063 1286 1236 1.00 0.00021 0.00015
p[29, 23] 0.041 0.0069 0.029 0.056 1286 1236 1.00 0.00019 0.00014
p[29, 24] 0.0357 0.0063 0.025 0.049 1286 1236 1.00 0.00017 0.00012

751 rows × 9 columns

dogs_2 = CmdStanModel(stan_file="dogs_2.stan")
print_stan(dogs_2)
data {
  int<lower=0> J;
  int<lower=0> T;
  array[J, T] int<lower=0, upper=1> y;
}
transformed data {
  matrix<lower=0>[J, T] prev_shock;
  matrix<lower=0>[J, T] prev_avoid;
  for (j in 1:J){
    prev_shock[j,1] = 0;
    prev_avoid[j,1] = 0;
    for (t in 2:T) {
      prev_shock[j, t] = prev_shock[j, t-1] + y[j, t-1];
      prev_avoid[j, t] = prev_avoid[j, t-1] + 1 - y[j, t-1];
    }
  }
}
parameters {
  real<lower=0, upper=1> a;
  real<lower=0, upper=1> b;
}
model {
  for (j in 1:J) {
    for (t in 1:T) {
      real p = a^prev_shock[j, t] * b^prev_avoid[j, t];
      y[j, t] ~ bernoulli(p);
    }
  }
}
generated quantities {
  array[J, T] int<lower=0, upper=1> y_rep;
  {
    real prev_shock_rep;
    real prev_avoid_rep;
    real p_rep;
    for (j in 1:J) {
      prev_shock_rep = 0;
      prev_avoid_rep = 0;
      y_rep[j, 1] = 1;
      for (t in 2:T) {
        prev_shock_rep = prev_shock_rep + y_rep[j, t-1];
        prev_avoid_rep = prev_avoid_rep + 1 - y_rep[j, t-1];
        p_rep = a^prev_shock_rep * b^prev_avoid_rep;
        y_rep[j, t] = bernoulli_rng(p_rep);
      }
    }
  }
}
fit_2 = dogs_2.sample(data=dogs_data, seed=SEED, show_progress=False)
dt_2 = az.from_cmdstanpy(fit_2, posterior_predictive=["y_rep"])
az.summary(dt_2)
mean sd eti95_lb eti95_ub ess_bulk ess_tail r_hat mcse_mean mcse_sd
a 0.9228 0.011 0.9 0.94 1306 1701 1.00 0.0003 0.00022
b 0.786 0.0192 0.75 0.82 1396 1641 1.00 0.00051 0.00036
dogs_3 = CmdStanModel(stan_file="dogs_3.stan")
print_stan(dogs_3)
data {
  int<lower=0> J;
  int<lower=0> T;
  array[J, T] int<lower=0, upper=1> y;
}
parameters {
  vector[J] logit_a;
  real mu_logit_a;
  real<lower=0> sigma_logit_a;
}
transformed parameters {
  vector[J] a = inv_logit(logit_a);
  matrix<lower=0, upper=1>[J, T] p;
  for (j in 1:J) {
    for (t in 1:T) {
      p[j, t] = a[j]^(t-1);
    }
  }
}
model {
  for (j in 1:J) {
    for (t in 1:T) {
      y[j, t] ~ bernoulli(p[j, t]);
    }
  }
  logit_a ~ normal(mu_logit_a, sigma_logit_a);
}
generated quantities {
  array[J, T] int<lower=0, upper=1> y_rep;
  for (j in 1:J) {
    for (t in 1:T) {
      y_rep[j, t] = bernoulli_rng(p[j, t]);
    }
  }
}
fit_3 = dogs_3.sample(data=dogs_data, seed=SEED, show_progress=False)
dt_3 = az.from_cmdstanpy(fit_3, posterior_predictive=["y_rep"])
az.summary(dt_3, var_names=["mu_logit_a", "sigma_logit_a"])
mean sd eti95_lb eti95_ub ess_bulk ess_tail r_hat mcse_mean mcse_sd
mu_logit_a 1.866 0.086 1.7 2 2586 2820 1.00 0.0017 0.0013
sigma_logit_a 0.317 0.09 0.16 0.5 681 463 1.00 0.0032 0.0023
dogs_4 = CmdStanModel(stan_file="dogs_4.stan")
print_stan(dogs_4)
data {
  int<lower=0> J;
  int<lower=0> T;
  array[J, T] int<lower=0, upper=1> y;
}
transformed data {
  matrix<lower=0>[J, T] prev_shock;
  matrix<lower=0>[J, T] prev_avoid;
  for (j in 1:J) {
    prev_shock[j, 1] = 0;
    prev_avoid[j, 1] = 0;
    for (t in 2:T) {
      prev_shock[j, t] = prev_shock[j, t-1] + y[j, t-1];
      prev_avoid[j, t] = prev_avoid[j, t-1] + 1 - y[j, t-1];
    }
  }
}
parameters {
  matrix[J, 2] logit_ab;
  vector[2] mu_logit_ab;
  cov_matrix[2] Sigma_logit_ab;
}
transformed parameters {
  vector[J] a = inv_logit(logit_ab[, 1]);
  vector[J] b = inv_logit(logit_ab[, 2]);
}
model {
  for (j in 1:J) {
    for (t in 1:T) {
      real p = a[j]^prev_shock[j, t] * b[j]^prev_avoid[j, t];
      y[j, t] ~ bernoulli(p);
    }
    logit_ab[j, ] ~ multi_normal(mu_logit_ab, Sigma_logit_ab);
  }
}
generated quantities {
  array[J, T] int<lower=0, upper=1> y_rep;
  {
    real prev_shock_rep;
    real prev_avoid_rep;
    real p_rep;
    for (j in 1:J) {
      prev_shock_rep = 0;
      prev_avoid_rep = 0;
      y_rep[j, 1] = 1;
      for (t in 2:T) {
        prev_shock_rep = prev_shock_rep + y_rep[j, t-1];
        prev_avoid_rep = prev_avoid_rep + 1 - y_rep[j, t-1];
        p_rep = a[j]^prev_shock_rep * b[j]^prev_avoid_rep;
        y_rep[j, t] = bernoulli_rng(p_rep);
      }
    }
  }
}
fit_4 = dogs_4.sample(data=dogs_data, seed=SEED, show_progress=False)
dt_4 = az.from_cmdstanpy(fit_4, posterior_predictive=["y_rep"])
az.summary(dt_4, var_names=["mu_logit_ab", "Sigma_logit_ab"])
mean sd eti95_lb eti95_ub ess_bulk ess_tail r_hat mcse_mean mcse_sd
mu_logit_ab[0] 2.43 0.218 2 2.9 1413 2095 1.01 0.0058 0.0043
mu_logit_ab[1] 1.31 0.22 0.9 1.8 1447 1310 1.01 0.0058 0.005
Sigma_logit_ab[0, 0] 0.51 0.36 0.062 1.4 324 584 1.02 0.018 0.027
Sigma_logit_ab[0, 1] -0.33 0.35 -1.2 0.11 190 819 1.03 0.026 0.034
Sigma_logit_ab[1, 0] -0.33 0.35 -1.2 0.11 190 819 1.03 0.026 0.034
Sigma_logit_ab[1, 1] 0.66 0.6 0.079 2.1 320 385 1.01 0.027 0.052
dogs_5 = CmdStanModel(stan_file="dogs_5.stan")
print_stan(dogs_5)
data {
  int<lower=0> J;
  int<lower=0> T;
  array[J, T] int<lower=0, upper=1> y;
}
transformed data {
  matrix<lower=0>[J, T] prev_shock;
  matrix<lower=0>[J, T] prev_avoid;
  for (j in 1:J){
    prev_shock[j,1] = 0;
    prev_avoid[j,1] = 0;
    for (t in 2:T){
      prev_shock[j,t] = prev_shock[j,t-1] + y[j,t-1];
      prev_avoid[j,t] = prev_avoid[j,t-1] + 1 - y[j,t-1];
    }
  }
}
parameters {
  vector[2] mu_logit_ab;
  vector<lower=0>[2] sigma_logit_ab;
  cholesky_factor_corr[2] L_logit_ab;
  matrix[J,2] z;
}
transformed parameters {
  matrix[J,2] logit_ab = rep_vector(1, J) * mu_logit_ab' +
    z * diag_pre_multiply(sigma_logit_ab, L_logit_ab);
  corr_matrix[2] Omega_logit_ab = L_logit_ab * L_logit_ab';
  cov_matrix[2] Sigma_logit_ab = quad_form_diag(Omega_logit_ab, sigma_logit_ab);
  vector[J] a = inv_logit(logit_ab[,1]);
  vector[J] b = inv_logit(logit_ab[,2]);
}
model {
  for (j in 1:J){
    for (t in 1:T){
      real p = a[j]^prev_shock[j,t] * b[j]^prev_avoid[j,t];
      y[j,t] ~ bernoulli(p);
    }
  }
  mu_logit_ab ~ logistic(0, 1);
  sigma_logit_ab ~ normal(0, 1);
  L_logit_ab ~ lkj_corr_cholesky(2);
  to_vector(z) ~ normal(0, 1);
}
generated quantities {
  array[J, T] int<lower=0, upper=1> y_rep;
  {
    real prev_shock_rep;
    real prev_avoid_rep;
    real p_rep;
    for (j in 1:J){
      prev_shock_rep = 0;
      prev_avoid_rep = 0;
      y_rep[j,1] = 1;
      for (t in 2:T){
        prev_shock_rep = prev_shock_rep + y_rep[j,t-1];
        prev_avoid_rep = prev_avoid_rep + 1 - y_rep[j,t-1];
        p_rep = a[j]^prev_shock_rep * b[j]^prev_avoid_rep;
        y_rep[j,t] = bernoulli_rng(p_rep);
      }
    }
  }
}
fit_5 = dogs_5.sample(data=dogs_data, seed=SEED, show_progress=False)
dt_5 = az.from_cmdstanpy(fit_5, posterior_predictive=["y_rep"])
az.summary(dt_5, var_names=["mu_logit_ab", "sigma_logit_ab", "Omega_logit_ab", "a", "b"])
mean sd eti95_lb eti95_ub ess_bulk ess_tail r_hat mcse_mean mcse_sd
mu_logit_ab[0] 2.439 0.186 2.1 2.8 2797 2492 1.00 0.0035 0.0026
mu_logit_ab[1] 1.29 0.156 0.98 1.6 2864 2526 1.00 0.003 0.0023
sigma_logit_ab[0] 0.32 0.193 0.017 0.72 1212 1863 1.00 0.0054 0.0036
sigma_logit_ab[1] 0.41 0.24 0.032 0.96 1044 1274 1.00 0.0071 0.0059
Omega_logit_ab[0, 0] 1 0 1 1 4000 4000 NaN 0 NaN
... ... ... ... ... ... ... ... ... ...
b[25] 0.778 0.059 0.64 0.89 4367 2650 1.00 0.0009 0.00089
b[26] 0.732 0.088 0.51 0.85 2556 3014 1.00 0.0018 0.002
b[27] 0.791 0.052 0.68 0.89 5865 3049 1.00 0.00068 0.00057
b[28] 0.827 0.047 0.74 0.92 2869 3136 1.00 0.00089 0.00065
b[29] 0.786 0.054 0.67 0.89 5441 2990 1.00 0.00073 0.00068

68 rows × 9 columns

4 Plots

def empty_plot(ax, label=""):
    ax.axis("off")
    ax.text(0.5, 0.5, label, ha="center", va="center", transform=ax.transAxes, fontsize=8)


def plot_dogs(ax, y):
    J, T = y.shape
    max_y_times = np.full(J, -1)
    for j in range(J):
        shock_times = np.where(y[j] == 1)[0]
        if len(shock_times) > 0:
            max_y_times[j] = shock_times.max()
    order = np.argsort(max_y_times)[::-1]
    y_ordered = y[order]
    ax.imshow(y_ordered, aspect="auto", cmap=shock_cmap, origin="lower", interpolation="nearest")
    ax.set(xticks=[], yticks=[])



def plot_ppc(axes_row, idata, label):
    J = dogs_data["J"]
    T = dogs_data["T"]
    empty_plot(axes_row[0], label)
    y_rep = az.extract(idata, group="posterior_predictive", num_samples=6, random_seed=rng)
    for ax_i, sample_i in enumerate(y_rep.sample.values):
        plot_dogs(axes_row[ax_i + 1], y_rep.sel(sample=sample_i))
J = dogs_data["J"]
T = dogs_data["T"]
_, axes = plt.subplots(7, 7, figsize=(10, 10))

empty_plot(axes[0, 0], "Real dogs")
plot_dogs(axes[0, 1], shock)
for j in range(2, 7):
    empty_plot(axes[0, j])

models = [dt_0, dt_1, dt_2, dt_3, dt_4, dt_5]
labels = [
    "PPsims from M0:\nlogit model",
    "PPsims from M1:\n1-parameter\nlog model",
    "PPsims from M2:\n2-parameter\nlog model",
    "PPsims from M3:\nhier 1-par\nlog model",
    "PPsims from M4:\nhier 2-par\nlog model",
    "PPsims from M5:\nhier 2-par\nlog model\nwith prior",
]

for row, (idata, label) in enumerate(zip(models, labels), start=1):
    plot_ppc(axes[row], idata, label)
Figure 1
fig, axes = plt.subplots(2, 5, figsize=(10, 4), sharex=True, sharey=True)

posterior = az.extract(dt_5, var_names=["a", "b"], num_samples=10, random_seed=SEED)


for ax, sample_i in zip(axes.flatten(), posterior.sample):
    a_sim = posterior["a"].sel(sample=sample_i)
    b_sim = posterior["b"].sel(sample=sample_i)
    ax.plot([0.55, 1], [0.55, 1], color="lightgray")
    ax.scatter(a_sim, b_sim)
    ax.set(xlim=(0.55, 1),
            ylim=(0.55, 1),
            aspect="equal",
            xticks=[0.6, 0.8, 1.0],
            yticks=[0.6, 0.8, 1.0])

for ax in axes[:,0]:
    ax.set_ylabel("b")
for ax in axes[1,:]:
    ax.set_xlabel("a")
fig.suptitle("10 posterior simulations of the parameters of the 30 dogs")
Text(0.5, 0.98, '10 posterior simulations of the parameters of the 30 dogs')
(a)
(b)
Figure 2
ab_median = az.median(dt_5, var_names=["a", "b"])

_, ax = plt.subplots(figsize=(4, 4))
ax.plot([0.55, 1], [0.55, 1], color="lightgray")
ax.scatter(ab_median["a"], ab_median["b"])
ax.set(xlim=(0.55, 1),
       ylim=(0.55, 1),
       aspect="equal",
       xticks=[0.6, 0.8, 1.0],
       yticks=[0.6, 0.8, 1.0],
       xlabel=r"$\hat{a}$",
       ylabel=r"$\hat{b}$")
ax.set_title("Posterior medians from fitted model", fontsize=11);
Figure 3
new_dogs_mu_logit_ab = np.array([2.4, 1.3])
new_dogs_sigma_ab = np.array([0.32, 0.40])
new_dogs_rho_ab = 0
new_dogs_Sigma_ab = (
    np.diag(new_dogs_sigma_ab)
    @ np.array([[1, new_dogs_rho_ab], [new_dogs_rho_ab, 1]])
    @ np.diag(new_dogs_sigma_ab)
)

J = 30
new_dogs_ab = expit(rng.multivariate_normal(new_dogs_mu_logit_ab, new_dogs_Sigma_ab, size=J))
a = new_dogs_ab[:, 0]
b = new_dogs_ab[:, 1]
T = 25
new_dogs = np.zeros((J, T), dtype=int)
for j in range(J):
    prev_shock = 0
    prev_avoid = 0
    new_dogs[j, 0] = 1
    for t in range(1, T):
        prev_shock += new_dogs[j, t - 1]
        prev_avoid += 1 - new_dogs[j, t - 1]
        p = a[j] ** prev_shock * b[j] ** prev_avoid
        new_dogs[j, t] = rng.binomial(1, p)

new_dogs_data = {"y": new_dogs, "J": J, "T": T}
new_fit_5 = dogs_5.sample(data=new_dogs_data, seed=SEED, show_progress=False)
new_dt_5 = az.from_cmdstanpy(new_fit_5)
az.summary(new_dt_5, var_names=["mu_logit_ab", "sigma_logit_ab", "Omega_logit_ab", "a", "b"])
mean sd eti95_lb eti95_ub ess_bulk ess_tail r_hat mcse_mean mcse_sd
mu_logit_ab[0] 2.486 0.197 2.1 2.9 3132 2734 1.00 0.0035 0.0025
mu_logit_ab[1] 1.169 0.153 0.86 1.5 2780 2878 1.00 0.0029 0.0023
sigma_logit_ab[0] 0.34 0.24 0.019 0.9 1131 2043 1.00 0.0071 0.0056
sigma_logit_ab[1] 0.4 0.21 0.031 0.83 846 1314 1.00 0.0071 0.0052
Omega_logit_ab[0, 0] 1 0 1 1 4000 4000 NaN 0 NaN
... ... ... ... ... ... ... ... ... ...
b[25] 0.741 0.065 0.59 0.84 3451 2709 1.00 0.0012 0.0011
b[26] 0.699 0.096 0.44 0.83 1739 3063 1.00 0.0022 0.0024
b[27] 0.722 0.085 0.51 0.84 3348 3374 1.00 0.0016 0.0017
b[28] 0.771 0.055 0.65 0.88 5884 3419 1.00 0.00071 0.00064
b[29] 0.795 0.053 0.69 0.9 3959 3064 1.00 0.00085 0.00069

68 rows × 9 columns

_, ax = plt.subplots(figsize=(4, 4))
ax.plot([0.55, 1], [0.55, 1], color="lightgray")
ax.scatter(a, b)
ax.set(xlim=(0.55, 1),
       ylim=(0.55, 1),
       xticks=[0.6, 0.8, 1.0],
       yticks=[0.6, 0.8, 1.0],
       aspect="equal",
       xlabel="a",
       ylabel="b")
ax.set_title("Simulated parameters");
Figure 4
_, ax = plt.subplots(figsize=(5, 4))
plot_dogs(ax, new_dogs)
ax.set_title("Simulated data");
Figure 5
new_dt = az.from_cmdstanpy(new_fit_5)
posterior = az.extract(new_dt, var_names=["a", "b"])
a_post = posterior["a"]
b_post = posterior["b"]

_, ax = plt.subplots(figsize=(4, 4))
ax.set(xlim=(0.55, 1),
       ylim=(0.55, 1),
       xticks=[0.6, 0.8, 1.0],
       yticks=[0.6, 0.8, 1.0],
       aspect="equal",
       xlabel="Posterior inference",
       ylabel="True parameter value")

ax.set_title("Calibration check of posterior intervals", fontsize=11)
ax.plot([0.55, 1], [0.55, 1], color="lightgray")


a_median = az.median(a_post, dim="sample")
quants = a_post.quantile([0.25, 0.75], dim="sample")

ax.scatter(a_median, a, s=10, color="C0")
ax.plot(quants, [a, a], color="C0", lw=0.5)


b_median = az.median(b_post, dim="sample")
quants = b_post.quantile([0.25, 0.75], dim="sample")

ax.scatter(b_median, b, s=10, color="C1")
ax.plot(quants, [b, b], color="C1", lw=0.5);
Figure 6
J = 300
new_dogs_ab = expit(rng.multivariate_normal(new_dogs_mu_logit_ab, new_dogs_Sigma_ab, size=J))
a = new_dogs_ab[:, 0]
b = new_dogs_ab[:, 1]
T = 25
new_dogs = np.zeros((J, T), dtype=int)
for j in range(J):
    prev_shock = 0
    prev_avoid = 0
    new_dogs[j, 0] = 1
    for t in range(1, T):
        prev_shock += new_dogs[j, t - 1]
        prev_avoid += 1 - new_dogs[j, t - 1]
        p = a[j] ** prev_shock * b[j] ** prev_avoid
        new_dogs[j, t] = rng.binomial(1, p)

new_dogs_data = {"y": new_dogs, "J": J, "T": T}
new_fit_5 = dogs_5.sample(data=new_dogs_data, seed=SEED, show_progress=False)
new_dt_5 = az.from_cmdstanpy(new_fit_5)
az.summary(new_dt_5, var_names=["mu_logit_ab", "sigma_logit_ab", "Omega_logit_ab", "a", "b"])
mean sd eti95_lb eti95_ub ess_bulk ess_tail r_hat mcse_mean mcse_sd
mu_logit_ab[0] 2.512 0.059 2.4 2.6 2291 2657 1.00 0.0012 0.00087
mu_logit_ab[1] 1.266 0.044 1.2 1.4 1884 2190 1.00 0.001 0.0007
sigma_logit_ab[0] 0.27 0.129 0.016 0.48 317 845 1.01 0.0072 0.0042
sigma_logit_ab[1] 0.288 0.071 0.13 0.42 733 659 1.00 0.0027 0.0022
Omega_logit_ab[0, 0] 1 0 1 1 4000 4000 NaN 0 NaN
... ... ... ... ... ... ... ... ... ...
b[295] 0.766 0.046 0.66 0.84 6502 3013 1.00 0.00058 0.00057
b[296] 0.766 0.042 0.67 0.84 4919 2392 1.00 0.00059 0.0005
b[297] 0.77 0.043 0.67 0.84 6544 2756 1.00 0.00054 0.00047
b[298] 0.745 0.05 0.63 0.83 3089 2776 1.00 0.00094 0.00083
b[299] 0.808 0.0342 0.74 0.87 5370 3101 1.00 0.00047 0.00034

608 rows × 9 columns

T = 50
new_dogs = np.zeros((J, T), dtype=int)
for j in range(J):
    prev_shock = 0
    prev_avoid = 0
    new_dogs[j, 0] = 1
    for t in range(1, T):
        prev_shock += new_dogs[j, t - 1]
        prev_avoid += 1 - new_dogs[j, t - 1]
        p = a[j] ** prev_shock * b[j] ** prev_avoid
        new_dogs[j, t] = rng.binomial(1, p)

new_dogs_data = {"y": new_dogs, "J": J, "T": T}
new_fit_5 = dogs_5.sample(data=new_dogs_data, seed=SEED, show_progress=False)
new_dt_5 = az.from_cmdstanpy(new_fit_5)
az.summary(new_dt_5, var_names=["mu_logit_ab", "sigma_logit_ab", "Omega_logit_ab", "a", "b"])
mean sd eti95_lb eti95_ub ess_bulk ess_tail r_hat mcse_mean mcse_sd
mu_logit_ab[0] 2.347 0.058 2.2 2.5 2244 3163 1.00 0.0012 0.00088
mu_logit_ab[1] 1.339 0.047 1.2 1.4 1636 2238 1.00 0.0012 0.00081
sigma_logit_ab[0] 0.34 0.084 0.15 0.49 525 436 1.00 0.0039 0.0032
sigma_logit_ab[1] 0.456 0.057 0.36 0.58 1402 2046 1.00 0.0015 0.0011
Omega_logit_ab[0, 0] 1 0 1 1 4000 4000 NaN 0 NaN
... ... ... ... ... ... ... ... ... ...
b[295] 0.791 0.055 0.67 0.88 5407 3202 1.00 0.00075 0.00058
b[296] 0.754 0.069 0.6 0.87 6049 2558 1.00 0.0009 0.00072
b[297] 0.734 0.073 0.57 0.86 5146 2920 1.00 0.0011 0.00082
b[298] 0.69 0.08 0.5 0.83 5661 2843 1.00 0.0011 0.00094
b[299] 0.76 0.067 0.61 0.87 5372 2685 1.00 0.00094 0.0008

608 rows × 9 columns

_, ax = plt.subplots(figsize=(5, 4))
plot_dogs(ax, new_dogs)
ax.set_title("Simulated data:  50 trials");
Figure 7
new_dt = az.from_cmdstanpy(new_fit_5)
posterior = az.extract(new_dt, var_names=["a", "b"])
a_post = posterior["a"]
b_post = posterior["b"]

_, ax = plt.subplots(figsize=(4, 4))
ax.set(xlim=(0.55, 1),
       ylim=(0.55, 1),
       xticks=[0.6, 0.8, 1.0],
       yticks=[0.6, 0.8, 1.0],
       aspect="equal",
       xlabel="Posterior inference",
       ylabel="True parameter value")

ax.set_title("Calibration check of posterior intervals", fontsize=11)
ax.plot([0.55, 1], [0.55, 1], color="lightgray")


a_median = az.median(a_post, dim="sample")
quants = a_post.quantile([0.25, 0.75], dim="sample")

ax.scatter(a_median, a, s=10, color="C0")
ax.plot(quants, [a, a], color="C0", lw=0.5)


b_median = az.median(b_post, dim="sample")
quants = b_post.quantile([0.25, 0.75], dim="sample")

ax.scatter(b_median, b, s=10, color="C1")
ax.plot(quants, [b, b], color="C1", lw=0.5);
Figure 8

References

Bush, R. R., and F. Mosteller. 1955. Stochastic Models for Learning. Wiley.

Licenses

  • Code © 2023–2025, Andrew Gelman, licensed under BSD-3.
  • Text © 2023–2025, Andrew Gelman, licensed under CC-BY-NC 4.0.