Life contingencies and actuarial mathematics in Python, with dynamic life tables and mortality forecasting
Project description
pylifecontingencies
A native Python port of the R lifecontingencies package, extended with dynamic life tables and mortality-rate forecasting (Lee-Carter, CBD M5).
No R runtime required. Pure NumPy + pandas at install time; rpy2 is only used in the validation test suite.
What it includes
- Single-life actuarial present values: annuities, insurances, endowments, increasing/decreasing benefits, premiums, and reserves
- Mortality and demographic utilities:
pxt,qxt,mxt,Lxt,Tx,exn - Dynamic mortality forecasting: Lee-Carter, CBD M5, projected life tables, stochastic scenario support
- Monte Carlo PV simulation with full sample output via
StochasticResult - Parametric mortality graduation with
GompertzMakehamandHeligmanPollard - Bundled data: SOA ILT, BR-EMS tables, and R-sourced parquet tables such as
soa08,AM92Lt,AF92Lt,demoUsa, anddemoGermany
Installation
Core package:
pip install pylifecontingencies
With SOA XTbML support:
pip install "pylifecontingencies[soa]"
For local development and release checks:
pip install -e ".[dev]"
Quick examples
Static actuarial values
from pylifecontingencies import load_table, ActuarialTable, axn, Axn, AExn
lt = load_table("soa_ilt")
at = ActuarialTable(lt, interest=0.06)
axn(at, x=40) # whole-life annuity-due
Axn(at, x=40, n=20) # 20-year term insurance
AExn(at, x=40, n=20) # 20-year endowment insurance
Bundled R tables
from pylifecontingencies import load_table, list_columns
list_columns("demoUsa")
lt_usa = load_table("demoUsa", column="USSS2007M")
lt_soa = load_table("soa08")
BR-EMS tables
from pylifecontingencies import load_table, ActuarialTable, axn, qxt
# BR-EMS Sobrevivencia 2021, male
lt_br = load_table("br_emssb_2021_m")
at_br = ActuarialTable(lt_br, interest=0.03)
qxt(lt_br, x=40, t=10) # 10-year death probability at age 40
axn(at_br, x=65) # whole-life annuity-due at age 65
Stochastic simulation
from pylifecontingencies import simulate_pv
# Annual term insurance
r = simulate_pv(at, x=40, n=20, benefit="term", n_sim=50_000, random_state=42)
r.mean, r.std, r.quantile(0.95)
# Monthly annuity (k=12 payments per year)
r_monthly = simulate_pv(at, x=40, n=20, benefit="annuity", k=12, n_sim=50_000)
# 10-year deferred whole-life annuity (pension starting at age 50)
r_deferred = simulate_pv(at, x=40, benefit="annuity", m=10, n_sim=50_000)
# Export samples to pandas for custom analysis
df = r.to_dataframe() # DataFrame with column "pv"
df["pv"].describe()
Mortality-law fitting
import numpy as np
from pylifecontingencies import available_mortality_laws, fit_mortality_law
fit = fit_mortality_law(lt, "gompertz_makeham", ages=np.arange(40, 90))
fit.params_dict, fit.rmse
available_mortality_laws()
Development and release
GitHub Actions workflows live under .github/workflows/:
ci.ymlruns the main pytest matrix on Python 3.10, 3.11, and 3.12, plus coverage and distribution builds.r-parity.ymlinstalls R +lifecontingenciesand runs the parity tests that compare Python results against the R package.publish.ymlbuilds and publishes to PyPI on tags matchingv*after the test suite passes.
For publishing, configure GitHub Trusted Publishing for the PyPI project, then create a version tag such as:
git tag v0.1.0
git push origin v0.1.0
Quick start — static life table
from pylifecontingencies import load_table, ActuarialTable
from pylifecontingencies import axn, Axn, Exn, AExn, IAxn, DAxn, exn
# Load a bundled table
lt = load_table("soa_ilt") # SOA Illustrative Life Table
at = ActuarialTable(lt, interest=0.06)
# Whole-life annuity-due at age 40
axn(at, x=40) # ä_40
# 20-year term insurance
Axn(at, x=40, n=20) # A^1_{40:20|}
# 20-year endowment insurance
AExn(at, x=40, n=20) # A_{40:20|}
# 20-year pure endowment
Exn(at, x=40, n=20) # _20 E_40
# Increasing whole-life insurance
IAxn(at, x=40) # (IA)_40
# 20-year decreasing term
DAxn(at, x=40, n=20) # (DA)^1_{40:20|}
# Curtate future lifetime expectation
exn(at, x=40) # e_40
Semi-annual payments (k=2)
axn(at, x=40, n=20, k=2) # ä^(2)_{40:20|} via UDD
Axn(at, x=40, n=20, k=2) # A^(2)_{40:20|} via UDD
Quick start — dynamic life tables
from pylifecontingencies.dynamic import MortalityRates, LeeCarter, ProjectedLifeTable
# Build a rates surface from a DataFrame (ages as index, years as columns, values = log(mx))
rates = MortalityRates.from_dataframe(df_log_mx)
# Fit Lee-Carter
lc = LeeCarter().fit(rates)
print(lc.ax) # age-specific levels
print(lc.bx) # age-specific sensitivities
print(lc.kt) # period index
# Forecast 50 years ahead with 95% bootstrap prediction interval
forecast = lc.forecast(horizon=50, n_bootstrap=500, ci=0.95)
# Build a cohort life table for someone born in 1985
cohort_lt = ProjectedLifeTable(forecast, birth_year=1985).to_life_table()
at_cohort = ActuarialTable(cohort_lt, interest=0.03)
axn(at_cohort, x=40) # cohort-true annuity at 40
Stochastic PV simulation
from pylifecontingencies import load_table, ActuarialTable, simulate_pv
lt = load_table("soa_ilt")
at = ActuarialTable(lt, interest=0.03)
# Annual term insurance — PV distribution
r = simulate_pv(at, x=40, n=20, benefit="term", n_sim=50_000, random_state=42)
print(r.mean, r.std)
print(r.quantile(0.95))
# Monthly annuity (k=12 payments per year, UDD)
r_monthly = simulate_pv(at, x=40, n=20, benefit="annuity", k=12, n_sim=50_000, random_state=42)
# 10-year deferred whole-life annuity (pension, m=10 deferral)
r_deferred = simulate_pv(at, x=40, benefit="annuity", m=10, n_sim=50_000, random_state=42)
# Combine: monthly pension starting in 10 years
r_pension = simulate_pv(at, x=40, n=20, benefit="annuity", k=12, m=10, n_sim=50_000)
# Export to pandas for custom analysis
df = r.to_dataframe() # DataFrame with column "pv"
df["pv"].hist(bins=30)
df["pv"].describe()
# Convenience method on the table object
r_ann = at.simulate_pv(x=40, n=20, benefit="annuity", k=12, n_sim=10_000)
Supported benefit types: term, whole / whole_life, annuity,
pure_endowment, endowment, increasing, decreasing.
k > 1 (fractional payments) and m > 0 (deferral) are supported for annuity.
m > 0 is also supported for all insurance benefit types.
Mortality-law graduation
import numpy as np
from pylifecontingencies import (
available_mortality_laws,
load_table,
fit_mortality_law,
GompertzMakeham,
HeligmanPollard,
)
lt = load_table("soa_ilt")
# String dispatch
gm_fit = fit_mortality_law(lt, "gompertz_makeham", ages=np.arange(40, 90))
print(gm_fit.params_dict)
print(gm_fit.rmse, gm_fit.aic)
# Explicit law object
hp_fit = fit_mortality_law(lt, HeligmanPollard(), ages=np.arange(1, 90))
df_fit = hp_fit.to_dataframe()
available_mortality_laws()
MortalityLawFit stores fitted parameters, observed/fitted q_x, residuals,
and goodness-of-fit metrics (loglik, AIC, BIC, RMSE, MAE). The current
implementation is Python-only. R packages can be used as inspiration for future
models, but they are not required at runtime.
from pylifecontingencies import available_mortality_laws
available_mortality_laws()
Interest-rate utilities
from pylifecontingencies import InterestRate
ir = InterestRate(i=0.05)
ir.v # 0.952... discount factor
ir.delta # 0.04879... force of interest
ir.d # 0.04762... annual discount rate
ir.i_m(12) # monthly nominal rate
ir.d_m(12) # monthly nominal discount rate
InterestRate.from_delta(0.05) # from force of interest
InterestRate.from_discount(0.04) # from annual discount rate
Demographic functions
from pylifecontingencies import pxt, qxt, dxt, mxt, Lxt, Tx
lt = load_table("soa_ilt")
pxt(lt, x=40, t=10) # _10 p_40
qxt(lt, x=40, t=10) # _10 q_40
exn(lt, x=40) # e_40 (curtate)
Bundled tables
| Name | Description |
|---|---|
soa_ilt |
SOA Illustrative Life Table (Bowers et al., ages 0–99) |
R-sourced parquet tables are also bundled, including soa08, AM92Lt,
AF92Lt, demoUsa, demoUk, demoFrance, demoIta, demoGermany,
demoJapan, demoChina, and demoCanada.
Multi-column R tables
Some bundled parquet datasets contain several sub-tables in one file. Use
list_columns(name) to inspect the available columns and load_table(..., column=...)
to select one:
from pylifecontingencies import load_table, list_columns
list_columns("demoUsa")
# ['USSS2007M', 'USSS2007F', 'USSS2000M', 'USSS2000F', 'USSS1990M', 'USSS1990F']
lt_usa = load_table("demoUsa", column="USSS2007M")
lt_ger = load_table("demoGermany", column="qxMale")
Single-column parquet tables such as soa08, AM92Lt, and AF92Lt can be loaded
directly:
lt = load_table("soa08")
Additional tables (AM92, AF92, demoUsa, etc.) can be imported from R using the provided conversion script:
# requires R + lifecontingencies + rpy2 + pyarrow
python scripts/convert_rda_to_parquet.py
Comparison with R lifecontingencies
| R | Python |
|---|---|
axn(at, x=40, n=20) |
axn(at, x=40, n=20) |
Axn(at, x=40, n=20) |
Axn(at, x=40, n=20) |
Exn(at, x=40, n=20) |
Exn(at, x=40, n=20) |
exn(lt, x=40) |
exn(lt, x=40) |
pxt(lt, x=40, t=5) |
pxt(lt, x=40, t=5) |
new("lifetable", x=..., lx=..., name=...) |
LifeTable(lx, x_min=0, name=...) |
new("actuarialtable", ..., interest=0.06) |
ActuarialTable(lt, interest=0.06) |
Scope and roadmap
Current: Single-life EPVs, stochastic PV simulation (k-thly payments, deferral, pandas export), mortality-law fitters, interest-rate utilities, demographic functions, bundled tables, Lee-Carter and CBD M5 mortality forecasting.
Planned next: Multi-decrement tables, Renshaw-Haberman and APC forecasting models, and broader stochastic simulation equivalents to rLifeContingencies.
Release status
If this is the first public PyPI release, 0.1.0 is still a sensible version:
the package already has a meaningful usable feature set, but the roadmap still
has planned actuarial and forecasting extensions. If you want to signal “first
public release after internal polishing”, keep 0.1.0. If 0.1.0 was already
published privately or tagged elsewhere, bump to 0.1.1 for this release.
License
GPL-2.0 — matching the upstream lifecontingencies R package.
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
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 pylifecontingencies-0.3.0.tar.gz.
File metadata
- Download URL: pylifecontingencies-0.3.0.tar.gz
- Upload date:
- Size: 104.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67fd48f3b27c1ee3c01e515f5485fb9d31c108c653bcb06a9e5e1e29a96d6c15
|
|
| MD5 |
1f27b36743c454feb98494217708d2a4
|
|
| BLAKE2b-256 |
e9074d4ac9756dec1c8d1750b4e8e274b81e6cb8d9813d3861fa0bc84c77daf1
|
Provenance
The following attestation bundles were made for pylifecontingencies-0.3.0.tar.gz:
Publisher:
publish.yml on filipeclduarte/pylifecontingencies
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pylifecontingencies-0.3.0.tar.gz -
Subject digest:
67fd48f3b27c1ee3c01e515f5485fb9d31c108c653bcb06a9e5e1e29a96d6c15 - Sigstore transparency entry: 1364413451
- Sigstore integration time:
-
Permalink:
filipeclduarte/pylifecontingencies@6cb3b21a6dc9b1ac6f688aa6716aeefd8408f90b -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/filipeclduarte
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6cb3b21a6dc9b1ac6f688aa6716aeefd8408f90b -
Trigger Event:
push
-
Statement type:
File details
Details for the file pylifecontingencies-0.3.0-py3-none-any.whl.
File metadata
- Download URL: pylifecontingencies-0.3.0-py3-none-any.whl
- Upload date:
- Size: 113.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
73be283eccc18c50069c4645b17315f296183bd3fbd726499c613746aa5b02ff
|
|
| MD5 |
1ca7887f3bb4ed67d481a0b0ee10f2cc
|
|
| BLAKE2b-256 |
20106d2caee018a813fc0cd210b66f5c935ab27194c8a7d497367e84e1346f8d
|
Provenance
The following attestation bundles were made for pylifecontingencies-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on filipeclduarte/pylifecontingencies
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pylifecontingencies-0.3.0-py3-none-any.whl -
Subject digest:
73be283eccc18c50069c4645b17315f296183bd3fbd726499c613746aa5b02ff - Sigstore transparency entry: 1364413606
- Sigstore integration time:
-
Permalink:
filipeclduarte/pylifecontingencies@6cb3b21a6dc9b1ac6f688aa6716aeefd8408f90b -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/filipeclduarte
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6cb3b21a6dc9b1ac6f688aa6716aeefd8408f90b -
Trigger Event:
push
-
Statement type: