Skip to main content

insurance-competing-risks

Fine-Gray subdistribution hazard regression for competing risks — built for insurance pricing.

The problem

When a policy can exit in more than one way, standard survival models are wrong.

A motor policy that lapses cannot also generate a mid-term cancellation. A house that burns cannot also flood. Once one event happens, the others are permanently prevented. These are competing risks, and they require a different statistical framework.

The standard fix — fitting a separate Cox model per cause and treating the other causes as censored — answers the wrong question. It tells you how the hazard rate among currently-at-risk subjects changes with covariates. It does not tell you how the probability of a specific exit route changes. For pricing, underwriting, and retention analysis, you almost always want the probability.

Fine and Gray (1999) solved this. Their subdistribution hazard model has a one-to-one correspondence with the Cumulative Incidence Function (CIF): the probability that cause k occurs before time t, given covariates. Fit a Fine-Gray model, and you can directly predict "what is the probability this customer lapses within 12 months?" while properly accounting for mid-term cancellation and claim-driven churn as competing events.

The gap this fills

No pure-Python, pip-installable library provides Fine-Gray regression:

  • lifelines: has Aalen-Johansen CIF, no Fine-Gray regression
  • scikit-survival: non-parametric CIF from v0.24, no regression
  • hazardous: gradient-boosted CIF, no interpretable SHRs
  • cmprsk (Python): wraps R via rpy2, requires R runtime
  • pydts: discrete time only

insurance-competing-risks fills the gap with a pure NumPy/SciPy implementation.

Insurance use cases

Home insurance — competing perils: model time-to-first-claim where causes are fire, escape of water, flood, and subsidence. The Fine-Gray CIF gives the probability of each peril being the first reported, accounting for the fact that claiming flood prevents a separate subsidence claim on the same policy.

Retention analysis: a policy exits via lapse, mid-term cancellation (MTC), non-taken-up (NTU), or claim-driven churn. Fine-Gray on premium uplift and tenure directly estimates the lapse probability at renewal, properly accounting for competing exits.

Motor claims: first claim type (own damage, TPPD, TPBI, windscreen, theft) as competing events. Useful for understanding which perils drive early claims by risk segment.

Installation

pip install insurance-competing-risks

Quick start

from insurance_competing_risks import FineGrayFitter, AalenJohansenFitter
from insurance_competing_risks.datasets import simulate_insurance_retention

df = simulate_insurance_retention(n=1000, seed=0)

# 1. Non-parametric CIF: what is the marginal lapse probability over time?
aj = AalenJohansenFitter()
aj.fit(df["T"], df["E"], event_of_interest=1)
aj.plot()  # step plot with 95% confidence band

# 2. Regression: how does premium uplift affect lapse probability?
fg = FineGrayFitter()
fg.fit(
    df[["T", "E", "premium_uplift", "tenure_years", "ncd_years"]],
    duration_col="T",
    event_col="E",
    event_of_interest=1,  # lapse
)
print(fg.summary)  # SHR, 95% CI, p-value per covariate

# 3. Predict CIF for new customers
import numpy as np
times = np.array([0.25, 0.5, 1.0])  # policy years
cif = fg.predict_cumulative_incidence(df.head(5), times=times)
print(cif)  # shape (5, 3): probability of lapsing before each time

# 4. Partial effects: how does a 20% vs 5% premium uplift change lapse risk?
fg.plot_partial_effects_on_outcome("premium_uplift", values=[-0.05, 0.10, 0.30])

Modules

Module What it does
cif Aalen-Johansen non-parametric CIF estimator with confidence bands
fine_gray Fine-Gray regression: FineGrayFitter with lifelines-compatible API
gray_test Gray's K-sample test for CIF equality across groups
metrics IPCW Brier score, integrated Brier score, cause-specific C-index, calibration curves
datasets Bone marrow transplant benchmark; synthetic insurance retention data
plots Forest plot, stacked CIF, Brier score over time

Fine-Gray: the key ideas

The subdistribution hazard for cause k is:

lambda_k(t) = -d/dt log(1 - F_k(t))

where F_k(t) is the CIF. This is modelled proportionally:

lambda_k(t | x) = lambda_k0(t) * exp(beta_k' x)

Because of the one-to-one relationship between the subdistribution hazard and the CIF, exp(beta_k) is the subdistribution hazard ratio (SHR). An SHR of 1.5 for premium uplift means the subdistribution hazard for lapse is 50% higher for each unit increase in premium uplift — which translates directly to a higher CIF (higher lapse probability), though not proportionally.

The key estimation challenge is the extended risk set: subjects who already experienced a competing event remain in the risk set (with downweighted IPCW weights), reflecting that they are still "at risk" of the cause-k event in the subdistribution sense. This is what makes Fine-Gray different from cause-specific Cox.

Model summary output

Fine-Gray Subdistribution Hazard Model
Event of interest: 1
Duration column: T
Event column: E
Log partial-likelihood: -487.3201

                coef  exp(coef)  se(coef)      z         p  lower_95%  upper_95%
covariate
premium_uplift  1.52       4.57      0.21   7.24  4.5e-13       1.11       1.93
tenure_years   -0.14       0.87      0.03  -4.81  1.5e-06      -0.20      -0.08
ncd_years      -0.05       0.95      0.02  -2.50  1.2e-02      -0.09      -0.01

Gray's test

Before fitting a regression model, test whether the CIFs differ between groups:

from insurance_competing_risks import gray_test

result = gray_test(df["T"], df["E"], df["rating_band"], event_of_interest=1)
print(result)
# Gray's 3-Sample CIF Test (cause 1)
#   chi^2 = 12.34  df = 2  p = 0.0021

Evaluation

from insurance_competing_risks.metrics import (
    competing_risks_brier_score,
    integrated_brier_score,
    competing_risks_c_index,
)

times = np.linspace(0.1, 2.0, 20)
cif_test = fg.predict_cumulative_incidence(test_df, times=times)

# Brier score at each time
bs = competing_risks_brier_score(
    cif_test, test_df["T"], test_df["E"],
    train_df["T"], train_df["E"],
    times, event_of_interest=1
)

# Integrated Brier Score
ibs = integrated_brier_score(
    cif_test, test_df["T"], test_df["E"],
    train_df["T"], train_df["E"],
    times, event_of_interest=1
)
print(f"IBS: {ibs:.4f}")  # lower is better; 0.25 = useless model

References

Fine, J.P. & Gray, R.J. (1999). A proportional hazards model for the subdistribution of a competing risk. Journal of the American Statistical Association, 94(446), 496–509.

Gray, R.J. (1988). A class of K-sample tests for comparing the cumulative incidence of a competing risk. Annals of Statistics, 16(3), 1141–1154.

Milhaud, X. & Dutang, C. (2018). Lapse tables for lapse risk management in insurance: a competing risk approach. European Actuarial Journal, 8(1), 97–126.

Putter, H., Fiocco, M. & Geskus, R.B. (2007). Tutorial in biostatistics: Competing risks and multi-state models. Statistics in Medicine, 26(11), 2389–2430.


Part of the Burning Cost insurance pricing library ecosystem.

Metadata

Release files for insurance-competing-risks 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for insurance-competing-risks 0.1.0
File Size Uploaded
insurance_competing_risks-0.1.0.tar.gz 34.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for insurance-competing-risks 0.1.0
File Interpreter ABI Platform
insurance_competing_risks-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 63.1 kB

Release files / insurance_competing_risks-0.1.0.tar.gz

Download URL insurance_competing_risks-0.1.0.tar.gz
Size 34.7 kB
Tags Source
SHA-256 checksum
How to use checksums
667bdd1045f98ffa1f4e7c6c02a9e6e215a4234551029e0aa362eaca9a2e0d9d
BLAKE2b-256 checksum
How to use checksums
7d9b7e69900373e89e0fc3a9970a7735c4a3ccd7933af63339e9af0514646d41
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release files / insurance_competing_risks-0.1.0-py3-none-any.whl

Download URL insurance_competing_risks-0.1.0-py3-none-any.whl
Size 28.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
82702143efb7c8a8fd8826e69540174571ff6667c7b70e6a9d06ff2ee6fe010d
BLAKE2b-256 checksum
How to use checksums
22e68473b66109983a7167c4ca200ffbc243507e4b63a22e078f2e187ab05c52
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page