| 12 | 24 | 36 | 48 | 60 | 72 | 84 | 96 | 108 | 120 | |
|---|---|---|---|---|---|---|---|---|---|---|
| 1988 | 41,821 | 76,550 | 96,697 | 112,662 | 123,947 | 129,871 | 134,646 | 138,388 | 141,823 | 144,781 |
| 1989 | 48,167 | 87,662 | 112,106 | 130,284 | 141,124 | 148,503 | 154,186 | 158,944 | 162,903 | |
| 1990 | 52,058 | 99,517 | 126,876 | 144,792 | 156,240 | 165,086 | 170,955 | 176,346 | ||
| 1991 | 57,251 | 106,761 | 133,797 | 154,668 | 168,972 | 179,524 | 187,266 | |||
| 1992 | 59,213 | 113,342 | 142,908 | 165,392 | 179,506 | 189,506 | ||||
| 1993 | 59,475 | 111,551 | 138,387 | 160,719 | 175,475 | |||||
| 1994 | 65,607 | 110,255 | 137,317 | 159,972 | ||||||
| 1995 | 56,748 | 96,063 | 122,811 | |||||||
| 1996 | 52,212 | 92,242 | ||||||||
| 1997 | 43,962 |
This post rebuilds the original Bayesian Chain Ladder analysis with a Python-first workflow. Instead of rstanarm, we will:
- pull the same loss triangle from the CAS Loss Reserve Database via the Python
chainladderpackage, - fit the cross-classified negative binomial GLM with Bambi on top of PyMC, and
- rely on ArviZ for posterior summaries and diagnostics.
Throughout the post we reuse the R outputs from the prior article so you can see how the Python results line up without needing to re-run the original code.
Data: matching the original triangle
The latest diagonal of this triangle sums to the same $1,310,483 observed in the R output. The original chain ladder estimate produced an IBNR of $373,432 with a 3.1% coefficient of variation, while the R-based Bayesian fit landed at an IBNR of $375,090 and a 3.2% CV. We’ll keep those figures in mind as we work through the Python model.
Preparing incremental data for Bambi
Bambi expects a long-format dataset. We convert the cumulative triangle to increments, reshape to long form, and flag which cells belong to the upper-right portion of the triangle that require prediction.
| accident_year | dev | incremental | |
|---|---|---|---|
| 0 | 1988 | 12 | 41821.0 |
| 1 | 1989 | 12 | 48167.0 |
| 2 | 1990 | 12 | 52058.0 |
| 3 | 1991 | 12 | 57251.0 |
| 4 | 1992 | 12 | 59213.0 |
Fitting the chain ladder GLM with Bambi
We fit a negative binomial model with an intercept plus categorical accident-year and development effects—the same structure used in the original post. To keep run times manageable we use two chains and 1,000 draws per chain.
| mean | sd | hdi_3% | hdi_97% | mcse_mean | mcse_sd | ess_bulk | ess_tail | r_hat | |
|---|---|---|---|---|---|---|---|---|---|
| Intercept | 10.648 | 0.035 | 10.586 | 10.717 | 0.002 | 0.001 | 414.618 | 572.850 | 1.004 |
| accident_year[1989] | 0.146 | 0.033 | 0.085 | 0.206 | 0.001 | 0.001 | 842.756 | 944.160 | 0.999 |
| accident_year[1990] | 0.242 | 0.036 | 0.172 | 0.309 | 0.001 | 0.001 | 711.267 | 719.834 | 1.000 |
| accident_year[1991] | 0.370 | 0.038 | 0.299 | 0.442 | 0.001 | 0.001 | 784.555 | 909.125 | 1.002 |
| accident_year[1992] | 0.391 | 0.039 | 0.322 | 0.467 | 0.001 | 0.001 | 798.052 | 874.145 | 1.005 |
| accident_year[1993] | 0.371 | 0.043 | 0.291 | 0.455 | 0.001 | 0.001 | 854.046 | 929.378 | 1.003 |
| accident_year[1994] | 0.356 | 0.045 | 0.276 | 0.445 | 0.002 | 0.001 | 716.792 | 933.953 | 1.001 |
| accident_year[1995] | 0.246 | 0.051 | 0.154 | 0.344 | 0.002 | 0.001 | 1042.878 | 1114.721 | 1.000 |
| accident_year[1996] | 0.186 | 0.059 | 0.076 | 0.299 | 0.002 | 0.001 | 1200.642 | 1213.755 | 1.002 |
| accident_year[1997] | 0.047 | 0.082 | -0.096 | 0.209 | 0.002 | 0.002 | 1323.893 | 1142.059 | 1.002 |
| dev[24] | -0.206 | 0.034 | -0.266 | -0.137 | 0.001 | 0.001 | 871.752 | 1082.866 | 1.004 |
| dev[36] | -0.745 | 0.034 | -0.808 | -0.678 | 0.001 | 0.001 | 723.484 | 1139.556 | 1.003 |
| dev[48] | -1.016 | 0.038 | -1.086 | -0.945 | 0.001 | 0.001 | 770.665 | 1189.792 | 1.003 |
| dev[60] | -1.450 | 0.038 | -1.520 | -1.378 | 0.001 | 0.001 | 716.672 | 1178.781 | 1.002 |
| dev[72] | -1.842 | 0.043 | -1.922 | -1.762 | 0.002 | 0.001 | 798.927 | 1036.208 | 1.003 |
| dev[84] | -2.147 | 0.046 | -2.230 | -2.061 | 0.001 | 0.001 | 1011.148 | 1332.764 | 1.001 |
| dev[96] | -2.346 | 0.050 | -2.441 | -2.253 | 0.002 | 0.001 | 932.087 | 949.459 | 1.001 |
| dev[108] | -2.506 | 0.058 | -2.621 | -2.407 | 0.002 | 0.001 | 779.275 | 882.311 | 1.002 |
| dev[120] | -2.653 | 0.083 | -2.816 | -2.507 | 0.003 | 0.002 | 975.849 | 1131.176 | 1.001 |
ArviZ makes it straightforward to inspect the fitted effects. The posterior means of the development parameters mirror the R-based link ratios, reinforcing that we are estimating the same cross-classified GLM.
array([<Axes: title={'center': '94.0% HDI'}>], dtype=object)

Predicting the unobserved cells
We now draw from the posterior predictive distribution for the upper-right triangle. The resulting samples let us aggregate accident-year reserves and compare them to the R outputs.
| accident_year | mean | std | cv | latest_paid | |
|---|---|---|---|---|---|
| 0 | 1989 | 3446.2220 | 381.051175 | 0.110571 | 162903.0 |
| 1 | 1990 | 8175.3530 | 629.538458 | 0.077004 | 176346.0 |
| 2 | 1991 | 15139.8520 | 960.055843 | 0.063412 | 187266.0 |
| 3 | 1992 | 22740.5845 | 1245.752161 | 0.054781 | 189506.0 |
| 4 | 1993 | 31950.9575 | 1742.643281 | 0.054541 | 175475.0 |
| 5 | 1994 | 45607.7375 | 2380.651393 | 0.052198 | 159972.0 |
| 6 | 1995 | 60363.6955 | 3533.341381 | 0.058534 | 122811.0 |
| 7 | 1996 | 80967.9675 | 5270.660732 | 0.065096 | 92242.0 |
| 8 | 1997 | 106750.6195 | 9178.185181 | 0.085978 | 43962.0 |
| 9 | total | 375142.9890 | 12801.201699 | 0.034124 | 1455264.0 |
The Bambi-based estimate yields a total IBNR of roughly $375,000 with a 3.4% CV, landing within a few basis points of the R-based Bayesian fit ($375,090 IBNR and 3.2% CV) and only 0.4% above the traditional chain ladder estimate ($373,432 IBNR and 3.1% CV).
Posterior predictive check
To confirm the model is generating reasonable incremental values, we compare the observed increments to draws from the posterior predictive distribution for the observed cells.

The posterior predictive distribution comfortably envelopes the realized increments, suggesting the negative binomial GLM is flexible enough for this triangle while staying consistent with the earlier R-based results.