Table Taxonomy#
Tables in Lactuca are classified along three orthogonal dimensions:
Table type — Life (
lactuca.LifeTable), Disability (lactuca.DisabilityTable), or Exit (lactuca.ExitTable). This determines which Python class is used to instantiate the table.Insured structure — Aggregate vs. Select-Ultimate.
Temporal nature — Static (no projection) vs. Generational (with improvement factors).
Dimensions 2 and 3, together with the mathematical formula used to project rates, define the nine category types described below (Gen (d) applies only to Select-Ultimate tables). Every included table belongs to exactly one category and one table type.
Type |
Python class |
Decrement columns |
|---|---|---|
Life |
|
|
Disability |
|
|
Exit |
|
|
Category |
Aggregate |
Select-Ultimate |
|---|---|---|
Static |
rates loaded as-is; |
rates by select duration; |
Generational (a) |
time-constant MI per age ( |
time-constant MI, uniform across all durations — exponential / linear / discrete |
Generational (b) |
year-indexed MI grid ( |
year-indexed MI grid, uniform across durations — exponential / linear / discrete |
Generational (c) |
year-indexed MI grid — cumulative product projection |
year-indexed MI grid — cumulative product projection |
Generational (d) |
(not applicable) |
independent MI vector per duration — exponential / linear / discrete |
Gen (d) is not applicable to aggregate tables because per-duration MI factors presuppose rates that vary by duration since underwriting — a concept that only exists in select-ultimate tables.
Aggregate – Static#
Columns: qx_m, qx_f / ix_m, ix_f / ox_m, ox_f (raw annual decrement rates,
where m = male and f = female). No improvement factors.
The decrement vector is loaded as-is from the .ltk file. No cohort parameter is accepted.
from lactuca import LifeTable, DisabilityTable, ExitTable
# Life table
pasem = LifeTable("PASEM2020_Rel_1o", "m")
# Disability table (individual business)
peai_ind = DisabilityTable("PEAI2007_IAP_Ind", "m")
# Sex-independent disability table (same rates for "m" and "f")
iass90 = DisabilityTable("IASS90", "m")
# Exit table
exit_t = ExitTable("DummyEXIT", "m")
Aggregate – Generational#
Columns: qx_m, qx_f plus MI (Mortality Improvement) factors mi_*.
MI factors quantify the annual reduction of mortality rates over time. Column names starting
with mi_ carry these factors; mi_m / mi_f hold a constant vector per age and sex,
while mi_m_YYYY / mi_f_YYYY hold values indexed by calendar year. See
Mortality Improvement (MI) for the full explanation of each formula and how MI is applied.
A cohort (birth year) is mandatory. For each age \(x\) the projected rate is evaluated at
the calendar year \(t = \text{cohort} + x\). Sub-types (a)–(c) differ in how the MI factor
is structured and in the projection formula applied.
a) Constant improvement factors — cohort projection#
The improvement factor is a single age-vector per sex (mi_m, mi_f), constant across
calendar years. Three projection formulas are available:
Formula |
Projected rate \(q_{x,t}\) |
|---|---|
|
\(q_{x,0} \cdot e^{-\lambda_x(t-t_0)}\) |
|
\(q_{x,0} - mi_x\,(t-t_0)\) |
|
\(q_{x,0} \cdot (1-AA_x)^{t-t_0}\) |
from lactuca import LifeTable, DisabilityTable, ExitTable
# Life — exponential improvement (real table)
per = LifeTable("PER2020_Ind_1o", "m", cohort=1969)
# Life — discrete improvement (real table)
gam = LifeTable("GAM94_AA", "m", cohort=1955)
# Life — linear improvement (no real table available)
lin = LifeTable("DummyLIFE_LinearGen", "m", cohort=1970)
# Disability — no real generational disability table available
sd = DisabilityTable("DummySD2015Gen", "m", cohort=1970)
# Exit — no real generational exit table available
ex = ExitTable("DummyEXIT_Gen", "m", cohort=1970)
b) Year-indexed improvement grid — cohort projection#
Improvement factors are provided as an annual grid: columns mi_m_YYYY, mi_f_YYYY
(YYYY ∈ [1900, 2200]). Three projection formulas are available
(exponential_improvement, linear_improvement, discrete_improvement). For each age
\(x\) the calendar year \(t = \text{cohort} + x\) is mapped to the nearest available grid
column (years outside the grid are clamped to its boundary).
from lactuca import LifeTable, DisabilityTable, ExitTable
# Life
dav = LifeTable("DAV2004R_Agg_2o", "m", cohort=1960)
# Disability — year-indexed exponential improvement
sd_g2o = DisabilityTable("DummySD_Gen2o", "m", cohort=1970)
# Exit — year-indexed exponential improvement
ex_g2o = ExitTable("DummyEXIT_Gen2o", "m", cohort=1970)
c) Year-indexed improvement grid — cumulative product cohort projection#
The improvement factor \(AA_{x,t}\) (projected_improvement) is indexed by calendar year
(columns mi_m_YYYY, mi_f_YYYY). The projected rate is the base rate multiplied by the
cumulative product of annual reduction factors along the insured’s diagonal:
For ages where \(\text{cohort} + x \leq t_0\) the base rate is returned unchanged. Beyond
the last year in the grid the last available factor is used (flat extrapolation). The cohort
parameter is the only projection input exposed in the public API.
from lactuca import LifeTable, DisabilityTable, ExitTable
# Male-only table
cb = LifeTable("CB_H_2020", "m", cohort=1975)
# Female-only table
rv = LifeTable("RV_M_2020", "f", cohort=1975)
print(rv.metadata['generational_formula_type'])
# Disability with projected improvement
sd_proj = DisabilityTable("DummySD_ProjectedGen", "m", cohort=1975)
# Exit with projected improvement
ex_proj = ExitTable("DummyEXIT_ProjectedGen", "m", cohort=1975)
Select-Ultimate tables: actuarial context#
Why select tables exist. When a life insurance policy is issued, the insured has recently passed underwriting — a medical examination or a declaration of good health. Freshly underwritten lives are, on average, healthier than a random individual of the same age drawn from the general population: this is the selection effect. Over the following years (the select period) the advantage wears off as the initial cohort ages and any surviving impaired lives re-enter the pool. Once the select period has elapsed the insured is treated as having reverted to ultimate (population-level) mortality.
When to use duration vs "ult" in reserves. For a contract that has been in
force \(d\) full years since inception, pass duration=d while \(d\) is within the select
period; once the select period is exhausted, pass duration="ult". Statutory reserves
for recently underwritten lives must use select mortality rates if the table provides
them; using ultimate rates before the select period ends overstates projected
mortality and understates the reserve, which is non-conservative.
Select-Ultimate — duration indexing#
All select-ultimate tables use an integer duration parameter at construction time to select
the appropriate mortality column. Most tables index select columns starting at duration 1
(qx_m_s1, qx_m_s2, …qx_m_s{sp}). A small number of tables — notably those following the
CMI / UK convention, such as AM92 and AF92 — index from duration 0
(qx_m_s0, qx_m_s1, …qx_m_s{sp}). The start_duration attribute of TableSource
reports the minimum valid duration for a given table (1 for the standard convention, 0
for CMI/UK-style tables). Pass the corresponding minimum duration (or any integer up to
select_period − 1 + start_duration) when constructing the table:
from lactuca import LifeTable
# Standard convention (duration-1): select columns named qx_m_s1, qx_m_s2, …
# Example: DAV 2004 R Select-Ultimate (generational — cohort required)
dav_su = LifeTable("DAV2004R_SelUlt_1o", "m", cohort=1960, duration=1)
# CMI/UK convention (duration-0): first select column is qx_m_s0
am92_d0 = LifeTable("AM92_AF92", "m", duration=0) # freshly underwritten
am92_d1 = LifeTable("AM92_AF92", "m", duration=1) # 1 year since underwriting
am92_ult = LifeTable("AM92_AF92", "m", duration="ult") # ultimate column
print(am92_d0.table.start_duration) # 0
Select-Ultimate – Static#
Columns: qx_m_s1 … qx_m_s{k}, qx_m_ult (and female equivalents). No improvement
factors. No cohort parameter is accepted.
The select period (duration since underwriting) determines which column is used. Once the
select period is exhausted (\(d \ge \text{select_period} + \text{start_duration}\)) the
ultimate column applies. The duration parameter selects the column at construction time
(see duration indexing conventions above).
from lactuca import LifeTable, DisabilityTable, ExitTable
am92_d0 = LifeTable("AM92_AF92", "m", duration=0)
am92_ult = LifeTable("AM92_AF92", "m", duration="ult")
# Disability select-ultimate (select_period=2, standard convention: duration-1 to "ult")
sd_sel1 = DisabilityTable("DummySD_Select", "m", duration=1)
sd_ult = DisabilityTable("DummySD_Select", "m", duration="ult")
# Exit select-ultimate (select_period=2, standard convention: duration-1 to "ult")
ex_sel1 = ExitTable("DummyEXIT_Select", "m", duration=1)
ex_ult = ExitTable("DummyEXIT_Select", "m", duration="ult")
Select-Ultimate – Generational#
Select-ultimate tables with improvement factors behave like their aggregate counterparts. The key distinction is whether MI factors are applied uniformly across all select durations (sub-types a–c) or independently per duration (sub-type d).
a) Constant improvement factors — cohort projection#
Flat improvement columns (mi_m, mi_f) applied uniformly to all durations. Three
projection formulas are available (exponential_improvement, linear_improvement,
discrete_improvement); each duration uses the same MI vector.
from lactuca import LifeTable, DisabilityTable, ExitTable
# Life
dav_su = LifeTable("DAV2004R_SelUlt_1o", "m", cohort=1960, duration=2)
print(dav_su.summary())
# Disability — constant exponential MI, uniform across select durations
sd_sa = DisabilityTable("DummySD_SelectGen_a", "m", cohort=1970, duration=1)
# Exit — constant exponential MI, uniform across select durations
ex_sa = ExitTable("DummyEXIT_SelectGen_a", "m", cohort=1970, duration=1)
b) Year-indexed improvement grid — cohort projection#
Annual improvement grid (mi_m_YYYY, mi_f_YYYY) applied uniformly to all durations.
Three projection formulas are available (exponential_improvement, linear_improvement,
discrete_improvement). Projection diagonal: \(t = \text{cohort} + x\).
from lactuca import LifeTable, DisabilityTable, ExitTable
# Life
dav_su2 = LifeTable("DAV2004R_SelUlt_2o", "m", cohort=1960, duration=2)
print(dav_su2.metadata['generational_formula_type']) # 'exponential_improvement'
# Disability — year-indexed MI, uniform across select durations
sd_sb = DisabilityTable("DummySD_SelectGen_b", "m", cohort=1970, duration=1)
# Exit — year-indexed MI, uniform across select durations
ex_sb = ExitTable("DummyEXIT_SelectGen_b", "m", cohort=1970, duration=1)
c) Year-indexed improvement grid — cumulative product cohort projection#
Select-ultimate rates combined with year-indexed improvement \(AA_{x,t}\)
(projected_improvement), projected along each insured’s select diagonal
\(t = \text{cohort} + x + d\) (where \(d\) is the duration since underwriting):
Demo tables DummyLIFE_SelectGen_c, DummySD_SelectGen_c, and DummyEXIT_SelectGen_c
are bundled for testing and illustration.
from lactuca import LifeTable, DisabilityTable, ExitTable
# Life — projected improvement, select-ultimate
lt_c = LifeTable("DummyLIFE_SelectGen_c", "m", cohort=1975, duration=1)
# Disability
sd_c = DisabilityTable("DummySD_SelectGen_c", "m", cohort=1975, duration=1)
# Exit
ex_c = ExitTable("DummyEXIT_SelectGen_c", "m", cohort=1975, duration=1)
print(lt_c.metadata['generational_formula_type']) # 'projected_improvement'
d) Per-duration improvement vectors — cohort projection#
Each select duration carries its own improvement vector: columns mi_m_s1 … mi_m_s{k},
mi_m_ult (and female equivalents). Three projection formulas are available, applied
independently per duration \([d]\), with \(t = \text{cohort}+x\):
Formula |
Projected rate \(q_{x,t}^{[d]}\) |
|---|---|
|
\(q_{x,t_0}^{[d]} \cdot e^{-mi_x^{[d]}(t-t_0)}\) |
|
\(q_{x,t_0}^{[d]} - mi_x^{[d]}(t-t_0)\) |
|
\(q_{x,t_0}^{[d]} \cdot (1-AA_x^{[d]})^{t-t_0}\) |
Demo tables DummyLIFE_SelectGen_d, DummySD_SelectGen_d, and DummyEXIT_SelectGen_d
are bundled for testing and illustration.
from lactuca import LifeTable, DisabilityTable, ExitTable
# Life — per-duration independent exponential MI
lt_d = LifeTable("DummyLIFE_SelectGen_d", "m", cohort=1975, duration=1)
# Disability
sd_d = DisabilityTable("DummySD_SelectGen_d", "m", cohort=1975, duration=1)
# Exit
ex_d = ExitTable("DummyEXIT_SelectGen_d", "m", cohort=1975, duration=1)
print(lt_d.metadata['generational_formula_type']) # 'exponential_improvement'
How each table maps to this taxonomy#
For full details — sex coverage, country, and actuarial order — see Bundled Actuarial Tables.
Category |
Life |
Disability |
Exit |
|---|---|---|---|
Aggregate – Static |
|
|
|
Aggregate – Gen (a), exponential |
|
|
|
Aggregate – Gen (a), linear |
|
|
|
Aggregate – Gen (a), discrete |
|
|
|
Aggregate – Gen (b) |
|
|
|
Aggregate – Gen (c) |
|
|
|
Select-Ultimate – Static |
|
|
|
Select-Ultimate – Gen (a) |
|
|
|
Select-Ultimate – Gen (b) |
|
|
|
Select-Ultimate – Gen (c) |
|
|
|
Select-Ultimate – Gen (d) |
|
|
|
† Test/demo table. ‡ sex_independent = True (unisex). ♂ Male-only (qx_m). ♀ Female-only (qx_f).
Warning
Tables marked with † are synthetic test/demo tables included for documentation examples
and unit testing only. Their data is entirely artificial and must not be used for
production actuarial calculations, valuations, reserving, or any other professional purpose.
See Bundled Actuarial Tables for the complete development-table listing.
See also#
Mortality Improvement (MI) — MI formulas, year-indexed factors, and generational usage
Bundled Actuarial Tables — full list of bundled table identifiers with metadata
Using Actuarial Tables — constructor reference, vectorial creation, and dynamic cohort assignment
Modifying Decrements —
modify_qx,reset_modificationslx Interpolation — fractional-age interpolation methods