> ## Documentation Index
> Fetch the complete documentation index at: https://docs.canary.effectiveai.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Fit relativities from experience

> Fit territory relativities to claim experience with a GLM and build a proposed program with them.

This guide fits collision territory relativities to synthetic claim experience, writes them into the example program's territory table and builds a proposed program. Fitting needs the `models` extra: `pip install "relativities[models]"`.

```python theme={null}
from decimal import Decimal as D
import numpy as np
import pandas as pd
from relativities import Program
from relativities.models import FactorModel

current = Program.load("effective://example-ca-ppa")
book = current.example_book
territories = current.rater.tables["TerritoryFactor"]
collision = territories[territories.coverage == "collision"]
len(collision)
# 17
```

## Prepare the experience

A frequency model reads one row per risk: its level of each rating factor, its earned exposure and its claim count. This page simulates five years of collision experience with a seeded generator, so the example runs alone. The simulated frequency is higher than the current factors in urban territories and lower elsewhere:

> [accordion] Simulate the experience
>
> `truth` and the simulation are ordinary Python and pandas:
>
> ```python theme={null}
> rng = np.random.default_rng(2026)
> truth = {t: float(f) * (1.2 if "urban" in t else 0.9) for t, f in zip(collision.territory, collision.factor)}
> experience = pd.DataFrame({"territory": rng.choice(list(truth), 60000), "exposure": rng.uniform(0.5, 1.0, 60000)})
> experience["claims"] = rng.poisson(0.05 * experience.exposure * experience.territory.map(truth))
> int(experience.claims.sum())
> # 2512
> ```

## Fit the relativities

Fit a Poisson frequency model with territory as its factor and Los Angeles urban as the base level:

```python theme={null}
model = FactorModel(family="poisson", factors=["territory"], target="claims", exposure="exposure",
                    base={"territory": "la_urban"})
fitted = model.fit(experience)
fitted.relativities[["level", "relativity", "lower", "upper", "weight"]].head(4).round(3)
#              level  relativity  lower  upper    weight
# 0     bay_suburban       0.749  0.619  0.906  2634.557
# 1        bay_urban       0.751  0.621  0.908  2642.856
# 2    central_coast       0.439  0.350  0.551  2613.454
# 3  desert_mountain       0.378  0.298  0.479  2666.240
```

Each relativity compares a territory with the base. `lower` and `upper` bound its 95% interval, and `weight` is its earned exposure. The model sums the rows into one cell per combination of levels before it fits, so a book of millions of rows fits in seconds.

Add more columns to `factors` to fit several rating factors together. `offset` names a column of multipliers to hold fixed, such as the current relativities of the other factors. The fit then measures what they leave unexplained. For claim severity, use `family="gamma"` with one row per claim and its amount as the target.

A level with zero claims would get a relativity of zero, so `fit` stops and names each such level. Group it with a neighboring level and fit again.

## Build the proposed program

`table` gives one factor's relativities as exact `Decimal`s, rounded half up, with the base at exactly 1. Levels keep their type from the data, and a rater's table keys are text, so convert numeric levels such as territory codes with `astype(str)` first:

```python theme={null}
table = fitted.table("territory", scale=3)
anchor = collision.set_index("territory").factor["la_urban"]
proposed_factors = dict(zip(table.territory, (r * anchor for r in table.relativity)))
```

Scale the relativities to keep the base territory at its current factor. Write them into the collision rows of the rater's table, and build the proposed program:

```python theme={null}
edited = territories.copy()
rows = edited.coverage == "collision"
edited.loc[rows, "factor"] = edited.loc[rows, "territory"].map(proposed_factors).map(lambda f: f.quantize(D("0.01")))
edited[rows].head(4)
#           territory   coverage factor
# 5          la_urban  collision   1.60
# 11      la_suburban  collision   1.40
# 17   orange_ventura  collision   0.93
# 23    inland_empire  collision   0.96

proposed = current.with_rater(current.rater.with_tables({"TerritoryFactor": edited}))
```

`with_tables` compiles the edited table, so every key the rater looks up stays covered. [Compare programs with a market](https://docs.canary.effectiveai.app/relativities/how-to/create-a-market.md) rates `current` and `proposed` on the same book and shows each policy's change. To keep total premium at the indicated level, search the collision base rate with `search_lever`, as [Model a commercial book](https://docs.canary.effectiveai.app/relativities/how-to/model-commercial-books.md) shows for a class rate.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.