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 deployment rating tables for the fitted model. |
|
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']