Source code for lossmodels.aggregate.collective

import numpy as np

from ..frequency.base import FrequencyModel
from ..severity.base import SeverityModel
from ..utils.random import RNGLike, resolve_rng
from .base import AggregateModel


[docs] class CollectiveRiskModel(AggregateModel): """ Collective risk model for aggregate loss: S = X1 + X2 + ... + XN where: - N is the claim count random variable (frequency) - Xi are iid claim severities Assumes: - severities are iid - N is independent of severities """ def __init__(self, frequency: FrequencyModel, severity: SeverityModel): self.frequency = frequency self.severity = severity
[docs] def sample(self, size: int = 1, rng: RNGLike = None) -> np.ndarray: """ Generate random samples of aggregate loss. ``rng`` may be ``None`` (legacy global ``numpy.random`` state), an ``int`` seed, or a ``numpy.random.Generator``; a seed or generator is threaded through both the frequency and severity draws so the whole aggregate simulation is reproducible. Vectorized: all claim counts are drawn at once, then ``counts.sum()`` severities in a single call, and each simulation's aggregate is a segment sum. Because a ``numpy`` generator is a sequential stream, drawing the severities in one call yields the *same* sequence as the former per-simulation loop, so results are unchanged for a given seed -- only much faster for large ``size``. """ if size <= 0: raise ValueError("size must be positive") rng = None if rng is None else resolve_rng(rng) counts = ( self.frequency.sample(size=size) if rng is None else self.frequency.sample(size=size, rng=rng) ) totals = np.asarray(counts).astype(int) total = int(totals.sum()) aggregate_losses = np.zeros(size, dtype=float) if total > 0: draws = ( self.severity.sample(size=total) if rng is None else self.severity.sample(size=total, rng=rng) ) draws = np.asarray(draws, dtype=float) # map each drawn severity to its simulation, then sum per simulation. # Simulations with zero claims contribute no draws and stay 0. sim_index = np.repeat(np.arange(size), totals) aggregate_losses = np.bincount(sim_index, weights=draws, minlength=size).astype(float) return aggregate_losses
[docs] def mean(self) -> float: """ E[S] = E[N] * E[X] """ return self.frequency.mean() * self.severity.mean()
[docs] def variance(self) -> float: """ Var(S) = E[N] Var(X) + Var(N) (E[X])^2 """ en = self.frequency.mean() vn = self.frequency.variance() ex = self.severity.mean() vx = self.severity.variance() return en * vx + vn * (ex ** 2)
def frequency_mean(self) -> float: return self.frequency.mean() def severity_mean(self) -> float: return self.severity.mean()
[docs] def summary(self) -> dict: """ Return a small summary of the model. """ return { "frequency_model": repr(self.frequency), "severity_model": repr(self.severity), "frequency_mean": self.frequency.mean(), "severity_mean": self.severity.mean(), "aggregate_mean": self.mean(), "aggregate_variance": self.variance(), "aggregate_std": self.std(), }
def __repr__(self): return ( f"CollectiveRiskModel(" f"frequency={repr(self.frequency)}, " f"severity={repr(self.severity)})" )