Chapter 8 - Expected Claims Technique#

import numpy as np
import pandas as pd
import chainladder as cl
from IPython.display import display

pd.set_option("display.max_columns", None)
pd.set_option("display.width", 1000)

# Helper functions, skip to the next section for actual exhibits
def as_series(tri):
    s = tri.to_frame(origin_as_datetime=False).iloc[:, 0]
    s.index = [int(getattr(i, "year", i)) for i in s.index]
    return s


def avg_ex_high_low(values):
    values = np.asarray(values, dtype=float).flatten()
    return (values.sum() - values.max() - values.min()) / (len(values) - 2)


def unpaid_exhibit(reported, paid, expected):
    out = pd.DataFrame(index=list(reported.origin.year))
    out["Reported (2)"] = as_series(reported.latest_diagonal).values
    out["Paid (3)"] = as_series(paid.latest_diagonal).values
    out["Expected Claims (4)"] = as_series(expected).values
    out["Case Outstanding (5)"] = out["Reported (2)"] - out["Paid (3)"]
    out["IBNR (6)"] = out["Expected Claims (4)"] - out["Reported (2)"]
    out["Total Unpaid (7)"] = out["Expected Claims (4)"] - out["Paid (3)"]
    return out

P140 (Exhibit I Sheet 1)#

auto_bi = cl.load_sample("friedland_auto_bi_insurer")

reported_pattern = {
    12: 4, 24: 2.9, 36: 1.8, 48: 1.4, 60: 1.2, 72: 1.1, 84: 1.03, 96: 1.02, 108: 1.005,
}
paid_pattern = {
    12: 90, 24: 15, 36: 5, 48: 2.5, 60: 1.75, 72: 1.35, 84: 1.25, 96: 1.15, 108: 1.05,
}

reported_bi = cl.DevelopmentConstant(patterns=reported_pattern, style="cdf").fit_transform(
    auto_bi["Reported Claims"]
)
paid_bi = cl.DevelopmentConstant(patterns=paid_pattern, style="cdf").fit_transform(
    auto_bi["Paid Claims"]
)
reported_ultimate = cl.Chainladder().fit(reported_bi).ultimate_
paid_ultimate = cl.Chainladder().fit(paid_bi).ultimate_
initial_selected = (reported_ultimate + paid_ultimate) / 2

trend_factors = np.round(
    cl.Trend(trends=[0.145], dates=[("2008-12-31", "2000-01-01")])
    .fit(auto_bi["Earned Premium"])
    .trend_.latest_diagonal,
    3,
)
tort_factors = np.array([0.670, 0.670, 0.670, 0.670, 0.750, 1.0, 1.0, 1.0, 1.0])
trended_adj = np.round(
    trend_factors * initial_selected * tort_factors.reshape(1, 1, -1, 1), 0
)
claim_ratio = np.round(trended_adj / auto_bi["Earned Premium"].latest_diagonal, 2)

years = list(auto_bi["Reported Claims"].origin.year)
exhibit_i_s1 = pd.DataFrame(index=years)
exhibit_i_s1["Reported (2)"] = as_series(auto_bi["Reported Claims"].latest_diagonal).values
exhibit_i_s1["Paid (3)"] = as_series(auto_bi["Paid Claims"].latest_diagonal).values
exhibit_i_s1["CDF Reported (4)"] = as_series(
    cl.model_diagnostics(cl.Chainladder().fit(reported_bi))["CDF"]
).values
exhibit_i_s1["CDF Paid (5)"] = as_series(
    cl.model_diagnostics(cl.Chainladder().fit(paid_bi))["CDF"]
).values
exhibit_i_s1["Ult Reported (6)"] = as_series(reported_ultimate).values
exhibit_i_s1["Ult Paid (7)"] = as_series(paid_ultimate).values
exhibit_i_s1["Initial Selected (8)"] = as_series(initial_selected).values
exhibit_i_s1["Earned Premium (9)"] = as_series(auto_bi["Earned Premium"].latest_diagonal).values
exhibit_i_s1["Trend to 7/1/08 (10)"] = as_series(trend_factors).values
exhibit_i_s1["Tort Reform (11)"] = tort_factors
exhibit_i_s1["Trended Adj Ult (12)"] = as_series(trended_adj).values
exhibit_i_s1["Trended Adj Claim Ratio (13)"] = as_series(claim_ratio).values
display(exhibit_i_s1)

ratios = as_series(claim_ratio)
avg_00_05 = float(np.round(ratios.loc[2000:2005].mean(), 3))
avg_00_05_xhl = float(np.round(avg_ex_high_low(ratios.loc[2000:2005]), 3))
avg_01_06 = float(np.round(ratios.loc[2001:2006].mean(), 3))
avg_01_06_xhl = float(np.round(avg_ex_high_low(ratios.loc[2001:2006]), 3))
selected_claim_ratio = 0.80
el_reported = cl.ExpectedLoss(apriori=selected_claim_ratio).fit(
    auto_bi["Reported Claims"], sample_weight=auto_bi["Earned Premium"].latest_diagonal
)
el_paid = cl.ExpectedLoss(apriori=selected_claim_ratio).fit(
    auto_bi["Paid Claims"], sample_weight=auto_bi["Earned Premium"].latest_diagonal
)
expected_2008 = float(el_reported.ultimate_.loc[:, :, "2008", :].sum())
unpaid_2008 = float(el_paid.ibnr_.loc[:, :, "2008", :].sum())
ibnr_2008 = float(el_reported.ibnr_.loc[:, :, "2008", :].sum())

exhibit_i_s1_summary = pd.Series(
    {
        "Avg 2000-2005 (14)": avg_00_05,
        "Avg 2000-2005 ex Hi/Lo (14)": avg_00_05_xhl,
        "Avg 2001-2006 (14)": avg_01_06,
        "Avg 2001-2006 ex Hi/Lo (14)": avg_01_06_xhl,
        "Selected Claim Ratio (15)": selected_claim_ratio,
        "Expected Claims 2008 (16)": expected_2008,
        "Total Unpaid 2008 (17)": unpaid_2008,
        "IBNR 2008 (17)": ibnr_2008,
    },
    name="Items (14)-(17)",
)
display(
    exhibit_i_s1_summary.map(
        lambda x: f"{x:,.0f}" if abs(x) >= 1 else f"{x:.3f}"
    ).to_frame()
)
Reported (2) Paid (3) CDF Reported (4) CDF Paid (5) Ult Reported (6) Ult Paid (7) Initial Selected (8) Earned Premium (9) Trend to 7/1/08 (10) Tort Reform (11) Trended Adj Ult (12) Trended Adj Claim Ratio (13)
2000 10000000.0 9500000.0 1.005 1.05 10050000.0 9975000.0 10012500.0 24000000.0 2.954 0.67 19816540.0 0.83
2001 8000000.0 7200000.0 1.020 1.15 8160000.0 8280000.0 8220000.0 18000000.0 2.580 0.67 14209092.0 0.79
2002 9400000.0 7600000.0 1.030 1.25 9682000.0 9500000.0 9591000.0 19000000.0 2.253 0.67 14477710.0 0.76
2003 15600000.0 7800000.0 1.100 1.35 17160000.0 10530000.0 13845000.0 23000000.0 1.968 0.67 18255463.0 0.79
2004 16500000.0 11200000.0 1.200 1.75 19800000.0 19600000.0 19700000.0 32000000.0 1.719 0.75 25398225.0 0.79
2005 18500000.0 10200000.0 1.400 2.50 25900000.0 25500000.0 25700000.0 47000000.0 1.501 1.00 38575700.0 0.82
2006 16500000.0 6000000.0 1.800 5.00 29700000.0 30000000.0 29850000.0 50000000.0 1.311 1.00 39133350.0 0.78
2007 14000000.0 3000000.0 2.900 15.00 40600000.0 45000000.0 42800000.0 57000000.0 1.145 1.00 49006000.0 0.86
2008 8700000.0 750000.0 4.000 90.00 34800000.0 67500000.0 51150000.0 62000000.0 1.000 1.00 51150000.0 0.82
Items (14)-(17)
Avg 2000-2005 (14) 0.797
Avg 2000-2005 ex Hi/Lo (14) 0.798
Avg 2001-2006 (14) 0.788
Avg 2001-2006 ex Hi/Lo (14) 0.787
Selected Claim Ratio (15) 0.800
Expected Claims 2008 (16) 49,600,000
Total Unpaid 2008 (17) 48,850,000
IBNR 2008 (17) 40,900,000
# Exhibit I Sheet 1 — reconcile to Friedland PDF p140
assert np.allclose(
    exhibit_i_s1["Reported (2)"],
    [10000000, 8000000, 9400000, 15600000, 16500000, 18500000, 16500000, 14000000, 8700000],
)
assert np.allclose(
    exhibit_i_s1["Paid (3)"],
    [9500000, 7200000, 7600000, 7800000, 11200000, 10200000, 6000000, 3000000, 750000],
)
assert np.allclose(
    exhibit_i_s1["Ult Reported (6)"],
    [10050000, 8160000, 9682000, 17160000, 19800000, 25900000, 29700000, 40600000, 34800000],
)
assert np.allclose(
    exhibit_i_s1["Ult Paid (7)"],
    [9975000, 8280000, 9500000, 10530000, 19600000, 25500000, 30000000, 45000000, 67500000],
)
assert np.allclose(
    exhibit_i_s1["Initial Selected (8)"],
    [10012500, 8220000, 9591000, 13845000, 19700000, 25700000, 29850000, 42800000, 51150000],
)
assert np.allclose(
    exhibit_i_s1["Earned Premium (9)"],
    [24000000, 18000000, 19000000, 23000000, 32000000, 47000000, 50000000, 57000000, 62000000],
)
assert np.allclose(
    exhibit_i_s1["Trend to 7/1/08 (10)"],
    [2.954, 2.58, 2.253, 1.968, 1.719, 1.501, 1.311, 1.145, 1],
)
assert np.allclose(
    exhibit_i_s1["Trended Adj Ult (12)"],
    [19816540, 14209092, 14477710, 18255463, 25398225, 38575700, 39133350, 49006000, 51150000],
)
assert np.allclose(
    exhibit_i_s1["Trended Adj Claim Ratio (13)"],
    [0.83, 0.79, 0.76, 0.79, 0.79, 0.82, 0.78, 0.86, 0.82],
)
assert np.isclose(avg_00_05, 0.797)
assert np.isclose(avg_00_05_xhl, 0.798)
assert np.isclose(avg_01_06, 0.788)
assert np.isclose(avg_01_06_xhl, 0.788, atol=0.001)
assert np.isclose(expected_2008, 49600000)
assert np.isclose(unpaid_2008, 48850000)
assert np.isclose(ibnr_2008, 40900000)

P141 (Exhibit I Sheet 2)#

gl = cl.load_sample("friedland_gl_self_insurer")

reported_pattern = {
    12: 3.104, 24: 1.940, 36: 1.616, 48: 1.394, 60: 1.244, 72: 1.131,
    84: 1.077, 96: 1.051, 108: 1.030, 120: 1.020, 132: 1.015,
}
paid_pattern = {
    12: 20.373, 24: 5.093, 36: 3.183, 48: 2.274, 60: 1.749, 72: 1.489,
    84: 1.306, 96: 1.187, 108: 1.109, 120: 1.067, 132: 1.046,
}

reported = cl.DevelopmentConstant(patterns=reported_pattern, style="cdf").fit_transform(
    gl["Reported Claims"]
)
paid = cl.DevelopmentConstant(patterns=paid_pattern, style="cdf").fit_transform(
    gl["Paid Claims"]
)
reported_ultimate = cl.Chainladder().fit(reported).ultimate_
paid_ultimate = cl.Chainladder().fit(paid).ultimate_
selected_ultimate = (reported_ultimate + paid_ultimate) / 2
population = gl["Population"].latest_diagonal
trend_factors = np.round(
    cl.Trend(trends=[0.075], dates=[("2008-12-31", "1998-01-01")])
    .fit(gl["Population"])
    .trend_.latest_diagonal,
    3,
)
trended_ult = np.round(selected_ultimate * trend_factors, 0)
pure_premium = np.round(trended_ult / population, 2)

years = list(gl["Reported Claims"].origin.year)
exhibit_i_s2 = pd.DataFrame(index=years)
exhibit_i_s2["Reported (2)"] = as_series(gl["Reported Claims"].latest_diagonal).values
exhibit_i_s2["Paid (3)"] = as_series(gl["Paid Claims"].latest_diagonal).values
exhibit_i_s2["CDF Reported (4)"] = as_series(
    cl.model_diagnostics(cl.Chainladder().fit(reported))["CDF"]
).values
exhibit_i_s2["CDF Paid (5)"] = as_series(
    cl.model_diagnostics(cl.Chainladder().fit(paid))["CDF"]
).values
exhibit_i_s2["Ult Reported (6)"] = as_series(reported_ultimate).values
exhibit_i_s2["Ult Paid (7)"] = as_series(paid_ultimate).values
exhibit_i_s2["Initial Selected (8)"] = as_series(selected_ultimate).values
exhibit_i_s2["Population (9)"] = as_series(population).values
exhibit_i_s2["Trend to 7/1/08 (10)"] = as_series(trend_factors).values
exhibit_i_s2["Trended Ult (11)"] = as_series(trended_ult).values
exhibit_i_s2["Trended Pure Premium (12)"] = as_series(pure_premium).values
display(exhibit_i_s2)

pp = as_series(pure_premium)
avg_00_05 = float(np.round(pp.loc[2000:2005].mean(), 2))
avg_00_05_xhl = float(np.round(avg_ex_high_low(pp.loc[2000:2005]), 2))
avg_01_06 = float(np.round(pp.loc[2001:2006].mean(), 2))
avg_01_06_xhl = float(np.round(avg_ex_high_low(pp.loc[2001:2006]), 2))
selected_pure_premium = 3.50
el_reported = cl.ExpectedLoss(apriori=selected_pure_premium).fit(
    reported, sample_weight=population
)
el_paid = cl.ExpectedLoss(apriori=selected_pure_premium).fit(
    paid, sample_weight=population
)
expected_2008 = float(el_reported.ultimate_.loc[:, :, "2008", :].sum())
unpaid_2008 = float(el_paid.ibnr_.loc[:, :, "2008", :].sum())
ibnr_2008 = float(el_reported.ibnr_.loc[:, :, "2008", :].sum())

exhibit_i_s2_summary = pd.Series(
    {
        "Avg 2000-2005 (13)": avg_00_05,
        "Avg 2000-2005 ex Hi/Lo (13)": avg_00_05_xhl,
        "Avg 2001-2006 (13)": avg_01_06,
        "Avg 2001-2006 ex Hi/Lo (13)": avg_01_06_xhl,
        "Selected Pure Premium (14)": selected_pure_premium,
        "Expected Claims 2008 (15)": expected_2008,
        "Total Unpaid 2008 (16)": unpaid_2008,
        "IBNR 2008 (16)": ibnr_2008,
    },
    name="Items (13)-(16)",
)
display(
    exhibit_i_s2_summary.map(
        lambda x: f"{x:,.0f}" if abs(x) >= 100 else f"{x:.2f}"
    ).to_frame()
)
Reported (2) Paid (3) CDF Reported (4) CDF Paid (5) Ult Reported (6) Ult Paid (7) Initial Selected (8) Population (9) Trend to 7/1/08 (10) Trended Ult (11) Trended Pure Premium (12)
1998 900000.0 890000.0 1.015 1.046 913500.0 930940.0 922220.0 709000.0 2.061 1900695.0 2.68
1999 1200000.0 1170000.0 1.020 1.067 1224000.0 1248390.0 1236195.0 724000.0 1.917 2369786.0 3.27
2000 1300000.0 1265000.0 1.030 1.109 1339000.0 1402885.0 1370942.5 736000.0 1.783 2444390.0 3.32
2001 1800000.0 1600000.0 1.051 1.187 1891800.0 1899200.0 1895500.0 740000.0 1.659 3144634.0 4.25
2002 1450000.0 1200000.0 1.077 1.306 1561650.0 1567200.0 1564425.0 750000.0 1.543 2413908.0 3.22
2003 1400000.0 1050000.0 1.131 1.489 1583400.0 1563450.0 1573425.0 760000.0 1.436 2259438.0 2.97
2004 2400000.0 900000.0 1.244 1.749 2985600.0 1574100.0 2279850.0 770000.0 1.335 3043600.0 3.95
2005 1800000.0 860000.0 1.394 2.274 2509200.0 1955640.0 2232420.0 775000.0 1.242 2772666.0 3.58
2006 1500000.0 525000.0 1.616 3.183 2424000.0 1671075.0 2047537.5 780000.0 1.156 2366953.0 3.03
2007 1200000.0 750000.0 1.940 5.093 2328000.0 3819750.0 3073875.0 785000.0 1.075 3304416.0 4.21
2008 600000.0 170000.0 3.104 20.373 1862400.0 3463410.0 2662905.0 790000.0 1.000 2662905.0 3.37
Items (13)-(16)
Avg 2000-2005 (13) 3.55
Avg 2000-2005 ex Hi/Lo (13) 3.52
Avg 2001-2006 (13) 3.50
Avg 2001-2006 ex Hi/Lo (13) 3.44
Selected Pure Premium (14) 3.50
Expected Claims 2008 (15) 2,765,000
Total Unpaid 2008 (16) 2,595,000
IBNR 2008 (16) 2,165,000
# Exhibit I Sheet 2 — reconcile to Friedland PDF p141
assert np.allclose(
    exhibit_i_s2["Reported (2)"],
    [900000, 1200000, 1300000, 1800000, 1450000, 1400000, 2400000, 1800000, 1500000, 1200000, 600000],
)
assert np.allclose(
    exhibit_i_s2["Paid (3)"],
    [890000, 1170000, 1265000, 1600000, 1200000, 1050000, 900000, 860000, 525000, 750000, 170000],
)
assert np.allclose(
    exhibit_i_s2["Ult Reported (6)"],
    [913500, 1224000, 1339000, 1891800, 1561650, 1583400, 2985600, 2509200, 2424000, 2328000, 1862400],
    atol=1,
)
assert np.allclose(
    exhibit_i_s2["Ult Paid (7)"],
    [930940, 1248390, 1402885, 1899200, 1567200, 1563450, 1574100, 1955640, 1671075, 3819750, 3463410],
    atol=1,
)
assert np.allclose(
    exhibit_i_s2["Population (9)"],
    [709000, 724000, 736000, 740000, 750000, 760000, 770000, 775000, 780000, 785000, 790000],
)
assert np.allclose(
    exhibit_i_s2["Trend to 7/1/08 (10)"],
    [2.061, 1.917, 1.783, 1.659, 1.543, 1.436, 1.335, 1.242, 1.156, 1.075, 1],
)
assert np.allclose(
    exhibit_i_s2["Trended Ult (11)"],
    [1900695, 2369786, 2444390, 3144635, 2413908, 2259438, 3043600, 2772666, 2366953, 3304416, 2662905],
    atol=1,
)
assert np.allclose(
    exhibit_i_s2["Trended Pure Premium (12)"],
    [2.68, 3.27, 3.32, 4.25, 3.22, 2.97, 3.95, 3.58, 3.03, 4.21, 3.37],
    atol=0.01,
)
assert np.isclose(avg_00_05, 3.55)
assert np.isclose(avg_00_05_xhl, 3.52)
assert np.isclose(avg_01_06, 3.50)
assert np.isclose(avg_01_06_xhl, 3.45, atol=0.011)  # PDF 3.45; 3.445 rounds to 3.44
assert np.isclose(expected_2008, 2765000)
assert np.isclose(unpaid_2008, 2595000)
assert np.isclose(ibnr_2008, 2165000)

P142 (Exhibit II Sheet 1)#

ia = cl.load_sample("friedland_us_industry_auto")

reported_pattern = {
    12: 1.292, 24: 1.110, 36: 1.051, 48: 1.023, 60: 1.011,
    72: 1.006, 84: 1.003, 96: 1.001, 108: 1.000, 120: 1.000,
}
paid_pattern = {
    12: 2.390, 24: 1.404, 36: 1.184, 48: 1.085, 60: 1.040,
    72: 1.020, 84: 1.011, 96: 1.006, 108: 1.004, 120: 1.002,
}

reported = cl.DevelopmentConstant(patterns=reported_pattern, style="cdf").fit_transform(
    ia["Reported Claims"]
)
paid = cl.DevelopmentConstant(patterns=paid_pattern, style="cdf").fit_transform(
    ia["Paid Claims"]
)
reported_ultimate = cl.Chainladder().fit(reported).ultimate_
paid_ultimate = cl.Chainladder().fit(paid).ultimate_
selected_ultimate = np.round((reported_ultimate + paid_ultimate) / 2, 0)
earned_premium = ia["Earned Premium"].latest_diagonal
estimated_claim_ratios = np.round(selected_ultimate / earned_premium, 3)
selected_claim_ratio = np.array(
    [0.75, 0.75, 0.75, 0.75, 0.75, 0.65, 0.65, 0.65, 0.65, 0.65]
)
sample_weight = earned_premium * selected_claim_ratio.reshape(1, 1, -1, 1)
el_reported = cl.ExpectedLoss(apriori=1).fit(
    ia["Reported Claims"], sample_weight=sample_weight
)
el_paid = cl.ExpectedLoss(apriori=1).fit(
    ia["Paid Claims"], sample_weight=sample_weight
)
expected_claims = np.round(el_reported.ultimate_, 0)

years = list(ia["Reported Claims"].origin.year)
exhibit_ii_s1 = pd.DataFrame(index=years)
exhibit_ii_s1["Reported (2)"] = as_series(ia["Reported Claims"].latest_diagonal).values
exhibit_ii_s1["Paid (3)"] = as_series(ia["Paid Claims"].latest_diagonal).values
exhibit_ii_s1["CDF Reported (4)"] = as_series(
    cl.model_diagnostics(cl.Chainladder().fit(reported))["CDF"]
).values
exhibit_ii_s1["CDF Paid (5)"] = as_series(
    cl.model_diagnostics(cl.Chainladder().fit(paid))["CDF"]
).values
exhibit_ii_s1["Ult Reported (6)"] = as_series(np.round(reported_ultimate, 0)).values
exhibit_ii_s1["Ult Paid (7)"] = as_series(np.round(paid_ultimate, 0)).values
exhibit_ii_s1["Initial Selected (8)"] = as_series(selected_ultimate).values
exhibit_ii_s1["Earned Premium (9)"] = as_series(earned_premium).values
exhibit_ii_s1["Estimated Claim Ratio (10)"] = as_series(estimated_claim_ratios).values
exhibit_ii_s1["Selected Claim Ratio (11)"] = selected_claim_ratio
exhibit_ii_s1["Expected Claims (12)"] = as_series(expected_claims).values
display(exhibit_ii_s1)
Reported (2) Paid (3) CDF Reported (4) CDF Paid (5) Ult Reported (6) Ult Paid (7) Initial Selected (8) Earned Premium (9) Estimated Claim Ratio (10) Selected Claim Ratio (11) Expected Claims (12)
1998 47742304.0 47644187.0 1.000 1.002 47742304.0 47739475.0 47740890.0 68574209.0 0.696 0.75 51430657.0
1999 51185767.0 51000534.0 1.000 1.004 51185767.0 51204536.0 51195152.0 68544981.0 0.747 0.75 51408736.0
2000 54837929.0 54533225.0 1.001 1.006 54892767.0 54860424.0 54876596.0 68907977.0 0.796 0.75 51680983.0
2001 56299562.0 55878421.0 1.003 1.011 56468461.0 56493084.0 56480772.0 72544955.0 0.779 0.75 54408716.0
2002 58592712.0 57807215.0 1.006 1.020 58944268.0 58963359.0 58953814.0 79228887.0 0.744 0.75 59421665.0
2003 57565344.0 55930654.0 1.011 1.040 58198563.0 58167880.0 58183221.0 86643542.0 0.672 0.65 56318302.0
2004 56976657.0 53774672.0 1.023 1.085 58287120.0 58345519.0 58316320.0 91763523.0 0.636 0.65 59646290.0
2005 56786410.0 50644994.0 1.051 1.184 59682517.0 59963673.0 59823095.0 94115312.0 0.636 0.65 61174953.0
2006 54641339.0 43606497.0 1.110 1.404 60651886.0 61223522.0 60937704.0 95272279.0 0.640 0.65 61926981.0
2007 48853563.0 27229969.0 1.292 2.390 63118803.0 65079626.0 64099215.0 95176240.0 0.673 0.65 61864556.0
# Exhibit II Sheet 1 — reconcile to Friedland PDF p142
assert np.allclose(
    exhibit_ii_s1["Reported (2)"],
    [47742304, 51185767, 54837929, 56299562, 58592712, 57565344, 56976657, 56786410, 54641339, 48853563],
)
assert np.allclose(
    exhibit_ii_s1["Paid (3)"],
    [47644187, 51000534, 54533225, 55878421, 57807215, 55930654, 53774672, 50644994, 43606497, 27229969],
)
assert np.allclose(
    exhibit_ii_s1["Ult Reported (6)"],
    [47742304, 51185767, 54892767, 56468461, 58944268, 58198563, 58287120, 59682517, 60651886, 63118803],
    atol=1,
)
assert np.allclose(
    exhibit_ii_s1["Ult Paid (7)"],
    [47739475, 51204536, 54860424, 56493084, 58963359, 58167880, 58345519, 59963673, 61223522, 65079626],
    atol=1,
)
assert np.allclose(
    exhibit_ii_s1["Initial Selected (8)"],
    [47740890, 51195152, 54876596, 56480772, 58953814, 58183221, 58316320, 59823095, 60937704, 64099215],
)
assert np.allclose(
    exhibit_ii_s1["Earned Premium (9)"],
    [68574209, 68544981, 68907977, 72544955, 79228887, 86643542, 91763523, 94115312, 95272279, 95176240],
)
assert np.allclose(
    exhibit_ii_s1["Estimated Claim Ratio (10)"],
    [0.696, 0.747, 0.796, 0.779, 0.744, 0.672, 0.636, 0.636, 0.640, 0.673],
)
assert np.allclose(
    exhibit_ii_s1["Expected Claims (12)"],
    [51430657, 51408736, 51680983, 54408716, 59421665, 56318302, 59646290, 61174953, 61926981, 61864556],
)

P143 (Exhibit II Sheet 2)#

exhibit_ii_s2 = unpaid_exhibit(ia["Reported Claims"], ia["Paid Claims"], expected_claims)
display(exhibit_ii_s2)
display(exhibit_ii_s2.sum().rename("Total").to_frame().T)
Reported (2) Paid (3) Expected Claims (4) Case Outstanding (5) IBNR (6) Total Unpaid (7)
1998 47742304.0 47644187.0 51430657.0 98117.0 3688353.0 3786470.0
1999 51185767.0 51000534.0 51408736.0 185233.0 222969.0 408202.0
2000 54837929.0 54533225.0 51680983.0 304704.0 -3156946.0 -2852242.0
2001 56299562.0 55878421.0 54408716.0 421141.0 -1890846.0 -1469705.0
2002 58592712.0 57807215.0 59421665.0 785497.0 828953.0 1614450.0
2003 57565344.0 55930654.0 56318302.0 1634690.0 -1247042.0 387648.0
2004 56976657.0 53774672.0 59646290.0 3201985.0 2669633.0 5871618.0
2005 56786410.0 50644994.0 61174953.0 6141416.0 4388543.0 10529959.0
2006 54641339.0 43606497.0 61926981.0 11034842.0 7285642.0 18320484.0
2007 48853563.0 27229969.0 61864556.0 21623594.0 13010993.0 34634587.0
Reported (2) Paid (3) Expected Claims (4) Case Outstanding (5) IBNR (6) Total Unpaid (7)
Total 543481587.0 498050368.0 569281839.0 45431219.0 25800252.0 71231471.0
# Exhibit II Sheet 2 — reconcile to Friedland PDF p143
assert np.allclose(
    exhibit_ii_s2["Case Outstanding (5)"],
    [98117, 185233, 304704, 421141, 785497, 1634690, 3201985, 6141416, 11034842, 21623594],
)
assert np.allclose(
    exhibit_ii_s2["IBNR (6)"],
    [3688353, 222969, -3156946, -1890846, 828953, -1247042, 2669633, 4388543, 7285642, 13010993],
)
assert np.allclose(
    exhibit_ii_s2["Total Unpaid (7)"],
    [3786470, 408202, -2852242, -1469705, 1614450, 387648, 5871618, 10529959, 18320484, 34634587],
)
assert np.isclose(exhibit_ii_s2["Reported (2)"].sum(), 543481587)
assert np.isclose(exhibit_ii_s2["Paid (3)"].sum(), 498050368)
assert np.isclose(exhibit_ii_s2["Expected Claims (4)"].sum(), 569281839)
assert np.isclose(exhibit_ii_s2["Case Outstanding (5)"].sum(), 45431219)
assert np.isclose(exhibit_ii_s2["IBNR (6)"].sum(), 25800252)
assert np.isclose(exhibit_ii_s2["Total Unpaid (7)"].sum(), 71231471)

P144 (Exhibit III Sheet 1)#

xyz = cl.load_sample("friedland_xyz_auto_bi")

reported_pattern = {
    12: 2.551, 24: 1.512, 36: 1.196, 48: 1.085, 60: 1.064, 72: 1.013,
    84: 1.003, 96: 0.992, 108: 0.992, 120: 0.999, 132: 1.000,
}
paid_pattern = {
    12: 21.999, 24: 6.569, 36: 3.160, 48: 2.007, 60: 1.525, 72: 1.268,
    84: 1.116, 96: 1.054, 108: 1.031, 120: 1.014, 132: 1.010,
}

reported = cl.DevelopmentConstant(patterns=reported_pattern, style="cdf").fit_transform(
    xyz["Reported Claims"]
)
paid = cl.DevelopmentConstant(patterns=paid_pattern, style="cdf").fit_transform(
    xyz["Paid Claims"]
)
reported_ultimate = np.round(cl.Chainladder().fit(reported).ultimate_, 0)
paid_ultimate = np.round(cl.Chainladder().fit(paid).ultimate_, 0)
selected_ultimate = (reported_ultimate + paid_ultimate) / 2
earned_premium = xyz["Earned Premium"].latest_diagonal
estimated_claim_ratios = np.round(selected_ultimate / earned_premium, 3)
avg_98_03 = float(np.round((selected_ultimate / earned_premium).iloc[:, :, 0:6, :].mean(), 3))
selected_claim_ratio = np.array(
    [0.783, 0.783, 0.783, 0.783, 0.783, 0.783, 0.871, 0.783, 0.658, 0.638, 0.825]
)
sample_weight = earned_premium * selected_claim_ratio.reshape(1, 1, -1, 1)
el_reported = cl.ExpectedLoss(apriori=1).fit(
    xyz["Reported Claims"], sample_weight=sample_weight
)
el_paid = cl.ExpectedLoss(apriori=1).fit(
    xyz["Paid Claims"], sample_weight=sample_weight
)
expected_claims = np.round(el_reported.ultimate_, 0)
xyz_reported_ultimate = reported_ultimate
xyz_paid_ultimate = paid_ultimate
xyz_expected_claims = expected_claims

years = list(xyz["Reported Claims"].origin.year)
exhibit_iii_s1 = pd.DataFrame(index=years)
exhibit_iii_s1["Reported (2)"] = as_series(xyz["Reported Claims"].latest_diagonal).values
exhibit_iii_s1["Paid (3)"] = as_series(xyz["Paid Claims"].latest_diagonal).values
exhibit_iii_s1["CDF Reported (4)"] = as_series(
    cl.model_diagnostics(cl.Chainladder().fit(reported))["CDF"]
).values
exhibit_iii_s1["CDF Paid (5)"] = as_series(
    cl.model_diagnostics(cl.Chainladder().fit(paid))["CDF"]
).values
exhibit_iii_s1["Ult Reported (6)"] = as_series(reported_ultimate).values
exhibit_iii_s1["Ult Paid (7)"] = as_series(paid_ultimate).values
exhibit_iii_s1["Initial Selected (8)"] = as_series(selected_ultimate).values
exhibit_iii_s1["Earned Premium (9)"] = as_series(earned_premium).values
exhibit_iii_s1["Estimated Claim Ratio (10)"] = as_series(estimated_claim_ratios).values
exhibit_iii_s1["Selected Claim Ratio (11)"] = selected_claim_ratio
exhibit_iii_s1["Expected Claims (12)"] = as_series(expected_claims).values
display(exhibit_iii_s1)
print(f"Average estimated claim ratio 1998-2003: {avg_98_03}")
Reported (2) Paid (3) CDF Reported (4) CDF Paid (5) Ult Reported (6) Ult Paid (7) Initial Selected (8) Earned Premium (9) Estimated Claim Ratio (10) Selected Claim Ratio (11) Expected Claims (12)
1998 15822.0 15822.0 1.000 1.010 15822.0 15980.0 15901.0 20000.0 0.795 0.783 15660.0
1999 25107.0 24817.0 0.999 1.014 25082.0 25164.0 25123.0 31500.0 0.798 0.783 24664.0
2000 37246.0 36782.0 0.992 1.031 36948.0 37922.0 37435.0 45000.0 0.832 0.783 35235.0
2001 38798.0 38519.0 0.992 1.054 38488.0 40599.0 39543.5 50000.0 0.791 0.783 39150.0
2002 48169.0 44437.0 1.003 1.116 48314.0 49592.0 48953.0 61183.0 0.800 0.783 47906.0
2003 44373.0 39320.0 1.013 1.268 44950.0 49858.0 47404.0 69175.0 0.685 0.783 54164.0
2004 70288.0 52811.0 1.064 1.525 74786.0 80537.0 77661.5 99322.0 0.782 0.871 86509.0
2005 70655.0 40026.0 1.085 2.007 76661.0 80332.0 78496.5 138151.0 0.568 0.783 108172.0
2006 48804.0 22819.0 1.196 3.160 58370.0 72108.0 65239.0 107578.0 0.606 0.658 70786.0
2007 31732.0 11865.0 1.512 6.569 47979.0 77941.0 62960.0 62438.0 1.008 0.638 39835.0
2008 18632.0 3409.0 2.551 21.999 47530.0 74995.0 61262.5 47797.0 1.282 0.825 39433.0
Average estimated claim ratio 1998-2003: 0.783
# Exhibit III Sheet 1 — reconcile to Friedland PDF p144
assert np.allclose(
    exhibit_iii_s1["Reported (2)"],
    [15822, 25107, 37246, 38798, 48169, 44373, 70288, 70655, 48804, 31732, 18632],
)
assert np.allclose(
    exhibit_iii_s1["Paid (3)"],
    [15822, 24817, 36782, 38519, 44437, 39320, 52811, 40026, 22819, 11865, 3409],
)
assert np.allclose(
    exhibit_iii_s1["Ult Reported (6)"],
    [15822, 25082, 36948, 38487, 48313, 44950, 74787, 76661, 58370, 47979, 47530],
    atol=1,
)
assert np.allclose(
    exhibit_iii_s1["Ult Paid (7)"],
    [15980, 25164, 37922, 40600, 49592, 49858, 80537, 80333, 72108, 77941, 74995],
    atol=1,
)
assert np.allclose(
    exhibit_iii_s1["Initial Selected (8)"],
    [15901, 25123, 37435, 39543, 48953, 47404, 77662, 78497, 65239, 62960, 61262],
    atol=1,
)
assert np.allclose(
    exhibit_iii_s1["Earned Premium (9)"],
    [20000, 31500, 45000, 50000, 61183, 69175, 99322, 138151, 107578, 62438, 47797],
)
assert np.allclose(
    exhibit_iii_s1["Estimated Claim Ratio (10)"],
    [0.795, 0.798, 0.832, 0.791, 0.800, 0.685, 0.782, 0.568, 0.606, 1.008, 1.282],
)
assert np.isclose(avg_98_03, 0.783)
assert np.allclose(
    exhibit_iii_s1["Expected Claims (12)"],
    [15660, 24665, 35235, 39150, 47906, 54164, 86509, 108172, 70786, 39835, 39433],
    atol=1,
)

P145 (Exhibit III Sheet 2)#

def relative_level_triangle(base, years=range(2004, 2009)):
    pieces = []
    for year in years:
        rel = base / base.loc[:, :, str(year), :]
        rel.columns = [str(year)]
        pieces.append(rel)
    return cl.concat(pieces, axis=1)


selected_view = np.round(selected_ultimate.iloc[:, :, 4:, :], 0)
earned_premium_view = earned_premium.iloc[:, :, 4:, :]

base_trend_2008 = (
    cl.Trend(trends=[0.03425], dates=[("2008-12-31", "2002-01-01")])
    .fit(selected_view)
    .trend_
)
severity_trend_adjustment = relative_level_triangle(base_trend_2008)

base_tort_2008 = (
    cl.Trend(
        trends=[-0.25, 0.670 / 0.75 - 1],
        dates=[("2007-12-31", "2006-12-31"), ("2006-12-31", "2005-12-31")],
    )
    .fit(selected_view)
    .trend_
)
tort_reform_adjustment = relative_level_triangle(base_tort_2008)

rate_changes = [0, 0.05, 0.075, 0.15, 0.1, -0.2, -0.2]
olf = cl.parallelogram_olf(
    rate_changes,
    pd.to_datetime([f"{y}-01-01" for y in range(2002, 2009)]),
    vertical_line=True,
)["OLF"].values
base_rate = selected_view * 0 + olf.reshape(selected_view.shape)
rate_level_adjustment = relative_level_triangle(base_rate)

adjusted_claim_ratios = (
    selected_view * severity_trend_adjustment * tort_reform_adjustment
) / (earned_premium_view * rate_level_adjustment)

ay = list(range(2002, 2009))
ty = list(range(2004, 2009))

exhibit_iii_s2_ult = pd.DataFrame(
    {"Initial Selected Ult (2)": as_series(selected_view).values}, index=ay
)
display(exhibit_iii_s2_ult)

exhibit_iii_s2_trend = pd.DataFrame(
    np.round(severity_trend_adjustment.values.squeeze().T, 3),
    index=ay,
    columns=[f"Trend to {y} ({n})" for y, n in zip(ty, [3, 4, 5, 6, 7])],
)
display(exhibit_iii_s2_trend)

exhibit_iii_s2_tort = pd.DataFrame(
    np.round(tort_reform_adjustment.values.squeeze().T, 3),
    index=ay,
    columns=[f"Tort to {y} ({n})" for y, n in zip(ty, [8, 9, 10, 11, 12])],
)
display(exhibit_iii_s2_tort)

exhibit_iii_s2_prem = pd.DataFrame(
    {"Earned Premium (13)": as_series(earned_premium_view).values}, index=ay
)
display(exhibit_iii_s2_prem)

exhibit_iii_s2_rate = pd.DataFrame(
    np.round(rate_level_adjustment.values.squeeze().T, 3),
    index=ay,
    columns=[f"Rate to {y} ({n})" for y, n in zip(ty, [14, 15, 16, 17, 18])],
)
display(exhibit_iii_s2_rate)

exhibit_iii_s2_lr = pd.DataFrame(
    np.round(adjusted_claim_ratios.values.squeeze().T, 3),
    index=ay,
    columns=[f"OL Claim Ratio {y} ({n})" for y, n in zip(ty, [19, 20, 21, 22, 23])],
)
display(exhibit_iii_s2_lr)

vals = adjusted_claim_ratios.values.squeeze()
all_years = adjusted_claim_ratios.mean(axis="origin").values.squeeze()
ex_high_low = (vals.sum(axis=1) - vals.max(axis=1) - vals.min(axis=1)) / 5
latest_5 = (
    adjusted_claim_ratios.loc[:, :, "2004":, :].mean(axis="origin").values.squeeze()
)
latest_3 = (
    adjusted_claim_ratios.loc[:, :, "2006":, :].mean(axis="origin").values.squeeze()
)
average_claim_ratios = np.vstack([all_years, ex_high_low, latest_5, latest_3])
selected_expected_claim_ratio = ex_high_low

exhibit_iii_s2_avgs = pd.DataFrame(
    np.round(average_claim_ratios, 3),
    index=["All Years (24)", "All Years ex Hi/Lo (24)", "Latest 5 (24)", "Latest 3 (24)"],
    columns=ty,
)
exhibit_iii_s2_avgs.loc["Selected (25)"] = np.round(selected_expected_claim_ratio, 3)
display(exhibit_iii_s2_avgs)
Initial Selected Ult (2)
2002 48953.0
2003 47404.0
2004 77662.0
2005 78496.0
2006 65239.0
2007 62960.0
2008 61262.0
Trend to 2004 (3) Trend to 2005 (4) Trend to 2006 (5) Trend to 2007 (6) Trend to 2008 (7)
2002 1.070 1.106 1.144 1.183 1.224
2003 1.034 1.070 1.106 1.144 1.183
2004 1.000 1.034 1.070 1.106 1.144
2005 0.967 1.000 1.034 1.070 1.106
2006 0.935 0.967 1.000 1.034 1.070
2007 0.904 0.935 0.967 1.000 1.034
2008 0.874 0.904 0.935 0.967 1.000
Tort to 2004 (8) Tort to 2005 (9) Tort to 2006 (10) Tort to 2007 (11) Tort to 2008 (12)
2002 1.000 1.000 0.893 0.67 0.67
2003 1.000 1.000 0.893 0.67 0.67
2004 1.000 1.000 0.893 0.67 0.67
2005 1.000 1.000 0.893 0.67 0.67
2006 1.119 1.119 1.000 0.75 0.75
2007 1.493 1.493 1.333 1.00 1.00
2008 1.493 1.493 1.333 1.00 1.00
Earned Premium (13)
2002 61183.0
2003 69175.0
2004 99322.0
2005 138151.0
2006 107578.0
2007 62438.0
2008 47797.0
Rate to 2004 (14) Rate to 2005 (15) Rate to 2006 (16) Rate to 2007 (17) Rate to 2008 (18)
2002 1.129 1.298 1.428 1.142 0.914
2003 1.075 1.236 1.360 1.088 0.870
2004 1.000 1.150 1.265 1.012 0.810
2005 0.870 1.000 1.100 0.880 0.704
2006 0.791 0.909 1.000 0.800 0.640
2007 0.988 1.136 1.250 1.000 0.800
2008 1.235 1.420 1.562 1.250 1.000
OL Claim Ratio 2004 (19) OL Claim Ratio 2005 (20) OL Claim Ratio 2006 (21) OL Claim Ratio 2007 (22) OL Claim Ratio 2008 (23)
2002 0.758 0.682 0.573 0.555 0.718
2003 0.659 0.593 0.498 0.483 0.624
2004 0.782 0.703 0.591 0.573 0.740
2005 0.632 0.568 0.477 0.463 0.598
2006 0.803 0.722 0.606 0.588 0.760
2007 1.377 1.238 1.040 1.008 1.304
2008 1.354 1.217 1.022 0.991 1.282
2004 2005 2006 2007 2008
All Years (24) 0.909 0.818 0.687 0.666 0.861
All Years ex Hi/Lo (24) 0.871 0.783 0.658 0.638 0.825
Latest 5 (24) 0.989 0.890 0.747 0.725 0.937
Latest 3 (24) 1.178 1.059 0.890 0.863 1.115
Selected (25) 0.871 0.783 0.658 0.638 0.825
# Exhibit III Sheet 2 — reconcile to Friedland PDF p145
assert np.allclose(
    as_series(selected_view).values,
    [48953, 47404, 77662, 78497, 65239, 62960, 61262],
    atol=1,
)
assert np.allclose(
    exhibit_iii_s2_trend.values,
    [
        [1.070, 1.106, 1.144, 1.183, 1.224],
        [1.034, 1.070, 1.106, 1.144, 1.183],
        [1.000, 1.034, 1.070, 1.106, 1.144],
        [0.967, 1.000, 1.034, 1.070, 1.106],
        [0.935, 0.967, 1.000, 1.034, 1.070],
        [0.904, 0.935, 0.967, 1.000, 1.034],
        [0.874, 0.904, 0.935, 0.967, 1.000],
    ],
)
assert np.allclose(
    exhibit_iii_s2_tort.values,
    [
        [1.000, 1.000, 0.893, 0.670, 0.670],
        [1.000, 1.000, 0.893, 0.670, 0.670],
        [1.000, 1.000, 0.893, 0.670, 0.670],
        [1.000, 1.000, 0.893, 0.670, 0.670],
        [1.119, 1.119, 1.000, 0.750, 0.750],
        [1.493, 1.493, 1.333, 1.000, 1.000],
        [1.493, 1.493, 1.333, 1.000, 1.000],
    ],
)
assert np.allclose(
    exhibit_iii_s2_prem["Earned Premium (13)"],
    [61183, 69175, 99322, 138151, 107578, 62438, 47797],
)
assert np.allclose(
    exhibit_iii_s2_rate.values,
    [
        [1.129, 1.298, 1.428, 1.142, 0.914],
        [1.075, 1.236, 1.360, 1.088, 0.870],
        [1.000, 1.150, 1.265, 1.012, 0.810],
        [0.870, 1.000, 1.100, 0.880, 0.704],
        [0.791, 0.909, 1.000, 0.800, 0.640],
        [0.988, 1.136, 1.250, 1.000, 0.800],
        [1.235, 1.420, 1.562, 1.250, 1.000],
    ],
)
assert np.allclose(
    exhibit_iii_s2_lr.values,
    [
        [0.758, 0.682, 0.573, 0.555, 0.718],
        [0.659, 0.593, 0.498, 0.483, 0.624],
        [0.782, 0.703, 0.591, 0.573, 0.740],
        [0.632, 0.568, 0.477, 0.463, 0.598],
        [0.803, 0.722, 0.606, 0.588, 0.760],
        [1.377, 1.238, 1.040, 1.008, 1.304],
        [1.354, 1.217, 1.022, 0.991, 1.282],
    ],
)
assert np.allclose(
    np.round(average_claim_ratios, 3),
    [
        [0.909, 0.818, 0.687, 0.666, 0.861],
        [0.871, 0.783, 0.658, 0.638, 0.825],
        [0.989, 0.890, 0.747, 0.725, 0.937],
        [1.178, 1.059, 0.890, 0.863, 1.115],
    ],
)
assert np.allclose(
    np.round(selected_expected_claim_ratio, 3), [0.871, 0.783, 0.658, 0.638, 0.825]
)

P146 (Exhibit III Sheet 3)#

exhibit_iii_s3 = unpaid_exhibit(
    xyz["Reported Claims"], xyz["Paid Claims"], xyz_expected_claims
)
display(exhibit_iii_s3)
display(exhibit_iii_s3.sum().rename("Total").to_frame().T)
Reported (2) Paid (3) Expected Claims (4) Case Outstanding (5) IBNR (6) Total Unpaid (7)
1998 15822.0 15822.0 15660.0 0.0 -162.0 -162.0
1999 25107.0 24817.0 24664.0 290.0 -443.0 -153.0
2000 37246.0 36782.0 35235.0 464.0 -2011.0 -1547.0
2001 38798.0 38519.0 39150.0 279.0 352.0 631.0
2002 48169.0 44437.0 47906.0 3732.0 -263.0 3469.0
2003 44373.0 39320.0 54164.0 5053.0 9791.0 14844.0
2004 70288.0 52811.0 86509.0 17477.0 16221.0 33698.0
2005 70655.0 40026.0 108172.0 30629.0 37517.0 68146.0
2006 48804.0 22819.0 70786.0 25985.0 21982.0 47967.0
2007 31732.0 11865.0 39835.0 19867.0 8103.0 27970.0
2008 18632.0 3409.0 39433.0 15223.0 20801.0 36024.0
Reported (2) Paid (3) Expected Claims (4) Case Outstanding (5) IBNR (6) Total Unpaid (7)
Total 449626.0 330627.0 561514.0 118999.0 111888.0 230887.0
# Exhibit III Sheet 3 — reconcile to Friedland PDF p146
assert np.allclose(
    exhibit_iii_s3["Expected Claims (4)"],
    [15660, 24665, 35235, 39150, 47906, 54164, 86509, 108172, 70786, 39835, 39433],
    atol=1,
)
assert np.allclose(
    exhibit_iii_s3["Case Outstanding (5)"],
    [0, 290, 465, 278, 3731, 5052, 17477, 30629, 25985, 19867, 15223],
    atol=1,
)
assert np.allclose(
    exhibit_iii_s3["IBNR (6)"],
    [-162, -442, -2011, 352, -262, 9791, 16221, 37517, 21982, 8103, 20801],
    atol=1,
)
assert np.allclose(
    exhibit_iii_s3["Total Unpaid (7)"],
    [-162, -152, -1547, 631, 3469, 14844, 33698, 68146, 47967, 27970, 36024],
    atol=1,
)
assert np.isclose(exhibit_iii_s3["Reported (2)"].sum(), 449626)
assert np.isclose(exhibit_iii_s3["Paid (3)"].sum(), 330629)
assert np.isclose(exhibit_iii_s3["Expected Claims (4)"].sum(), 561516, atol=1)
assert np.isclose(exhibit_iii_s3["Case Outstanding (5)"].sum(), 118997, atol=1)
assert np.isclose(exhibit_iii_s3["IBNR (6)"].sum(), 111890, atol=1)
assert np.isclose(exhibit_iii_s3["Total Unpaid (7)"].sum(), 230887, atol=1)

P147 (Exhibit III Sheet 4)#

exhibit_iii_s4 = pd.DataFrame(index=years)
exhibit_iii_s4["Reported (2)"] = exhibit_iii_s1["Reported (2)"]
exhibit_iii_s4["Paid (3)"] = exhibit_iii_s1["Paid (3)"]
exhibit_iii_s4["Dev Ult Reported (4)"] = exhibit_iii_s1["Ult Reported (6)"]
exhibit_iii_s4["Dev Ult Paid (5)"] = exhibit_iii_s1["Ult Paid (7)"]
exhibit_iii_s4["Expected Claims (6)"] = exhibit_iii_s1["Expected Claims (12)"]
display(exhibit_iii_s4)
display(exhibit_iii_s4.sum().rename("Total").to_frame().T)
Reported (2) Paid (3) Dev Ult Reported (4) Dev Ult Paid (5) Expected Claims (6)
1998 15822.0 15822.0 15822.0 15980.0 15660.0
1999 25107.0 24817.0 25082.0 25164.0 24664.0
2000 37246.0 36782.0 36948.0 37922.0 35235.0
2001 38798.0 38519.0 38488.0 40599.0 39150.0
2002 48169.0 44437.0 48314.0 49592.0 47906.0
2003 44373.0 39320.0 44950.0 49858.0 54164.0
2004 70288.0 52811.0 74786.0 80537.0 86509.0
2005 70655.0 40026.0 76661.0 80332.0 108172.0
2006 48804.0 22819.0 58370.0 72108.0 70786.0
2007 31732.0 11865.0 47979.0 77941.0 39835.0
2008 18632.0 3409.0 47530.0 74995.0 39433.0
Reported (2) Paid (3) Dev Ult Reported (4) Dev Ult Paid (5) Expected Claims (6)
Total 449626.0 330627.0 514930.0 605028.0 561514.0
# Exhibit III Sheet 4 — reconcile to Friedland PDF p147
assert np.allclose(
    exhibit_iii_s4["Dev Ult Reported (4)"],
    [15822, 25082, 36948, 38487, 48313, 44950, 74787, 76661, 58370, 47979, 47530],
    atol=1,
)
assert np.allclose(
    exhibit_iii_s4["Dev Ult Paid (5)"],
    [15980, 25164, 37922, 40600, 49592, 49858, 80537, 80333, 72108, 77941, 74995],
    atol=1,
)
assert np.allclose(
    exhibit_iii_s4["Expected Claims (6)"],
    [15660, 24665, 35235, 39150, 47906, 54164, 86509, 108172, 70786, 39835, 39433],
    atol=1,
)
assert np.isclose(exhibit_iii_s4["Dev Ult Reported (4)"].sum(), 514929, atol=1)
assert np.isclose(exhibit_iii_s4["Dev Ult Paid (5)"].sum(), 605030, atol=1)
assert np.isclose(exhibit_iii_s4["Expected Claims (6)"].sum(), 561516, atol=1)

P148 (Exhibit III Sheet 5)#

exhibit_iii_s5 = pd.DataFrame(index=years)
exhibit_iii_s5["Case Outstanding (2)"] = exhibit_iii_s3["Case Outstanding (5)"]
exhibit_iii_s5["Dev IBNR Reported (3)"] = (
    exhibit_iii_s4["Dev Ult Reported (4)"] - exhibit_iii_s4["Reported (2)"]
)
exhibit_iii_s5["Dev IBNR Paid (4)"] = (
    exhibit_iii_s4["Dev Ult Paid (5)"] - exhibit_iii_s4["Reported (2)"]
)
exhibit_iii_s5["Expected IBNR (5)"] = exhibit_iii_s3["IBNR (6)"]
display(exhibit_iii_s5)
display(exhibit_iii_s5.sum().rename("Total").to_frame().T)
Case Outstanding (2) Dev IBNR Reported (3) Dev IBNR Paid (4) Expected IBNR (5)
1998 0.0 0.0 158.0 -162.0
1999 290.0 -25.0 57.0 -443.0
2000 464.0 -298.0 676.0 -2011.0
2001 279.0 -310.0 1801.0 352.0
2002 3732.0 145.0 1423.0 -263.0
2003 5053.0 577.0 5485.0 9791.0
2004 17477.0 4498.0 10249.0 16221.0
2005 30629.0 6006.0 9677.0 37517.0
2006 25985.0 9566.0 23304.0 21982.0
2007 19867.0 16247.0 46209.0 8103.0
2008 15223.0 28898.0 56363.0 20801.0
Case Outstanding (2) Dev IBNR Reported (3) Dev IBNR Paid (4) Expected IBNR (5)
Total 118999.0 65304.0 155402.0 111888.0
# Exhibit III Sheet 5 — reconcile to Friedland PDF p148
assert np.allclose(
    exhibit_iii_s5["Dev IBNR Reported (3)"],
    [0, -25, -298, -311, 144, 577, 4499, 6006, 9566, 16247, 28898],
    atol=1,
)
assert np.allclose(
    exhibit_iii_s5["Dev IBNR Paid (4)"],
    [158, 58, 676, 1802, 1423, 5485, 10249, 9678, 23304, 46209, 56363],
    atol=1,
)
assert np.allclose(
    exhibit_iii_s5["Expected IBNR (5)"],
    [-162, -442, -2011, 352, -262, 9791, 16221, 37517, 21982, 8103, 20801],
    atol=1,
)
assert np.isclose(exhibit_iii_s5["Case Outstanding (2)"].sum(), 118997, atol=1)
assert np.isclose(exhibit_iii_s5["Dev IBNR Reported (3)"].sum(), 65303, atol=1)
assert np.isclose(exhibit_iii_s5["Dev IBNR Paid (4)"].sum(), 155405, atol=3)
assert np.isclose(exhibit_iii_s5["Expected IBNR (5)"].sum(), 111890, atol=1)

P149 (Exhibit IV Sheet 1)#

def changing_conditions_exhibit(triangle, claim_ratio, actual_ibnr_values):
    earned = np.round(triangle["Earned Premium"].latest_diagonal, 0)
    el = cl.ExpectedLoss(apriori=claim_ratio).fit(
        triangle["Reported Claims"], sample_weight=earned
    )
    expected = np.round(el.ultimate_, 0)
    reported = triangle["Reported Claims"].latest_diagonal
    estimated_ibnr = np.round(el.ibnr_, 0).fillzero()
    actual_ibnr = estimated_ibnr.copy()
    actual_ibnr.values = np.array(actual_ibnr_values, dtype=float).reshape(
        estimated_ibnr.shape
    )
    difference = np.round(actual_ibnr - estimated_ibnr, 0).fillzero()
    out = pd.DataFrame(index=list(triangle["Reported Claims"].origin.year))
    out["Earned Premium (2)"] = as_series(earned).values
    out["Claim Ratio (3)"] = claim_ratio
    out["Expected Claims (4)"] = as_series(expected).values
    out["Reported (5)"] = as_series(reported).values
    out["Estimated IBNR (6)"] = as_series(estimated_ibnr).values
    out["Actual IBNR (7)"] = as_series(actual_ibnr).values
    out["Difference (8)"] = as_series(difference).values
    return out


uspp = cl.load_sample("friedland_uspp")

exhibit_iv_steady = changing_conditions_exhibit(
    uspp.loc["Steady State"],
    0.70,
    [0, 0, 0, 0, 8508, 8934, 18761, 49249, 103422, 249764],
)
exhibit_iv_incr_claim = changing_conditions_exhibit(
    uspp.loc["Increasing Claim"],
    0.70,
    [0, 0, 0, 0, 8508, 10210, 22782, 63320, 140358, 356805],
)

print("Steady-State")
display(exhibit_iv_steady)
display(exhibit_iv_steady[["Earned Premium (2)", "Expected Claims (4)", "Reported (5)", "Estimated IBNR (6)", "Actual IBNR (7)", "Difference (8)"]].sum().rename("Total").to_frame().T)

print("Increasing Claim Ratios")
display(exhibit_iv_incr_claim)
display(exhibit_iv_incr_claim[["Earned Premium (2)", "Expected Claims (4)", "Reported (5)", "Estimated IBNR (6)", "Actual IBNR (7)", "Difference (8)"]].sum().rename("Total").to_frame().T)
Steady-State
Earned Premium (2) Claim Ratio (3) Expected Claims (4) Reported (5) Estimated IBNR (6) Actual IBNR (7) Difference (8)
1999 1000000.0 0.7 700000.0 700000.0 0.0 0.0 0.0
2000 1050000.0 0.7 735000.0 735000.0 0.0 0.0 0.0
2001 1102500.0 0.7 771750.0 771750.0 0.0 0.0 0.0
2002 1157625.0 0.7 810338.0 810338.0 0.0 0.0 0.0
2003 1215506.0 0.7 850854.0 842346.0 8508.0 8508.0 0.0
2004 1276282.0 0.7 893397.0 884463.0 8934.0 8934.0 0.0
2005 1340096.0 0.7 938067.0 919306.0 18761.0 18761.0 0.0
2006 1407100.0 0.7 984970.0 935722.0 49248.0 49249.0 1.0
2007 1477455.0 0.7 1034218.0 930797.0 103421.0 103422.0 1.0
2008 1551328.0 0.7 1085930.0 836166.0 249764.0 249764.0 0.0
Earned Premium (2) Expected Claims (4) Reported (5) Estimated IBNR (6) Actual IBNR (7) Difference (8)
Total 12577892.0 8804524.0 8365888.0 438636.0 438638.0 2.0
Increasing Claim Ratios
Earned Premium (2) Claim Ratio (3) Expected Claims (4) Reported (5) Estimated IBNR (6) Actual IBNR (7) Difference (8)
1999 1000000.0 0.7 700000.0 700000.0 0.0 0.0 0.0
2000 1050000.0 0.7 735000.0 735000.0 0.0 0.0 0.0
2001 1102500.0 0.7 771750.0 771750.0 0.0 0.0 0.0
2002 1157625.0 0.7 810338.0 810338.0 0.0 0.0 0.0
2003 1215506.0 0.7 850854.0 842346.0 8508.0 8508.0 0.0
2004 1276282.0 0.7 893397.0 1010815.0 -117418.0 10210.0 127628.0
2005 1340096.0 0.7 938067.0 1116300.0 -178233.0 22782.0 201015.0
2006 1407100.0 0.7 984970.0 1203071.0 -218101.0 63320.0 281421.0
2007 1477455.0 0.7 1034218.0 1263224.0 -229006.0 140358.0 369364.0
2008 1551328.0 0.7 1085930.0 1194523.0 -108593.0 356805.0 465398.0
Earned Premium (2) Expected Claims (4) Reported (5) Estimated IBNR (6) Actual IBNR (7) Difference (8)
Total 12577892.0 8804524.0 9647367.0 -842843.0 601983.0 1444826.0
# Exhibit IV Sheet 1 — reconcile to Friedland PDF p149
assert np.allclose(
    exhibit_iv_steady["Earned Premium (2)"],
    [1000000, 1050000, 1102500, 1157625, 1215506, 1276282, 1340096, 1407100, 1477455, 1551328],
)
assert np.allclose(
    exhibit_iv_steady["Expected Claims (4)"],
    [700000, 735000, 771750, 810338, 850854, 893397, 938067, 984970, 1034219, 1085930],
    atol=1,
)
assert np.isclose(exhibit_iv_steady["Earned Premium (2)"].sum(), 12577893, atol=1)
assert np.isclose(exhibit_iv_steady["Expected Claims (4)"].sum(), 8804525, atol=1)
assert np.isclose(exhibit_iv_steady["Estimated IBNR (6)"].sum(), 438638, atol=2)
assert np.isclose(exhibit_iv_steady["Difference (8)"].sum(), 0, atol=2)

assert np.allclose(
    exhibit_iv_incr_claim["Estimated IBNR (6)"],
    [0, 0, 0, 0, 8508, -117418, -178233, -218101, -229006, -108593],
    atol=1,
)
assert np.isclose(exhibit_iv_incr_claim["Estimated IBNR (6)"].sum(), -842841, atol=2)
assert np.isclose(exhibit_iv_incr_claim["Actual IBNR (7)"].sum(), 601984, atol=1)
assert np.isclose(exhibit_iv_incr_claim["Difference (8)"].sum(), 1444824, atol=2)

P150 (Exhibit IV Sheet 2)#

exhibit_iv_case = changing_conditions_exhibit(
    uspp.loc["Increasing Case"],
    0.70,
    [0, 0, 0, 0, 8509, 8934, 4690, 22162, 54296, 154745],
)
exhibit_iv_both = changing_conditions_exhibit(
    uspp.loc["Increasing Claim Case"],
    0.70,
    [0, 0, 0, 0, 8509, 10210, 5695, 28494, 73688, 221064],
)

print("Increasing Case Outstanding Strength")
display(exhibit_iv_case)
display(exhibit_iv_case[["Earned Premium (2)", "Expected Claims (4)", "Reported (5)", "Estimated IBNR (6)", "Actual IBNR (7)", "Difference (8)"]].sum().rename("Total").to_frame().T)

print("Increasing Claim Ratios and Case Outstanding Strength")
display(exhibit_iv_both)
display(exhibit_iv_both[["Earned Premium (2)", "Expected Claims (4)", "Reported (5)", "Estimated IBNR (6)", "Actual IBNR (7)", "Difference (8)"]].sum().rename("Total").to_frame().T)
Increasing Case Outstanding Strength
Earned Premium (2) Claim Ratio (3) Expected Claims (4) Reported (5) Estimated IBNR (6) Actual IBNR (7) Difference (8)
1999 1000000.0 0.7 700000.0 700000.0 0.0 0.0 0.0
2000 1050000.0 0.7 735000.0 735000.0 0.0 0.0 0.0
2001 1102500.0 0.7 771750.0 771750.0 0.0 0.0 0.0
2002 1157625.0 0.7 810338.0 810338.0 0.0 0.0 0.0
2003 1215506.0 0.7 850854.0 842346.0 8508.0 8509.0 1.0
2004 1276282.0 0.7 893397.0 884463.0 8934.0 8934.0 0.0
2005 1340096.0 0.7 938067.0 933377.0 4690.0 4690.0 0.0
2006 1407100.0 0.7 984970.0 962808.0 22162.0 22162.0 0.0
2007 1477455.0 0.7 1034218.0 979922.0 54296.0 54296.0 0.0
2008 1551328.0 0.7 1085930.0 931185.0 154745.0 154745.0 0.0
Earned Premium (2) Expected Claims (4) Reported (5) Estimated IBNR (6) Actual IBNR (7) Difference (8)
Total 12577892.0 8804524.0 8551189.0 253335.0 253336.0 1.0
Increasing Claim Ratios and Case Outstanding Strength
Earned Premium (2) Claim Ratio (3) Expected Claims (4) Reported (5) Estimated IBNR (6) Actual IBNR (7) Difference (8)
1999 1000000.0 0.7 700000.0 700000.0 0.0 0.0 0.0
2000 1050000.0 0.7 735000.0 735000.0 0.0 0.0 0.0
2001 1102500.0 0.7 771750.0 771750.0 0.0 0.0 0.0
2002 1157625.0 0.7 810338.0 810338.0 0.0 0.0 0.0
2003 1215506.0 0.7 850854.0 842346.0 8508.0 8509.0 1.0
2004 1276282.0 0.7 893397.0 1010815.0 -117418.0 10210.0 127628.0
2005 1340096.0 0.7 938067.0 1133386.0 -195319.0 5695.0 201014.0
2006 1407100.0 0.7 984970.0 1237897.0 -252927.0 28494.0 281421.0
2007 1477455.0 0.7 1034218.0 1329895.0 -295677.0 73688.0 369365.0
2008 1551328.0 0.7 1085930.0 1330264.0 -244334.0 221064.0 465398.0
Earned Premium (2) Expected Claims (4) Reported (5) Estimated IBNR (6) Actual IBNR (7) Difference (8)
Total 12577892.0 8804524.0 9901691.0 -1097167.0 347660.0 1444827.0
# Exhibit IV Sheet 2 — reconcile to Friedland PDF p150
assert np.isclose(exhibit_iv_case["Estimated IBNR (6)"].sum(), 253336, atol=1)
assert np.isclose(exhibit_iv_case["Difference (8)"].sum(), 0, atol=1)
assert np.isclose(exhibit_iv_both["Estimated IBNR (6)"].sum(), -1097165, atol=2)
assert np.isclose(exhibit_iv_both["Actual IBNR (7)"].sum(), 347660, atol=1)
assert np.isclose(exhibit_iv_both["Difference (8)"].sum(), 1444824, atol=3)

P151 (Exhibit V)#

us_auto = cl.load_sample("friedland_us_auto")

exhibit_v_steady = changing_conditions_exhibit(
    us_auto.loc["Steady State"],
    0.75,
    [0, 0, 0, 0, 8509, 29354, 61644, 173073, 363454, 758599],
)
exhibit_v_mix = changing_conditions_exhibit(
    us_auto.loc["Changing Product Mix"],
    0.75,
    [0, 0, 0, 0, 8509, 29354, 71855, 239057, 596924, 1445385],
)

print("Steady-State (No Change in Product Mix)")
display(exhibit_v_steady)
display(exhibit_v_steady[["Earned Premium (2)", "Expected Claims (4)", "Reported (5)", "Estimated IBNR (6)", "Actual IBNR (7)", "Difference (8)"]].sum().rename("Total").to_frame().T)

print("Changing Product Mix")
display(exhibit_v_mix)
display(exhibit_v_mix[["Earned Premium (2)", "Expected Claims (4)", "Reported (5)", "Estimated IBNR (6)", "Actual IBNR (7)", "Difference (8)"]].sum().rename("Total").to_frame().T)
Steady-State (No Change in Product Mix)
Earned Premium (2) Claim Ratio (3) Expected Claims (4) Reported (5) Estimated IBNR (6) Actual IBNR (7) Difference (8)
1999 2000000.0 0.75 1500000.0 1500000.0 0.0 0.0 0.0
2000 2100000.0 0.75 1575000.0 1575000.0 0.0 0.0 0.0
2001 2205000.0 0.75 1653750.0 1653750.0 0.0 0.0 0.0
2002 2315250.0 0.75 1736438.0 1736438.0 0.0 0.0 0.0
2003 2431013.0 0.75 1823260.0 1814751.0 8509.0 8509.0 0.0
2004 2552563.0 0.75 1914422.0 1885068.0 29354.0 29354.0 0.0
2005 2680191.0 0.75 2010143.0 1948499.0 61644.0 61644.0 0.0
2006 2814201.0 0.75 2110651.0 1937577.0 173074.0 173073.0 -1.0
2007 2954911.0 0.75 2216183.0 1852729.0 363454.0 363454.0 0.0
2008 3102656.0 0.75 2326992.0 1568393.0 758599.0 758599.0 0.0
Earned Premium (2) Expected Claims (4) Reported (5) Estimated IBNR (6) Actual IBNR (7) Difference (8)
Total 25155785.0 18866839.0 17472205.0 1394634.0 1394633.0 -1.0
Changing Product Mix
Earned Premium (2) Claim Ratio (3) Expected Claims (4) Reported (5) Estimated IBNR (6) Actual IBNR (7) Difference (8)
1999 2000000.0 0.75 1500000.0 1500000.0 0.0 0.0 0.0
2000 2100000.0 0.75 1575000.0 1575000.0 0.0 0.0 0.0
2001 2205000.0 0.75 1653750.0 1653750.0 0.0 0.0 0.0
2002 2315250.0 0.75 1736438.0 1736438.0 0.0 0.0 0.0
2003 2431013.0 0.75 1823260.0 1814751.0 8509.0 8509.0 0.0
2004 2552563.0 0.75 1914422.0 1885068.0 29354.0 29354.0 0.0
2005 2999262.0 0.75 2249446.0 2193545.0 55902.0 71855.0 15953.0
2006 3564016.0 0.75 2673012.0 2471446.0 201566.0 239057.0 37491.0
2007 4281446.0 0.75 3211084.0 2680487.0 530598.0 596924.0 66326.0
2008 5196516.0 0.75 3897387.0 2556695.0 1340692.0 1445385.0 104693.0
Earned Premium (2) Expected Claims (4) Reported (5) Estimated IBNR (6) Actual IBNR (7) Difference (8)
Total 29645066.0 22233799.0 20067180.0 2166621.0 2391084.0 224463.0
# Exhibit V — reconcile to Friedland PDF p151
assert np.allclose(
    exhibit_v_steady["Earned Premium (2)"],
    [2000000, 2100000, 2205000, 2315250, 2431013, 2552563, 2680191, 2814201, 2954911, 3102656],
    atol=1,
)
assert np.isclose(exhibit_v_steady["Earned Premium (2)"].sum(), 25155785, atol=1)
assert np.isclose(exhibit_v_steady["Expected Claims (4)"].sum(), 18866839, atol=1)
assert np.isclose(exhibit_v_steady["Estimated IBNR (6)"].sum(), 1394634, atol=1)
assert np.isclose(exhibit_v_steady["Difference (8)"].sum(), 0, atol=1)

assert np.isclose(exhibit_v_mix["Earned Premium (2)"].sum(), 29645066, atol=1)
assert np.isclose(exhibit_v_mix["Expected Claims (4)"].sum(), 22233799, atol=1)
assert np.isclose(exhibit_v_mix["Estimated IBNR (6)"].sum(), 2166620, atol=1)
assert np.isclose(exhibit_v_mix["Actual IBNR (7)"].sum(), 2391084, atol=1)
assert np.isclose(exhibit_v_mix["Difference (8)"].sum(), 224465, atol=2)