Source code for actuarialpy.margins

"""Margin primitives.

Underwriting margin is premium net of losses and expense loadings (retention,
commission, overhead, profit provision). Generic across lines: in health this is
the dollars left after medical expense and administrative loadings; in P&C it is
premium less losses and expenses.
"""

from __future__ import annotations

from collections.abc import Iterable
from typing import Any

import pandas as pd

from actuarialpy.columns import sum_columns, validate_columns
from actuarialpy.metrics import per_exposure, safe_divide


[docs] def margin(premium: Any, expenses: Any) -> Any: """Margin = premium - expenses, element-wise. ``expenses`` should already be the total of losses plus any loadings. """ return premium - expenses
[docs] def margin_ratio(margin_amount: Any, premium: Any) -> Any: """Margin as a fraction of premium = margin / premium.""" return safe_divide(margin_amount, premium)
[docs] def add_margin( df: pd.DataFrame, *, premium_col: str, expense_cols: str | Iterable[str], out_col: str = "margin", ratio_col: str | None = None, exposure_col: str | None = None, per_exposure_col: str | None = None, copy: bool = True, ) -> pd.DataFrame: """Add an underwriting-margin column (premium minus summed expense columns). ``expense_cols`` is summed row-wise and may mix losses and loadings (e.g. medical/claims, retention, commission, allocated overhead). Optionally also add the margin ratio (``ratio_col``) and a per-exposure margin (``per_exposure_col``, requires ``exposure_col``) such as margin PMPM. """ validate_columns(df, [premium_col]) result = df.copy() if copy else df total_expense = sum_columns(result, expense_cols) result[out_col] = result[premium_col] - total_expense if ratio_col is not None: result[ratio_col] = safe_divide(result[out_col], result[premium_col]) if per_exposure_col is not None: if exposure_col is None: raise ValueError("exposure_col is required when per_exposure_col is set.") validate_columns(result, [exposure_col]) result[per_exposure_col] = per_exposure(result[out_col], result[exposure_col]) return result