Frequency-Severity Modelling

This tutorial covers compound distribution modelling in PAL, where total losses are the sum of a random number (frequency) of random amounts (severity).

Setup

import numpy as np

from pal import config, distributions, set_random_seed
from pal.frequency_severity import FrequencySeverityModel

config.n_sims = 10_000
set_random_seed(42)

1. Creating a Frequency-Severity Model

A FrequencySeverityModel combines a frequency distribution (how many events) with a severity distribution (how large each event is):

model = FrequencySeverityModel(
    freq_dist=distributions.Poisson(mean=50),
    sev_dist=distributions.LogNormal(mu=10, sigma=1.5),
)
events = model.generate()

This generates individual events across all simulations. With 10,000 simulations and an average of 50 events each, you get roughly 500,000 individual events:

Total events:            499,780
Events per sim (first 5): [56, 59, 56, 41, 47]

The result is a FreqSevSims object that stores both event values and simulation indices — it knows which events belong to which simulation.

2. Aggregation and Occurrence

Aggregate Loss

Sum all events within each simulation to get the total loss per year:

agg = events.aggregate()
Aggregate mean:       3,373,050
Aggregate std:        1,420,195
Aggregate 99.5th:     9,390,666

aggregate() returns a StochasticScalar — the same type as any other simulated variable in PAL. You can use it for statistics, copulas, arithmetic, and plotting.

Occurrence (Maximum Event)

Find the largest single event in each simulation:

occ = events.occurrence()
Occurrence mean:        837,677
Occurrence std:         875,484
Occurrence 99.5th:    5,533,691

This is useful for pricing per-occurrence reinsurance layers.

Coupling Groups

Both aggregate() and occurrence() are automatically coupled with the underlying FreqSevSims events. If the events are reordered by a copula, the aggregate and occurrence are reordered together:

len(agg.coupled_variable_group)
# => 7  (events, freq, sev, agg, occ, and intermediates)

3. Arithmetic on Events

FreqSevSims objects support standard arithmetic and numpy operations. Each operation applies element-wise to the individual event values.

Capping Individual Losses

capped_events = np.minimum(events, 500_000)
capped_agg = capped_events.aggregate()
Capped at 500k:
  Aggregate mean:     2,902,038
  Aggregate 99.5th:   5,202,702

Capping reduces both the mean and tail because large events are truncated.

Stochastic Inflation

Multiply by a simulation-level inflation factor. PAL automatically broadcasts the StochasticScalar (one value per simulation) across all events in that simulation:

inflation = distributions.Normal(0.05, 0.02).generate()
inflated = events * (1 + inflation)
With 5% stochastic inflation:
  Aggregate mean:     3,541,639

Other Operations

# Deductible per event
excess = np.maximum(events - 100_000, 0)

# Scale by a factor
scaled = events * 1.10

# Conditional logic
large_only = np.where(events > 200_000, events, 0)

All of these return new FreqSevSims objects that are automatically coupled with the originals.

4. Choosing the Frequency Distribution

Poisson

The standard choice for claim counts. Variance equals the mean:

model = FrequencySeverityModel(
    freq_dist=distributions.Poisson(mean=25),
    sev_dist=distributions.LogNormal(mu=10, sigma=1.5),
)
Aggregate mean:     1,689,715
Aggregate std:      1,001,221
Aggregate 99.5th:   6,020,593

Negative Binomial

Use when claim counts are over-dispersed (variance > mean). This happens when there is uncertainty in the underlying rate, or heterogeneity across risk units:

model = FrequencySeverityModel(
    freq_dist=distributions.NegBinomial(n=25, p=0.5),
    sev_dist=distributions.LogNormal(mu=10, sigma=1.5),
)
Aggregate mean:     1,689,863
Aggregate std:      1,067,447    (higher than Poisson!)
Aggregate 99.5th:   6,243,797    (fatter tail)

Both models have the same mean (~25 events), but the Negative Binomial produces a wider distribution of aggregate losses because the claim count itself is more variable.

5. Common Modelling Patterns

Attritional + Large Loss Split

Model small frequent losses separately from rare large losses:

set_random_seed(42)

# Small frequent claims
attritional = FrequencySeverityModel(
    freq_dist=distributions.Poisson(mean=500),
    sev_dist=distributions.LogNormal(mu=8, sigma=0.5),
).generate()

# Rare large claims
large = FrequencySeverityModel(
    freq_dist=distributions.Poisson(mean=3),
    sev_dist=distributions.Pareto(shape=1.5, scale=1_000_000),
).generate()

total = attritional.aggregate() + large.aggregate()

Loss Development / Expense Loading

Apply multiplicative loadings after generating events:

events = FrequencySeverityModel(
    freq_dist=distributions.Poisson(mean=50),
    sev_dist=distributions.LogNormal(mu=10, sigma=1.5),
).generate()

# Development factor
developed = events * 1.05

# Loss adjustment expenses
with_lae = developed * 1.08

# Final aggregate
net_agg = with_lae.aggregate()

Feeding into Reinsurance Contracts

FreqSevSims objects are the natural input to XoL and XoLTower:

from pal.contracts import XoL

layer = XoL(
    name="1m xs 500k",
    limit=1_000_000,
    excess=500_000,
    premium=30_000,
)
result = layer.apply(events)
recoveries = result.recoveries.aggregate()

See the XoL Reinsurance tutorial for full details.

6. Applying Copulas to FreqSev Results

After aggregation, you can link frequency-severity results with other variables using copulas:

from pal import copulas

set_random_seed(42)
motor = FrequencySeverityModel(
    freq_dist=distributions.Poisson(mean=50),
    sev_dist=distributions.LogNormal(mu=10, sigma=1.5),
).generate().aggregate()

property_loss = FrequencySeverityModel(
    freq_dist=distributions.Poisson(mean=20),
    sev_dist=distributions.Pareto(shape=2, scale=50_000),
).generate().aggregate()

copulas.GaussianCopula(
    [[1, 0.6], [0.6, 1]]
).apply([motor, property_loss])

combined = motor + property_loss

See the Coupling Groups and Copulas tutorial for worked examples including how coupling groups ensure consistency across derived variables.

Key Classes

Class

Description

FrequencySeverityModel

Creates compound models from freq + sev distributions

FreqSevSims

Container for event-level simulations with sim indices

StochasticScalar

Simulation-level vector returned by aggregate() / occurrence()

See Also

  • Getting Started — basic PAL concepts

  • Distributions Guide — choosing frequency and severity distributions

  • Pricing an XoL Reinsurance Program — applying reinsurance structures to FreqSev losses