Vine copulas for multi-peril home insurance pricing — exposure-weighted dependence modelling
Project description
insurance-copula
Vine copulas for multi-peril home insurance pricing.
The problem
UK home insurance portfolios have a pricing methodology problem. Perils — flood, subsidence, storm, escape of water, fire, theft — are typically modelled independently. Each peril gets its own frequency/severity model, and the components are added up. This is tractable and auditable, but it ignores the fact that these perils are correlated.
Flood and escape of water spike together in wet winters. Storm and flood co-move. Subsidence clusters with drought. When multiple perils hit at once, the joint loss is larger than the sum of independent expectations. If you price assuming independence, you systematically underprice the scenarios that matter most for reserving and capital.
The standard actuarial fix is copulas. The challenge is that vine copulas — the right tool for multi-dimensional loss dependence — have a steep API and require insurance-specific wrapping to be useful: exposure weighting, pseudo-observation transforms, marginal back-transformation, and aggregate loss simulation for capital modelling.
This library does that wrapping.
What it does
- Fits a vine copula to multi-peril loss data using
pyvinecopulib - Supports exposure-weighted fitting (weight by days at risk or earned premium)
- Simulates from the fitted copula in both uniform and loss space
- Estimates conditional expectations: E[flood | storm > threshold]
- Simulates aggregate portfolio loss distributions for PML/SCR calculation
- Serialises fitted models to JSON for production deployment
Installation
pip install insurance-copula[vine]
The [vine] extra installs pyvinecopulib. Pre-built wheels are available for x86_64 Linux, macOS, and Windows. ARM64 Linux requires building from source.
Quick start
from insurance_copula import PerilVine
from scipy.stats import gamma
# Fit the vine
vine = PerilVine(perils=['flood', 'subsidence', 'storm', 'eow'])
vine.fit(losses_df, exposure=earned_premium_series)
# Simulate losses
marginals = {
'flood': gamma(a=0.8, scale=5_000),
'subsidence': gamma(a=0.6, scale=3_000),
'storm': gamma(a=1.2, scale=800),
'eow': gamma(a=1.5, scale=600),
}
samples = vine.simulate(10_000, marginals=marginals, seed=42)
# Portfolio aggregate loss (Solvency II SCR)
result = vine.aggregate_loss(n_sim=10_000, marginals=marginals, n_policies=50_000, seed=42)
print(f"PML 1-in-200: £{result.pml_1_in_200:,.0f}")
# Conditional expectation
flood_given_storm = vine.conditional_expectation(
target_peril='flood',
condition_peril='storm',
threshold=marginals['storm'].ppf(0.9),
marginals=marginals,
)
# Model diagnostics
diag = vine.diagnostics()
print(diag.kendall_tau_matrix)
# Serialise for production
json_str = vine.to_json()
restored = PerilVine.from_json(json_str)
Design choices
pyvinecopulib over statsmodels or copulae: C++ backend, proper vine structure selection, exposure weights natively supported. The alternatives are either slower or don't support R-vine structure selection.
Nonparametric PIT by default: We use empirical CDF ranks rather than assuming a parametric marginal family. This means the copula fit is robust to heavy tails in the loss data. The marginals are specified separately when you want loss-space simulation.
trunc_lvl=3 default: For 4-6 perils, a full vine overfits on typical insurance datasets (a few hundred observations). Truncation at tree 3 keeps the most important pair dependencies without fitting noise in the higher-order conditional structures.
Accept-reject for conditional expectation: Simpler than analytical conditioning via D-vine inverse Rosenblatt, works for any vine structure, and gives accurate results when n_samples is large enough. We warn when fewer than 500 samples pass the filter.
Notebook demo
See notebooks/demo_multi_peril_vine.ipynb for a full worked example on synthetic UK home insurance data.
Requirements
- Python >= 3.10
- pyvinecopulib >= 0.7 (x86_64 only; ARM64 requires source build)
- scipy, numpy, pandas, matplotlib
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file insurance_copula-0.1.0.tar.gz.
File metadata
- Download URL: insurance_copula-0.1.0.tar.gz
- Upload date:
- Size: 133.8 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
649c390aa09a0d26e23333f6819f7b303f19612f61db6b698e46e1406646447e
|
|
| MD5 |
56b9c5601ba69076380c30ef48a37695
|
|
| BLAKE2b-256 |
b8976376951c6bc8b44c26148b1960a902eedde277c0e2aa7f164beac10fa0b5
|
File details
Details for the file insurance_copula-0.1.0-py3-none-any.whl.
File metadata
- Download URL: insurance_copula-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6e150b9d36b335d8c578d53aa360fb216ca2be915cfaa1a15cc4028dbef0b7ff
|
|
| MD5 |
04e3cf78ea23010294c1fb5ce311355a
|
|
| BLAKE2b-256 |
b6d731eb67890d43649e6ee2999bb5fbe2c05dc7ad5fe48449ac8fdca29245b0
|