Skip to main content

easy_glm

LASSO GLMs → rate tables → calibrate in a browser → score portfolios.

Python toolkit for insurance pricing: fit a regularised GLM on your data, export per-variable relativities, tweak them in an interactive editor (with A/E charts), and ship a portable .easyglm model for scoring. Built on glum; inspired by R aglm.

pip install git+https://github.com/serband/easy_glm.git
# optional UI: pip install "easy_glm[ui]"

1. Fit a model (one shot)

Most of the time you only need EasyGLM.fit. It builds a design spec (step knots for numeric factors, one-hot with an Other bucket for categoricals), fits an L1-penalised GLM with glum, and reads exact rate tables and a calibrated base rate straight off the coefficients.

Add a traintest column to your data: 1 = train (fitting), 0 = holdout (validation). Pass the full dataframe; only traintest == 1 rows are used to build the spec and fit the GLM.

import easy_glm
import polars as pl
import numpy as np

df = easy_glm.load_external_dataframe()
df = df.with_columns(
    pl.Series("traintest", np.random.rand(len(df)) < 0.7, dtype=pl.Int64)
)

predictors = ["VehAge", "Region", "VehGas", "DrivAge", "BonusMalus", "Density"]

eglm = easy_glm.EasyGLM.fit(
    data=df,
    target="ClaimNb",
    model_type="Poisson",
    predictors=predictors,
    weight_col="Exposure",
    train_test_col="traintest",
    divide_target_by_weight=True,   # frequency = ClaimNb / Exposure
    cv=5,                           # or alpha=0.001 for a quick fit
    monotone={"BonusMalus": "increasing"},   # optional sign constraints
)
print(eglm)

Families: "Poisson", "Gamma", "Tweedie", "Gaussian" (all log link), "Binomial" (logit; no multiplicative tables).

View relativities

Per-variable tables are on eglm.relativities — a dict of Polars frames with from / to (bin edges or level), label, coef, relativity and is_base. Relativity 1.0 sits on the most exposed bin of each variable; the null / Other row is last. eglm.coef_table(drop_zero=True) lists the knots and levels the lasso kept.

print(eglm.relativities["DrivAge"])
print(eglm.coef_table(drop_zero=True))

# Optional: matplotlib charts
easy_glm.plot_all_ratetables(eglm.relativities)

Score

eglm.rate_model is a portable lookup-table scorer. It reproduces the GLM exactly (to floating-point precision), nulls and unseen levels included, and its base rate is calibrated automatically (pass base_rate= to override).

test = df.filter(pl.col("traintest") == 0)
preds = eglm.rate_model.predict(test)          # multiplied by Exposure
freq = eglm.predict(test)                      # GLM, per unit exposure
print(f"Test A/E: {test['ClaimNb'].sum() / preds.sum():.4f}")

eglm.save("my_model")                          # spec + glum model + tables
eglm.rate_model.to_json("model.easyglm")       # scorer only
eglm.to_excel("rate_tables.xlsx")              # Summary, Coefficients, one sheet per variable

Any RateModel — including one edited in the browser and downloaded as .easyglm — exports the same way: RateModel.from_json("revised.easyglm").to_excel("revised.xlsx").

Performance. On the bundled French motor set (~680k rows, 6 predictors) a fixed-alpha fit takes about a second and cv=5 over a 20-point alpha path around 10–20 seconds; peak memory is ~1 GB (the design matrix is dense float64).


2. Building blocks (when you need control)

EasyGLM.fit is three calls you can make yourself:

Step Function What it does
1 DesignSpec.from_data(train_df, predictors) Quantile knots per numeric, frequency-ordered levels per categorical — train only. JSON round-trip; edit by hand.
2 fit_glm(train_df, spec, target, ...) glum L1/elastic-net fit on spec.build(train_df); alpha= or cv=; monotone=
3 rate_tables(fit) / to_rate_model(fit) Exact relativities + base rate from the coefficients
from easy_glm import DesignSpec, fit_glm, rate_tables, to_rate_model

train_df = df.filter(pl.col("traintest") == 1)

spec = DesignSpec.from_data(
    train_df, predictors,
    n_bins=20, min_level_share=0.0025,
    knots={"VehAge": list(range(1, 21))},     # hand-picked knots
    weight_col="Exposure",
)

fit = fit_glm(
    train_df, spec, target="ClaimNb", family="poisson",
    weight_col="Exposure", divide_target_by_weight=True,
    alpha=0.001, monotone={"DrivAge": "decreasing"},
)
print(fit.coef_table(drop_zero=True))

tables = rate_tables(fit)
rm = to_rate_model(fit, exposure_col="Exposure", train_test_col="traintest")
rm.to_json("model.easyglm")

Full script: examples/advanced_pipeline.py.

Upgrading from 0.2? generate_blueprint, prepare_data, fit_lasso_glm, ratetable and generate_all_ratetables still work but are deprecated and will be removed in 0.4 (prepare_data needs pip install "easy_glm[legacy]" for DuckDB). Models saved by 0.2 must be refitted. use_cv=False now requires alpha=: the old fast path silently returned an almost unregularised model.


3. Adjust relativities, save, and score

After fitting, open the relativity editor to review A/E by band, nudge factors, and export a revised model. The fitted baseline is never overwritten — edits live in a working copy until you save or download.

# From a fitted EasyGLM or any RateModel
eglm.rate_model.launch_editor(data=df)   # opens a browser tab; Python keeps running
# or:  rm.launch_editor(data=df)

In the UI

  1. Pick a variable → relativity curve + A/E chart + editable table.
  2. Change Revised relativities; charts update (toggle auto-recompute off on large data).
  3. Download → saves your_name.easyglm, or save named copies in-session.

Score with the saved model (no refit — pure lookup tables):

from easy_glm.engine import RateModel

rm = RateModel.from_json("my_revision.easyglm")

holdout = df.filter(pl.col("traintest") == 0)
premiums_or_freq = rm.predict(holdout)

# Per-variable calibration check
ae = rm.compute_ae_for_variable(new_business, "DrivAge")

Install UI dependencies if needed: pip install "easy_glm[ui]" (Streamlit + Plotly).


4. The Workbench — the whole workflow in the browser

pip install "easy_glm[ui]"
easy-glm-workbench                      # or: python -m easy_glm.app my.easyglm-project.json

An Emblem-style GUI over the same engine. Nine pages, one project file:

Page What you do
Project & data open/save a project, point at parquet / csv / sas7bdat / xlsx, optional sample
Variables roles (target, weight, exposure, offset, split, id, predictor, ignore), renames, type overrides, level recodes, derived columns (polars expressions), row filters
Explore exposure & observed rate by band; leakage report (single-factor deviance explained, target proxies, identifier-like columns, post-outcome names) with one-click ignore / acknowledge
Split indicator column or seeded random split; train/holdout balance
Design per-predictor knots (quantile / integer / custom), null column, level share, monotone direction; exposure + rate preview per bin
Model family, target/weight/offset, penalty (fixed alpha or CV), predictors; fit; coefficients kept; regularisation path
Diagnostics A/E by any variable (in or out of the model, champion vs challenger), lift & Gini, double lift vs a challenger or a premium column, residual factor search
Rate tables relativities with A/E, inline edits saved as adjustments (no refit), Excel / .easyglm download
Export the whole workflow as a runnable Python script (explicit knots, levels, resolved alpha, adjustments), project JSON, artefacts

Everything the GUI does edits a Project spec (easy_glm.workflow) that is autosaved as JSON; the exported script reproduces the GUI model exactly (this is tested). Design notes: docs/WORKBENCH_PLAN.md.


Install (development)

uv venv && uv pip install -e ".[dev,ui]"
# or: python scripts/setup_dev.py

Python 3.10–3.13. Optional extras: [ui], [dev], [viz].


Architecture

Raw data → DesignSpec → fit_glm (glum) → rate_tables / to_rate_model → RateModel (.easyglm)
                                   ↑
                         EasyGLM.fit() runs all of this
Component Role
DesignSpec Feature definitions (step knots, levels); builds the design matrix; JSON
fit_glm / GLMFit Penalised glum fit, coefficient table, predictions
rate_tables / to_rate_model Exact relativities and base rate from coefficients
to_excel / write_rate_tables_xlsx Rate tables as an .xlsx workbook (one sheet per variable)
easy_glm.workflow Project spec, prep steps, leakage report, diagnostics, run_model, to_script
easy_glm.app Streamlit workbench over the workflow engine
EasyGLM One-call fit, save/load full pipeline
RateModel Production scoring, A/E, JSON roundtrip, editor

Package layout, benchmarks, and module map: see AGENTS.md.


Development

black . && ruff check . && pytest -q

See CONTRIBUTING.md.


Roadmap

  • Monotone constraints (monotone={"DrivAge": "decreasing"})
  • Configurable knots / levels per variable (DesignSpec)
  • Two-way interactions (A × B tables)
  • Piecewise-linear (L-dummy) terms
  • CLI (python -m easy_glm build ...)
  • Drag-to-edit relativities (GAMChanger-style)
  • Multi-model A/E comparison in the editor

License

MIT — see LICENSE.

Release files for easy-glm 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for easy-glm 0.3.0
File Size Uploaded
easy_glm-0.3.0.tar.gz 115.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for easy-glm 0.3.0
File Interpreter ABI Platform
easy_glm-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 229.2 kB

Release files / easy_glm-0.3.0.tar.gz

Download URL easy_glm-0.3.0.tar.gz
Size 115.4 kB
Tags Source
SHA-256 checksum
How to use checksums
92c3cd234bfd09868d2712ffdb7397df14d0aea714e4c3a2bfe47897e3373f92
BLAKE2b-256 checksum
How to use checksums
f361cca3e80793f139aac3b7ecfab5fe7624eec0f5c458cc547a7720cbe3d5c7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release files / easy_glm-0.3.0-py3-none-any.whl

Download URL easy_glm-0.3.0-py3-none-any.whl
Size 113.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5324ac42f48e4b0bcaa33cb8d78c92d6969034e948764705a3f21d2d8ec0057d
BLAKE2b-256 checksum
How to use checksums
d17f4b23d6dd5f9fdc5da2d8f874b31da19b88510545506e0b37858b677cb1d2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release history Release notifications | RSS feed

0.471

2 release files

0.470

2 release files

0.464

2 release files

0.463

2 release files

0.462

2 release files

0.461

2 release files

0.460

2 release files

0.452

2 release files

0.451

2 release files

0.4.5

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

This release

0.3.0 This release

2 release files

0.2.2

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page