DecrementTable#

DecrementTable is the abstract base class for all single-decrement table types (LifeTable, DisabilityTable, ExitTable). It provides the shared logic for table construction, the survival function \(l_x\) with radix \(l_0 = 1{,}000{,}000\), the governing decrement probability (\(q_x\), \(i_x\), or \(o_x\) depending on the concrete subclass), and decrement modification (modify_qx() on life tables; modify_ix() and modify_ox() on the other subclasses). LifeTable extends it further with the full suite of annuity, insurance, commutation, and life expectancy calculations.

Direct instantiation of DecrementTable is not supported — use one of the concrete subclasses. Import the base class as from lactuca.tables import DecrementTable (it is not re-exported from the top-level lactuca package).

See also

Table Taxonomy — Overview of table types and decrement conventions.
Using Actuarial Tables — Loading, inspecting, and modifying tables.
Modifying Decrements — Scaling, aggravated risk, and table_combination.
LifeTableLifeTable reference.
DisabilityTableDisabilityTable reference.
ExitTableExitTable reference.
Table registry utilitiesTableRegistry and configure_all().

class lactuca.tables.DecrementTable(table_name: str | list | tuple, sex: Literal['m', 'f', 'u'] | Sequence[Literal['m', 'f', 'u']] | None = None, cohort: int | Sequence | None = None, unisex_blend: object = None, duration: int | str | Sequence | None = None, cartesian: bool = False, return_dict: bool = False, pending: bool = False, **_kw)#

Bases: ABC

Abstract base class for actuarial decrement tables (e.g., life, disability, exit).

This class provides robust, consistent, and memory-efficient logic for loading, validating, and handling actuarial tables. It is designed to be inherited by concrete subclasses such as LifeTable, DisabilityTable, or ExitTable.

Key features:

  • Strict validation of required columns and metadata.

  • Efficient memory usage via compact NumPy array storage.

  • Fast access to configuration and table metadata.

  • Cohort and sex validation for generational/static tables.

  • Modification and reset logic for decrement rates.

  • Abstract contract for table_type and shared base implementation of summary() that subclasses can extend.

Observable public state is accessible via properties, including table_name, sex, cohort, omega, w, generational, select, modified, modifications_applied, and unisex_blend.

Notes

Actuarial Notation (IAA/IAE Conventions)

Decrement rates follow standard international actuarial notation. The one-year decrement probability at integer age \(x\) is denoted:

\[{}_{1}q_x \quad \text{(life)}, \quad {}_{1}i_x \quad \text{(disability)}, \quad {}_{1}o_x \quad \text{(exit)}\]

where:

  • \(q_x\) (life tables): probability that a life aged \(x\) dies before age \(x+1\)

  • \(i_x\) (disability tables): probability that a healthy life aged \(x\) becomes disabled before age \(x+1\)

  • \(o_x\) (exit tables): probability that an active life aged \(x\) exits (e.g., withdrawal, retirement) before age \(x+1\)

Survival Probabilities

The complementary survival probability is denoted \(p_x = 1 - q_x\), representing the probability of surviving (or remaining in the active state) from age \(x\) to \(x+1\).

Survival Function

The survival function \(l_x\) represents the expected number of survivors at exact age \(x\) from an initial cohort (\(l_0 = 1{,}000{,}000\) in Lactuca). For fractional ages, \(l_x\) is computed via interpolation between integer ages.

Interpolation for Fractional Ages

When calculations require fractional ages (e.g., \(x = 50.5\) or monthly payment frequencies), the survival function \(l_x\) is interpolated between integer ages using one of two methods controlled by the Config.lx_interpolation setting:

  • 'linear' (default): UDD (Uniform Distribution of Deaths) — assumes deaths are uniformly distributed between integer ages. This is arithmetic interpolation of \(l_x\).

  • 'exponential': CFM (Constant Force of Mortality) — assumes constant force of mortality between integer ages. This is geometric (log-linear) interpolation of \(l_x\).

Implementation Notes

  • Subclasses must implement the abstract table_type property, which determines the decrement column naming convention (\(q_x\) for life, \(i_x\) for disability, \(o_x\) for exit).

  • The summary() method is implemented in this base class and provides a common metadata/state report; subclasses can append type-specific details.

  • For select-ultimate tables, duration is required.

  • For generational tables, cohort is required.

  • For unisex computations without a native unisex column, unisex_blend is required when sex='u'.

See also

LifeTable

Concrete decrement table for mortality rates.

DisabilityTable

Concrete decrement table for disability rates.

ExitTable

Concrete decrement table for exit rates.

TableSource

Table loader and metadata/data accessor.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable('PASEM2010', 'm')
>>> lt.table_name
'PASEM2010'
>>> lt.w
112
>>> lt.qx(50)
0.00249
>>> lt.modified
False
>>> lt.modify_qx({'decrement_multiplier': 1.1})
>>> lt.modified
True
batch_update() _BatchUpdateContext#

Context manager to batch multiple setter assignments into one rebuild.

Use with table.batch_update(): to suspend rebuilds during a sequence of property assignments. A single decrement rebuild is performed when the context manager exits successfully, regardless of how many setters were called inside the block. If no setter changed any value (all dirty-checks returned early), no rebuild is performed.

This provides the same single-rebuild guarantee as configure() but uses an imperative with block instead of a single method call, which may be preferable when assignments are spread across multiple statements or depend on intermediate results.

Returns:

A context manager bound to the table instance.

Return type:

context manager

Raises:
  • ValueError – If the table is still pending after all assignments (missing metadata).

  • RuntimeError – If batch_update contexts are nested on the same instance.

Notes

If an exception is raised inside the with block, all setter changes made up to that point are rolled back and the table is restored to its state at block entry (metadata and decrement arrays).

batch_update is not reentrant: nesting two batch_update blocks on the same instance raises RuntimeError immediately.

Prefer configure() for declarative single-call configuration. batch_update is suited to imperative workflows where individual assignments depend on intermediate calculations.

See also

configure()

Declarative atomic configuration with the same single-rebuild guarantee.

metadata_pending()

Read-only flag indicating pending state.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable('PER2020_Ind_1o', pending=True)
>>> with lt.batch_update():
...     lt.sex = 'm'
...     lt.cohort = 1972
>>> lt.metadata_pending
False
configure(sex: Literal['m', 'f', 'u'] | None = None, cohort: int | None = None, duration: object = None, unisex_blend: object = None, **_extra) DecrementTable#

Configure table metadata atomically with a single rebuild.

Validates all arguments before touching self; if any validation fails the table state is unchanged (transactional / rollback). Performs exactly one decrement rebuild regardless of how many parameters are supplied. Returns self so calls can be chained.

Omitted keyword arguments preserve their current value (they are not reset). For example, lt.configure(cohort=1972) on a table that already has sex='m' keeps sex='m'.

Parameters:
  • sex ({'m', 'f', 'u'} or None, optional) – Sex for calculations. None means “keep current value”.

  • cohort (int or None, optional) – Birth cohort year for generational tables. None means “keep current value”.

  • duration (int, 'ult', or None, optional) – Select-table duration. None means “keep current value”.

  • unisex_blend (float or None, optional) – Male weight for unisex blending in [0.0, 1.0]. None means “keep current value”.

Returns:

self, enabling method chaining.

Return type:

DecrementTable

Raises:
  • ValueError – If called with no arguments (at least one keyword is required). If a supplied value is invalid for the current table configuration.

  • TypeError – If a supplied value has an incorrect type.

Notes

configure() always performs a single rebuild when all required metadata is complete after applying the supplied arguments. There is no dirty-check: if you want to avoid redundant rebuilds, use the individual property setters (which do dirty-check).

The internal order of application is: sexcohortunisex_blendduration → single decrement rebuild. This ordering ensures the unisex column (sex='u') is generated after cohort is set.

configure() is also used to reconfigure an already-configured table (e.g., change cohort in a loop without reinstantiating the object). Kwargs not supplied keep their current value; for example, lt.configure(cohort=1972) on a table with sex='m' already set keeps sex='m'.

If the supplied kwargs leave required metadata still incomplete (e.g., configure(cohort=1969) on a table that still lacks sex), the table remains in the pending state and no rebuild is performed.

On select tables in the pending state, sex and duration (including 'ult') may be supplied in separate configure() calls in any order.

For sex='u' on tables without a native unisex column, supply unisex_blend in the same configure() call (or set it on a prior call); validation mirrors __init__().

See also

batch_update()

Context manager for imperative setter-based configuration.

metadata_pending()

Read-only flag indicating pending state.

configure_all

Apply configure(**kwargs) to a collection of tables at once.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable('PER2020_Ind_1o', pending=True)
>>> lt.configure(sex='m', cohort=1969).ax(65, ir=0.03)

Reconfigure in a loop (single instance, minimal allocation):

>>> for c in [1960, 1970, 1980]:
...     result = lt.configure(cohort=c).ax(65, ir=0.03)
copy()#

Return a deep copy of the instance.

Returns:

Independent copy of the table with all state and caches duplicated.

Return type:

DecrementTable

Notes

Uses copy.deepcopy() and preserves shared configuration behavior while duplicating table state and computation caches.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable("PASEM2010", "m")
>>> lt2 = lt.copy()
>>> lt.modify_qx({"decrement_multiplier": 1.1})
>>> lt2.qx(50)  # original copy is unmodified
0.00249
dx(x: object = None) object#

Return \(d_x\) values (number of decrements between ages \(x\) and \(x+1\)).

Computes the expected number of decrements occurring between integer ages x and x+1, using the formula: \(d_x = l_x - l_{x+1}\)

Parameters:

x (float, int, sequence of float, NDArray[np.float64], or None, optional) – Age(s) to evaluate. Pandas and Polars Series are accepted; converted to float64 internally. If None, returns all dx values from age 0 to omega.

Returns:

dx value(s), or all dx if x is None. Returns scalar if x is scalar.

Return type:

Union[float, NDArray[np.float64]]

Raises:
  • ValueError – If any x is negative.

  • TypeError – If x is not numeric or a sequence of numerics.

Notes

  • For fractional ages, uses \(l_x\) interpolation as configured.

  • Results rounded to config.decimals.dx.

  • For x > \(\omega\), returns 0. At x = \(\omega\), returns \(l_{\omega}\), which equals 0 for standard life tables (where \(q_{\omega-1} = 1\)) but may be non-zero for disability or exit tables.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable('PASEM2010', 'm')
>>> lt.dx(50)
243.78
>>> lt.dx([50, 51, 52])
array([243.78, 247.33, 250.12])

See also

DecrementTable.lx

Number of lives at age x.

DecrementTable.qx

Decrement probability at age x.

head(n: int = 10, include_age: bool = True, show_normalized: bool = False) DataFrame#

Return the first n rows of the computed instance data.

Shows only columns relevant to this instance (sex, duration, modifications). By default starts from start_age (excludes padding rows below the original table start).

Parameters:
  • n (int, default 10) – Number of rows to return from the start.

  • include_age (bool, default True) – If True, include ‘age’ column. If False, exclude ‘age’ column from output.

  • show_normalized (bool, default False) – If True, start from age 0 (show padding if present). If False (default), start from start_age (exclude padding).

Returns:

The first n rows with or without age column.

Return type:

polars.DataFrame

See also

DecrementTable.tail

View last n rows.

DecrementTable.view_data

View full computed data.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable("GAM71", sex="m")
>>> lt.head(3)
>>> lt.head(3, show_normalized=True)
lx(x: object = None) object#

Return lx values (number of lives at age x) for the specified age(s).

Computes the expected number of lives remaining at age(s) x from an initial cohort. Supports integer and fractional ages with configurable interpolation.

Parameters:

x (float, int, sequence of float, NDArray[np.float64], or None, optional) – Age(s) to evaluate. Pandas and Polars Series are accepted; converted to float64 internally. If None, returns all lx values from age 0 to omega.

Returns:

lx value(s). Returns scalar if x is scalar, array otherwise.

Return type:

Union[float, NDArray[np.float64]]

Raises:
  • ValueError – If any age is negative.

  • TypeError – If x is not numeric or a sequence of numerics.

Notes

  • If x is None, returns the full \(l_x\) array for ages 0 to \(\omega\).

  • For fractional ages, uses interpolation method from config.lx_interpolation (‘linear’ or ‘exponential’).

  • Ages beyond \(\omega\) return 0.0 (actuarial convention).

  • Results rounded to config.decimals.lx.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable('PASEM2010', 'm')
>>> lt.lx(50)
97856.23
>>> lt.lx([50, 51, 52])
array([97856.23, 97612.45, 97365.12])

See also

DecrementTable.qx

Decrement probability at age x.

DecrementTable.px

Survival probability at age x.

DecrementTable.dx

Decrement count between ages x and x+1.

modify_qx(modifications: ModifyDecrement) None#

Apply modifications to the table’s decrement rates.

Actuarial adjustments to the base rates — age shifting, scaling, aggravated risk factors, or table combinations — are applied to a copy of the base rates and can be reset with reset_modifications().

Parameters:

modifications (dict) –

Dictionary specifying one or more modification operations. See ModifyDecrement TypedDict for supported keys:

  • ’age_shift’int

    Shift all ages by N years (drops first N ages).

  • ’decrement_multiplier’float or sequence of floats

    Multiply all qx values by a constant factor (e.g., 1.1 for +10%). Can be a scalar (applied to all ages) or an array/list of length w+1 (age-specific adjustments).

  • ’decrement_geometric_increase’tuple[float, int]

    Apply geometric growth to tail: (growth_rate, start_age).

  • ’aggravated_risk’float

    Apply aggravated-risk transform on survival probabilities: \(p'_x = p_x^{\text{factor}}\), equivalently \(q'_x = 1 - (1 - q_x)^{\text{factor}}\).

  • ’table_combination’Union[DecrementTable, list, tuple]

    Combine with one or more tables using independent competitive risks (default) or UDD associated singles when combination_mode='udd'. Pass a single DecrementTable, or a non-empty list/tuple of DecrementTable instances. Allowed combinations (self.table_type → other.table_type): 'life''exit', 'disability'; 'disability''exit'; 'exit''exit'. All combined tables must have the same sex as self. Array index i is integer age x = i; the host table keeps its length and omega; shorter other tables contribute q = 0 beyond their last index. Other tables with len(_decrement) != len(_decrement_base) are rejected. When both tables are generational, cohort years must match. When both tables are select tables with explicit integer durations, durations must match.

  • ’combination_mode’'independent' or 'udd'

    Optional actuarial assumption for table_combination (default 'independent' when omitted). 'udd' applies the UDD associated-single formula for two or three causes (host plus one or two others). Requires table_combination in the same dict. For two or three causes, 'independent' and 'udd' yield the same collapsed total q.

Raises:
  • ValueError – If modification parameters are invalid, including incompatible table types, sex/cohort/duration mismatches, empty table_combination sequences, or non-actuarial results. If the table is still in pending state (metadata_pending=True).

  • TypeError – If modification keys or values have incorrect types.

Notes

  • Modifications are applied in iteration order (dict order in Python 3.7+).

  • All computations use float64 precision internally.

  • Final results are rounded to the decimal precision configured for the current table type (qx, ix, or ox decimals from Config.decimals).

  • Combined tables must have matching sex.

  • Use reset_modifications() to restore original base rates.

  • Non-accumulative: each call to modify_qx replaces any previous modification entirely — it always starts from the original unmodified base rates. To accumulate multiple operations, pass all keys in a single call as a dict, or call reset_modifications() explicitly between independent experiments.

``table_combination`` actuarial contract (see also Modifying Decrements):

  • Default combination_mode='independent': independent competing risks \(q^{\mathrm{comb}}_x = 1 - (1-q^{\mathrm{host}}_x)\prod_j(1-q^{(j)}_x)\).

  • combination_mode='udd': UDD associated singles (two or three causes) collapsed to the same total \(q^{\mathrm{comb}}_x\); per-cause \(q'^{(j)}\) are not returned by the public API.

  • Array index i is integer age x = i when no prior age_shift in the same dict; after age_shift=n, index i is calendar age n + i. Tables with start_age > 0 must have zero padding below start_age.

  • Host keeps its array length through combination; shorter other tables imply \(q_{\mathrm{other}} = 0\) beyond their last index.

  • Reads each other._decrement (active float64 vector; may reflect prior modify_* on that table). Use other.reset_modifications() or a fresh instance for file/base rates. Not the public qx()/ix()/ox() accessors (rounded; beyond-\(\omega\) API may differ from alignment).

  • Rejects the host instance in others and duplicate table instances in others.

  • Rejects host padding below start_age (including after a prior age_shift in the same dict) and rates outside [0, 1] on the host or any other table before combining.

  • Truncates at the first combined \(q = 1\).

  • Rejects len(other._decrement) != len(other._decrement_base) and combined \(q > 1\). The same combined rates feed all four calculation_mode values on downstream calculations. Modes are actuarially coherent (same product/conventions) but not required to return numerically identical results.

  • Modification keys are applied in dict order; combination_mode may appear before or after table_combination, but other keys such as decrement_multiplier interact with table_combination in insertion order.

Examples

>>> from lactuca import ExitTable, LifeTable
>>> lt = LifeTable("PASEM2010", "m")
>>> lt.modify_qx({"decrement_multiplier": 1.1})
>>> lt.qx(50)
0.00275

Apply age-specific multipliers:

>>> import numpy as np
>>> factors = np.ones(113)
>>> factors[50:70] *= 0.95
>>> lt.reset_modifications()
>>> lt.modify_qx({"decrement_multiplier": factors})
>>> lt.reset_modifications()
>>> lt.modify_qx({"age_shift": 5, "aggravated_risk": 1.5})
>>> lt.w
107
>>> et_combine = ExitTable("DummyEXIT", sex="m")
>>> lt.modify_qx({"table_combination": et_combine})

See also

DecrementTable.reset_modifications

Restore original base rates.

px(x: object = None, m: Literal[1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365] = 1) float | NDArray[float64]#

Return probability of surviving one period (px) at age x.

Computes the survival probability for one payment period of length 1/m years, starting at age x. Supports fractional payment frequencies and vectorized operations.

Parameters:
  • x (float, int, sequence of float, NDArray[np.float64], or None, optional) – Entry age(s) (scalar or array). If None, returns px for all ages in the table.

  • m (PaymentFrequencyLiteral, optional) – Number of payments per year (1=annual, 12=monthly, etc.). Default is 1.

Returns:

Survival probability value(s). Returns scalar if x is scalar, array otherwise.

Return type:

Union[float, NDArray[np.float64]]

Notes

  • For integer ages and m=1, returns the standard survival probability \(p_x = 1 - q_x\).

  • For non-integer ages or m>1, uses interpolation: \({}^{1/m}p_x = \frac{l_{x + 1/m}}{l_x}\)

  • If x is None, returns the full \(p_x\) array and ignores m parameter.

  • All calculations vectorized using NumPy for maximum speed.

  • Results rounded to configured decimals (config.decimals.px).

Raises:
  • ValueError – If any age in x is negative. If the table is still in pending state (metadata_pending=True).

  • TypeError – If x is not numeric, or if m is not a scalar integer.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable('PASEM2010', 'm')
>>> lt.px(50)
0.99751
>>> lt.px(50, m=12)  # Monthly survival probability
0.99979

See also

DecrementTable.qx

Decrement probability (complement of px for m=1).

DecrementTable.tpx

Multi-period survival probability.

qx(x: object = None, m: Literal[1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365] = 1) float | NDArray[float64]#

Return probability of decrement at age x.

Computes the decrement probability (\(q_x\) for life, \(i_x\) for disability, \(o_x\) for exit) for one payment period of length \(1/m\) years, starting at age \(x\). Supports fractional payment frequencies and vectorized operations.

Parameters:
  • x (float, int, sequence of float, NDArray[np.float64], or None, optional) – Entry age(s) (scalar or array). If None, returns decrement for all ages in the table.

  • m (PaymentFrequencyLiteral, optional) – Number of payments per year (1=annual, 12=monthly, etc.). Default is 1.

Returns:

Decrement probability value(s). Returns scalar if x is scalar, array otherwise.

Return type:

Union[float, NDArray[np.float64]]

Notes

  • For integer ages and m=1, returns the precomputed decrement value.

  • For ages \(x \ge \omega\), returns \(q_x = 1.0\) (no survivors at or beyond the table limit).

  • For non-integer ages or m>1, uses interpolation: \({}^{1/m}q_x = 1 - \frac{l_{x + 1/m}}{l_x}\)

  • If x is None, returns the full decrement array and ignores m parameter.

  • All calculations vectorized using NumPy for maximum speed.

  • Results rounded to configured decimals based on table_type.

  • Consistent with international actuarial practice.

Raises:
  • ValueError – If any age in x is negative. If the table is still in pending state (metadata_pending=True).

  • TypeError – If x is not numeric, or if m is not a scalar integer.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable('PASEM2010', 'm')
>>> lt.qx(50)
0.00249
>>> lt.qx(50, m=12)  # Monthly decrement probability
0.00021

See also

DecrementTable.px

Survival probability (complement of qx for m=1).

DecrementTable.tqx

Multi-period decrement probability.

DecrementTable.modify_qx

Apply actuarial adjustments to the decrement rates.

reset_modifications() None#

Restore decrement to the original base values and clear all caches.

Notes

  • Resets the decrement array to its pre-modification state (equivalent to the table as loaded, before any modify_qx() call).

  • Sets modified to False and clears modifications_applied, so repr() no longer shows stale entries.

  • Restores w to its original value, undoing any omega reduction caused by an age_shift modification.

  • Invalidates all cached derived values (\(l_x\), \(p_x\), and related caches).

See also

DecrementTable.modify_qx

Apply modifications to decrement rates.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable('PASEM2020_Rel_1o', 'm')
>>> lt.modify_qx({'decrement_multiplier': 1.1})
>>> lt.modified
True
>>> lt.reset_modifications()
>>> lt.modified
False
>>> lt.modifications_applied
[]
summary() str#

Return a summary of the table metadata and current state.

This base implementation provides common information:

  • Class name and table name

  • Sex (and unisex blend weight if sex='u')

  • Table taxonomy: type, age range, valid sexes, generational/select flags

  • Cohort year (generational tables only)

  • Duration column (select tables only)

  • w (current): effective omega, which may be lower than omega after an age_shift or table_combination truncation at combined \(q = 1\)

  • Modified: True/False

  • Modifications applied: key=value, ... (only when modified)

  • Note: table_combination truncated omega (...) when combination shortened the host below its pre-combination omega

Subclasses call this method via super().summary() and append rate-type–specific information (decimals, sample values).

Returns:

Multi-line summary string with table metadata and modification state.

Return type:

str

Notes

When metadata_pending=True, this method returns an abbreviated summary that lists only the table name, a "Metadata: pending (needs: ...)" line showing which fields are still missing, the taxonomy block, and w (current). No calculation is attempted on the unconfigured table. Subclasses such as LifeTable detect the pending state and delegate entirely to this early-return path, so no sample qx/px values are shown.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable('PASEM2010', 'm')
>>> print(lt.summary())
LifeTable: PASEM2010
Sex: m
Type: life | Ages: 0–112 | Sexes: m, f | Period table
w (current): 112
Modified: False
>>> lt_p = LifeTable('PER2020_Ind_1o', pending=True)
>>> print(lt_p.summary())
LifeTable: PER2020_Ind_1o
Metadata: pending (needs: sex, cohort)
Sex: None
  ...
tail(n: int = 10, include_age: bool = True, show_normalized: bool = False) DataFrame#

Return the last n rows of the computed instance data, always ending at w.

Shows only columns relevant to this instance (sex, duration, modifications).

Parameters:
  • n (int, default 10) – Number of rows to return from the end.

  • include_age (bool, default True) – If True, include ‘age’ column. If False, exclude ‘age’ column from output.

  • show_normalized (bool, default False) – If True, consider full normalized range (0 to w). If False (default), consider only meaningful range (start_age to w).

Returns:

The last n rows with or without age column.

Return type:

polars.DataFrame

See also

DecrementTable.head

View first n rows.

DecrementTable.view_data

View full computed data.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable("GAM71", sex="m")
>>> lt.tail(3)
tpx(x: object, *, t: object = 1) float | NDArray[float64]#

Return interval survival probability tpx for all (x, t) combinations.

Computes the probability that a life aged x survives for t years, using the formula: \({}_{t}p_x = \frac{l_{x+t}}{l_x}\) Supports both scalar and array inputs for ages and durations.

Parameters:
  • x (float, int, sequence of float, or NDArray[np.float64]) – Entry age(s) (scalar or array). Pandas and Polars Series are accepted.

  • t (float, int, sequence of float, or NDArray[np.float64], optional) – Duration(s) in years (scalar or array). Default is 1. Pandas and Polars Series are accepted.

Returns:

Survival probability(ies). Returns scalar if both x and t are scalar, otherwise array with appropriate broadcasting.

Return type:

Union[float, NDArray[np.float64]]

Notes

  • Fast path for t=1 with integer ages uses px() directly to avoid lx rounding inconsistencies.

  • For non-integer ages or durations, lx interpolation is used as configured.

  • All calculations vectorized using NumPy for maximum speed.

  • Results rounded to config.decimals.tpx.

  • Consistent with international actuarial practice.

Raises:
  • ValueError – If any age in x or any duration in t is negative.

  • TypeError – If x or t is not numeric.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable('PASEM2010', 'm')
>>> lt.tpx(50, t=10)  # 10-year survival probability
0.97234
>>> lt.tpx([50, 55], t=[1, 5, 10])  # Grid of probabilities
array([[0.99751, 0.98876, 0.97234],
       [0.99623, 0.98456, 0.96123]])

See also

DecrementTable.tqx

Multi-period decrement probability.

DecrementTable.px

One-period survival probability.

tqx(x: object, *, t: object = 1) float | NDArray[float64]#

Return interval death probability tqx for all (x, t) combinations.

Computes the probability that a life aged x dies within t years, using the formula: \({}_{t}q_x = \frac{l_x - l_{x+t}}{l_x} = 1 - {}_{t}p_x\) Supports both scalar and array inputs for ages and durations.

Parameters:
  • x (float, int, sequence of float, or NDArray[np.float64]) – Entry age(s) (scalar or array). Pandas and Polars Series are accepted.

  • t (float, int, sequence of float, or NDArray[np.float64], optional) – Duration(s) in years (scalar or array). Default is 1. Pandas and Polars Series are accepted.

Returns:

Death probability(ies). Returns scalar if both x and t are scalar, otherwise array with appropriate broadcasting.

Return type:

Union[float, NDArray[np.float64]]

Notes

  • Computed as complement of interval survival: \({}_{t}q_x = 1 - {}_{t}p_x\).

  • For non-integer ages or durations, lx interpolation is used as configured.

  • All calculations vectorized using NumPy for maximum speed.

  • Results rounded to config.decimals.tqx.

  • Consistent with international actuarial practice.

Raises:
  • ValueError – If any age in x or any duration in t is negative.

  • TypeError – If x or t is not numeric.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable('PASEM2010', 'm')
>>> lt.tqx(50, t=10)  # 10-year death probability
0.02766
>>> lt.tqx([50, 55], t=[1, 5, 10])  # Grid of probabilities
array([[0.00249, 0.01124, 0.02766],
       [0.00377, 0.01544, 0.03877]])

See also

DecrementTable.tpx

Multi-period survival probability.

DecrementTable.qx

One-period decrement probability.

view_data(include_age: bool = True, show_normalized: bool = False) DataFrame#

Return the computed instance data, optionally excluding normalized padding rows.

Shows only columns relevant to this instance (sex, duration, modifications, and MI column when applicable). Does not delegate to the raw TableSource. For raw multi-column data (all sexes, all columns), use lt.table.view_data().

Parameters:
  • include_age (bool, default True) – If True, include ‘age’ column. If False, exclude ‘age’ column from output.

  • show_normalized (bool, default False) – If True, show full normalized range (0 to w). If False (default), show only meaningful range (start_age to w).

Returns:

Computed instance data with or without age column.

Return type:

polars.DataFrame

Raises:

ValueError – If the table is still in pending state (metadata_pending=True).

See also

DecrementTable.head

View first n rows.

DecrementTable.tail

View last n rows.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable("GAM71", sex="m")
>>> df = lt.view_data()
>>> df_full = lt.view_data(show_normalized=True)
property base_year: int | None#

Base year for generational improvement.

Returns:

Calendar year of the base mortality rates (e.g., 2020). None for period tables without generational improvement.

Return type:

int or None

Notes

Delegates to TableSource property base_year.

property cohort: int | None#

Current cohort (year of birth) for generational tables.

Returns:

Year of birth for generational tables, None for period tables.

Return type:

Union[int, None]

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable('PER2020_Ind_1o', 'm', cohort=1970)
>>> lt.cohort
1970
property data: DataFrame#

Raw underlying table data (all columns, all sexes).

Returns:

Complete table DataFrame with all decrement, improvement, and metadata columns for all sexes.

Return type:

polars.DataFrame

Notes

Delegates to TableSource property data.

This returns the full raw DataFrame as stored in the table file. For instance-specific computed data filtered to the active sex and cohort, use view_data() instead.

property decimals: _DecimalsConfig#

Access decimal precision settings for all actuarial functions.

This property provides convenient attribute-based access to global decimal precision configuration. All values are proxied dynamically from the Config singleton — no state is stored locally in the table instance.

Returns:

Decimal-precision proxy with attributes for actuarial table columns. Common attributes include:

  • lx : int — Survival function \(\ell_x\) precision

  • dx : int — Deaths \(d_x\) precision

  • qx : int — Mortality rate \(q_x\) precision

  • px : int — Survival probability \(p_x\) precision

  • tpx : int — Multi-year survival \({}_t p_x\) precision

  • tqx : int — Multi-year mortality \({}_t q_x\) precision

  • ix : int — Disability incidence \(i_x\) precision

  • ox : int — Exit rate \(o_x\) precision

  • Dx, Nx, Sx : int — Commutation functions precision

  • Cx, Mx, Rx : int — Insurance commutation functions precision. A_x = M_x/D_x uses end-of-year death placement; Cx applies mortality_placement offset (default 'mid'). Use mortality_placement='end' for the classical identity.

  • ex : int — Life expectancy \(\mathring{e}_x\) precision

  • ax, Ax : int — Annuities and insurances precision

Return type:

object

Raises:

AttributeError – If accessing a property blocked by the specific table subclass. See the subclass decimals docstring for the full list of blocked properties per table type.

Notes

  • Read-only proxy: self.decimals.xxx = value raises AttributeError by design; use Config().decimals.xxx = value to change precision globally.

  • Global scope: Changes to Config.decimals are immediately visible through all table instances.

  • For permanent changes, assign on the global config proxy, e.g. Config().decimals.lx = value or Config().set('decimals_lx', value).

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable('PASEM2010', 'm')
>>> lt.decimals.lx
2
>>> lt.decimals.qx
5
>>> lt.decimals.Dx
2

See also

Config.decimals : Global decimal precision configuration.

property description: str#

Human-readable table description from metadata.

Returns:

Descriptive text for the table (e.g., 'Spanish mortality table 2010').

Return type:

str

Notes

Delegates to TableSource property description.

property duration: int | str | None#

Return the current policy duration for select or non-select tables.

Returns the active select duration index, 'ult' when the ultimate column is selected, or None for non-select tables.

The minimum valid integer duration is start_duration (0 for CMI/UK convention, 1 for most other tables).

Returns:

Active select duration (integer), 'ult' for the ultimate column, or None for non-select tables.

Return type:

int or ‘ult’ or None

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable("DummyLIFE_Select", "m", duration=1)
>>> lt.duration
1
>>> lt.duration = "ult"
>>> lt.duration
'ult'
property file_name: str#

Basename of the loaded table file.

Returns:

File name with extension (e.g., 'PASEM2010.ltk').

Return type:

str

Notes

Delegates to TableSource property file_name.

property file_path: Path#

Absolute path to the loaded table file.

Returns:

Full filesystem path to the .ltk file.

Return type:

pathlib.Path

Notes

Delegates to TableSource property file_path.

property generational: bool#

Whether the table includes cohort-based improvement factors.

Returns:

True for generational tables with mortality improvement factors; False for period tables.

Return type:

bool

Notes

Delegates to TableSource property generational.

Examples

>>> from lactuca import LifeTable
>>> LifeTable("PASEM2010", "m").generational
False
property generational_formula_type: str | None#

Generational improvement formula type.

Returns:

Formula identifier (e.g., 'exponential_improvement', 'linear_improvement', 'discrete_improvement', 'projected_improvement'). None for period tables.

Return type:

str or None

Notes

Delegates to TableSource property generational_formula_type.

property grid_years: list[int] | None#

Projection calendar years for year-indexed MI tables.

Here, MI means mortality improvement factors (rate-reduction factors) indexed by calendar year, typically with columns such as mi_m_2020 or mi_f_2035.

Returns:

Sorted list of calendar years for which year-indexed MI columns exist, or None for tables without year-indexed improvement factors.

Return type:

list[int] or None

Notes

Delegates to TableSource property grid_years.

property is_select: bool#

Return True if the underlying table is a select-ultimate table.

Returns:

True if the table has select-period columns; False for period (non-select) tables.

Return type:

bool

Notes

This is an alias for select.

property metadata: dict[str, Any]#

Raw metadata dictionary from the table file.

Returns:

Complete metadata with all table properties (omega, base_year, description, etc.).

Return type:

dict

Notes

Delegates to TableSource property metadata.

property metadata_pending: bool#

True when required metadata has not yet been supplied via configure() or setters.

A pending table is one created with pending=True that still lacks at least one of: sex, cohort (generational tables), or duration (select tables). For select tables, duration='ult' is stored internally as _duration=None; completion is tracked separately from the numeric duration value. Attempting calculations on a pending table raises ValueError; use configure() or the individual property setters to complete configuration.

Returns:

True while the table is not yet fully configured, False otherwise.

Return type:

bool

Notes

The flag is cleared automatically once all required metadata is present — either by a successful configure() call or by the last individual setter that completes the configuration. It is also preserved across copy.deepcopy().

summary() and repr() display the pending state and list the missing metadata fields; all calculation methods raise ValueError until the flag is cleared.

See also

configure()

Atomically supply all missing metadata in one call.

batch_update()

Context manager for imperative setter-based configuration.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable('PER2020_Ind_1o', pending=True)
>>> lt.metadata_pending
True
>>> lt.configure(sex='m', cohort=1969)
>>> lt.metadata_pending
False
property mi_by_duration: bool#

Whether improvement factors are stored by policy duration.

True means the table stores separate mortality-improvement columns by duration (for example, mi_m_s1, mi_m_s2, …, mi_m_ult). False means a single improvement column per sex is shared across durations (for example, mi_m).

Returns:

True if per-duration MI columns are present; False if a single shared column is used.

Return type:

bool

Notes

Delegates to TableSource property mi_by_duration.

property mi_structure: str | None#

Mortality improvement column layout tag.

Returns:

Layout identifier (e.g., 'flat', 'year_indexed', 'select_period'). None for period tables.

Return type:

str or None

Notes

Delegates to TableSource property mi_structure.

property modifications_applied: list#

List of modification descriptors applied in the current call.

Each entry is a string of the form 'key=value', in the order the keys were applied. The list is empty when modified is False.

Returns:

A defensive copy of the internal list, e.g. ['age_shift=2', 'decrement_multiplier=1.05'].

Return type:

list of str

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable("PASEM2020_Rel_1o", "m")
>>> lt.modifications_applied
[]
>>> lt.modify_qx({"age_shift": 2, "decrement_multiplier": 1.05})
>>> lt.modifications_applied
['age_shift=2', 'decrement_multiplier=1.05']
property modified: bool#

True if a modification is currently active on this table.

Returns:

True after a successful modify_qx() (or modify_ix / modify_ox) call; False after reset_modifications() or after reassigning any of sex, cohort, duration, or unisex_blend.

Return type:

bool

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable("PASEM2020_Rel_1o", "m")
>>> lt.modified
False
>>> lt.modify_qx({"decrement_multiplier": 1.05})
>>> lt.modified
True
>>> lt.reset_modifications()
>>> lt.modified
False
property omega: int#

Original terminal age from metadata (immutable).

Returns:

Terminal age \(\omega\) as recorded in the table file.

Return type:

int

Notes

Delegates to TableSource property omega.

This is the original omega defined in the file metadata and never changes. For the current effective omega (which may decrease after age-shift modifications), use the mutable w property instead.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable("PASEM2010", "m")
>>> lt.omega
112
property select: bool#

Whether this is a select-ultimate table.

Returns:

True if the table has select-period columns; False for standard period (non-select) tables.

Return type:

bool

Notes

Delegates to TableSource property select.

See also

DecrementTable.is_select

Alias for this property.

Examples

>>> from lactuca import LifeTable
>>> LifeTable("PASEM2010", "m").select
False
property select_improvement_diagonal: str | None#

Calendar-year index for generational improvement on select columns.

Returns:

'cohort_plus_x_plus_d' for projected_improvement select tables, 'cohort_plus_x' for other generational select tables, or None for period or aggregate generational tables.

Return type:

str or None

Notes

Delegates to TableSource property select_improvement_diagonal. See that property for the actuarial definition of each diagonal convention.

See also

TableSource.select_improvement_diagonal

Full actuarial specification.

property select_period: int | None#

Select period in years.

Returns:

Number of years in the select period (e.g., 5 for a 5-year select table). None for non-select tables.

Return type:

int or None

Notes

Delegates to TableSource property select_period.

property sex: Literal['m', 'f', 'u']#

Current sex used for calculations.

Returns:

Sex identifier: ‘m’ (male), ‘f’ (female), or ‘u’ (unisex).

Return type:

SexLiteral

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable('PASEM2010', 'm')
>>> lt.sex
'm'
property sex_independent: bool#

Whether rates are identical for both sexes.

Returns:

True if male and female rates are identical (unisex table); False if rates differ by sex.

Return type:

bool

Notes

Delegates to TableSource property sex_independent.

property start_age: int#

Minimum defined age in the original table.

Returns:

First age with data (typically 0, occasionally 1 or higher).

Return type:

int

Notes

Delegates to TableSource property start_age.

property start_duration: int | None#

Minimum integer duration.

Returns:

First valid duration index (0 for CMI/UK convention, 1 for most tables). None for non-select tables.

Return type:

int or None

Notes

Delegates to TableSource property start_duration.

property table: TableSource#

Access the underlying TableSource instance with table data and metadata.

Returns a reference to the internal table source loaded during initialization. This property allows inspection of table metadata and structure.

Returns:

The underlying table source instance with raw data and metadata.

Return type:

TableSource

Notes

  • For the current effective omega (which may change after modifications), use w instead of table.w.

  • Most metadata properties are accessible directly on the DecrementTable instance (e.g. table_name, generational, start_age, select, etc.) without going through table.xxx.

  • Do not modify the returned object directly. Direct mutations bypass validation and invalidate internal caches, leading to incorrect results.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable("PASEM2010", "m")
>>> lt.table.table_name
'PASEM2010'
>>> lt.table.generational
False
>>> lt.table.w
112
>>> lt.modify_qx({"age_shift": 5})
>>> lt.w         # current effective omega after modification
107
>>> lt.table.w   # original omega (unchanged)
112

See also

DecrementTable.w

Current upper age limit (omega), accounting for modifications.

property table_name: str#

Name of the underlying actuarial table.

Returns:

Table file name without extension (e.g., 'PASEM2010').

Return type:

str

Notes

Delegates to TableSource property table_name.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable("PASEM2010", "m")
>>> lt.table_name
'PASEM2010'
abstract property table_type: str#

Return the canonical type identifier for this decrement table.

Returns:

One of 'life', 'disability', or 'exit'.

Return type:

str

Notes

Must be implemented by subclasses. The returned value determines:

  • Decrement column naming convention (qx_* for life, ix_* for disability, ox_* for exit).

  • Decimal precision configuration used for decrement outputs (decimals.qx, decimals.ix, decimals.ox).

  • Valid table combinations accepted by modify_qx().

property unisex_blend: float | None#

Male weight used to blend male/female rates into unisex rates.

Returns:

Male weight in [0.0, 1.0] if a unisex blend has been applied, None otherwise.

Return type:

Union[float, None]

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable('PER2020_Ind_1o', 'u', cohort=1970, unisex_blend=0.5)
>>> lt.unisex_blend
0.5
property valid_sexes: list[str]#

Valid sexes for this table (e.g. ['m', 'f']).

Returns:

Sex identifiers available in this table, e.g. ['m', 'f'] or ['m', 'f', 'u'] when a native unisex column is present.

Return type:

list of str

Notes

Delegates to TableSource property valid_sexes.

property w: int#

Current upper age limit (omega), accounting for modifications.

This value may differ from the original table omega after applying modifications like age_shift. For the original table omega value, use omega.

Returns:

Maximum age in the current (possibly modified) table state.

Return type:

int

See also

DecrementTable.omega

Original terminal age from the table metadata.

Examples

>>> from lactuca import LifeTable
>>> lt = LifeTable("PASEM2010", sex="m")
>>> lt.w
112
>>> lt.modify_qx({"age_shift": 5})
>>> lt.w  # reduced after age_shift
107
>>> lt.omega  # original table omega, unchanged
112