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 via
StochasticResult— k-thly payments, deferral,hist/plotvisualisation,var/tvarrisk metrics, pandas export - Parametric mortality graduation with
GompertzMakehamandHeligmanPollard - Bundled data: 100+ mortality tables via
load_table()/list_tables()— SOA ILT, BR-EMS series (2010–2021), AT, UP, RP, GAM, CSO, IBGE, and many historical tables; plus R-sourced parquets (soa08,AM92Lt,demoUsa, etc.)
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)
# Visualisation
r.hist() # histogram with mean and 95 % CI lines
r.plot() # empirical CDF
# Risk metrics (Solvency II)
r.var(0.995) # Value at Risk at 99.5 %
r.tvar(0.995) # Tail VaR (Expected Shortfall) at 99.5 %
# 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
Over 100 mortality tables are shipped with the package and discoverable at runtime:
from pylifecontingencies import list_tables, load_table
list_tables() # returns all available names
lt = load_table("at_2000_female")
lt = load_table("ibge_2020_homens")
lt = load_table("up_94_male")
CSV tables (automatically discovered)
Selected groups:
| Group | Examples |
|---|---|
| SOA | soa_ilt |
| AT (Annuity 2000) | at_2000_female, at_2000_male, at_49_female, at_49_male, at_83_female_basic, … |
| UP / RP (Group annuity) | up_84_f, up_84_m, up_94_female, up_94_male, rp_2000_female, rp_2000male, … |
| GAM | gam_71_female, gam_71_male, gam_83_female_suav_10, gam_94_female, … |
| CSO | cso_41, cso_58, cso58_female, cso58_male, cso80 |
| BR-EMS (Sobrevivência) | br_emssb_2010_m/f, br_emssb_2015_m/f, br_emssb_2021_m/f |
| BR-EMS (Mortalidade) | br_emsmt_2010_m/f, br_emsmt_2015_m/f, br_emsmt_2021_m/f |
| IBGE | ibge_2006_ambos_os_sexos, ibge_2020_homens, ibge_2020_mulheres, … |
| Historical | american_experience, bentzien, muller, rentiers_francais, zimmermann, … |
All CSV tables have age and qx columns; x_min is inferred automatically from
the first age in the file.
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, visualisation, VaR/TVaR, 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.
License
GPL-2.0 — matching the upstream lifecontingencies R package.
Metadata
Release files for pylifecontingencies 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pylifecontingencies-0.5.0.tar.gz | 146.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pylifecontingencies-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 327.7 kB
Release files / pylifecontingencies-0.5.0.tar.gz
| Download URL | pylifecontingencies-0.5.0.tar.gz |
|---|---|
| Size | 146.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5b80137dd6a81b461734e063ed7bb84403b46e70a8b099bdba2a145110351b05
|
|
BLAKE2b-256 checksum How to use checksums |
7f1bdbfcdbcc6e554a5e400d36956bc5245aba57f8fc80dd51193c66358ba6ce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Release files / pylifecontingencies-0.5.0-py3-none-any.whl
| Download URL | pylifecontingencies-0.5.0-py3-none-any.whl |
|---|---|
| Size | 181.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a15e3f8a557b9984793c0b7d5e4109059ab2c9e7a3da5a8c075842ee100692ba
|
|
BLAKE2b-256 checksum How to use checksums |
ca758671201a321529e2af7cca3df7462ac4b5397e60586cb0117d63126b53c7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|