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.2.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.2.0-py3-none-any.whl (112.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pylifecontingencies-0.2.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.2.0.tar.gz
Algorithm Hash digest
SHA256 f1fc52d533d4168da26613bee8fdf4f00c2b72f16f43b8a745cc84cd7a06ca26
MD5 0642b42d055ad5381eb1c6fa5669af84
BLAKE2b-256 09969469c1731acc7ae36ac8083ff14c225165f3a59aa638cde1fbf78d307258

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylifecontingencies-0.2.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.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pylifecontingencies-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 82758acfd8545ee0b74dff11d41052f433f5a8574ed5e5cf940c452620f3acd9
MD5 f896a922aeed330b332d065f21f8712f
BLAKE2b-256 370ddd2597d9a48b0d07f3e64cde68186a0ad35ea00e8cc76035b2a194d9e096

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylifecontingencies-0.2.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