Skip to main content

Credibility models for UK non-life insurance pricing: classical Bühlmann-Straub and individual-policy Bayesian experience rating

Project description

insurance-credibility

Bühlmann-Straub credibility and Bayesian experience rating for UK insurance pricing teams.

PyPI Python License


The problem

Small segments have unstable loss experience. A fleet scheme with 200 vehicle-years has a loss ratio that is mostly noise, but ignoring it entirely prices a segment you have genuine data on. How much should you trust the scheme's own history versus the portfolio average?

The same question arises at individual policy level: a commercial motor policy with 5 years of no-claims history deserves a discount, but how large? Flat NCD tables assign the same maximum discount regardless of policy size or the underlying claim frequency — a 0.5-vehicle-year policy gets the same credit as a 50-vehicle-year fleet.

Blog post: Bühlmann-Straub Credibility in Python: Blending Thin Segments with Portfolio Experience


Why this library?

Bühlmann-Straub is the actuarial standard for this problem — a statistically optimal blend of segment experience with the portfolio mean, weighted by earned exposure. Most existing implementations assume non-insurance data structures: equal group sizes, no exposure weights, no distinction between within-group and between-group variance.

This library is built for insurance: it handles unequal exposures, nested hierarchies (scheme → book, district → area), and individual policy experience rating in a consistent framework.


Compared to alternatives

Manual credibility weights Random effects GLM Hierarchical Bayes insurance-credibility
Statistically optimal blend No (rule-of-thumb) Yes Yes Yes (B-S formula)
No prior specification needed Yes Yes No Yes
Handles unequal exposures Manual Yes Yes Yes
Nested group hierarchies Manual Partial Yes Yes (HierarchicalBuhlmannStraub)
Individual policy experience rating No No Partial Yes
Closed-form, < 1 second Yes (simple) No No Yes
Full posterior distribution No No Yes Yes (DynamicPoissonGammaModel)

Quickstart

uv add insurance-credibility
import polars as pl
from insurance_credibility import BuhlmannStraub

df = pl.DataFrame({
    "scheme":    ["A", "A", "A", "B", "B", "B", "C", "C", "C"],
    "year":      [2022, 2023, 2024, 2022, 2023, 2024, 2022, 2023, 2024],
    "loss_rate": [0.12, 0.09, 0.11, 0.25, 0.28, 0.22, 0.08, 0.07, 0.09],
    "exposure":  [120.0, 135.0, 140.0, 45.0, 50.0, 48.0, 300.0, 310.0, 320.0],
})

bs = BuhlmannStraub()
bs.fit(df, group_col="scheme", period_col="year",
       loss_col="loss_rate", weight_col="exposure")

print(bs.z_)         # credibility factors per scheme
print(bs.k_)         # Bühlmann's k: noise-to-signal ratio
print(bs.premiums_)  # credibility-blended premium per scheme

Group credibility: schemes and large accounts

BuhlmannStraub fits structural parameters — within-group variance (v) and between-group variance (a) — from the portfolio using method of moments. It then computes the credibility factor Z_i for each group:

Z_i = w_i / (w_i + k)    where k = v/a

Z approaches 1.0 as exposure grows — thick schemes are trusted almost entirely. Z shrinks toward 0 on thin schemes — the portfolio mean gets most of the weight. On a 30-scheme, 5-year benchmark with known true parameters:

Tier Raw MAE Portfolio avg MAE Credibility MAE
Thin (< 500 exposure) 0.0074 0.0596 0.0069
Medium (500–2000) 0.0030 0.0423 0.0029
Thick (2000+) 0.0014 0.0337 0.0014 (tie)

Credibility beats raw experience on thin and medium tiers. On thick tiers, Z approaches 1.0 and the two methods converge — which is correct behaviour.

HierarchicalBuhlmannStraub extends this to nested group structures: scheme → book, sector → district → area. Following Jewell (1975).


Individual policy experience rating

For commercial motor and fleet pricing, where you want to move individual policies away from the GLM rate based on their own claims history:

from insurance_credibility import ClaimsHistory, StaticCredibilityModel

histories = [
    ClaimsHistory("POL001", periods=[1, 2, 3], claim_counts=[0, 1, 0],
                  exposures=[1.0, 1.0, 0.8], prior_premium=400.0),
    ClaimsHistory("POL002", periods=[1, 2, 3], claim_counts=[2, 1, 2],
                  exposures=[1.0, 1.0, 1.0], prior_premium=400.0),
]

model = StaticCredibilityModel()
model.fit(histories)

cf = model.predict(histories[0])
posterior_premium = histories[0].prior_premium * cf

exposures is the key parameter that distinguishes this from flat NCD tables: a policy with 0.5 years of exposure gets far less credibility than one with 5 years, regardless of claim count.


Model tiers

StaticCredibilityModel — Bühlmann-Straub at individual policy level. Fits kappa = sigma^2 / tau^2 from a portfolio of policy histories. Credibility weight for a policy is omega = e_total / (e_total + kappa). Closed-form, fast, suitable for production.

DynamicPoissonGammaModel — Poisson-gamma state-space model following Ahn, Jeong, Lu & Wüthrich (2023). Seniority-weighted updates: recent years count more. Produces the full posterior distribution per policy, not just a point estimate — useful when communicating uncertainty to a pricing committee or reinsurer.

SurrogateModel — IS-surrogate (Calcetero et al. 2024). For large portfolios where computing the exact posterior for every policy is expensive.


Structural parameter recovery

On a 30-group, 5-year benchmark with known true parameters (mu=0.650, v=0.020, a=0.005, k=4.0):

  • mu recovered within 1.4%
  • k recovered within factor of 2 (conservative shrinkage direction)

k is over-estimated in small samples — a known property of the method-of-moments estimator. Conservative shrinkage is safe: it means you trust thin segments slightly less than the theory would dictate. On portfolios with 100+ groups over 7+ years, k converges to the true value.

Full validation: notebooks/databricks_validation.py.


Bühlmann-Straub vs random effects GLM

The actuarial credibility approach and the random effects GLM (e.g. statsmodels MixedLM) estimate the same quantity under a Gaussian approximation. The differences are practical:

  • Bühlmann-Straub is closed-form and fits in under a second on a 150-row scheme panel. No iteration, no convergence issues.
  • Random effects GLM requires a correctly specified likelihood and converges slowly on unbalanced panels with many groups.
  • Bühlmann-Straub exposes the structural parameters (mu, v, a, k) directly, making them easy to inspect and challenge.

For Poisson-Gamma likelihoods and non-Gaussian random effects, use DynamicPoissonGammaModel.


Limitations

  • Structural parameter estimation (v, a) requires at least 30–50 groups and 3+ years to converge reliably. On the 30-group benchmark, VHM was underestimated by 57.6%. In thin portfolios, treat credibility factors as directional and apply a floor on Z.
  • StaticCredibilityModel assumes homoscedastic within-policy variance. Segment by policy size tier on portfolios with large fleets alongside small ones.
  • Kappa estimation needs at least 50–100 policies with 2+ years of history. Below this, the estimate is unreliable.
  • Structural parameters must be refitted as portfolio composition changes. Stale kappa from a different historical book produces miscalibrated experience adjustments.

Part of the Burning Cost stack

Takes segment-level experience data: earned exposure, observed loss ratios, scheme panels. Feeds credibility-weighted estimates into insurance-gam (as adjusted targets for tariff fitting). See the full stack

Library Description
insurance-whittaker Whittaker-Henderson smoothing — smooths the raw experience rates that credibility weighting then blends
insurance-gam Interpretable GAMs — credibility-adjusted targets as input to tariff fitting
insurance-conformal Distribution-free prediction intervals — uncertainty quantification for credibility-blended estimates
insurance-monitoring Model drift detection — monitors whether credibility parameters remain valid
insurance-governance Model validation and MRM governance — sign-off pack for credibility models

References

  • Bühlmann, H. & Gisler, A. (2005). A Course in Credibility Theory and Its Applications. Springer.
  • Jewell, W.S. (1975). "Multidimensional Credibility." Operations Research, 23(5), 904–920.
  • Ahn, J.Y., Jeong, H., Lu, Y. & Wüthrich, M.V. (2023). "Dynamic Bayesian Credibility." arXiv:2308.16058.
  • Calcetero, V., Badescu, A. & Lin, X.S. (2024). "Credibility theory for the 21st century." ASTIN Bulletin.

Community

Licence

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

insurance_credibility-0.1.8.tar.gz (446.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

insurance_credibility-0.1.8-py3-none-any.whl (46.4 kB view details)

Uploaded Python 3

File details

Details for the file insurance_credibility-0.1.8.tar.gz.

File metadata

  • Download URL: insurance_credibility-0.1.8.tar.gz
  • Upload date:
  • Size: 446.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for insurance_credibility-0.1.8.tar.gz
Algorithm Hash digest
SHA256 1dda10594a143307da4fea5a80d3b753c5d061b5c631a0ddaa7e8233ab23268d
MD5 14bbf02627239ca1283a74c5330c0aeb
BLAKE2b-256 e4eface2e012682eec1c4607d4c44756a5e587f6989e2c4377d665c114af0d39

See more details on using hashes here.

File details

Details for the file insurance_credibility-0.1.8-py3-none-any.whl.

File metadata

  • Download URL: insurance_credibility-0.1.8-py3-none-any.whl
  • Upload date:
  • Size: 46.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for insurance_credibility-0.1.8-py3-none-any.whl
Algorithm Hash digest
SHA256 ee355491265fc22078a7fcb400d58826951a887f69d4d797126d28f4ec18192c
MD5 93c60978f2b61a624c49347956b06f2f
BLAKE2b-256 7e96cf1a9e9598b678fdec9d68162f2b918807554134562e58b758b12506e280

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page