Skip to main content

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 GompertzMakeham and HeligmanPollard
  • Bundled data: SOA ILT, BR-EMS tables, and R-sourced parquet tables such as soa08, AM92Lt, AF92Lt, demoUsa, and demoGermany

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

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)

Mortality-law fitting

import numpy as np
from pylifecontingencies import fit_mortality_law

fit = fit_mortality_law(lt, "gompertz_makeham", ages=np.arange(40, 90))
fit.params_dict, fit.rmse

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)

# Monte Carlo 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))

# Same API as a convenience method on the table
r_ann = at.simulate_pv(x=40, n=20, benefit="annuity", n_sim=10_000, random_state=42)

Supported benefit types: term, whole / whole_life, annuity, pure_endowment, endowment, increasing, decreasing.


Mortality-law graduation

import numpy as np
from pylifecontingencies import (
    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()

MortalityLawFit stores fitted parameters, observed/fitted q_x, residuals, and goodness-of-fit metrics (loglik, AIC, BIC, RMSE, MAE).


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, 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

pylifecontingencies-0.1.0.tar.gz (101.8 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.1.0-py3-none-any.whl (112.3 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for pylifecontingencies-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ba4d7654f029657402420a4add7d8f51b28e090701fdb988fb2c56f8845c6509
MD5 ec287405dee3984647477b25b535a56e
BLAKE2b-256 6fe19adf10cefc02335f0803ec88d03ca07c082b7044092746ba575e0457f5a7

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylifecontingencies-0.1.0.tar.gz:

Publisher: publish.yml on filipeclduarte/pylifecontingencies

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

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

File metadata

File hashes

Hashes for pylifecontingencies-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a30dba5868cca4e4ac4927ff97008c8c770f6b5ecff4ac647b168dc95ed790f9
MD5 14754aab89d2f9ea1b2daddf72b4d5d4
BLAKE2b-256 79634f8651e42e0b3f3831b1d68c349ed41cf6b97191f9a5452fd36f06a35d85

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylifecontingencies-0.1.0-py3-none-any.whl:

Publisher: publish.yml on filipeclduarte/pylifecontingencies

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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