Skip to content
Property-cat reinsurance analytics for software engineers

Financial Modelling · Section 4.1

Loss filter

The term that selects a contract's covered losses, a pointwise predicate that keeps the YELT rows matching a peril, geography, or line of business and drops the rest.

A property-cat contract covers a defined slice of the cedent’s book: a peril, a geography, a line of business.1 A loss filter is the term that selects it: a predicate that keeps only the year event loss table (YELT) rows matching the criteria and drops the rest. It is pointwise (each row is evaluated independently, with no state across rows), and it changes which losses are present without transforming their values.

filter(P):keep row r of the YELT iff P(r) is true\text{filter}(P): \quad \text{keep row } r \text{ of the YELT iff } P(r) \text{ is true}

The predicate reads a row, not an occurrence. The rows of one occurrence (trial_id, timestamp, event_id) are evaluated independently, so a filter can keep part of an occurrence: a geography filter on a hurricane that hit Florida and Texas keeps the Florida rows and drops the Texas rows, and the occurrence excess downstream sees a partial occurrence (the covered share, which is exactly the contract’s view of it). What the predicate can express is bounded by the schema: inclusions and exclusions written on a column the YELT carries (peril, geography, line of business) and nothing written on one it does not.

Applied to Trial 9 in isolation, a Florida-hurricane filter, P=(peril=HU ∧ geography=FL)P = (\text{peril} = \text{HU} \,\wedge\, \text{geography} = \text{FL}), keeps four of the thirty occurrences and drops the rest. This is the same selection that opens Contract 1’s dissection:

Earthquake Hurricane

A Florida-hurricane filter on Trial 9: four FL HU occurrences are kept ($216.8M); the other 26 (earthquakes and non-Florida storms) are dropped.

Filter (peril = HU, geography = FL)OccurrencesSubject
Kept (Florida hurricane)4$216.8M
Dropped26$55.3M
Trial 9 subject30$272.1M
caldera_re/loss_filter.py Python
import pandas as pd

from caldera_re.data import cedents
from caldera_re.report import table


def loss_filter(loss: pd.DataFrame, **criteria) -> pd.DataFrame:
    """Keep only YELT rows matching every column = value criterion.

    Pointwise: each row is evaluated independently, with no state across rows.
    Used to restrict a contract to its covered peril, geography, or line of
    business. It changes which losses are present without altering their values.

    Parameters
    ----------
    loss : pd.DataFrame
        YELT.
    **criteria
        Column = value pairs, e.g. peril='HU', geography_id='FL'. A row is kept
        only if it matches every criterion.

    Returns a copy of the matching rows.

    Cost: one boolean mask per criterion, O(R) over the R input rows with no
    sort and no per-trial state. The loss filter is the term whose purpose is
    to reduce the row count and the only one that can be pushed into storage:
    in a columnar store the same predicate becomes partition or row-group
    pruning on the categorical columns, so it runs first and every downstream
    term costs less for it.
    """
    mask = pd.Series(True, index=loss.index)
    for column, value in criteria.items():
        mask &= loss[column] == value
    return loss.loc[mask].copy()


if __name__ == '__main__':
    sc = cedents.suncoast

    # Contract 1's coverage: Florida hurricane.
    fl_hu = loss_filter(sc, peril='HU', geography_id='FL')

    print("Filter: peril=HU, geography=FL (Florida hurricane)")
    print(f"  Rows in:  {len(sc)}")
    print(f"  Rows out: {len(fl_hu)}")
    print()

    N = 20
    rows = []
    for s in range(1, N + 1):
        full = sc[sc['trial_id'] == s]['loss'].sum()
        flhu = fl_hu[fl_hu['trial_id'] == s]['loss'].sum()
        rows.append((s, full, flhu))
    table(['Trial', 'Full subject', 'FL HU subject'], rows,
          fmt=['{}', '{:.1f}M', '{:.1f}M'])

    print(f"\nFull subject EL:  {sc['loss'].sum() / N:.2f}M")
    print(f"FL HU subject EL: {fl_hu['loss'].sum() / N:.2f}M")

Across all 20 trials of the demo tier, the filter reduces SunCoast’s full subject to its Florida-hurricane component, Contract 1’s covered loss:

Loss filter EP curves: SunCoast full subject vs the Florida-hurricane rows only (the covered stream, before any transforming term). The filter removes every non-FL-HU loss; the amber curve is what it drops.

Because a filter changes only which rows are present, not their values, it is the natural first term in a pipeline, making explicit the coverage a contract names. Contract 1’s full composition begins with one (the aggregate cap 2ℓ2\ell encodes its single reinstatement, and the whole composition is the contract at 100%, because participation applies at roll-up):

Contract 1=aggexcess(0,2ℓ)∘occexcess(A,ℓ)∘period(t0,t1)∘filter(P)\text{Contract 1} = \text{agg}_{\text{excess}}(0, 2\ell) \circ \text{occ}_{\text{excess}}(A, \ell) \circ \text{period}(t_0, t_1) \circ \text{filter}(P)

As with every composition, the formula reads right to left (the filter applies first) while data flows left to right. The contract period follows it, narrowing the kept rows to those inside the inception-to-expiry window. The catastrophe excess of loss (CatXoL) page assembles the full pipeline.

  1. Homeowners Choice Property & Casualty Insurance Company, Property Catastrophe Excess of Loss Reinsurance Contract, 2016, SEC EDGAR. The Business Covered article limits the contract to “direct and assumed business classified by the Company as the property perils of Homeowners, Condominium Owners, Renters and Dwelling”, the Territory article makes the territorial limits “identical with those of the Company’s Policies”, and the Exclusions article names what the contract does not cover. The wording calls those four lines of business “property perils”, an overload this site does not use. Guy Carpenter, Glossary of Reinsurance Terms, defines exclusions as “those risks, perils, or classes of insurance with respect to which the reinsurer will not pay loss or provide reinsurance”. ↩