Skip to main content

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/plot visualisation, var/tvar risk metrics, pandas export
  • Parametric mortality graduation with GompertzMakeham and HeligmanPollard
  • 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.yml runs the main pytest matrix on Python 3.10, 3.11, and 3.12, plus coverage and distribution builds.
  • r-parity.yml installs R + lifecontingencies and runs the parity tests that compare Python results against the R package.
  • publish.yml builds and publishes to PyPI on tags matching v* 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.

Download files

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

Source Distribution

pylifecontingencies-0.5.0.tar.gz (146.1 kB view details)

Uploaded Source

Built Distribution

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

pylifecontingencies-0.5.0-py3-none-any.whl (181.6 kB view details)

Uploaded Python 3

File details

Details for the file pylifecontingencies-0.5.0.tar.gz.

File metadata

  • Download URL: pylifecontingencies-0.5.0.tar.gz
  • Upload date:
  • Size: 146.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pylifecontingencies-0.5.0.tar.gz
Algorithm Hash digest
SHA256 5b80137dd6a81b461734e063ed7bb84403b46e70a8b099bdba2a145110351b05
MD5 af1f75384918e1347a19b25e7ba62ab8
BLAKE2b-256 7f1bdbfcdbcc6e554a5e400d36956bc5245aba57f8fc80dd51193c66358ba6ce

See more details on using hashes here.

File details

Details for the file pylifecontingencies-0.5.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pylifecontingencies-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a15e3f8a557b9984793c0b7d5e4109059ab2c9e7a3da5a8c075842ee100692ba
MD5 e4fd15dc2cdacfbafb70a7478856eddf
BLAKE2b-256 ca758671201a321529e2af7cca3df7462ac4b5397e60586cb0117d63126b53c7

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

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