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 |
|---|---|
|
Creates compound models from freq + sev distributions |
|
Container for event-level simulations with sim indices |
|
Simulation-level vector returned by |
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