Source code for lossmodels.severity.transformed_gamma

"""Transformed Gamma family of continuous severities (the inverse members).

Klugman (Loss Models, Appendix A.3) parameterizations. The forward members
(Gamma, Weibull, Exponential) already exist in ``lossmodels``; this module adds
their inverses. Parameter -> SciPy mapping (verified against the table moments):

    InverseGamma(alpha, theta)     invgamma(a=alpha, scale=theta)
    InverseWeibull(theta, tau)     invweibull(c=tau, scale=theta)
    InverseExponential(theta)      invweibull(c=1, scale=theta)
"""

from math import gamma as _G

import numpy as np
from ..utils.random import RNGLike, scipy_random_state
from scipy.stats import invgamma, invweibull

from .base import SeverityModel
from ..utils.numeric import eval_dist


[docs] class InverseGamma(SeverityModel): """Inverse gamma (Vinci) severity. X ~ InverseGamma(alpha, theta), support x > 0. F(x) = 1 - Gamma(alpha; theta/x) E[X^k] = theta^k Gamma(alpha - k) / Gamma(alpha), k < alpha. """ def __init__(self, alpha: float, theta: float): if alpha <= 0 or theta <= 0: raise ValueError("alpha and theta must be positive.") self.alpha = alpha self.theta = theta self._d = invgamma(a=alpha, scale=theta) def _moment(self, k: float) -> float: if k >= self.alpha: raise ValueError(f"E[X^{k}] does not exist; need k < alpha.") return self.theta ** k * _G(self.alpha - k) / _G(self.alpha)
[docs] def mean(self) -> float: if self.alpha <= 1: raise ValueError("Mean does not exist for alpha <= 1.") return self._moment(1)
[docs] def variance(self) -> float: if self.alpha <= 2: raise ValueError("Variance does not exist for alpha <= 2.") return self._moment(2) - self._moment(1) ** 2
[docs] def sample(self, size: int = 1, rng: RNGLike = None) -> np.ndarray: if size <= 0: raise ValueError("size must be positive.") return self._d.rvs(size=size, random_state=scipy_random_state(rng))
def pdf(self, x): return eval_dist(lambda v: self._d.pdf(v), x) def cdf(self, x): return eval_dist(lambda v: self._d.cdf(v), x)
[docs] def quantile(self, p): return eval_dist(lambda v: self._d.ppf(v), p)
def __repr__(self) -> str: return f"InverseGamma(alpha={self.alpha}, theta={self.theta})"
[docs] class InverseWeibull(SeverityModel): """Inverse Weibull (log-Gompertz) severity. X ~ InverseWeibull(theta, tau), support x > 0. F(x) = exp[-(theta/x)^tau] E[X^k] = theta^k Gamma(1 - k/tau), k < tau. """ def __init__(self, theta: float, tau: float): if theta <= 0 or tau <= 0: raise ValueError("theta and tau must be positive.") self.theta = theta self.tau = tau self._d = invweibull(c=tau, scale=theta) def _moment(self, k: float) -> float: if k >= self.tau: raise ValueError(f"E[X^{k}] does not exist; need k < tau.") return self.theta ** k * _G(1 - k / self.tau)
[docs] def mean(self) -> float: if self.tau <= 1: raise ValueError("Mean does not exist for tau <= 1.") return self._moment(1)
[docs] def variance(self) -> float: if self.tau <= 2: raise ValueError("Variance does not exist for tau <= 2.") return self._moment(2) - self._moment(1) ** 2
[docs] def sample(self, size: int = 1, rng: RNGLike = None) -> np.ndarray: if size <= 0: raise ValueError("size must be positive.") return self._d.rvs(size=size, random_state=scipy_random_state(rng))
def pdf(self, x): return eval_dist(lambda v: self._d.pdf(v), x) def cdf(self, x): return eval_dist(lambda v: self._d.cdf(v), x)
[docs] def quantile(self, p): return eval_dist(lambda v: self._d.ppf(v), p)
def __repr__(self) -> str: return f"InverseWeibull(theta={self.theta}, tau={self.tau})"
[docs] class InverseExponential(SeverityModel): """Inverse exponential severity. X ~ InverseExponential(theta), support x > 0. F(x) = exp(-theta/x) E[X^k] = theta^k Gamma(1 - k), k < 1. The mean (k = 1) does not exist, so :meth:`mean` and :meth:`variance` raise. """ def __init__(self, theta: float): if theta <= 0: raise ValueError("theta must be positive.") self.theta = theta self._d = invweibull(c=1, scale=theta) def _moment(self, k: float) -> float: if k >= 1: raise ValueError(f"E[X^{k}] does not exist; need k < 1.") return self.theta ** k * _G(1 - k)
[docs] def mean(self) -> float: raise ValueError( "Mean does not exist for the inverse exponential distribution." )
[docs] def variance(self) -> float: raise ValueError( "Variance does not exist for the inverse exponential distribution." )
[docs] def sample(self, size: int = 1, rng: RNGLike = None) -> np.ndarray: if size <= 0: raise ValueError("size must be positive.") return self._d.rvs(size=size, random_state=scipy_random_state(rng))
def pdf(self, x): return eval_dist(lambda v: self._d.pdf(v), x) def cdf(self, x): return eval_dist(lambda v: self._d.cdf(v), x)
[docs] def quantile(self, p): return eval_dist(lambda v: self._d.ppf(v), p)
def __repr__(self) -> str: return f"InverseExponential(theta={self.theta})"