Skip to main content

ratingmodels

CI PyPI

Actuarial pricing and rate-indication tools for experience-rated insurance portfolios.

ratingmodels covers the group rating workflow — the step that turns experience analysis and loss modeling into an actual rate. It answers the central pricing question: what rate should we charge, and why did it change?

What it does

  • Columns in, columns out — every numeric argument takes a scalar or a column (Series/array) under one contract: scalar in, float out; column in, column out on the same index. A whole book prices in one call, and grouped questions take by=.
  • Credibility — limited fluctuation (square-root rule), Bühlmann, and empirical Bühlmann-Straub with exposure weights.
  • Trend — midpoint-to-midpoint factors; frequency / severity split.
  • Manual rating — base rate × relativities, loaded to a charged rate.
  • Experience rating — pooling of large claims, trend, pooling charge, benefit/demographic adjustments, loading.
  • Rate build-up — an ordered, auditable evaluator (multiply / add-dollar / segment-conditional) with labeled subtotals and a reconciling breakdown, plus two-stream participation blending and additive stream combining (a health book's par/non-par and medical + drug). Supplies the grammar of a manual build-up; the factor values stay yours.
  • Base rate & off-balance — indicated base loss cost from book experience (base × relativities reproduces book losses); off-balance correction and base rebalancing when relativities are revised.
  • Retention & gross-up — charged rate from the fundamental insurance equation (loss & LAE, flat fixed expense, percent-of-premium loads, profit), with the target loss ratio as an output, not an input.
  • Blending & indication — credibility-weighted blend; build-up and loss-ratio indication methods.
  • Rate-change decomposition — multiplicative and percentage-point contribution-to-change with an explicit residual.
  • GLM relativities — Poisson / Gamma / Tweedie GLMs, so factors are estimated jointly (correcting for correlation between rating variables) rather than one-way. Estimation is delegated to statsmodels.GLM; ratingmodels owns the actuarial layer — design encoding, base levels, relativities and their confidence intervals, factor tables — and exposes the fitted statsmodels results as results_ for everything else.
  • GLM diagnostics — deviance / Pearson / standardized residuals, a relativity table with confidence intervals for every level, and deviance explained; the fit is inspectable, not just usable.
  • Frequency–severity modelsFrequencySeverityModel pairs a count GLM and a severity GLM (fit on claims, count-weighted) into one pure-premium model whose relativities are the product of the parts.
  • Credibility-smoothed relativities — one-way factors shrunk toward a prior by Bühlmann–Straub or limited-fluctuation credibility, plus sparse-level collapsing; the actuarial answer to thin cells.
  • Interactions & intervals — categorical x categorical and categorical x continuous interaction terms with treatment coding (fit(..., interactions=[...])), and delta-method confidence intervals for the fitted mean on any frame (predict_interval).
  • Validation — leakage-safe random_split / group_split / temporal_split, calibration and actual-to-expected tables, ordered-Lorenz Gini, lift tables, and a compare_models scorecard for honest out-of-sample comparison.
  • Indication assemblyExperienceExhibit turns per-period premium, losses, and adjustment factors (on-level, trend, development) into the worksheet and the point inputs RateIndication consumes, wired so the gross-up identity holds exactly.
  • Rating plans — the implemented plan as one object (RatingPlan): decomposed per-row build-up, unknown-level policy ("error" or table defaults) with a validate() audit, off-balance diagnostics, dict round-trip for filing, from_model(...) from a fitted GLM or frequency-severity model, and compare_rating_plans for the current-vs-proposed exhibit (summary, dislocation, by-group).
  • On-level factors — the parallelogram method in closed form (on_level_factors): exact piecewise-linear geometry for any policy term, float or datetime rate-change history.
  • Pooling chargespooling_charge_from_severity prices the excess layer above a pooling point from any severity object exposing sf/mean_excess (duck-typed; lossmodels distributions and extremeloss GPD tails both qualify), producing the pooling_charge that experience_rate consumes.
  • Constraints & renewal — rate caps/floors, banding, rounding, corridors, and unit-level re-rating.
  • Rate dislocation — band a book by rate change with premium in each band (rate_dislocation) and quantify what caps and concessions cost against the indication (constraint_impact).
  • Pricing scenarios & margin — evaluate a case at any rate action (issued, post-concession, plan) with the same expense algebra as the gross-up: premium, gross margin (loss tier), margin after retention expense, margin ratio; the closed-form rate for any margin target (zero-margin and plan-target premiums, with the standard indication as the m = profit_margin special case); persistency-weighted expected dollars; tidy long scenario tables (scenario_frame) so cohort rollups and key-case exhibits are pivots of library output; and a closed-form uniform uplift (uplift_for_target_margin) answering "actions must be X% higher to hold the book's target margin."

Dependencies are numpy, pandas, actuarialpy (which supplies the shared credibility primitives), and statsmodels (which supplies GLM estimation).

Installation

pip install ratingmodels

From source:

git clone https://github.com/OpenActuarial/ratingmodels
cd ratingmodels
pip install -e ".[dev]"
pytest

Quick start

import ratingmodels as rm

# --- experience side -------------------------------------------------------
large_claims = [612_000, 340_000, 128_000, 96_500]      # this group's largest claims
capped, excess = rm.pool_claims(large_claims, pooling_point=250_000)
exp = rm.ExperienceRate(
    incurred_claims=4_200_000,
    exposure=9_600,             # exposure units (member-months here)
    trend_annual=0.075,
    trend_years=1.5,            # experience midpoint -> rating midpoint
    pooled_excess=excess,
    pooling_charge=38.00,       # book-level charge for the pooled layer
    target_loss_ratio=0.85,
)

# --- manual side -----------------------------------------------------------
man = rm.ManualRate(
    base_loss_cost=480,
    factors={"area": 1.05, "industry": 0.97, "tier": 1.10},
    target_loss_ratio=0.85,
)

# --- credibility and indication -------------------------------------------
z = rm.limited_fluctuation_credibility(n=9_600, n_full=12_000)

ind = rm.RateIndication(
    experience_loss_cost=exp.loss_cost(),
    manual_loss_cost=man.loss_cost(),
    credibility=z,
    current_rate=520,
    target_loss_ratio=0.85,
    trend_total_factor=exp.trend_factor(),
    benefit_factor=1.00,
    demographic_factor=1.01,
)

print(f"indicated rate   : {ind.indicated_rate():.2f}")
print(f"indicated change : {ind.indicated_rate_change():+.2%}")

# why did the rate move?
print(ind.rate_change_decomposition().to_frame())

# apply a renewal cap
action = rm.renew(current_rate=520, indicated_rate=ind.indicated_rate(), cap=0.05)
print(f"proposed (capped): {action.proposed_rate:.2f} ({action.proposed_change:+.2%})")

Rate build-up with an audit trail

The labels and streams below are one health book's; the engine is domain-agnostic and ships no values.

import ratingmodels as rm

med_par = rm.evaluate([
    rm.start("Par Base Claim Cost", 941.63),
    rm.add("$30 specialist copay", -11.44),
    rm.multiply("Rating Region", 1.083),
    rm.checkpoint("Medical Par Base Claim Cost"),
])
med_par.value          # final running total
med_par.breakdown      # DataFrame: step, operation, label, operand, running_total

# blend in-/out-of-network, then add the drug stream
med = rm.participation_blend(med_par.value, nonpar=1478.56, participation_rate=0.90)
total = rm.combine_streams({"Medical": med, "Drug": 323.67})
total.value            # feeds into trend / credibility / retention

The package supplies the build-up grammar; you supply the factor values (cost-sharing, age/sex, area, ...) from your filed tables. ManualRate is a thin shortcut over this engine, so ManualRate(...).breakdown() returns the same audit trail.

Base rate and retention

import ratingmodels as rm
import pandas as pd

# indicated base loss cost from book experience (off-balance method)
book = pd.DataFrame({
    "exposure": [24_000, 18_000, 30_000, 24_000],
    "area":     [1.00, 1.20, 0.90, 1.05],
    "tier":     [1.00, 1.10, 0.95, 1.25],
    "loss":     [11_750_000, 11_400_000, 12_300_000, 15_200_000],
})
base = rm.base_rate_from_experience(book, "exposure", "loss",
                                    factor_cols=["area", "tier"])
base.base_loss_cost        # average loss cost / average relativity

# gross claims up to a charged rate; the loss ratio falls out
retention = rm.RetentionLoad.from_items(
    fixed_expense=22.0,
    variable_items={"commission": 0.03, "premium_tax": 0.023, "aca_fees": 0.005},
    profit_margin=0.03,
)
retention.gross_rate(540.0)            # charged rate
retention.implied_loss_ratio(540.0)   # target loss ratio (an output)

# rebalance the base when relativities are revised (hold level, then +8%)
rm.rebalance_base_rate(current_base=base.base_loss_cost,
                       current_avg_relativity=1.0928, new_avg_relativity=1.12,
                       overall_change=0.08)

GLM relativities

import ratingmodels as rm
from ratingmodels.datasets import sample_rating_data

df = sample_rating_data(n=20_000)
model = rm.GLMRelativities(family="poisson").fit(
    df, response="claims", predictors=["area", "industry", "tier"],
    exposure="exposure",                 # enters as a log offset
    base_levels={"area": "A"},           # optional; defaults to modal level
)
print(model.base_value_)                 # fitted base frequency
print(model.relativities_["industry"])   # relativity per level, base = 1.0

Scope and honest limitations

This is a modeling and workflow toolkit, not filed rate software. It does not manage rate filings, store filed factor tables with effective dating, or enforce state-specific rating rules. The pooling-charge helper is a simple group-level estimate; a production charge is normally derived book-wide or from an EVT tail model. All bundled data in ratingmodels.datasets is synthetic and carries no assumptions.

License

MIT. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ratingmodels-0.7.0.tar.gz (126.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ratingmodels-0.7.0-py3-none-any.whl (84.9 kB view details)

Uploaded Python 3

File details

Details for the file ratingmodels-0.7.0.tar.gz.

File metadata

  • Download URL: ratingmodels-0.7.0.tar.gz
  • Upload date:
  • Size: 126.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for ratingmodels-0.7.0.tar.gz
Algorithm Hash digest
SHA256 e0f09fcfd21a6bef4906983b85567575b3789ab0e8cba50caf3af35f68137b23
MD5 0d4d356d3052f93624cf47283b570dd1
BLAKE2b-256 46c493bad03c95fcc8751c7e38efa4a4e18ff42345afc8c8f94f848781bd0606

See more details on using hashes here.

Provenance

The following attestation bundles were made for ratingmodels-0.7.0.tar.gz:

Publisher: release.yml on OpenActuarial/ratingmodels

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ratingmodels-0.7.0-py3-none-any.whl.

File metadata

  • Download URL: ratingmodels-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 84.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for ratingmodels-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 087b56c954561c797a8ae7532b3122de7a4827489fa7ce44040fedb5d4f342b7
MD5 aed9b2864c131a59dbc2c85ead50818e
BLAKE2b-256 effacbfeb03f6a31c750be6eacb51550fb7c10a1886ac90e0f7299389bdff9fa

See more details on using hashes here.

Provenance

The following attestation bundles were made for ratingmodels-0.7.0-py3-none-any.whl:

Publisher: release.yml on OpenActuarial/ratingmodels

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page