TableBuilder#
TableBuilder provides a fluent interface for constructing custom
actuarial tables programmatically from raw decrement arrays and saving them as
.ltk files with automatic metadata validation and integrity checking.
Supported decrement types:
Array |
Symbol |
Table type |
|---|---|---|
|
\(q_x\) |
Mortality ( |
|
\(i_x\) |
Disability inception rate ( |
|
\(o_x\) |
Exit / turnover rate ( |
Generational tables additionally require improvement rate columns (mi_m, mi_f, and/or mi_u).
See also
Building Custom Table Files (.ltk) — Step-by-step guide to building custom tables.
Table Taxonomy — Overview of table types and column conventions.
- class lactuca.TableBuilder(data: dict | DataFrame | DataFrame, *, table_name: str, table_type: Literal['life', 'disability', 'exit'], generational: object = False, base_year: object = None, decrement_scale_factor: object = 1, mi_scale_factor: object = 1, omega: object = None, start_age: object = None, sex_independent: object = False, generational_formula_type: Literal['exponential_improvement', 'linear_improvement', 'discrete_improvement', 'projected_improvement'] | None = None, description: str = '', select: object = False, select_period: object = None, grid_years: list[int] | tuple[int, ...] | NDArray[int64] | None = None, validate_strict: object = True)#
Bases:
objectBuild, validate, scale and persist actuarial tables.
The
TableBuilderclass provides deterministic validation, canonical serialization and integrity hashing for actuarial tables (life, disability, exit). It accepts input as a dict,pandas.DataFrameorpolars.DataFrameand favors vectorized, float64 computations for numerical stability and speed.- Parameters:
data (Union[dict, pl.DataFrame, pd.DataFrame]) – Table data as a dict (keys=column names, values=list/array), Pandas DataFrame, or Polars DataFrame.
table_name (str) – Short name identifier for the table (e.g., “PERM2000P”, “DAV2004R”).
table_type (TableTypeLiteral) – Category of the actuarial table. Accepted values:
"life","disability", or"exit"(seeVALID_TABLE_TYPES).generational (bool, optional) – Whether the table includes generational improvement factors (default:
False). Must be a strictbool(e.g.generational=1raisesTypeError).base_year (Union[int, None], optional) – Base (reference) year for generational tables. Required when
generational=True; must beNoneotherwise.decrement_scale_factor (int, optional) – Public scale factor for decrement columns (
qx,ix, etc.). Default: 1 (no scaling). Must be a power of 10 (1, 10, 100, 1000, …).mi_scale_factor (int, optional) – Power-of-10 public scale factor for MI (mortality improvement) columns. Default: 1 (no scaling). Must be a power of 10 (1, 10, 100, 1000, …).
omega (Union[int, None], optional) – Limiting age (\(\omega\)), the last age index in the table. For life tables, \(q_{\omega}\) must equal 1.0 (enforced in
validate()and at load). Exit and disability tables may have rates below 1 at \(\omega\). WhenNone, inferred from the data.start_age (Union[int, None], optional) – First age represented in the data. Defaults to 0 when
None. Must be a non-negative integer ≤omega.sex_independent (bool, optional) – Whether the table is sex-independent/unisex (default: False). Must be a strict
bool.generational_formula_type (Union[GenerationalFormulaTypeLiteral, None], optional) – Improvement formula type for generational tables. One of
"exponential_improvement","linear_improvement","discrete_improvement", or"projected_improvement". Required whengenerational=True.description (str, optional) – Human-readable description of the table (default: “”).
select (bool, optional) – Whether to build a select-ultimate table (default: False). When
True,select_periodmust also be provided. Must be a strictbool.select_period (Union[int, None], optional) – Count of numbered select-duration columns per sex. Duration columns are named
_s{sd}through_s{sd+N-1}plus_ult, whereN = select_periodandsdis the start duration (auto-detected from the minimum integer key present: 0 for CMI/UK-style tables such as AM92/AF92, 1 for most others). Must be ≥ 1. Required whenselect=True.grid_years (list[int] or None, optional) – Calendar years present in a year-indexed MI grid (
mi_m_YYYY/mi_f_YYYYcolumns). WhenNone(default), inferred automatically from column names.
Notes
- Deterministic on-disk format:
Saved files include a SHA-256 integrity fingerprint computed over a canonical representation of data and metadata, so identical logical content always produces the same hash across platforms and Python versions.
- Uniform internal representation:
Regardless of the input type (
polars.DataFrame,pandas.DataFrame, ordict), all data is converted to and stored as apolars.DataFrameafter initialization, enabling uniform vectorized hashing and serialization for all input forms.- Metadata and column-order preservation:
The metadata
"columns"field records the explicit column order used when saving so loaders can restore the exact same order on load.- Auditable scaling:
Public scale factors (decrement_scale_factor, mi_scale_factor) are recorded in
applied_*metadata fields when applied;get_unscaled_data()reverses applied scaling without mutating the instance.
See also
read_tableRead a .ltk file and return a (DataFrame, metadata) pair.
TableBuilder.from_fileLoad a
TableBuilderinstance from a .ltk file.TableBuilder.from_payloadReconstruct a
TableBuilderfrom a payload dict.
Examples
Create and save from a Polars DataFrame:
>>> import polars as pl >>> from lactuca import TableBuilder, read_table >>> df = pl.DataFrame({"qx_m": [0.01, 0.02]}) >>> tb = TableBuilder(df, table_name="mortality", table_type="life", omega=1) >>> tb.validate() True >>> tb.save(file_name="mortality.ltk", path="/path/to/tables", overwrite=True)
Load a table from disk:
>>> tb2 = TableBuilder.from_file("mortality.ltk", path="/path/to/tables", strict=True)
Read payload without strict integrity enforcement:
>>> df_loaded, meta = read_table("mortality.ltk", path="/path/to/tables", validate=False)
Work with unscaled (original) values:
>>> original = tb.get_unscaled_data()
Quick summary for logging:
>>> print(tb.summary())
- classmethod from_file(file_name: str | PathLike[str], path: str | Path | None = None, strict: bool = True) TableBuilder#
Load a
TableBuilderinstance from a .ltk file on disk.When
strict=True, integrity or metadata problems raiseValueError; whenstrict=False, best-effort recovery is attempted and warnings are emitted instead of exceptions.- Parameters:
file_name (str or os.PathLike) – Filename (may include or omit the .ltk extension). Accepts
pathlib.Path/pathlib.WindowsPath.path (Union[str, Path, None], optional) – Directory to look for the file; when
Nonethe configured tables_path is used.strict (bool, optional) – When
Trueintegrity/metadata problems raise ValueError; whenFalsebest-effort recovery is attempted.
- Returns:
Initialized instance with
data(polars.DataFrame) and metadata restored.- Return type:
- Raises:
FileNotFoundError – If the file does not exist at the resolved path.
ValueError – If the file does not have a valid
.ltksignature; if a parsing or integrity (hash mismatch) error occurs whenstrict=True; if critical metadata fields (table_name,table_type,omega) are missing or have invalid values; ifgenerational_formula_typeis not a recognised literal; or ifomegaorstart_ageare not non-negative integers.
See also
TableBuilder.savePersist a
TableBuilderinstance to a .ltk file.read_tableRead a .ltk file and return a (DataFrame, metadata) pair.
TableBuilder.from_payloadReconstruct a
TableBuilderinstance from a payload dict.
Examples
Load a table in strict mode (raises on integrity errors):
>>> from lactuca import TableBuilder >>> tb = TableBuilder.from_file("PERM2000P.ltk", strict=True)
Load a table with best-effort recovery:
>>> from lactuca import TableBuilder >>> tb = TableBuilder.from_file("PERM2000P.ltk", strict=False)
- classmethod from_payload(payload: dict, *, strict: bool = True) TableBuilder#
Reconstruct a
TableBuilderfrom a payload dict.Accepts any dict with a
"data"key — including a dict obtained by deserializing a.ltkfile or a manually constructed payload. Column names are lower-cased, year-keyed MI sub-dicts and select-duration sub-dicts are expanded into flat columns, and all remaining keys are treated as metadata fields forwarded to__init__()for validation.- Parameters:
payload (dict) – A dict containing at least a
"data"key whose value is a column dict, a list of row dicts, or apandas.DataFrame/polars.DataFrame. All remaining keys are treated as metadata fields (table_name,table_type,generational,base_year,omega,start_age,select,select_period,grid_years,decrement_scale_factor,mi_scale_factor,sex_independent,generational_formula_type,description).strict (bool, optional) – When
True(default),validate()runs in strict mode during construction (any issue raisesValueError). WhenFalse, validation issues emitUserWarningand construction still returns the instance.
- Returns:
A fully initialized instance with
datarestored as apolars.DataFrameand all metadata attributes set.- Return type:
- Raises:
TypeError – If
payloadis not a dict, or ifpayload["data"]has a type that cannot be converted to apolars.DataFrame.ValueError – If
payloaddoes not contain a"data"key; if nested column dicts remain after all expansion steps (indicates mismatchedselect/select_periodmetadata); or if the resultingpolars.DataFramecannot be constructed from the data.
See also
TableBuilder.from_fileLoad a
TableBuilderdirectly from a.ltkfile on disk.TableBuilder.savePersist a
TableBuilderto a.ltkfile.read_tableLow-level reader returning
(DataFrame, metadata)pair.
Examples
Round-trip via payload dict:
>>> import polars as pl >>> from lactuca import TableBuilder >>> df = pl.DataFrame({"qx_m": [0.001, 0.002, 0.003]}) >>> tb = TableBuilder(df, table_name="TestLife", table_type="life", omega=2) >>> payload = {"data": df, "table_name": "TestLife", "table_type": "life", "omega": 2} >>> tb2 = TableBuilder.from_payload(payload)
- get_unscaled_data() DataFrame#
Return the table data with applied public scale factors reversed.
Any previously applied decrement and MI (mortality improvement) scale factors are undone so the values reflect the original (unscaled) rates.
The
TableBuilderinstance is not modified.- Returns:
The table data as a
polars.DataFramewith decrement and MI columns multiplied by the corresponding applied scale factors to restore original values. When no scaling was applied, aclone()ofself.datais returned so callers cannot mutate the instance.- Return type:
polars.DataFrame
See also
TableBuilder.savePersist table with applied scale factors.
TableBuilderMain class documenting scale factor behavior.
Examples
Get original unscaled data:
>>> from lactuca import TableBuilder >>> tb = TableBuilder.from_file("PERM2000P.ltk") >>> original_df = tb.get_unscaled_data() >>> print(original_df["qx_m"])
- head(n: int = 10, include_age: bool = True, show_normalized: bool = False) DataFrame#
Return the first n rows of meaningful table data, optionally with age column.
By default, excludes normalized rows. Use show_normalized=True to include padding.
- Parameters:
n (int, default 10) – Number of rows to return.
include_age (bool, default True) – If True, prepend an ‘age’ column.
show_normalized (bool, default False) – If True, start from age 0 (including normalized padding). If False (default), start from start_age (meaningful data only).
- Returns:
The first n rows. If include_age=True, includes an ‘age’ column.
- Return type:
polars.DataFrame
See also
TableBuilder.tailView last n rows.
TableBuilder.view_dataView full table data.
Examples
View first 5 meaningful rows (default):
>>> from lactuca import TableBuilder >>> tb = TableBuilder.from_file("GAM71.ltk") >>> print(tb.head(5)) shape: (5, 3) ┌─────┬──────────┬──────────┐ │ age │ qx_f │ qx_m │ ├─────┼──────────┼──────────┤ │ 5 │ 0.000234 │ 0.000456 │ │ 6 │ 0.000193 │ 0.000424 │ │ 7 │ 0.000162 │ 0.000403 │ │ 8 │ 0.000143 │ 0.000392 │ │ 9 │ 0.000132 │ 0.000391 │ └─────┴──────────┴──────────┘
- save(file_name: str | PathLike[str] | None = None, path: str | Path | None = None, overwrite: bool = False) None#
Persist the
TableBuilderinstance to a .ltk file atomically with an integrity hash.- Parameters:
file_name (str, os.PathLike, or None, optional) – Target filename. If
None, thetable_nameattribute is used. Acceptspathlib.Path/pathlib.WindowsPath. Must be a valid filename (no path separators or invalid characters).path (str, path-like, or None, optional) – Directory in which to save the file. Accepts
pathlib.Path. IfNonethe configuredConfig.tables_pathis used. The resolved directory must be inside the configured tables path.overwrite (bool, optional) – When False (default) an existing file with different content causes FileExistsError. When True, existing files (including malformed .ltk files) will be replaced.
- Return type:
None
- Raises:
ValueError – If the table fails pre-save validation (see
validate()); if the provided filename is invalid; or if an existing file has an invalidTableBuildersignature andoverwriteisFalse.FileExistsError – If a file already exists with different content and
overwriteisFalse.OSError – For unexpected filesystem errors during atomic write/replace.
Notes
Each .ltk file contains a deterministic SHA-256 integrity hash computed over the payload data and metadata.
The
sha256field covers all metadata (excluding the hash itself) and the data, ensuring reproducible integrity checks across platforms.The save is performed atomically to prevent partial writes.
A
UserWarningis emitted after each completed write ("Table saved to <path>"); non-fatal conditions (file already up-to-date, replacing malformed file whenoverwrite=True) are also reported viaUserWarning.
See also
TableBuilder.from_fileLoad a
TableBuilderinstance from a .ltk file.read_tableRead a .ltk file and return a (DataFrame, metadata) pair.
TableBuilder.validateValidate the table before saving.
Examples
Save with default filename (table_name):
>>> from lactuca import TableBuilder >>> import polars as pl >>> df = pl.DataFrame({"qx_m": [0.01, 0.02]}) >>> tb = TableBuilder(df, table_name="Test", table_type="life", omega=1) >>> tb.save(path="/path/to/tables")
Save with explicit filename and allow overwrite:
>>> tb.save(file_name="custom_name.ltk", path="/path/to/tables", overwrite=True)
- summary() str#
Return a human-readable, multi-line summary of the table metadata and key statistics.
The summary includes:
Table label (type and name)
Valid sexes
Age range (
start_age–omega) with explicitstart_ageandomegavaluesWhether the table is sex-independent (only when
sex_independent=True)Applied decrement scale factor (with brief explanation when different from 1); for generational tables, also the MI (mortality improvement) scale factor
Whether the table is generational and, if so,
base_yearand generational formula typeOptional
descriptionwhen present
- Returns:
A multi-line string intended for display in logs, REPLs or reports. The method does not modify the
TableBuilderinstance.- Return type:
str
See also
TableBuilder.view_dataView the underlying table data as a DataFrame.
Examples
Display detailed table summary:
>>> from lactuca import TableBuilder >>> tb = TableBuilder.from_file("PERM2000P.ltk") >>> print(tb.summary()) Life Table: PERM2000P Valid sexes: m, f Age range: 0–120 (start_age=0, omega=120) ...
- tail(n: int = 10, include_age: bool = True, show_normalized: bool = False) DataFrame#
Return the last n rows of the table data, optionally with age column.
Always shows rows ending at omega. Use show_normalized parameter to control which range is considered.
- Parameters:
n (int, default 10) – Number of rows to return from the end of the table.
include_age (bool, default True) – If True, prepend an ‘age’ column showing ages ending at omega.
show_normalized (bool, default False) – If True, consider full normalized range (0 to omega). If False (default), consider only meaningful range (start_age to omega).
- Returns:
The last n rows. If include_age=True, includes an ‘age’ column.
- Return type:
polars.DataFrame
See also
TableBuilder.headView first n rows.
TableBuilder.view_dataView full table data.
Examples
View last 5 rows (default, always from meaningful data):
>>> from lactuca import TableBuilder >>> tb = TableBuilder.from_file("GAM71.ltk") >>> print(tb.tail(5)) shape: (5, 3) ┌─────┬──────────┬──────────┐ │ age │ qx_f │ qx_m │ ├─────┼──────────┼──────────┤ │ 106 │ 0.716944 │ 0.640345 │ │ 107 │ 0.760237 │ 0.695679 │ │ 108 │ 0.80763 │ 0.757522 │ │ 109 │ 0.860909 │ 0.806309 │ │ 110 │ 0.999999 │ 0.999999 │ └─────┴──────────┴──────────┘
- validate(strict: bool = True) bool#
Validate the
TableBuilderinstance for correctness and consistency.- Parameters:
strict (bool, optional) – If True (default) any validation failure raises a ValueError. If False, validation issues are reported via UserWarning and the method returns False rather than raising.
- Returns:
True when all validation checks pass. If
strictisFalseand issues are found, returnsFalse.- Return type:
bool
- Raises:
ValueError – Raised when
strictisTrueand a validation check fails.
Notes
The validation covers:
No null values in columns.
Column naming conventions for the configured
table_type.Value ranges for decrement columns (must be in \([0, 1]\)) and MI columns (must be non-negative).
Consistency requirements for generational tables (
base_year,generational_formula_type, MI columns present for each valid sex).Scale factors that are positive, non-zero, and a power of 10 (1, 10, 100, …).
Generational tables and \(q_x\) validity
For generational tables (
generational=True) the validator additionally evaluates the configured improvement formula to verify that generated decrement probabilities remain finite and within the unit interval \([0, 1]\).For cohort-based formulas (
exponential_improvement,linear_improvement,discrete_improvement), representative cohorts (base year, base year + 100, base year + 250, base year + 500) are tested for each sex. Cohorts strictly before the base year are skipped because all three formulas clamp improvement to zero for those ages, leaving base rates unchanged — these are already verified by the decrement range check above. Forprojected_improvement, a single last-grid-year stress check is used instead (no cohort-based iteration).In strict mode (
strict=True) any out-of-range or non-finite value raisesValueError. In non-strict mode aUserWarningis emitted and the method returnsFalse.Use this method before saving or publishing tables to ensure the table follows project conventions and actuarial expectations.
See also
TableBuilder.savePersist validated table to disk.
TableBuilderMain class for building and validating actuarial tables.
Examples
Validate in strict mode (raises on errors):
>>> import polars as pl >>> from lactuca import TableBuilder >>> df = pl.DataFrame({"qx_m": [0.01, 0.02]}) >>> tb = TableBuilder(df, table_name="Test", table_type="life", omega=1) >>> tb.validate(strict=True) True
Validate with warnings instead of exceptions:
>>> is_valid = tb.validate(strict=False) >>> if not is_valid: ... print("Table has validation issues (see warnings)")
- view_data(include_age: bool = True, show_normalized: bool = False) DataFrame#
Return a copy of the table data for viewing, optionally with age column.
By default, excludes zero-padded rows for ages below
start_age. Use show_normalized=True to see all rows.- Parameters:
include_age (bool, default True) – If True, prepend an ‘age’ column showing ages.
show_normalized (bool, default False) – If True, show all rows including normalized padding (ages 0 to start_age-1). If False (default), show only meaningful data (ages start_age to omega).
- Returns:
Table data. If include_age=True, includes an ‘age’ column as the first column.
- Return type:
polars.DataFrame
See also
TableBuilder.headView first n rows with optional age column.
TableBuilder.tailView last n rows with optional age column.
TableBuilder.start_ageGet the starting age from original table definition.
Examples
View meaningful data only (default, excludes normalized padding):
>>> from lactuca import TableBuilder >>> tb = TableBuilder.from_file("GAM71.ltk") >>> df = tb.view_data() >>> print(df) shape: (106, 3) ┌─────┬──────────┬──────────┐ │ age │ qx_f │ qx_m │ ├─────┼──────────┼──────────┤ │ 5 │ 0.000234 │ 0.000456 │ │ 6 │ 0.000193 │ 0.000424 │ │ ... │ ... │ ... │ └─────┴──────────┴──────────┘
View all rows including normalized padding:
>>> df_all = tb.view_data(show_normalized=True) >>> print(df_all.head(6)) shape: (6, 3) ┌─────┬──────────┬──────────┐ │ age │ qx_f │ qx_m │ ├─────┼──────────┼──────────┤ │ 0 │ 0.0 │ 0.0 │ │ 1 │ 0.0 │ 0.0 │ │ 2 │ 0.0 │ 0.0 │ │ 3 │ 0.0 │ 0.0 │ │ 4 │ 0.0 │ 0.0 │ │ 5 │ 0.000234 │ 0.000456 │ └─────┴──────────┴──────────┘
- property start_age: int#
First age for which the table contains meaningful data.
Returns the starting age as originally defined, regardless of the internal data layout. For example, GAM71 starts at age 5 and GAM94 starts at age 1;
view_data()andhead()default to showing data from this age onward.- Returns:
First meaningful age in the table (0 when the table covers age 0).
- Return type:
int
See also
TableBuilder.view_dataBy default shows rows from
start_agetoomega.TableBuilder.headView the first n rows from
start_age.
Examples
>>> from lactuca import TableBuilder >>> tb = TableBuilder({"qx_m": [0.01, 0.02]}, table_name="Test", ... table_type="life", omega=6, start_age=5) >>> tb.start_age 5
- property tables_path: str#
Return the absolute path to the configured tables directory.
The value is taken from the global
Configsingleton. Treat this property as read-only.- Returns:
Absolute path to the directory where actuarial table files are stored.
- Return type:
str
See also
Config.tables_pathUnderlying configuration setting.
Examples
>>> from lactuca import TableBuilder >>> tb = TableBuilder.from_file("GAM71.ltk") >>> print(tb.tables_path) /absolute/path/to/actuarial_tables
read_table#
Lightweight function for loading a .ltk file without building a full TableBuilder
instance. Returns a (pl.DataFrame, dict) pair — a Polars DataFrame with the table
data and a dict of normalised metadata — and supports both strict integrity-checking
mode and best-effort recovery mode.
- lactuca.read_table(file_name: str | PathLike[str], path: str | Path | None = None, validate: bool = False) tuple[DataFrame, dict]#
Read a .ltk file and return a (DataFrame, metadata) pair.
This function loads a
TableBuilder.ltk file produced byTableBuilder.save(). Behavior depends on thevalidateflag:validate=True(strict mode):The file must exist and have a valid
.ltksignature; otherwiseValueErroris raised.Parsing, structural, or SHA-256 integrity errors raise
ValueError.Returns a Polars DataFrame (scaled per metadata) and a normalized metadata dict (string values, lowercase keys).
validate=False(non-strict mode):Invalid file signatures still raise
ValueError.Non-critical parsing or integrity issues emit
UserWarningand may return an empty DataFrame with minimal metadata.Hash mismatches or a missing
sha256metadata field emitUserWarningbut do not raise.Metadata normalization and legacy scaling are applied where possible.
- Parameters:
file_name (str or os.PathLike) – Name of the .ltk file (may omit extension; must not include path separators). Accepts
pathlib.Path/pathlib.WindowsPath.path (str, path-like, or None, optional) – Directory in which to look for the file. Accepts
pathlib.Path. IfNone, the configuredConfig.tables_pathis used.validate (bool, optional) – When
True, integrity and parsing errors raise ValueError. WhenFalse, the function attempts best-effort recovery and emits warnings instead of raising for non-critical errors; missingtable_nameortable_typein metadata always raise regardless.
- Returns:
A tuple (df, metadata) where
dfis apolars.DataFrame(scaled according to metadata) andmetadatais a normalized dict with string values and lowercase keys.- Return type:
tuple[pl.DataFrame, dict]
- Raises:
TypeError – If file_name is not a string.
ValueError – If file extension is invalid, signature invalid, critical metadata fields are missing, or (when validate=True) parsing/hash/metadata errors occur.
FileNotFoundError – If the specified file does not exist at the resolved path.
- Warns:
UserWarning – When validate=False, emitted on hash mismatches, missing SHA-256, or parsing errors.
Notes
Invalid file signatures raise
ValueErrorbefore parsing begins.When
validate=True, SHA-256 integrity mismatches raiseValueError.When
validate=False, non-critical integrity or parsing issues emitUserWarningand may return an empty DataFrame with minimal metadata.Returned metadata keys are normalized to lowercase strings.
See also
TableBuilder.saveSave a table as a .ltk file with integrity hashing.
TableBuilder.from_fileLoad a
TableBuilderinstance from a .ltk file.TableBuilderMain class for building and validating actuarial tables.
Examples
Read a table in strict mode (raises on errors):
>>> from lactuca import read_table >>> df, meta = read_table("PERM2000P.ltk", validate=True) >>> print(meta["table_name"]) PERM2000P
Read a table in recovery mode (warns on errors):
>>> from lactuca import read_table >>> df, meta = read_table("PERM2000P.ltk", validate=False) >>> if df.is_empty(): ... print("Failed to read table")