Deploy#

export_rating_tables() writes the deployment rating tables for the fitted model; rating_table_payload() builds the renderer-independent payload behind them, for when you need the tables as objects rather than files. The how-to on deploying a fitted model shows the export end to end.

export_rating_tables

Export deployment rating tables for the fitted model.

rating_table_payload

Build the renderer-independent deployment rating-table payload.

Example#

The same simulated motor book as the rest of this section: 4,000 policies, a non-linear age effect, four regions and a mild vehicle-power slope, with exposure carried as a log offset. Export needs the fitting frame back, because the tables carry the weight behind every band.

import numpy as np
import pandas as pd

from superglm import Categorical, Numeric, Spline, SuperGLM

rng = np.random.default_rng(0)
n = 4000
region = rng.choice(["North", "South", "East", "West"], n, p=[0.35, 0.3, 0.2, 0.15])
age = rng.uniform(18, 80, n)
veh_power = rng.uniform(4, 12, n)
exposure = rng.uniform(0.1, 1.0, n)
region_effect = pd.Series(region).map(
    {"North": 0.0, "South": 0.25, "East": -0.2, "West": 0.4}
).to_numpy()
log_rate = (
    -1.2
    + 1.1 * np.exp(-((age - 24) ** 2) / 90.0)
    + 0.004 * (age - 50) ** 2 / 10.0
    + region_effect
    + 0.09 * (veh_power - 8)
)
claims = rng.poisson(np.exp(log_rate) * exposure)
X = pd.DataFrame({"age": age, "region": region, "veh_power": veh_power})
offset = np.log(exposure)

model = SuperGLM(
    family="poisson",
    features={
        "age": Spline(kind="cr", k=10),
        "region": Categorical(),
        "veh_power": Numeric(),
    },
).fit_reml(X, claims, offset=offset)
model.reml_diagnostics()["converged"]
True

rating_table_payload is the objects behind the workbook. Each main effect becomes a block that knows its own shape: the age smooth is banded into a continuous grid, region stays categorical, the numeric power term exports as a single per-unit factor, and the log offset becomes its own multiplier block. The base relativity is the rest of the rate — what a risk pays before any factor applies.

payload = model.rating_table_payload(X, claims, offset=offset)
print(
    f"base relativity {payload.base_relativity:.4f}, "
    f"{payload.selected_n_bins} bands chosen for the banded curves"
)
pd.DataFrame(
    [
        {"block": block.name, "kind": block.kind, "rows": len(block.table)}
        for block in payload.main_effects
    ]
)
base relativity 0.2042, 150 bands chosen for the banded curves
block kind rows
0 age continuous 150
1 region categorical 4
2 veh_power numeric 1
3 Offset Multiplier offset_per_unit 1

A block’s table is an ordinary DataFrame, keyed by the rating value with the relativity and the fitting weight behind it — one per row here, because no sample_weight was passed. This is the table a rater keys on.

region_block = next(
    block for block in payload.main_effects if block.name == "region"
)
region_block.table.round(3)
region Relativity Weight
0 East 0.805 816.0
1 North 1.000 1396.0
2 South 1.435 1209.0
3 West 1.493 579.0

Banding a smooth curve is an approximation, and the payload measures it rather than asserting it: discretization_impact refits at each candidate band count and reports what the banded model does to the deviance and to individual predictions. The exported grid changes the deviance by about a hundredth of a percent and no single prediction by more than two percent.

payload.discretization_impact[
    [
        "feature",
        "n_bins",
        "exported",
        "deviance_change_pct",
        "max_abs_prediction_change_pct",
    ]
].round(3)
feature n_bins exported deviance_change_pct max_abs_prediction_change_pct
0 age 20 False 0.075 11.096
1 age 50 False 0.026 4.684
2 age 100 False 0.017 2.472
3 age 150 True -0.011 1.722
4 age 200 False -0.009 1.687
5 age 250 False 0.007 1.285

bin_strategy="exact" places bands where the curve moves rather than where the exposure is. It uses the fewest bands that keep every value’s band average within one standard error of the fitted curve, and never more than 10% from it (band_se=1.0, band_max_error=0.10), with n_bins as the maximum. When the maximum is too small, the limit widens by the least factor that fits: the export warns, and the impact sheet’s band_* columns and discretization_impact(...).band_diagnostics report it. The limit holds at the observed values the bands are placed on. A band holding only the largest value is written as a closed key, [x, x]. A model carrying editor edits, or a term with a post-fit shape repair, is refused, because its standard errors are not the published curve’s.

export_rating_tables writes the same payload as an Excel workbook: one sheet of rating tables laid out side by side for a rater to key on, one for the discretization impact above, and one for the model summary. The format comes from the file suffix, so the path must name an .xlsx file rather than a directory.

import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as folder:
    workbook = Path(folder) / "rating_tables.xlsx"
    model.export_rating_tables(workbook, X, claims, offset=offset)
    with pd.ExcelFile(workbook) as book:
        print(workbook.name, "->", book.sheet_names)
rating_tables.xlsx -> ['Rating Tables', 'Discretization Impact', 'Model Summary']