From 51ad44329aefc11c2a5587c44205988f08b0f4c9 Mon Sep 17 00:00:00 2001 From: "henrydingliu@gmail.com" Date: Wed, 2 Sep 2026 22:06:02 +0000 Subject: [PATCH 1/3] ruff fix --- chainladder/core/base.py | 2 +- chainladder/utils/data/_manifest.py | 11 ++++++++++- chainladder/utils/tests/test_utilities.py | 14 ++++++-------- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/chainladder/core/base.py b/chainladder/core/base.py index 40240a3f..5ef31137 100644 --- a/chainladder/core/base.py +++ b/chainladder/core/base.py @@ -601,7 +601,7 @@ def _drop_subtriangles(self): del self.sigma_ if "std_err_" in sub_tris: del self.std_err_ - #w_ is currently stored as an ndarray + # w_ is currently stored as an ndarray if hasattr(self, "w_"): del self.w_ diff --git a/chainladder/utils/data/_manifest.py b/chainladder/utils/data/_manifest.py index 854551ab..b16b293c 100644 --- a/chainladder/utils/data/_manifest.py +++ b/chainladder/utils/data/_manifest.py @@ -268,7 +268,16 @@ "origin": "Accident Year", "development": "Calendar Year", "index": None, - "columns": ["Paid Claims", "Reported Claims", "Closed Claim Counts", "Reported Claim Counts", "Case Outstanding", "Reported Severities", "Earned Premium","Incremental Paid Severity"], + "columns": [ + "Paid Claims", + "Reported Claims", + "Closed Claim Counts", + "Reported Claim Counts", + "Case Outstanding", + "Reported Severities", + "Earned Premium", + "Incremental Paid Severity" + ], "cumulative": True, }, "genins": { diff --git a/chainladder/utils/tests/test_utilities.py b/chainladder/utils/tests/test_utilities.py index 752af77d..484c6ae3 100644 --- a/chainladder/utils/tests/test_utilities.py +++ b/chainladder/utils/tests/test_utilities.py @@ -12,7 +12,6 @@ from chainladder import ( __dt64_unit__ ) -from chainladder.utils.utility_functions import date_delta_adjustment from chainladder.utils.data._manifest import SAMPLES from chainladder.utils.utility_functions import ( date_delta_adjustment, @@ -59,7 +58,6 @@ def from_sequence(seq): def test_triangle_json_io(clrd): - xp = clrd.get_array_module() clrd2 = cl.read_json(clrd.to_json(), array_backend=clrd.array_backend) assert clrd == clrd2 assert np.all(clrd.kdims == clrd2.kdims) @@ -153,7 +151,7 @@ def test_concat(clrd): ) -def test_model_diagnostics_erorr(raa,atol): +def test_model_diagnostics_erorr(raa, atol): with pytest.raises(ValueError): cl.model_diagnostics(raa) dev = cl.Development().fit_transform(raa) @@ -162,7 +160,7 @@ def test_model_diagnostics_erorr(raa,atol): md = cl.model_diagnostics(est) assert np.allclose( md['Run Off 1'].values, - emerg[emerg.valuation.year==1991].latest_diagonal.values, + emerg[emerg.valuation.year == 1991].latest_diagonal.values, atol=atol, equal_nan=True ) @@ -186,13 +184,13 @@ def test_model_diagnostics_erorr(raa,atol): ) -def test_model_diagnostics_groupby(prism,atol): +def test_model_diagnostics_groupby(prism, atol): dev = cl.Development().fit(prism["Incurred"].sum()) est = cl.Chainladder().fit(dev.transform(prism["Incurred"])) - lhs = cl.model_diagnostics(est,groupby=['Line']) + lhs = cl.model_diagnostics(est, groupby=['Line']) rhs = cl.model_diagnostics(cl.Chainladder().fit(dev.transform(prism["Incurred"].groupby('Line').sum()))) - assert np.allclose(lhs['Ultimate'].values,rhs['Ultimate'].values,atol=atol,equal_nan=True) - assert np.allclose(np.nan_to_num(lhs['IBNR'].values),np.nan_to_num(rhs['IBNR'].values),atol=atol,equal_nan=True) + assert np.allclose(lhs['Ultimate'].values,rhs['Ultimate'].values,atol=atol, equal_nan=True) + assert np.allclose(np.nan_to_num(lhs['IBNR'].values),np.nan_to_num(rhs['IBNR'].values),atol=atol, equal_nan=True) def test_concat_immutability(raa): From f7d59a467f4c1b7cae7e7d0da97e1bca3e19e44c Mon Sep 17 00:00:00 2001 From: "henrydingliu@gmail.com" Date: Wed, 2 Sep 2026 22:32:43 +0000 Subject: [PATCH 2/3] ruff fix --- chainladder/utils/tests/test_utilities.py | 18 +- chainladder/utils/triangle_weight.py | 25 +- chainladder/utils/weighted_regression.py | 6 +- docs/friedland/chapter_11.ipynb | 1852 ++++++++++++--------- 4 files changed, 1115 insertions(+), 786 deletions(-) diff --git a/chainladder/utils/tests/test_utilities.py b/chainladder/utils/tests/test_utilities.py index 484c6ae3..5f33c24e 100644 --- a/chainladder/utils/tests/test_utilities.py +++ b/chainladder/utils/tests/test_utilities.py @@ -189,16 +189,16 @@ def test_model_diagnostics_groupby(prism, atol): est = cl.Chainladder().fit(dev.transform(prism["Incurred"])) lhs = cl.model_diagnostics(est, groupby=['Line']) rhs = cl.model_diagnostics(cl.Chainladder().fit(dev.transform(prism["Incurred"].groupby('Line').sum()))) - assert np.allclose(lhs['Ultimate'].values,rhs['Ultimate'].values,atol=atol, equal_nan=True) - assert np.allclose(np.nan_to_num(lhs['IBNR'].values),np.nan_to_num(rhs['IBNR'].values),atol=atol, equal_nan=True) + assert np.allclose(lhs['Ultimate'].values, rhs['Ultimate'].values,atol=atol, equal_nan=True) + assert np.allclose(np.nan_to_num(lhs['IBNR'].values), np.nan_to_num(rhs['IBNR'].values),atol=atol, equal_nan=True) def test_concat_immutability(raa): u = cl.Chainladder().fit(raa).ultimate_ - l = raa.latest_diagonal - u.columns = l.columns + latest = raa.latest_diagonal + u.columns = latest.columns u_new = copy.deepcopy(u) - cl.concat((l, u), axis=3) + cl.concat((latest, u), axis=3) assert u == u_new @@ -680,7 +680,7 @@ def test_options_defaults() -> None: """ options = cl.Options() assert options.ARRAY_BACKEND == "numpy" - assert options.AUTO_SPARSE == True + assert options.AUTO_SPARSE assert options.ARRAY_PRIORITY == ["dask", "sparse", "cupy", "numpy"] assert isinstance(options.ULT_VAL, str) @@ -1271,6 +1271,6 @@ def test_triangleweight_full_triangle(raa: Triangle) -> None: Testing new path that allows weights on full triangles ''' ult = cl.Chainladder().fit(raa) - tw = cl.TriangleWeight(n_periods = 4).fit(raa) - tw_full = cl.TriangleWeight(n_periods = 4).fit(ult.full_triangle_) - assert tw.w_.iloc[:,:,:,0] == tw_full.w_.iloc[:,:,:,0] \ No newline at end of file + tw = cl.TriangleWeight(n_periods=4).fit(raa) + tw_full = cl.TriangleWeight(n_periods=4).fit(ult.full_triangle_) + assert tw.w_.iloc[:, :, :, 0] == tw_full.w_.iloc[:, :, :, 0] \ No newline at end of file diff --git a/chainladder/utils/triangle_weight.py b/chainladder/utils/triangle_weight.py index d3261840..a122515c 100644 --- a/chainladder/utils/triangle_weight.py +++ b/chainladder/utils/triangle_weight.py @@ -5,7 +5,6 @@ import numpy as np import pandas as pd -from chainladder.utils.sparse import sp from sklearn.base import BaseEstimator, TransformerMixin import warnings @@ -19,7 +18,7 @@ from chainladder.core.typing import TriangleProtocol -class TriangleWeight(BaseEstimator,TransformerMixin): +class TriangleWeight(BaseEstimator, TransformerMixin): """ Helper class that produces a triangle of weights based on pattern selections @@ -123,7 +122,7 @@ def fit(self, X: TriangleProtocol, y: None = None, sample_weight: None = None): # in practie, there is no realistic use case for a sparse triangle to need weights backend = "numpy" if X.array_backend in ["sparse", "numpy"] else "cupy" obj = X.set_backend(backend) - self.w_ = self._set_weight_func(X=obj,secondary_rank=sample_weight) + self.w_ = self._set_weight_func(X=obj, secondary_rank=sample_weight) return self def transform(self, X: TriangleProtocol) -> Triangle: @@ -146,8 +145,8 @@ def transform(self, X: TriangleProtocol) -> Triangle: def _cascade_param( self, - size:int, - param: bool | int | float | str | None | list[bool|int|float|str|None], + size: int, + param: bool | int | float | str | None | list[bool | int | float | str | None], default_param: bool | int | float | str | None ) -> np.ndarray: """ @@ -237,7 +236,7 @@ def _assign_n_periods_weight_func(self, X: TriangleProtocol) -> np.ndarray: dev_len = X.shape[3] n_periods_param = self._cascade_param(dev_len, self.n_periods, -1) - #helper function that generates the weights for individual n_periods + # helper function that generates the weights for individual n_periods def _assign_n_periods_weight_int(X, n_periods): xp = X.get_array_module() val_offset = { @@ -250,10 +249,10 @@ def _assign_n_periods_weight_int(X, n_periods): return X.values * 0 + 1 else: z = -n_periods * val_offset[X.development_grain][X.origin_grain] - #adding new path to handle full triangle (e.g. full_triangle_, ultimate_, et.c) + # adding new path to handle full triangle (e.g. full_triangle_, ultimate_, et.c) if X.is_full: w = X.copy() - w.values[:,:,:-n_periods,:] = np.nan + w.values[:, :, :-n_periods, :] = np.nan else: val_date_min = X.valuation[X.valuation <= X.valuation_date] val_date_min = val_date_min.drop_duplicates().sort_values() @@ -346,8 +345,8 @@ def _drop_n_func( # getting weights that are within the max and min ranks w = ( - X_ranks < max_rank[:,:,None,:] - ) & (X_ranks > min_rank[:,:,None,:] - 1) + X_ranks < max_rank[:, :, None, :] + ) & (X_ranks > min_rank[:, :, None, :] - 1) # NOTE: The "Some exclusions have been ignored..." UserWarning below is # asserted by the test suite (see chainladder/development/tests/ @@ -484,8 +483,8 @@ def _drop_x_func(self, X: TriangleProtocol) -> np.ndarray: w = ~np.isnan(X_val) # weights without considering preserve - index_array_weights = (X_val < drop_above_array[:,:,None,:]) & ( - X_val > drop_below_array[:,:,None,:] + index_array_weights = (X_val < drop_above_array[:, :, None, :]) & ( + X_val > drop_below_array[:, :, None, :] ) # counting remaining factors @@ -494,7 +493,7 @@ def _drop_x_func(self, X: TriangleProtocol) -> np.ndarray: # applying preserve warning_flag = np.any(valid_count < preserve_array) w = np.where( - valid_count[:,:,None,:] < preserve_array[:,:,None,:], w, index_array_weights + valid_count[:, :, None, :] < preserve_array[:, :, None, :], w, index_array_weights ) # NOTE: The "Some exclusions have been ignored..." UserWarning below is diff --git a/chainladder/utils/weighted_regression.py b/chainladder/utils/weighted_regression.py index 9e118c94..1b6aec58 100644 --- a/chainladder/utils/weighted_regression.py +++ b/chainladder/utils/weighted_regression.py @@ -65,9 +65,9 @@ def infer_x_w(self): def fit( self, - X:BackendArray, - y:BackendArray|None=None, - sample_weight:BackendArray|None=None, + X: BackendArray, + y: BackendArray | None = None, + sample_weight: BackendArray | None = None, average: Literal["volume", "simple", "regression", "geometric"] | None = None ): """ diff --git a/docs/friedland/chapter_11.ipynb b/docs/friedland/chapter_11.ipynb index de5953ad..4a270d4f 100644 --- a/docs/friedland/chapter_11.ipynb +++ b/docs/friedland/chapter_11.ipynb @@ -43,86 +43,106 @@ "import chainladder as cl\n", "from IPython.display import display as nb_display\n", "\n", + "\n", "def format_exh(\n", - " exh_df:pd.DataFrame,\n", - " value_cols: list[str] = [],\n", - " factor_cols: list[str] = [],\n", - " other_formats: dict = {},\n", - " date_format: str = \"{:%Y}\"\n", + " exh_df: pd.DataFrame,\n", + " value_cols: list[str] = [],\n", + " factor_cols: list[str] = [],\n", + " other_formats: dict = {},\n", + " date_format: str = \"{:%Y}\",\n", "):\n", - " return exh_df \\\n", - " .reset_index() \\\n", - " .rename(columns={'origin':'Accident Year'}) \\\n", - " .style \\\n", - " .hide(axis='index') \\\n", - " .set_properties(**{\"text-align\":\"right\"}) \\\n", - " .format({\n", - " 'Accident Year':date_format,\n", - " **{x:\"{:,.0f}\" for x in value_cols},\n", - " **{x:\"{:,.3f}\" for x in factor_cols},\n", - " **other_formats\n", - " }) \n", + " return (\n", + " exh_df.reset_index()\n", + " .rename(columns={\"origin\": \"Accident Year\"})\n", + " .style.hide(axis=\"index\")\n", + " .set_properties(**{\"text-align\": \"right\"})\n", + " .format(\n", + " {\n", + " \"Accident Year\": date_format,\n", + " **{x: \"{:,.0f}\" for x in value_cols},\n", + " **{x: \"{:,.3f}\" for x in factor_cols},\n", + " **other_formats,\n", + " }\n", + " )\n", + " )\n", "\n", - "#create reported/paid summary exhibit\n", - "def summary_exh(reported_tri:cl.Triangle,paid_tri:cl.Triangle,ult:cl.Triangle) -> pd.DataFrame:\n", + "\n", + "# create reported/paid summary exhibit\n", + "def summary_exh(\n", + " reported_tri: cl.Triangle, paid_tri: cl.Triangle, ult: cl.Triangle\n", + ") -> pd.DataFrame:\n", " output = reported_tri.latest_diagonal.to_frame()\n", - " output.columns = ['Reported']\n", - " output['Paid'] = paid_tri.latest_diagonal.to_frame()\n", - " #using floating point offset to achieve standard rounding\n", - " output['Ult Claims'] = ult.latest_diagonal.to_frame()\n", - " output['Case Outstanding'] = output[\"Reported\"] - output[\"Paid\"]\n", - " output['IBNR'] = output[\"Ult Claims\"] - output[\"Reported\"]\n", - " output['Unpaid'] = output[\"Ult Claims\"] - output[\"Paid\"]\n", - " output.index.name = 'origin'\n", + " output.columns = [\"Reported\"]\n", + " output[\"Paid\"] = paid_tri.latest_diagonal.to_frame()\n", + " # using floating point offset to achieve standard rounding\n", + " output[\"Ult Claims\"] = ult.latest_diagonal.to_frame()\n", + " output[\"Case Outstanding\"] = output[\"Reported\"] - output[\"Paid\"]\n", + " output[\"IBNR\"] = output[\"Ult Claims\"] - output[\"Reported\"]\n", + " output[\"Unpaid\"] = output[\"Ult Claims\"] - output[\"Paid\"]\n", + " output.index.name = \"origin\"\n", " return output\n", "\n", - "#create a dict of developed triangles for each of the selection assumptions on a given page\n", - "def average_dev(tri: cl.Triangle, avg_params: dict[str,int]) -> dict[cl.Triangle]:\n", - " return {k:cl.Development(**v).fit_transform(tri) for k,v in avg_params.items()}\n", "\n", - "#combine a dict of triangles into a singla triangle\n", + "# create a dict of developed triangles for each of the selection assumptions on a given page\n", + "def average_dev(tri: cl.Triangle, avg_params: dict[str, int]) -> dict[cl.Triangle]:\n", + " return {k: cl.Development(**v).fit_transform(tri) for k, v in avg_params.items()}\n", + "\n", + "\n", + "# combine a dict of triangles into a singla triangle\n", "def combine_tri(devs: dict[cl.Triangle]) -> cl.Triangle:\n", - " avgs = [v.rename('index',k) for k,v in devs.items()]\n", - " return cl.concat(avgs,axis=0)\n", + " avgs = [v.rename(\"index\", k) for k, v in devs.items()]\n", + " return cl.concat(avgs, axis=0)\n", + "\n", "\n", - "#combine the ldf_ of a dict of triangles into a singla triangle\n", + "# combine the ldf_ of a dict of triangles into a singla triangle\n", "def combine_ldf(devs: dict[cl.Triangle]) -> cl.Triangle:\n", - " return combine_tri({k:v.ldf_ for k,v in devs.items()})\n", + " return combine_tri({k: v.ldf_ for k, v in devs.items()})\n", "\n", - "#create a dict of DisposalRate estimators for each of the selection assumptions on a given page\n", - "def average_dr(tri: cl.Triangle, w:cl.Triangle, avg_params: dict[str,int]) -> dict[cl.DisposalRate]:\n", - " return {k:cl.DisposalRate(**v).fit(X=tri,sample_weight = w) for k,v in avg_params.items()}\n", "\n", - "#combine the disposal_rate_ of a dict of triangles into a singla triangle\n", + "# create a dict of DisposalRate estimators for each of the selection assumptions on a given page\n", + "def average_dr(\n", + " tri: cl.Triangle, w: cl.Triangle, avg_params: dict[str, int]\n", + ") -> dict[cl.DisposalRate]:\n", + " return {\n", + " k: cl.DisposalRate(**v).fit(X=tri, sample_weight=w)\n", + " for k, v in avg_params.items()\n", + " }\n", + "\n", + "\n", + "# combine the disposal_rate_ of a dict of triangles into a singla triangle\n", "def combine_disposal(devs: dict[cl.Triangle]) -> cl.Triangle:\n", - " return combine_tri({k:v.disposal_rate_ for k,v in devs.items()})\n", + " return combine_tri({k: v.disposal_rate_ for k, v in devs.items()})\n", + "\n", + "\n", + "# create a dict of weighted regression objects for each of the selection assumptions on a given page\n", + "def regs(tri: cl.Triangle, avg_params: dict[str, int]) -> dict[cl.WeightedRegression]:\n", + " return {\n", + " k: cl.WeightedRegression(\n", + " axis=2, thru_orig=False, xp=tri.get_array_module()\n", + " ).fit(\n", + " np.ones(tri.shape) * tri.origin.year.values[None, None, :, None],\n", + " np.log(tri.values),\n", + " cl.TriangleWeight(**v).fit(tri).w_.values,\n", + " )\n", + " for k, v in avg_params.items()\n", + " }\n", "\n", - "#create a dict of weighted regression objects for each of the selection assumptions on a given page\n", - "def regs(tri: cl.Triangle, avg_params: dict[str,int]) -> dict[cl.WeightedRegression]:\n", - " return {k:cl.WeightedRegression(\n", - " axis=2,\n", - " thru_orig=False,\n", - " xp=tri.get_array_module()\n", - " ).fit(\n", - " np.ones(tri.shape) * tri.origin.year.values[None,None,:,None],\n", - " np.log(tri.values),\n", - " cl.TriangleWeight(**v).fit(tri).w_.values\n", - " ) for k,v in avg_params.items()}\n", "\n", - "#combine all the trends and r-squared values into pandas DataFrame\n", - "def reg_outputs(regs: dict[cl.WeightedRegression],devs:pd.Series) -> pd.DataFrame:\n", - " trend = {k:(np.exp(v.slope_)-1).flatten() for k,v in regs.items()}\n", - " trend = pd.DataFrame.from_dict(trend,orient='index')\n", + "# combine all the trends and r-squared values into pandas DataFrame\n", + "def reg_outputs(regs: dict[cl.WeightedRegression], devs: pd.Series) -> pd.DataFrame:\n", + " trend = {k: (np.exp(v.slope_) - 1).flatten() for k, v in regs.items()}\n", + " trend = pd.DataFrame.from_dict(trend, orient=\"index\")\n", " trend.columns = devs\n", - " rsq = {k:v.rsq_.flatten() for k,v in regs.items()}\n", - " rsq = pd.DataFrame.from_dict(rsq,orient='index')\n", + " rsq = {k: v.rsq_.flatten() for k, v in regs.items()}\n", + " rsq = pd.DataFrame.from_dict(rsq, orient=\"index\")\n", " rsq.columns = devs\n", - " return trend,rsq\n", + " return trend, rsq\n", + "\n", "\n", - "#create a dict of weighted regression objects for each of the selection assumptions on a given page\n", - "def average_sev(tri: cl.Triangle, avg_params: dict[str,int]) -> dict[cl.Triangle]:\n", + "# create a dict of weighted regression objects for each of the selection assumptions on a given page\n", + "def average_sev(tri: cl.Triangle, avg_params: dict[str, int]) -> dict[cl.Triangle]:\n", " output = {}\n", - " for k,v in avg_params.items():\n", + " for k, v in avg_params.items():\n", " w = cl.TriangleWeight(**v).fit(tri)\n", " weighted_tri = tri * w.w_.values\n", " output[k] = weighted_tri.mean()\n", @@ -167,43 +187,59 @@ }, "outputs": [], "source": [ - "#loading data and assumptions\n", - "e1_tri = cl.load_sample('friedland_auto_freq_sev')\n", + "# loading data and assumptions\n", + "e1_tri = cl.load_sample(\"friedland_auto_freq_sev\")\n", "e1_cnt_assumptions = {}\n", - "e1_cnt_assumptions['simple_5'] = {'n_periods':5, 'average':'simple'}\n", - "e1_cnt_assumptions['simple_3'] = {'n_periods':3, 'average':'simple'}\n", - "e1_cnt_assumptions['medial_5x1'] = {'n_periods':5, 'average':'simple','drop_high':1, 'drop_low':1}\n", - "e1_cnt_assumptions['volume_5'] = {'n_periods':5, 'average':'volume'}\n", - "e1_cnt_assumptions['volume_3'] = {'n_periods':3, 'average':'volume'}\n", + "e1_cnt_assumptions[\"simple_5\"] = {\"n_periods\": 5, \"average\": \"simple\"}\n", + "e1_cnt_assumptions[\"simple_3\"] = {\"n_periods\": 3, \"average\": \"simple\"}\n", + "e1_cnt_assumptions[\"medial_5x1\"] = {\n", + " \"n_periods\": 5,\n", + " \"average\": \"simple\",\n", + " \"drop_high\": 1,\n", + " \"drop_low\": 1,\n", + "}\n", + "e1_cnt_assumptions[\"volume_5\"] = {\"n_periods\": 5, \"average\": \"volume\"}\n", + "e1_cnt_assumptions[\"volume_3\"] = {\"n_periods\": 3, \"average\": \"volume\"}\n", "e1_sev_assumptions = {}\n", - "e1_sev_assumptions['simple_5'] = {'n_periods':5, 'average':'simple'}\n", - "e1_sev_assumptions['simple_3'] = {'n_periods':3, 'average':'simple'}\n", - "e1_sev_assumptions['medial_5x1'] = {'n_periods':5, 'average':'simple','drop_high':1, 'drop_low':1}\n", + "e1_sev_assumptions[\"simple_5\"] = {\"n_periods\": 5, \"average\": \"simple\"}\n", + "e1_sev_assumptions[\"simple_3\"] = {\"n_periods\": 3, \"average\": \"simple\"}\n", + "e1_sev_assumptions[\"medial_5x1\"] = {\n", + " \"n_periods\": 5,\n", + " \"average\": \"simple\",\n", + " \"drop_high\": 1,\n", + " \"drop_low\": 1,\n", + "}\n", "\n", - "#developing closed claim counts\n", - "e1_ccc_devs = average_dev(e1_tri['Closed Claim Counts'],e1_cnt_assumptions)\n", - "e1_ccc_selected = cl.TailConstant(tail = 1.0, projection_period = 0).fit_transform(e1_ccc_devs['simple_3'])\n", + "# developing closed claim counts\n", + "e1_ccc_devs = average_dev(e1_tri[\"Closed Claim Counts\"], e1_cnt_assumptions)\n", + "e1_ccc_selected = cl.TailConstant(tail=1.0, projection_period=0).fit_transform(\n", + " e1_ccc_devs[\"simple_3\"]\n", + ")\n", "e1_ccc_selected.ldf_ = e1_ccc_selected.ldf_.round(3)\n", "\n", - "#developing reported claim counts\n", - "e1_rcc_devs = average_dev(e1_tri['Reported Claim Counts'],e1_cnt_assumptions)\n", - "e1_rcc_selected = cl.TailConstant(tail = 1.0, projection_period = 0).fit_transform(e1_rcc_devs['simple_3'])\n", + "# developing reported claim counts\n", + "e1_rcc_devs = average_dev(e1_tri[\"Reported Claim Counts\"], e1_cnt_assumptions)\n", + "e1_rcc_selected = cl.TailConstant(tail=1.0, projection_period=0).fit_transform(\n", + " e1_rcc_devs[\"simple_3\"]\n", + ")\n", "e1_rcc_selected.ldf_ = e1_rcc_selected.ldf_.round(3)\n", "\n", - "#combining closed and reported claim counts\n", + "# combining closed and reported claim counts\n", "e1_ccc_cl = cl.Chainladder().fit(e1_ccc_selected)\n", "e1_rcc_cl = cl.Chainladder().fit(e1_rcc_selected)\n", - "e1_cc_ult = (e1_ccc_cl.ultimate_ + e1_rcc_cl.ultimate_)/2\n", - "e1_cc_ult.iloc[:,:,-1,:] = e1_rcc_cl.ultimate_.iloc[:,:,-1,:]\n", + "e1_cc_ult = (e1_ccc_cl.ultimate_ + e1_rcc_cl.ultimate_) / 2\n", + "e1_cc_ult.iloc[:, :, -1, :] = e1_rcc_cl.ultimate_.iloc[:, :, -1, :]\n", "\n", - "#calculating and developing reported severity\n", - "e1_rsev = e1_tri['Reported Claims'] / e1_tri['Reported Claim Counts']\n", - "e1_rsev_devs = average_dev(e1_rsev,e1_sev_assumptions)\n", - "e1_rsev_selected = cl.TailConstant(tail = 1.0, projection_period = 0).fit_transform(e1_rsev_devs['medial_5x1'])\n", + "# calculating and developing reported severity\n", + "e1_rsev = e1_tri[\"Reported Claims\"] / e1_tri[\"Reported Claim Counts\"]\n", + "e1_rsev_devs = average_dev(e1_rsev, e1_sev_assumptions)\n", + "e1_rsev_selected = cl.TailConstant(tail=1.0, projection_period=0).fit_transform(\n", + " e1_rsev_devs[\"medial_5x1\"]\n", + ")\n", "e1_rsev_selected.ldf_ = e1_rsev_selected.ldf_.round(3)\n", "e1_rsev_cl = cl.Chainladder().fit(e1_rsev_selected)\n", "\n", - "#combining developed count and severity\n", + "# combining developed count and severity\n", "e1_ult = e1_rsev_cl.ultimate_ * e1_cc_ult / 1000" ] }, @@ -241,19 +277,19 @@ }, "outputs": [], "source": [ - "print('PART 1 - Data Triangle')\n", - "nb_display(e1_tri['Closed Claim Counts'])\n", - "print('PART 2 - Age-to-Age Factors')\n", - "nb_display(e1_tri['Closed Claim Counts'].age_to_age.round(3))\n", - "print('PART 3 - Average Age-to-Age Factor')\n", + "print(\"PART 1 - Data Triangle\")\n", + "nb_display(e1_tri[\"Closed Claim Counts\"])\n", + "print(\"PART 2 - Age-to-Age Factors\")\n", + "nb_display(e1_tri[\"Closed Claim Counts\"].age_to_age.round(3))\n", + "print(\"PART 3 - Average Age-to-Age Factor\")\n", "nb_display(combine_ldf(e1_ccc_devs).round(3).to_frame().rename_axis(\"\"))\n", - "print('PART 4 - Selected Age-to-Age Factors')\n", - "print('Selected')\n", + "print(\"PART 4 - Selected Age-to-Age Factors\")\n", + "print(\"Selected\")\n", "nb_display(e1_ccc_selected.ldf_)\n", - "print('CDF to Ultimate')\n", + "print(\"CDF to Ultimate\")\n", "nb_display(e1_ccc_selected.cdf_.round(3))\n", - "print('Percent Closed')\n", - "nb_display(1/e1_ccc_selected.cdf_.round(3))" + "print(\"Percent Closed\")\n", + "nb_display(1 / e1_ccc_selected.cdf_.round(3))" ] }, { @@ -290,19 +326,19 @@ }, "outputs": [], "source": [ - "print('PART 1 - Data Triangle')\n", - "nb_display(e1_tri['Reported Claim Counts'])\n", - "print('PART 2 - Age-to-Age Factors')\n", - "nb_display(e1_tri['Reported Claim Counts'].age_to_age.round(3))\n", - "print('PART 3 - Average Age-to-Age Factor')\n", + "print(\"PART 1 - Data Triangle\")\n", + "nb_display(e1_tri[\"Reported Claim Counts\"])\n", + "print(\"PART 2 - Age-to-Age Factors\")\n", + "nb_display(e1_tri[\"Reported Claim Counts\"].age_to_age.round(3))\n", + "print(\"PART 3 - Average Age-to-Age Factor\")\n", "nb_display(combine_ldf(e1_rcc_devs).round(3).to_frame().rename_axis(\"\"))\n", - "print('PART 4 - Selected Age-to-Age Factors')\n", - "print('Selected')\n", + "print(\"PART 4 - Selected Age-to-Age Factors\")\n", + "print(\"Selected\")\n", "nb_display(e1_rcc_selected.ldf_)\n", - "print('CDF to Ultimate')\n", + "print(\"CDF to Ultimate\")\n", "nb_display(e1_rcc_selected.cdf_.round(3))\n", - "print('Percent Reported')\n", - "nb_display(1/e1_rcc_selected.cdf_.round(3))" + "print(\"Percent Reported\")\n", + "nb_display(1 / e1_rcc_selected.cdf_.round(3))" ] }, { @@ -339,34 +375,44 @@ }, "outputs": [], "source": [ - "e1_ccc_df = cl.model_diagnostics(e1_ccc_cl).to_frame(keepdims=True,implicit_axis=True).set_index('origin')\n", - "e1_rcc_df = cl.model_diagnostics(e1_rcc_cl).to_frame(keepdims=True,implicit_axis=True).set_index('origin')\n", - "e1_s3 = e1_ccc_df[['development','Latest','CDF','Ultimate']].rename(\n", + "e1_ccc_df = (\n", + " cl.model_diagnostics(e1_ccc_cl)\n", + " .to_frame(keepdims=True, implicit_axis=True)\n", + " .set_index(\"origin\")\n", + ")\n", + "e1_rcc_df = (\n", + " cl.model_diagnostics(e1_rcc_cl)\n", + " .to_frame(keepdims=True, implicit_axis=True)\n", + " .set_index(\"origin\")\n", + ")\n", + "e1_s3 = e1_ccc_df[[\"development\", \"Latest\", \"CDF\", \"Ultimate\"]].rename(\n", " columns={\n", - " 'development':'Age',\n", - " 'Latest':'Closed Claim Counts',\n", - " 'CDF':'Closed CDF',\n", - " 'Ultimate':'Ult Count Using CCC'\n", + " \"development\": \"Age\",\n", + " \"Latest\": \"Closed Claim Counts\",\n", + " \"CDF\": \"Closed CDF\",\n", + " \"Ultimate\": \"Ult Count Using CCC\",\n", " }\n", ")\n", - "e1_s3[['Reported Claim Counts','Reported CDF','Ult Count Using RCC']] = e1_rcc_df[['Latest','CDF','Ultimate']]\n", + "e1_s3[[\"Reported Claim Counts\", \"Reported CDF\", \"Ult Count Using RCC\"]] = e1_rcc_df[\n", + " [\"Latest\", \"CDF\", \"Ultimate\"]\n", + "]\n", "e1_s3[\"Selected Ult CC\"] = e1_cc_ult.latest_diagonal.to_frame()\n", "format_exh(\n", " e1_s3,\n", " [\n", - " 'Closed Claim Counts',\n", - " 'Reported Claim Counts',\n", - " 'Ult Count Using CCC',\n", - " 'Ult Count Using RCC',\n", - " 'Selected Ult CC',\n", + " \"Closed Claim Counts\",\n", + " \"Reported Claim Counts\",\n", + " \"Ult Count Using CCC\",\n", + " \"Ult Count Using RCC\",\n", + " \"Selected Ult CC\",\n", " ],\n", " [\n", - " 'Closed CDF',\n", - " 'Reported CDF',\n", + " \"Closed CDF\",\n", + " \"Reported CDF\",\n", " ],\n", " {},\n", - " \"{:%Y%m}\"\n", - ") " + " \"{:%Y%m}\",\n", + ")" ] }, { @@ -403,7 +449,7 @@ }, "outputs": [], "source": [ - "(e1_tri['Closed Claim Counts']/e1_tri['Reported Claim Counts']).round(3)" + "(e1_tri[\"Closed Claim Counts\"] / e1_tri[\"Reported Claim Counts\"]).round(3)" ] }, { @@ -440,7 +486,7 @@ }, "outputs": [], "source": [ - "nb_display(e1_tri['Reported Claims']/1000)\n", + "nb_display(e1_tri[\"Reported Claims\"] / 1000)\n", "nb_display(e1_rsev)" ] }, @@ -478,15 +524,15 @@ }, "outputs": [], "source": [ - "print('PART 1 - Data Triangle')\n", + "print(\"PART 1 - Data Triangle\")\n", "nb_display(e1_rsev)\n", - "print('PART 2 - Age-to-Age Factors')\n", + "print(\"PART 2 - Age-to-Age Factors\")\n", "nb_display(e1_rsev.age_to_age.round(3))\n", - "print('PART 3 - Average Age-to-Age Factors')\n", + "print(\"PART 3 - Average Age-to-Age Factors\")\n", "nb_display(combine_ldf(e1_rsev_devs).round(3).to_frame().rename_axis(\"\"))\n", - "print('PART 4 - Selected Age-to-Age Factors')\n", + "print(\"PART 4 - Selected Age-to-Age Factors\")\n", "nb_display(e1_rsev_selected.ldf_)\n", - "print('CDF to Ultimate')\n", + "print(\"CDF to Ultimate\")\n", "nb_display(e1_rsev_selected.cdf_.round(3))" ] }, @@ -524,27 +570,31 @@ }, "outputs": [], "source": [ - "e1_rsev_ult_df = cl.model_diagnostics(e1_rsev_cl).to_frame(keepdims=True,implicit_axis=True).set_index('origin')\n", - "e1_s7 = e1_rsev_ult_df[['development','Latest','CDF','Ultimate']].rename(\n", + "e1_rsev_ult_df = (\n", + " cl.model_diagnostics(e1_rsev_cl)\n", + " .to_frame(keepdims=True, implicit_axis=True)\n", + " .set_index(\"origin\")\n", + ")\n", + "e1_s7 = e1_rsev_ult_df[[\"development\", \"Latest\", \"CDF\", \"Ultimate\"]].rename(\n", " columns={\n", - " 'development':'Age',\n", - " 'Latest':'Reported Severity',\n", - " 'Ultimate':'Ult Severity'\n", + " \"development\": \"Age\",\n", + " \"Latest\": \"Reported Severity\",\n", + " \"Ultimate\": \"Ult Severity\",\n", " }\n", ")\n", - "e1_s7['Selected Ult CC'] = e1_s3['Selected Ult CC']\n", + "e1_s7[\"Selected Ult CC\"] = e1_s3[\"Selected Ult CC\"]\n", "e1_s7[\"Ult Claims\"] = e1_ult.latest_diagonal.to_frame()\n", "format_exh(\n", " e1_s7,\n", " [\n", - " 'Reported Severity',\n", - " 'Ult Severity',\n", - " 'Selected Ult CC',\n", - " 'Ult Claims',\n", + " \"Reported Severity\",\n", + " \"Ult Severity\",\n", + " \"Selected Ult CC\",\n", + " \"Ult Claims\",\n", " ],\n", - " ['CDF'],\n", + " [\"CDF\"],\n", " {},\n", - " \"{:%Y%m}\"\n", + " \"{:%Y%m}\",\n", ")" ] }, @@ -620,23 +670,32 @@ }, "outputs": [], "source": [ - "#Exhibit I Sheet 1\n", - "assert np.all(e1_ccc_selected.cdf_.round(3).values == np.array([1.305, 1.010, 1.001, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000]))\n", - "#Exhibit I Sheet 2\n", - "assert np.all(e1_rcc_selected.cdf_.round(3).values == np.array([0.975, 0.997, 0.999, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000]))\n", - "#Exhibit I Sheet 3\n", + "# Exhibit I Sheet 1\n", + "assert np.all(\n", + " e1_ccc_selected.cdf_.round(3).values\n", + " == np.array([1.305, 1.010, 1.001, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000])\n", + ")\n", + "# Exhibit I Sheet 2\n", + "assert np.all(\n", + " e1_rcc_selected.cdf_.round(3).values\n", + " == np.array([0.975, 0.997, 0.999, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000])\n", + ")\n", + "# Exhibit I Sheet 3\n", "assert np.allclose(\n", - " e1_s3['Selected Ult CC'].values,\n", + " e1_s3[\"Selected Ult CC\"].values,\n", " np.array([3292, 3243, 2699, 2757, 2635, 2699, 2665, 2524, 2764, 3061]),\n", - " atol=1\n", + " atol=1,\n", ")\n", - "#Exhibit I Sheet 6\n", - "assert np.all(e1_rsev_selected.cdf_.round(3).values == np.array([1.036, 0.997, 0.998, 0.999, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000]))\n", - "#Exhibit I Sheet 7\n", + "# Exhibit I Sheet 6\n", + "assert np.all(\n", + " e1_rsev_selected.cdf_.round(3).values\n", + " == np.array([1.036, 0.997, 0.998, 0.999, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000])\n", + ")\n", + "# Exhibit I Sheet 7\n", "assert np.allclose(\n", - " e1_s7['Ult Severity'].values,\n", + " e1_s7[\"Ult Severity\"].values,\n", " np.array([4510, 4507, 4632, 4295, 4465, 4356, 4754, 4535, 4613, 4644]),\n", - " atol=1\n", + " atol=1,\n", ")" ] }, @@ -676,42 +735,58 @@ }, "outputs": [], "source": [ - "#loading data and assumptions\n", - "e2_tri = cl.load_sample('friedland_xyz_auto_bi')\n", + "# loading data and assumptions\n", + "e2_tri = cl.load_sample(\"friedland_xyz_auto_bi\")\n", "e2_cnt_assumptions = {}\n", - "e2_cnt_assumptions['simple_3'] = {'n_periods':3, 'average':'simple'}\n", - "e2_cnt_assumptions['simple_2'] = {'n_periods':2, 'average':'simple'}\n", - "e2_cnt_assumptions['medial_5x1'] = {'n_periods':5, 'average':'simple','drop_high':1, 'drop_low':1}\n", - "e2_cnt_assumptions['volume_3'] = {'n_periods':3, 'average':'volume'}\n", - "e2_cnt_assumptions['volume_2'] = {'n_periods':2, 'average':'volume'}\n", + "e2_cnt_assumptions[\"simple_3\"] = {\"n_periods\": 3, \"average\": \"simple\"}\n", + "e2_cnt_assumptions[\"simple_2\"] = {\"n_periods\": 2, \"average\": \"simple\"}\n", + "e2_cnt_assumptions[\"medial_5x1\"] = {\n", + " \"n_periods\": 5,\n", + " \"average\": \"simple\",\n", + " \"drop_high\": 1,\n", + " \"drop_low\": 1,\n", + "}\n", + "e2_cnt_assumptions[\"volume_3\"] = {\"n_periods\": 3, \"average\": \"volume\"}\n", + "e2_cnt_assumptions[\"volume_2\"] = {\"n_periods\": 2, \"average\": \"volume\"}\n", "e2_sev_assumptions = {}\n", - "e2_sev_assumptions['simple_3'] = {'n_periods':3, 'average':'simple'}\n", - "e2_sev_assumptions['simple_2'] = {'n_periods':2, 'average':'simple'}\n", - "e2_sev_assumptions['medial_5x1'] = {'n_periods':5, 'average':'simple','drop_high':1, 'drop_low':1}\n", + "e2_sev_assumptions[\"simple_3\"] = {\"n_periods\": 3, \"average\": \"simple\"}\n", + "e2_sev_assumptions[\"simple_2\"] = {\"n_periods\": 2, \"average\": \"simple\"}\n", + "e2_sev_assumptions[\"medial_5x1\"] = {\n", + " \"n_periods\": 5,\n", + " \"average\": \"simple\",\n", + " \"drop_high\": 1,\n", + " \"drop_low\": 1,\n", + "}\n", "\n", - "#developing closed claim counts\n", - "e2_ccc_devs = average_dev(e2_tri['Closed Claim Counts'],e2_cnt_assumptions)\n", - "e2_ccc_selected = cl.TailConstant(tail = 1.0, projection_period = 0).fit_transform(e2_ccc_devs['volume_2'])\n", + "# developing closed claim counts\n", + "e2_ccc_devs = average_dev(e2_tri[\"Closed Claim Counts\"], e2_cnt_assumptions)\n", + "e2_ccc_selected = cl.TailConstant(tail=1.0, projection_period=0).fit_transform(\n", + " e2_ccc_devs[\"volume_2\"]\n", + ")\n", "e2_ccc_selected.ldf_ = e2_ccc_selected.ldf_.round(3)\n", "\n", - "#developing reported claim counts\n", - "e2_rcc_devs = average_dev(e2_tri['Reported Claim Counts'],e2_cnt_assumptions)\n", - "e2_rcc_selected = cl.TailConstant(tail = 1.0, projection_period = 0).fit_transform(e2_rcc_devs['volume_2'])\n", + "# developing reported claim counts\n", + "e2_rcc_devs = average_dev(e2_tri[\"Reported Claim Counts\"], e2_cnt_assumptions)\n", + "e2_rcc_selected = cl.TailConstant(tail=1.0, projection_period=0).fit_transform(\n", + " e2_rcc_devs[\"volume_2\"]\n", + ")\n", "e2_rcc_selected.ldf_ = e2_rcc_selected.ldf_.round(3)\n", "\n", - "#combining closed and reported claim counts\n", + "# combining closed and reported claim counts\n", "e2_ccc_cl = cl.Chainladder().fit(e2_ccc_selected)\n", "e2_rcc_cl = cl.Chainladder().fit(e2_rcc_selected)\n", - "e2_cc_ult = (e2_ccc_cl.ultimate_ + e2_rcc_cl.ultimate_)/2\n", + "e2_cc_ult = (e2_ccc_cl.ultimate_ + e2_rcc_cl.ultimate_) / 2\n", "\n", - "#calculating and developing reported severity\n", - "e2_rsev = e2_tri['Reported Claims'] / e2_tri['Reported Claim Counts'] * 1000\n", - "e2_rsev_devs = average_dev(e2_rsev,e2_sev_assumptions)\n", - "e2_rsev_selected = cl.TailConstant(tail = 1.0, projection_period = 0).fit_transform(e2_rsev_devs['simple_2'])\n", + "# calculating and developing reported severity\n", + "e2_rsev = e2_tri[\"Reported Claims\"] / e2_tri[\"Reported Claim Counts\"] * 1000\n", + "e2_rsev_devs = average_dev(e2_rsev, e2_sev_assumptions)\n", + "e2_rsev_selected = cl.TailConstant(tail=1.0, projection_period=0).fit_transform(\n", + " e2_rsev_devs[\"simple_2\"]\n", + ")\n", "e2_rsev_selected.ldf_ = e2_rsev_selected.ldf_.round(3)\n", "e2_rsev_cl = cl.Chainladder().fit(e2_rsev_selected)\n", "\n", - "#combining developed count and severity\n", + "# combining developed count and severity\n", "e2_ult = e2_rsev_cl.ultimate_ * e2_cc_ult / 1000" ] }, @@ -800,19 +875,19 @@ }, "outputs": [], "source": [ - "print('PART 1 - Data Triangle')\n", - "nb_display(e2_tri['Reported Claim Counts'])\n", - "print('PART 2 - Age-to-Age Factors')\n", - "nb_display(e2_tri['Reported Claim Counts'].age_to_age.round(3))\n", - "print('PART 3 - Average Age-to-Age Factor')\n", + "print(\"PART 1 - Data Triangle\")\n", + "nb_display(e2_tri[\"Reported Claim Counts\"])\n", + "print(\"PART 2 - Age-to-Age Factors\")\n", + "nb_display(e2_tri[\"Reported Claim Counts\"].age_to_age.round(3))\n", + "print(\"PART 3 - Average Age-to-Age Factor\")\n", "nb_display(combine_ldf(e2_rcc_devs).round(3).to_frame().rename_axis(\"\"))\n", - "print('PART 4 - Selected Age-to-Age Factors')\n", - "print('Selected')\n", + "print(\"PART 4 - Selected Age-to-Age Factors\")\n", + "print(\"Selected\")\n", "nb_display(e2_rcc_selected.ldf_)\n", - "print('CDF to Ultimate')\n", + "print(\"CDF to Ultimate\")\n", "nb_display(e2_rcc_selected.cdf_.round(3))\n", - "print('Percent Reported')\n", - "nb_display(1/e2_rcc_selected.cdf_.round(3))" + "print(\"Percent Reported\")\n", + "nb_display(1 / e2_rcc_selected.cdf_.round(3))" ] }, { @@ -850,32 +925,42 @@ }, "outputs": [], "source": [ - "e2_ccc_df = cl.model_diagnostics(e2_ccc_cl).to_frame(keepdims=True,implicit_axis=True).set_index('origin')\n", - "e2_rcc_df = cl.model_diagnostics(e2_rcc_cl).to_frame(keepdims=True,implicit_axis=True).set_index('origin')\n", - "e2_s3 = e2_ccc_df[['development','Latest','CDF','Ultimate']].rename(\n", + "e2_ccc_df = (\n", + " cl.model_diagnostics(e2_ccc_cl)\n", + " .to_frame(keepdims=True, implicit_axis=True)\n", + " .set_index(\"origin\")\n", + ")\n", + "e2_rcc_df = (\n", + " cl.model_diagnostics(e2_rcc_cl)\n", + " .to_frame(keepdims=True, implicit_axis=True)\n", + " .set_index(\"origin\")\n", + ")\n", + "e2_s3 = e2_ccc_df[[\"development\", \"Latest\", \"CDF\", \"Ultimate\"]].rename(\n", " columns={\n", - " 'development':'Age',\n", - " 'Latest':'Closed Claim Counts',\n", - " 'CDF':'Closed CDF',\n", - " 'Ultimate':'Ult Count Using CCC'\n", + " \"development\": \"Age\",\n", + " \"Latest\": \"Closed Claim Counts\",\n", + " \"CDF\": \"Closed CDF\",\n", + " \"Ultimate\": \"Ult Count Using CCC\",\n", " }\n", ")\n", - "e2_s3[['Reported Claim Counts','Reported CDF','Ult Count Using RCC']] = e2_rcc_df[['Latest','CDF','Ultimate']]\n", + "e2_s3[[\"Reported Claim Counts\", \"Reported CDF\", \"Ult Count Using RCC\"]] = e2_rcc_df[\n", + " [\"Latest\", \"CDF\", \"Ultimate\"]\n", + "]\n", "e2_s3[\"Selected Ult CC\"] = e2_cc_ult.latest_diagonal.to_frame()\n", "format_exh(\n", " e2_s3,\n", " [\n", - " 'Closed Claim Counts',\n", - " 'Reported Claim Counts',\n", - " 'Ult Count Using CCC',\n", - " 'Ult Count Using RCC',\n", - " 'Selected Ult CC',\n", + " \"Closed Claim Counts\",\n", + " \"Reported Claim Counts\",\n", + " \"Ult Count Using CCC\",\n", + " \"Ult Count Using RCC\",\n", + " \"Selected Ult CC\",\n", " ],\n", " [\n", - " 'Closed CDF',\n", - " 'Reported CDF',\n", - " ]\n", - ") " + " \"Closed CDF\",\n", + " \"Reported CDF\",\n", + " ],\n", + ")" ] }, { @@ -913,7 +998,7 @@ }, "outputs": [], "source": [ - "nb_display(e2_tri['Reported Claims'])\n", + "nb_display(e2_tri[\"Reported Claims\"])\n", "nb_display(e2_rsev)" ] }, @@ -952,15 +1037,15 @@ }, "outputs": [], "source": [ - "print('PART 1 - Data Triangle')\n", + "print(\"PART 1 - Data Triangle\")\n", "nb_display(e2_rsev)\n", - "print('PART 2 - Age-to-Age Factors')\n", + "print(\"PART 2 - Age-to-Age Factors\")\n", "nb_display(e2_rsev.age_to_age.round(3))\n", - "print('PART 3 - Average Age-to-Age Factors')\n", + "print(\"PART 3 - Average Age-to-Age Factors\")\n", "nb_display(combine_ldf(e2_rsev_devs).round(3).to_frame().rename_axis(\"\"))\n", - "print('PART 4 - Selected Age-to-Age Factors')\n", + "print(\"PART 4 - Selected Age-to-Age Factors\")\n", "nb_display(e2_rsev_selected.ldf_)\n", - "print('CDF to Ultimate')\n", + "print(\"CDF to Ultimate\")\n", "nb_display(e2_rsev_selected.cdf_.round(3))" ] }, @@ -999,27 +1084,31 @@ }, "outputs": [], "source": [ - "e2_rsev_df = cl.model_diagnostics(e2_rsev_cl).to_frame(keepdims=True,implicit_axis=True).set_index('origin')\n", - "e2_s6 = e2_rsev_df[['development','Latest','CDF','Ultimate']].rename(\n", + "e2_rsev_df = (\n", + " cl.model_diagnostics(e2_rsev_cl)\n", + " .to_frame(keepdims=True, implicit_axis=True)\n", + " .set_index(\"origin\")\n", + ")\n", + "e2_s6 = e2_rsev_df[[\"development\", \"Latest\", \"CDF\", \"Ultimate\"]].rename(\n", " columns={\n", - " 'development':'Age',\n", - " 'Latest':'Reported Severity',\n", - " 'Ultimate':'Ult Severity'\n", + " \"development\": \"Age\",\n", + " \"Latest\": \"Reported Severity\",\n", + " \"Ultimate\": \"Ult Severity\",\n", " }\n", ")\n", - "e2_s6['Selected Ult CC'] = e2_s3['Selected Ult CC']\n", + "e2_s6[\"Selected Ult CC\"] = e2_s3[\"Selected Ult CC\"]\n", "e2_s6[\"Ult Claims\"] = e2_ult.latest_diagonal.to_frame()\n", "format_exh(\n", " e2_s6,\n", " [\n", - " 'Reported Severity',\n", - " 'Ult Severity',\n", - " 'Selected Ult CC',\n", - " 'Ult Claims',\n", + " \"Reported Severity\",\n", + " \"Ult Severity\",\n", + " \"Selected Ult CC\",\n", + " \"Ult Claims\",\n", " ],\n", " [\n", - " 'CDF',\n", - " ]\n", + " \"CDF\",\n", + " ],\n", ")" ] }, @@ -1058,8 +1147,8 @@ }, "outputs": [], "source": [ - "e2_s7 = summary_exh(e2_tri['Reported Claims'],e2_tri['Paid Claims'],e2_ult)\n", - "format_exh(e2_s7,e2_s7.columns)" + "e2_s7 = summary_exh(e2_tri[\"Reported Claims\"], e2_tri[\"Paid Claims\"], e2_ult)\n", + "format_exh(e2_s7, e2_s7.columns)" ] }, { @@ -1080,23 +1169,40 @@ }, "outputs": [], "source": [ - "#Exhibit II Sheet 1\n", - "assert np.all(e2_ccc_selected.cdf_.round(3).values == np.array([6.085, 2.281, 1.612, 1.318, 1.161, 1.085, 1.035, 1.021, 1.015, 1.003, 1.000]))\n", - "#Exhibit II Sheet 2\n", - "assert np.all(e2_rcc_selected.cdf_.round(3).values == np.array([1.131, 1.035, 1.013, 1.005, 1.002, 1.001, 1.000, 1.000, 1.000, 1.000, 1.000]))\n", - "#Exhibit II Sheet 3\n", + "# Exhibit II Sheet 1\n", + "assert np.all(\n", + " e2_ccc_selected.cdf_.round(3).values\n", + " == np.array(\n", + " [6.085, 2.281, 1.612, 1.318, 1.161, 1.085, 1.035, 1.021, 1.015, 1.003, 1.000]\n", + " )\n", + ")\n", + "# Exhibit II Sheet 2\n", + "assert np.all(\n", + " e2_rcc_selected.cdf_.round(3).values\n", + " == np.array(\n", + " [1.131, 1.035, 1.013, 1.005, 1.002, 1.001, 1.000, 1.000, 1.000, 1.000, 1.000]\n", + " )\n", + ")\n", + "# Exhibit II Sheet 3\n", "assert np.allclose(\n", - " e2_s3['Selected Ult CC'].values,\n", + " e2_s3[\"Selected Ult CC\"].values,\n", " np.array([637, 1047, 1416, 1466, 1565, 1666, 2309, 2483, 1807, 1556, 1426]),\n", - " atol=1\n", + " atol=1,\n", ")\n", - "#Exhibit II Sheet 5\n", - "assert np.all(e2_rsev_selected.cdf_.round(3).values == np.array([2.310, 1.498, 1.212, 1.100, 1.066, 1.016, 1.003, 0.992, 0.990, 0.999, 1.000]))\n", - "#Exhibit II Sheet 6\n", + "# Exhibit II Sheet 5\n", + "assert np.all(\n", + " e2_rsev_selected.cdf_.round(3).values\n", + " == np.array(\n", + " [2.310, 1.498, 1.212, 1.100, 1.066, 1.016, 1.003, 0.992, 0.990, 0.999, 1.000]\n", + " )\n", + ")\n", + "# Exhibit II Sheet 6\n", "assert np.allclose(\n", - " e2_s6['Ult Severity'].values,\n", - " np.array([24839, 23956, 26189, 26452, 31090, 27675, 33183, 32519, 35697, 37606, 41544]),\n", - " rtol=0.001\n", + " e2_s6[\"Ult Severity\"].values,\n", + " np.array(\n", + " [24839, 23956, 26189, 26452, 31090, 27675, 33183, 32519, 35697, 37606, 41544]\n", + " ),\n", + " rtol=0.001,\n", ")" ] }, @@ -1136,71 +1242,93 @@ }, "outputs": [], "source": [ - "#loading data and assumptions\n", - "e3_tri = cl.load_sample('friedland_wc_self_insurer')\n", + "# loading data and assumptions\n", + "e3_tri = cl.load_sample(\"friedland_wc_self_insurer\")\n", "e3_cnt_assumptions = {}\n", - "e3_cnt_assumptions['simple_5'] = {'n_periods':5, 'average':'simple'}\n", - "e3_cnt_assumptions['simple_3'] = {'n_periods':3, 'average':'simple'}\n", - "e3_cnt_assumptions['medial_5x1'] = {'n_periods':5, 'average':'simple','drop_high':1, 'drop_low':1}\n", - "e3_cnt_assumptions['volume_5'] = {'n_periods':5, 'average':'volume'}\n", - "e3_cnt_assumptions['volume_3'] = {'n_periods':3, 'average':'volume'}\n", + "e3_cnt_assumptions[\"simple_5\"] = {\"n_periods\": 5, \"average\": \"simple\"}\n", + "e3_cnt_assumptions[\"simple_3\"] = {\"n_periods\": 3, \"average\": \"simple\"}\n", + "e3_cnt_assumptions[\"medial_5x1\"] = {\n", + " \"n_periods\": 5,\n", + " \"average\": \"simple\",\n", + " \"drop_high\": 1,\n", + " \"drop_low\": 1,\n", + "}\n", + "e3_cnt_assumptions[\"volume_5\"] = {\"n_periods\": 5, \"average\": \"volume\"}\n", + "e3_cnt_assumptions[\"volume_3\"] = {\"n_periods\": 3, \"average\": \"volume\"}\n", "e3_sev_assumptions = {}\n", - "e3_sev_assumptions['simple_5'] = {'n_periods':5, 'average':'simple'}\n", - "e3_sev_assumptions['simple_3'] = {'n_periods':3, 'average':'simple'}\n", - "e3_sev_assumptions['medial_5x1'] = {'n_periods':5, 'average':'simple','drop_high':1, 'drop_low':1}\n", + "e3_sev_assumptions[\"simple_5\"] = {\"n_periods\": 5, \"average\": \"simple\"}\n", + "e3_sev_assumptions[\"simple_3\"] = {\"n_periods\": 3, \"average\": \"simple\"}\n", + "e3_sev_assumptions[\"medial_5x1\"] = {\n", + " \"n_periods\": 5,\n", + " \"average\": \"simple\",\n", + " \"drop_high\": 1,\n", + " \"drop_low\": 1,\n", + "}\n", "e3_sevavg_assumptions = {}\n", - "e3_sevavg_assumptions['all_years'] = {}\n", - "e3_sevavg_assumptions['all_years_excl_hilo'] = {'drop_high':1, 'drop_low':1}\n", - "e3_sevavg_assumptions['latest_3'] = {'n_periods':3}\n", + "e3_sevavg_assumptions[\"all_years\"] = {}\n", + "e3_sevavg_assumptions[\"all_years_excl_hilo\"] = {\"drop_high\": 1, \"drop_low\": 1}\n", + "e3_sevavg_assumptions[\"latest_3\"] = {\"n_periods\": 3}\n", "\n", - "#developing closed claim counts\n", - "#There is a typo in the text. We will use a tail of 1.002 to match the 84-ult factor'\n", - "e3_ccc_devs = average_dev(e3_tri['Closed Claim Counts'],e3_cnt_assumptions)\n", - "e3_ccc_selected = cl.TailConstant(tail = 1.002, projection_period = 0).fit_transform(e3_ccc_devs['volume_5'])\n", + "# developing closed claim counts\n", + "# There is a typo in the text. We will use a tail of 1.002 to match the 84-ult factor'\n", + "e3_ccc_devs = average_dev(e3_tri[\"Closed Claim Counts\"], e3_cnt_assumptions)\n", + "e3_ccc_selected = cl.TailConstant(tail=1.002, projection_period=0).fit_transform(\n", + " e3_ccc_devs[\"volume_5\"]\n", + ")\n", "e3_ccc_selected.ldf_ = e3_ccc_selected.ldf_.round(3)\n", "\n", - "#developing reported claim counts\n", - "e3_rcc_devs = average_dev(e3_tri['Reported Claim Counts'],e3_cnt_assumptions)\n", - "e3_rcc_selected = cl.TailConstant(tail = 1.000, projection_period = 0).fit_transform(e3_rcc_devs['volume_5'])\n", + "# developing reported claim counts\n", + "e3_rcc_devs = average_dev(e3_tri[\"Reported Claim Counts\"], e3_cnt_assumptions)\n", + "e3_rcc_selected = cl.TailConstant(tail=1.000, projection_period=0).fit_transform(\n", + " e3_rcc_devs[\"volume_5\"]\n", + ")\n", "e3_rcc_selected.ldf_ = e3_rcc_selected.ldf_.round(3)\n", "\n", - "#combining closed and reported claim counts\n", + "# combining closed and reported claim counts\n", "e3_ccc_cl = cl.Chainladder().fit(e3_ccc_selected)\n", "e3_rcc_cl = cl.Chainladder().fit(e3_rcc_selected)\n", - "e3_cc_ult = (e3_ccc_cl.ultimate_ + e3_rcc_cl.ultimate_)/2\n", + "e3_cc_ult = (e3_ccc_cl.ultimate_ + e3_rcc_cl.ultimate_) / 2\n", "\n", - "#calculating trended frequency\n", - "e3_freq_trend = cl.Trend(-.01).fit(e3_tri['Reported Claim Counts']).trend_\n", - "e3_payroll_trend = cl.Trend(.025).fit(e3_tri['Payroll']).trend_\n", - "e3_freq_2008 = .0036\n", - "e3_freq_2007 = e3_freq_2008 / e3_freq_trend.values[0,0,-2,0] * e3_payroll_trend.latest_diagonal.values[0,0,-2,0]\n", + "# calculating trended frequency\n", + "e3_freq_trend = cl.Trend(-0.01).fit(e3_tri[\"Reported Claim Counts\"]).trend_\n", + "e3_payroll_trend = cl.Trend(0.025).fit(e3_tri[\"Payroll\"]).trend_\n", + "e3_freq_2008 = 0.0036\n", + "e3_freq_2007 = (\n", + " e3_freq_2008\n", + " / e3_freq_trend.values[0, 0, -2, 0]\n", + " * e3_payroll_trend.latest_diagonal.values[0, 0, -2, 0]\n", + ")\n", "\n", - "#calculating and developing paid severity\n", - "e3_psev = e3_tri[\"Paid Claims\"] / e3_tri['Closed Claim Counts']\n", - "e3_psev_devs = average_dev(e3_psev,e3_sev_assumptions)\n", - "e3_psev_selected = cl.TailConstant(tail = 1.150, projection_period = 0).fit_transform(e3_psev_devs['medial_5x1'])\n", + "# calculating and developing paid severity\n", + "e3_psev = e3_tri[\"Paid Claims\"] / e3_tri[\"Closed Claim Counts\"]\n", + "e3_psev_devs = average_dev(e3_psev, e3_sev_assumptions)\n", + "e3_psev_selected = cl.TailConstant(tail=1.150, projection_period=0).fit_transform(\n", + " e3_psev_devs[\"medial_5x1\"]\n", + ")\n", "e3_psev_selected.ldf_ = e3_psev_selected.ldf_.round(3)\n", "\n", - "#calculating and developing reported severity\n", - "e3_rsev = e3_tri[\"Reported Claims\"] / e3_tri['Reported Claim Counts']\n", - "e3_rsev_devs = average_dev(e3_rsev,e3_sev_assumptions)\n", - "e3_rsev_selected = cl.TailConstant(tail = 1.025, projection_period = 0).fit_transform(e3_rsev_devs['medial_5x1'])\n", + "# calculating and developing reported severity\n", + "e3_rsev = e3_tri[\"Reported Claims\"] / e3_tri[\"Reported Claim Counts\"]\n", + "e3_rsev_devs = average_dev(e3_rsev, e3_sev_assumptions)\n", + "e3_rsev_selected = cl.TailConstant(tail=1.025, projection_period=0).fit_transform(\n", + " e3_rsev_devs[\"medial_5x1\"]\n", + ")\n", "e3_rsev_selected.ldf_ = e3_rsev_selected.ldf_.round(3)\n", "\n", - "#combining developed count and severity\n", + "# combining developed count and severity\n", "e3_psev_cl = cl.Chainladder().fit(e3_psev_selected)\n", "e3_resv_cl = cl.Chainladder().fit(e3_rsev_selected)\n", - "e3_sev_ult = (e3_psev_cl.ultimate_ + e3_resv_cl.ultimate_)/2\n", + "e3_sev_ult = (e3_psev_cl.ultimate_ + e3_resv_cl.ultimate_) / 2\n", "\n", - "#calculated trended severity\n", - "e3_sev_trend = cl.Trend(.075).fit(e3_tri['Reported Claims']).trend_\n", + "# calculated trended severity\n", + "e3_sev_trend = cl.Trend(0.075).fit(e3_tri[\"Reported Claims\"]).trend_\n", "e3_sev_ult_trended = e3_sev_ult * e3_sev_trend.latest_diagonal.values\n", - "#prep ult severity for average calculations\n", - "e3_sev_ult_trended_2006 = e3_sev_ult_trended[e3_sev_ult_trended.origin<='2006']\n", - "e3_sevs = average_sev(e3_sev_ult_trended_2006,e3_sevavg_assumptions)\n", - "#calculate selected severities\n", - "e3_sev_2008 = 7100.\n", - "e3_sev_2007 = e3_sev_2008 / e3_sev_trend.values[0,0,-2,0]\n" + "# prep ult severity for average calculations\n", + "e3_sev_ult_trended_2006 = e3_sev_ult_trended[e3_sev_ult_trended.origin <= \"2006\"]\n", + "e3_sevs = average_sev(e3_sev_ult_trended_2006, e3_sevavg_assumptions)\n", + "# calculate selected severities\n", + "e3_sev_2008 = 7100.0\n", + "e3_sev_2007 = e3_sev_2008 / e3_sev_trend.values[0, 0, -2, 0]" ] }, { @@ -1237,19 +1365,19 @@ }, "outputs": [], "source": [ - "print('PART 1 - Data Triangle')\n", + "print(\"PART 1 - Data Triangle\")\n", "nb_display(e3_tri[\"Closed Claim Counts\"])\n", - "print('PART 2 - Age-to-Age Factors')\n", - "nb_display(e3_tri['Closed Claim Counts'].age_to_age.round(3))\n", - "print('PART 3 - Average Age-to-Age Factor')\n", + "print(\"PART 2 - Age-to-Age Factors\")\n", + "nb_display(e3_tri[\"Closed Claim Counts\"].age_to_age.round(3))\n", + "print(\"PART 3 - Average Age-to-Age Factor\")\n", "nb_display(combine_ldf(e3_ccc_devs).round(3).to_frame().rename_axis(\"\"))\n", - "print('PART 4 - Selected Age-to-Age Factors')\n", - "print('Selected')\n", + "print(\"PART 4 - Selected Age-to-Age Factors\")\n", + "print(\"Selected\")\n", "nb_display(e3_ccc_selected.ldf_)\n", - "print('CDF to Ultimate')\n", + "print(\"CDF to Ultimate\")\n", "nb_display(e3_ccc_selected.cdf_.round(3))\n", - "print('Percent Closed')\n", - "nb_display((1/e3_ccc_selected.cdf_).round(3))" + "print(\"Percent Closed\")\n", + "nb_display((1 / e3_ccc_selected.cdf_).round(3))" ] }, { @@ -1286,18 +1414,18 @@ }, "outputs": [], "source": [ - "print('PART 1 - Data Triangle')\n", + "print(\"PART 1 - Data Triangle\")\n", "nb_display(e3_tri[\"Reported Claim Counts\"])\n", - "print('PART 2 - Age-to-Age Factors')\n", - "nb_display(e3_tri['Reported Claim Counts'].age_to_age.round(3))\n", - "print('PART 3 - Average Age-to-Age Factor')\n", + "print(\"PART 2 - Age-to-Age Factors\")\n", + "nb_display(e3_tri[\"Reported Claim Counts\"].age_to_age.round(3))\n", + "print(\"PART 3 - Average Age-to-Age Factor\")\n", "nb_display(combine_ldf(e3_rcc_devs).round(3).to_frame().rename_axis(\"\"))\n", - "print('PART 4 - Selected Age-to-Age Factors')\n", + "print(\"PART 4 - Selected Age-to-Age Factors\")\n", "nb_display(e3_rcc_selected.ldf_)\n", - "print('CDF to Ultimate')\n", + "print(\"CDF to Ultimate\")\n", "nb_display(e3_rcc_selected.cdf_.round(3))\n", - "print('Percent Reported')\n", - "nb_display((1/e3_rcc_selected.cdf_).round(3))" + "print(\"Percent Reported\")\n", + "nb_display((1 / e3_rcc_selected.cdf_).round(3))" ] }, { @@ -1334,31 +1462,41 @@ }, "outputs": [], "source": [ - "e3_ccc_df = cl.model_diagnostics(e3_ccc_cl).to_frame(keepdims=True,implicit_axis=True).set_index('origin')\n", - "e3_rcc_df = cl.model_diagnostics(e3_rcc_cl).to_frame(keepdims=True,implicit_axis=True).set_index('origin')\n", - "e3_s3 = e3_ccc_df[['development','Latest','CDF','Ultimate']].rename(\n", + "e3_ccc_df = (\n", + " cl.model_diagnostics(e3_ccc_cl)\n", + " .to_frame(keepdims=True, implicit_axis=True)\n", + " .set_index(\"origin\")\n", + ")\n", + "e3_rcc_df = (\n", + " cl.model_diagnostics(e3_rcc_cl)\n", + " .to_frame(keepdims=True, implicit_axis=True)\n", + " .set_index(\"origin\")\n", + ")\n", + "e3_s3 = e3_ccc_df[[\"development\", \"Latest\", \"CDF\", \"Ultimate\"]].rename(\n", " columns={\n", - " 'development':'Age',\n", - " 'Latest':'Closed Claim Counts',\n", - " 'CDF':'Closed CDF',\n", - " 'Ultimate':'Ult Count Using CCC'\n", + " \"development\": \"Age\",\n", + " \"Latest\": \"Closed Claim Counts\",\n", + " \"CDF\": \"Closed CDF\",\n", + " \"Ultimate\": \"Ult Count Using CCC\",\n", " }\n", ")\n", - "e3_s3[['Reported Claim Counts','Reported CDF','Ult Count Using RCC']] = e3_rcc_df[['Latest','CDF','Ultimate']]\n", + "e3_s3[[\"Reported Claim Counts\", \"Reported CDF\", \"Ult Count Using RCC\"]] = e3_rcc_df[\n", + " [\"Latest\", \"CDF\", \"Ultimate\"]\n", + "]\n", "e3_s3[\"Selected Ult CC\"] = e3_cc_ult.latest_diagonal.to_frame()\n", "format_exh(\n", " e3_s3,\n", " [\n", - " 'Closed Claim Counts',\n", - " 'Reported Claim Counts',\n", - " 'Ult Count Using CCC',\n", - " 'Ult Count Using RCC',\n", - " 'Selected Ult CC',\n", + " \"Closed Claim Counts\",\n", + " \"Reported Claim Counts\",\n", + " \"Ult Count Using CCC\",\n", + " \"Ult Count Using RCC\",\n", + " \"Selected Ult CC\",\n", " ],\n", " [\n", - " 'Closed CDF',\n", - " 'Reported CDF',\n", - " ]\n", + " \"Closed CDF\",\n", + " \"Reported CDF\",\n", + " ],\n", ")" ] }, @@ -1396,27 +1534,29 @@ }, "outputs": [], "source": [ - "e3_s4 = e3_s3[['Selected Ult CC']].copy()\n", - "e3_s4['CC Trend'] = e3_freq_trend.latest_diagonal.to_frame()\n", - "e3_s4['Trended Ult CC'] = e3_s4['Selected Ult CC'] * e3_s4['CC Trend']\n", - "e3_s4['Payroll'] = e3_tri['Payroll'].latest_diagonal.to_frame()\n", - "e3_s4['Payroll Trend'] = e3_payroll_trend.latest_diagonal.to_frame()\n", - "e3_s4['Trended Payroll'] = e3_s4['Payroll'] * e3_s4['Payroll Trend']\n", - "e3_s4['Trended Ultimate Freq'] = e3_s4['Trended Ult CC'] / e3_s4['Trended Payroll']\n", - "nb_display(format_exh(\n", - " e3_s4,\n", - " [\n", - " 'Selected Ult CC',\n", - " 'Trended Ult CC',\n", - " 'Payroll',\n", - " 'Trended Payroll',\n", - " ],\n", - " [\n", - " 'CC Trend',\n", - " 'Payroll Trend',\n", - " ],\n", - " {'Trended Ultimate Freq' : \"{:.2%}\"}\n", - "))\n", + "e3_s4 = e3_s3[[\"Selected Ult CC\"]].copy()\n", + "e3_s4[\"CC Trend\"] = e3_freq_trend.latest_diagonal.to_frame()\n", + "e3_s4[\"Trended Ult CC\"] = e3_s4[\"Selected Ult CC\"] * e3_s4[\"CC Trend\"]\n", + "e3_s4[\"Payroll\"] = e3_tri[\"Payroll\"].latest_diagonal.to_frame()\n", + "e3_s4[\"Payroll Trend\"] = e3_payroll_trend.latest_diagonal.to_frame()\n", + "e3_s4[\"Trended Payroll\"] = e3_s4[\"Payroll\"] * e3_s4[\"Payroll Trend\"]\n", + "e3_s4[\"Trended Ultimate Freq\"] = e3_s4[\"Trended Ult CC\"] / e3_s4[\"Trended Payroll\"]\n", + "nb_display(\n", + " format_exh(\n", + " e3_s4,\n", + " [\n", + " \"Selected Ult CC\",\n", + " \"Trended Ult CC\",\n", + " \"Payroll\",\n", + " \"Trended Payroll\",\n", + " ],\n", + " [\n", + " \"CC Trend\",\n", + " \"Payroll Trend\",\n", + " ],\n", + " {\"Trended Ultimate Freq\": \"{:.2%}\"},\n", + " )\n", + ")\n", "print(f\"Selected Frequency at 2008 level {e3_freq_2008:.2%}\")\n", "print(f\"Selected Frequency at 2007 level {float(e3_freq_2007):.2%}\")" ] @@ -1455,13 +1595,13 @@ }, "outputs": [], "source": [ - "print('Paid Claims')\n", + "print(\"Paid Claims\")\n", "nb_display(e3_tri[\"Paid Claims\"])\n", - "print('Paid Severities')\n", + "print(\"Paid Severities\")\n", "nb_display(e3_psev)\n", - "print('Reported Claims')\n", + "print(\"Reported Claims\")\n", "nb_display(e3_tri[\"Reported Claims\"])\n", - "print('Reported Severities')\n", + "print(\"Reported Severities\")\n", "nb_display(e3_rsev)" ] }, @@ -1499,16 +1639,16 @@ }, "outputs": [], "source": [ - "print('PART 1 - Data Triangle')\n", + "print(\"PART 1 - Data Triangle\")\n", "nb_display(e3_psev)\n", - "print('PART 2 - Age-to-Age Factors')\n", + "print(\"PART 2 - Age-to-Age Factors\")\n", "nb_display(e3_psev.age_to_age.round(3))\n", - "print('PART 3 - Average Age-to-Age Factor')\n", + "print(\"PART 3 - Average Age-to-Age Factor\")\n", "nb_display(combine_ldf(e3_psev_devs).round(3).to_frame().rename_axis(\"\"))\n", - "print('PART 4 - Selected Age-to-Age Factors')\n", - "print('Selected')\n", + "print(\"PART 4 - Selected Age-to-Age Factors\")\n", + "print(\"Selected\")\n", "nb_display(e3_psev_selected.ldf_)\n", - "print('CDF to Ultimate')\n", + "print(\"CDF to Ultimate\")\n", "nb_display(e3_psev_selected.cdf_.round(3))" ] }, @@ -1593,30 +1733,40 @@ }, "outputs": [], "source": [ - "e3_psev_df = cl.model_diagnostics(e3_psev_cl).to_frame(keepdims=True,implicit_axis=True).set_index('origin')\n", - "e3_resv_df = cl.model_diagnostics(e3_resv_cl).to_frame(keepdims=True,implicit_axis=True).set_index('origin')\n", - "e3_s8 = e3_psev_df[['development','Latest','CDF','Ultimate']].rename(\n", + "e3_psev_df = (\n", + " cl.model_diagnostics(e3_psev_cl)\n", + " .to_frame(keepdims=True, implicit_axis=True)\n", + " .set_index(\"origin\")\n", + ")\n", + "e3_resv_df = (\n", + " cl.model_diagnostics(e3_resv_cl)\n", + " .to_frame(keepdims=True, implicit_axis=True)\n", + " .set_index(\"origin\")\n", + ")\n", + "e3_s8 = e3_psev_df[[\"development\", \"Latest\", \"CDF\", \"Ultimate\"]].rename(\n", " columns={\n", - " 'development':'Age',\n", - " 'Latest':'Paid Sev',\n", - " 'CDF':'Paid CDF',\n", - " 'Ultimate':'Ult Paid Sev'\n", + " \"development\": \"Age\",\n", + " \"Latest\": \"Paid Sev\",\n", + " \"CDF\": \"Paid CDF\",\n", + " \"Ultimate\": \"Ult Paid Sev\",\n", " }\n", ")\n", - "e3_s8[['Reported Sev','Reported CDF','Ult Reported Sev']] = e3_resv_df[['Latest','CDF','Ultimate']]\n", + "e3_s8[[\"Reported Sev\", \"Reported CDF\", \"Ult Reported Sev\"]] = e3_resv_df[\n", + " [\"Latest\", \"CDF\", \"Ultimate\"]\n", + "]\n", "e3_s8[\"Selected Ult Sev\"] = e3_sev_ult.latest_diagonal.to_frame()\n", "format_exh(\n", - " e3_s8.iloc[:-2,:],\n", + " e3_s8.iloc[:-2, :],\n", " [\n", - " 'Paid Sev',\n", - " 'Reported Sev',\n", - " 'Ult Paid Sev',\n", - " 'Ult Reported Sev',\n", - " 'Selected Ult Sev',\n", + " \"Paid Sev\",\n", + " \"Reported Sev\",\n", + " \"Ult Paid Sev\",\n", + " \"Ult Reported Sev\",\n", + " \"Selected Ult Sev\",\n", " ],\n", " [\n", - " 'Paid CDF',\n", - " 'Reported CDF',\n", + " \"Paid CDF\",\n", + " \"Reported CDF\",\n", " ],\n", ")" ] @@ -1712,31 +1862,31 @@ }, "outputs": [], "source": [ - "e3_s10 = e3_s4[['Payroll']].copy()[-2:]\n", - "e3_s10['Selected Frequency'] = [e3_freq_2007,e3_freq_2008]\n", - "e3_s10['Ult CC'] = e3_s10['Payroll'] * e3_s10['Selected Frequency']\n", - "e3_s10['Selected Severity'] = [e3_sev_2007,e3_sev_2008]\n", - "e3_s10['Ult Claims'] = e3_s10['Ult CC'] * e3_s10['Selected Severity']\n", - "e3_s10['Reported Claims'] = e3_tri['Reported Claims'].latest_diagonal.to_frame()\n", - "e3_s10['Paid Claims'] = e3_tri['Paid Claims'].latest_diagonal.to_frame()\n", - "e3_s10['Case Outstanding'] = e3_s10['Reported Claims'] - e3_s10['Paid Claims']\n", - "e3_s10['IBNR'] = e3_s10['Ult Claims'] - e3_s10['Reported Claims']\n", - "e3_s10['Unpaid'] = e3_s10['IBNR'] + e3_s10['Case Outstanding']\n", + "e3_s10 = e3_s4[[\"Payroll\"]].copy()[-2:]\n", + "e3_s10[\"Selected Frequency\"] = [e3_freq_2007, e3_freq_2008]\n", + "e3_s10[\"Ult CC\"] = e3_s10[\"Payroll\"] * e3_s10[\"Selected Frequency\"]\n", + "e3_s10[\"Selected Severity\"] = [e3_sev_2007, e3_sev_2008]\n", + "e3_s10[\"Ult Claims\"] = e3_s10[\"Ult CC\"] * e3_s10[\"Selected Severity\"]\n", + "e3_s10[\"Reported Claims\"] = e3_tri[\"Reported Claims\"].latest_diagonal.to_frame()\n", + "e3_s10[\"Paid Claims\"] = e3_tri[\"Paid Claims\"].latest_diagonal.to_frame()\n", + "e3_s10[\"Case Outstanding\"] = e3_s10[\"Reported Claims\"] - e3_s10[\"Paid Claims\"]\n", + "e3_s10[\"IBNR\"] = e3_s10[\"Ult Claims\"] - e3_s10[\"Reported Claims\"]\n", + "e3_s10[\"Unpaid\"] = e3_s10[\"IBNR\"] + e3_s10[\"Case Outstanding\"]\n", "format_exh(\n", " e3_s10,\n", " [\n", - " 'Payroll',\n", - " 'Ult CC',\n", - " 'Selected Severity',\n", - " 'Ult Claims',\n", - " 'Reported Claims',\n", - " 'Paid Claims',\n", - " 'Case Outstanding',\n", - " 'IBNR',\n", - " 'Unpaid'\n", + " \"Payroll\",\n", + " \"Ult CC\",\n", + " \"Selected Severity\",\n", + " \"Ult Claims\",\n", + " \"Reported Claims\",\n", + " \"Paid Claims\",\n", + " \"Case Outstanding\",\n", + " \"IBNR\",\n", + " \"Unpaid\",\n", " ],\n", " [],\n", - " {'Selected Frequency':'{:.2%}'}\n", + " {\"Selected Frequency\": \"{:.2%}\"},\n", ")" ] }, @@ -1758,41 +1908,50 @@ }, "outputs": [], "source": [ - "#Exhibit III Sheet 1\n", - "assert np.all(e3_ccc_selected.cdf_.round(3).values[...,:7] == np.array([1.698, 1.133, 1.071, 1.034, 1.018, 1.014, 1.010]))\n", - "#Exhibit III Sheet 2\n", - "assert np.all(e3_rcc_selected.cdf_.round(3).values == np.array([1.094, 1.022, 1.007, 1.002, 1.000, 1.000, 1.000, 1.000]))\n", - "#Exhibit III Sheet 3\n", + "# Exhibit III Sheet 1\n", + "assert np.all(\n", + " e3_ccc_selected.cdf_.round(3).values[..., :7]\n", + " == np.array([1.698, 1.133, 1.071, 1.034, 1.018, 1.014, 1.010])\n", + ")\n", + "# Exhibit III Sheet 2\n", + "assert np.all(\n", + " e3_rcc_selected.cdf_.round(3).values\n", + " == np.array([1.094, 1.022, 1.007, 1.002, 1.000, 1.000, 1.000, 1.000])\n", + ")\n", + "# Exhibit III Sheet 3\n", "assert np.allclose(\n", - " e3_s3['Selected Ult CC'].values[...,1:],\n", + " e3_s3[\"Selected Ult CC\"].values[..., 1:],\n", " np.array([1700, 1774, 1749, 1651, 2982, 2909, 2658]),\n", - " rtol=0.001\n", + " rtol=0.001,\n", ")\n", - "#Exhibit III Sheet 4\n", + "# Exhibit III Sheet 4\n", "assert np.allclose(\n", - " e3_s4['Trended Ultimate Freq'].values[...,1:],\n", + " e3_s4[\"Trended Ultimate Freq\"].values[..., 1:],\n", " np.array([0.0053, 0.0053, 0.0054, 0.0043, 0.0035, 0.0036, 0.0036]),\n", - " atol = 0.0001\n", - " )\n", - "#Exhibit III Sheet 6\n", + " atol=0.0001,\n", + ")\n", + "# Exhibit III Sheet 6\n", "assert np.allclose(\n", " e3_psev_selected.ldf_.round(3).values,\n", " np.array([1.447, 1.249, 1.108, 1.066, 1.038, 1.037, 1.021, 1.150]),\n", - " atol=.001\n", + " atol=0.001,\n", + ")\n", + "# Exhibit III Sheet 7\n", + "assert np.all(\n", + " e3_rsev_selected.cdf_.round(3).values\n", + " == np.array([1.679, 1.314, 1.189, 1.130, 1.092, 1.065, 1.043, 1.025])\n", ")\n", - "#Exhibit III Sheet 7\n", - "assert np.all(e3_rsev_selected.cdf_.round(3).values == np.array([1.679, 1.314, 1.189, 1.130, 1.092, 1.065, 1.043, 1.025]))\n", - "#Exhibit III Sheet 8\n", + "# Exhibit III Sheet 8\n", "assert np.allclose(\n", - " e3_s8['Selected Ult Sev'].values[:-2],\n", + " e3_s8[\"Selected Ult Sev\"].values[:-2],\n", " np.array([4371, 4587, 4963, 5242, 5635, 6169]),\n", - " rtol=.001\n", + " rtol=0.001,\n", ")\n", - "#Exhibit III Sheet 9\n", + "# Exhibit III Sheet 9\n", "assert np.allclose(\n", - " e3_s9['Trended Ult Sev'].values[:-2],\n", + " e3_s9[\"Trended Ult Sev\"].values[:-2],\n", " np.array([7251, 7079, 7125, 7000, 7001, 7129]),\n", - " rtol=0.001\n", + " rtol=0.001,\n", ")" ] }, @@ -1832,67 +1991,91 @@ }, "outputs": [], "source": [ - "#loading data and assumptions\n", - "xyz_tort_data = pd.DataFrame({\n", - " \"EffDate\": [\"2006-01-01\", \"2007-01-01\"],\n", - " \"RateChange\": [(0.67/0.75) - 1, 0.75-1]\n", - "})\n", + "# loading data and assumptions\n", + "xyz_tort_data = pd.DataFrame(\n", + " {\n", + " \"EffDate\": [\"2006-01-01\", \"2007-01-01\"],\n", + " \"RateChange\": [(0.67 / 0.75) - 1, 0.75 - 1],\n", + " }\n", + ")\n", "xyz_tort_adjustment = cl.ParallelogramOLF(\n", " rate_history=xyz_tort_data,\n", " change_col=\"RateChange\",\n", " date_col=\"EffDate\",\n", - " vertical_line=True\n", + " vertical_line=True,\n", ")\n", - "#Both Chapter 6 and Chapter 8 contain the code to derive these on-level factors from underlying rate changes. We will simply use the factors as given\n", + "# Both Chapter 6 and Chapter 8 contain the code to derive these on-level factors from underlying rate changes. We will simply use the factors as given\n", "olf = cl.Triangle(\n", - " data = {\n", - " 'origin':[1998,2002,2003,2004,2005,2006,2007,2008],\n", - " 'valuation':[2008,2008,2008,2008,2008,2008,2008,2008],\n", - " 'On-Level Adjustment':[0,.914,.87,.81,.704,.64,.8,1],\n", + " data={\n", + " \"origin\": [1998, 2002, 2003, 2004, 2005, 2006, 2007, 2008],\n", + " \"valuation\": [2008, 2008, 2008, 2008, 2008, 2008, 2008, 2008],\n", + " \"On-Level Adjustment\": [0, 0.914, 0.87, 0.81, 0.704, 0.64, 0.8, 1],\n", " },\n", - " origin='origin',\n", - " development='valuation',\n", - " columns='On-Level Adjustment',\n", - " cumulative = True\n", + " origin=\"origin\",\n", + " development=\"valuation\",\n", + " columns=\"On-Level Adjustment\",\n", + " cumulative=True,\n", ")\n", "e4_freq_assumptions = {}\n", - "e4_freq_assumptions['all_years'] = {}\n", - "e4_freq_assumptions['all_years_excl_hilo'] = {'drop_high':1, 'drop_low':1}\n", - "e4_freq_assumptions['latest_2'] = {'n_periods':2}\n", + "e4_freq_assumptions[\"all_years\"] = {}\n", + "e4_freq_assumptions[\"all_years_excl_hilo\"] = {\"drop_high\": 1, \"drop_low\": 1}\n", + "e4_freq_assumptions[\"latest_2\"] = {\"n_periods\": 2}\n", "e4_sev_assumptions = {}\n", - "e4_sev_assumptions['latest_5_years'] = {'n_periods':5}\n", - "e4_sev_assumptions['latest_5_years_excl_hilo'] = {'n_periods':5,'drop_high':1, 'drop_low':1}\n", - "e4_sev_assumptions['latest_3'] = {'n_periods':3}\n", + "e4_sev_assumptions[\"latest_5_years\"] = {\"n_periods\": 5}\n", + "e4_sev_assumptions[\"latest_5_years_excl_hilo\"] = {\n", + " \"n_periods\": 5,\n", + " \"drop_high\": 1,\n", + " \"drop_low\": 1,\n", + "}\n", + "e4_sev_assumptions[\"latest_3\"] = {\"n_periods\": 3}\n", "\n", "# In Exhibit II, the selected ultimate was actually the average of reported and closed ultimates.\n", "e4_s1_tri = e2_rcc_cl.ultimate_.copy()\n", - "e4_s1_tri = e4_s1_tri.rename(axis='columns',value='Ult CC')\n", - "e4_s1_tri['CC Trend'] = cl.Trend(-.015,dates=('2008-12-31','2002-01-01')).fit(e4_s1_tri).trend_\n", - "e4_s1_tri['Trended Ult CC'] = e4_s1_tri['CC Trend'] * e4_s1_tri['Ult CC']\n", - "e4_s1_tri['Earned Premium'] = e2_tri['Earned Premium'].latest_diagonal\n", - "e4_s1_tri['On-Level Adjustment'] = olf['On-Level Adjustment']\n", - "e4_s1_tri['On-Level Premium'] = e4_s1_tri['On-Level Adjustment'] * e4_s1_tri['Earned Premium']\n", - "e4_s1_tri['Trended Ult Freq'] = e4_s1_tri['Trended Ult CC'] / e4_s1_tri['On-Level Premium']\n", - "e4_s1_tri_2006 = e4_s1_tri.iloc[:,:,4:9,:]\n", - "#calculate average frequencies\n", - "e4_freq = average_sev(e4_s1_tri_2006['Trended Ult Freq'],e4_freq_assumptions)\n", - "#calculate selected frequencies\n", - "e4_freq_2008 = e4_freq['latest_2'].round(4)\n", - "e4_freq_2007 = e4_freq_2008 / e4_s1_tri['CC Trend'].values[0,0,-2,0] * e4_s1_tri['On-Level Adjustment'].values[0,0,-2,0]\n", + "e4_s1_tri = e4_s1_tri.rename(axis=\"columns\", value=\"Ult CC\")\n", + "e4_s1_tri[\"CC Trend\"] = (\n", + " cl.Trend(-0.015, dates=(\"2008-12-31\", \"2002-01-01\")).fit(e4_s1_tri).trend_\n", + ")\n", + "e4_s1_tri[\"Trended Ult CC\"] = e4_s1_tri[\"CC Trend\"] * e4_s1_tri[\"Ult CC\"]\n", + "e4_s1_tri[\"Earned Premium\"] = e2_tri[\"Earned Premium\"].latest_diagonal\n", + "e4_s1_tri[\"On-Level Adjustment\"] = olf[\"On-Level Adjustment\"]\n", + "e4_s1_tri[\"On-Level Premium\"] = (\n", + " e4_s1_tri[\"On-Level Adjustment\"] * e4_s1_tri[\"Earned Premium\"]\n", + ")\n", + "e4_s1_tri[\"Trended Ult Freq\"] = (\n", + " e4_s1_tri[\"Trended Ult CC\"] / e4_s1_tri[\"On-Level Premium\"]\n", + ")\n", + "e4_s1_tri_2006 = e4_s1_tri.iloc[:, :, 4:9, :]\n", + "# calculate average frequencies\n", + "e4_freq = average_sev(e4_s1_tri_2006[\"Trended Ult Freq\"], e4_freq_assumptions)\n", + "# calculate selected frequencies\n", + "e4_freq_2008 = e4_freq[\"latest_2\"].round(4)\n", + "e4_freq_2007 = (\n", + " e4_freq_2008\n", + " / e4_s1_tri[\"CC Trend\"].values[0, 0, -2, 0]\n", + " * e4_s1_tri[\"On-Level Adjustment\"].values[0, 0, -2, 0]\n", + ")\n", "e4_freq_2007 = e4_freq_2007.round(4)\n", "\n", "e4_s2_tri = e2_rsev_cl.ultimate_.copy()\n", - "e4_s2_tri = e4_s2_tri.rename(axis='columns',value='Ult Sev')\n", - "e4_s2_tri['Sev Trend'] = cl.Trend(.05,dates=('2008-12-31','1998-01-01')).fit(e4_s2_tri).trend_.round(3)\n", - "#from Chapter 8 Exhibit III sheet 1\n", - "e4_s2_tri['Tort Reform Factors'] = xyz_tort_adjustment.fit(e4_s2_tri['Ult Sev']).olf_\n", - "e4_s2_tri['Trended Ult Sev'] = e4_s2_tri['Ult Sev'] * e4_s2_tri['Sev Trend'] * e4_s2_tri['Tort Reform Factors']\n", - "e4_s2_tri_2006 = e4_s2_tri[e4_s2_tri.origin <= '2006']\n", - "#calculate average severities\n", - "e4_sev = average_sev(e4_s2_tri_2006['Trended Ult Sev'],e4_sev_assumptions)\n", - "#calculate selected severities\n", - "e4_sev_2008 = e4_sev['latest_5_years_excl_hilo'].round(0)\n", - "e4_sev_2007 = e4_sev_2008 / e4_s2_tri['Sev Trend'].values[0,0,-2,0] / e4_s2_tri['Tort Reform Factors'].values[0,0,-2,0]\n", + "e4_s2_tri = e4_s2_tri.rename(axis=\"columns\", value=\"Ult Sev\")\n", + "e4_s2_tri[\"Sev Trend\"] = (\n", + " cl.Trend(0.05, dates=(\"2008-12-31\", \"1998-01-01\")).fit(e4_s2_tri).trend_.round(3)\n", + ")\n", + "# from Chapter 8 Exhibit III sheet 1\n", + "e4_s2_tri[\"Tort Reform Factors\"] = xyz_tort_adjustment.fit(e4_s2_tri[\"Ult Sev\"]).olf_\n", + "e4_s2_tri[\"Trended Ult Sev\"] = (\n", + " e4_s2_tri[\"Ult Sev\"] * e4_s2_tri[\"Sev Trend\"] * e4_s2_tri[\"Tort Reform Factors\"]\n", + ")\n", + "e4_s2_tri_2006 = e4_s2_tri[e4_s2_tri.origin <= \"2006\"]\n", + "# calculate average severities\n", + "e4_sev = average_sev(e4_s2_tri_2006[\"Trended Ult Sev\"], e4_sev_assumptions)\n", + "# calculate selected severities\n", + "e4_sev_2008 = e4_sev[\"latest_5_years_excl_hilo\"].round(0)\n", + "e4_sev_2007 = (\n", + " e4_sev_2008\n", + " / e4_s2_tri[\"Sev Trend\"].values[0, 0, -2, 0]\n", + " / e4_s2_tri[\"Tort Reform Factors\"].values[0, 0, -2, 0]\n", + ")\n", "e4_sev_2007 = e4_sev_2007.round(0)" ] }, @@ -1931,13 +2114,17 @@ "outputs": [], "source": [ "e4_s1 = e4_s1_tri.to_frame().T\n", - "e4_s1[['CC Trend','On-Level Adjustment']] = e4_s1[['CC Trend','On-Level Adjustment']].round(3)\n", - "#using floating point offset to achieve standard rounding\n", - "e4_s1[['Trended Ult CC','On-Level Premium']] = (e4_s1[['Trended Ult CC','On-Level Premium']] + 1e-9).round(0)\n", - "e4_s1['Trended Ult Freq'] = e4_s1[\"Trended Ult Freq\"].map(\"{:.2%}\".format)\n", + "e4_s1[[\"CC Trend\", \"On-Level Adjustment\"]] = e4_s1[\n", + " [\"CC Trend\", \"On-Level Adjustment\"]\n", + "].round(3)\n", + "# using floating point offset to achieve standard rounding\n", + "e4_s1[[\"Trended Ult CC\", \"On-Level Premium\"]] = (\n", + " e4_s1[[\"Trended Ult CC\", \"On-Level Premium\"]] + 1e-9\n", + ").round(0)\n", + "e4_s1[\"Trended Ult Freq\"] = e4_s1[\"Trended Ult Freq\"].map(\"{:.2%}\".format)\n", "nb_display(e4_s1.iloc[4:9])\n", - "print('Average Trended Frequency at 2008 Cost Level')\n", - "for k,v in e4_freq.items():\n", + "print(\"Average Trended Frequency at 2008 Cost Level\")\n", + "for k, v in e4_freq.items():\n", " print(f\"\\t{k:<20} \\t{v:.2%}\")\n", "print(f\"Selected 2008 Frequency \\t{e4_freq_2008:.2%}\")\n", "print(f\"Estimated 2007 Frequency \\t{e4_freq_2007:.2%}\")" @@ -1978,12 +2165,16 @@ "outputs": [], "source": [ "e4_s2 = e4_s2_tri.to_frame().T\n", - "e4_s2[['Sev Trend','Tort Reform Factors']] = e4_s2[['Sev Trend','Tort Reform Factors']].round(3)\n", - "#using floating point offset to achieve standard rounding\n", - "e4_s2[['Trended Ult Sev','Ult Sev']] = (e4_s2[['Trended Ult Sev','Ult Sev']] + 1e-9).round(0)\n", + "e4_s2[[\"Sev Trend\", \"Tort Reform Factors\"]] = e4_s2[\n", + " [\"Sev Trend\", \"Tort Reform Factors\"]\n", + "].round(3)\n", + "# using floating point offset to achieve standard rounding\n", + "e4_s2[[\"Trended Ult Sev\", \"Ult Sev\"]] = (\n", + " e4_s2[[\"Trended Ult Sev\", \"Ult Sev\"]] + 1e-9\n", + ").round(0)\n", "nb_display(e4_s2.iloc[:9])\n", - "print('Average Trended Severity at 2008 Cost Level')\n", - "for k,v in e4_sev.items():\n", + "print(\"Average Trended Severity at 2008 Cost Level\")\n", + "for k, v in e4_sev.items():\n", " print(f\"\\t{k:<25} \\t{v.round(0)}\")\n", "print(f\"Selected 2008 Severity \\t\\t\\t{e4_sev_2008}\")\n", "print(f\"Estimated 2007 Severity \\t\\t{e4_sev_2007}\")" @@ -2023,16 +2214,21 @@ }, "outputs": [], "source": [ - "e4_s3 = e4_s1_tri['Earned Premium'].to_frame(keepdims=True).set_index('origin').iloc[-2:][['Earned Premium']]\n", - "e4_s3['Selected Frequency'] = [e4_freq_2007,e4_freq_2008]\n", - "e4_s3['Ult CC'] = e4_s3['Earned Premium'] * e4_s3['Selected Frequency']\n", - "e4_s3['Selected Severity'] = [e4_sev_2007,e4_sev_2008]\n", - "e4_s3['Ult Claims'] = e4_s3['Ult CC'] * e4_s3['Selected Severity']\n", - "e4_s3['Reported Claims'] = (e2_tri['Reported Claims']*1000).latest_diagonal.to_frame()\n", - "e4_s3['Paid Claims'] = (e2_tri['Paid Claims']*1000).latest_diagonal.to_frame()\n", - "e4_s3['Case Outstanding'] = e4_s3['Reported Claims'] - e4_s3['Paid Claims']\n", - "e4_s3['IBNR'] = e4_s3['Ult Claims'] - e4_s3['Reported Claims']\n", - "e4_s3['Unpaid'] = e4_s3['IBNR'] + e4_s3['Case Outstanding']\n", + "e4_s3 = (\n", + " e4_s1_tri[\"Earned Premium\"]\n", + " .to_frame(keepdims=True)\n", + " .set_index(\"origin\")\n", + " .iloc[-2:][[\"Earned Premium\"]]\n", + ")\n", + "e4_s3[\"Selected Frequency\"] = [e4_freq_2007, e4_freq_2008]\n", + "e4_s3[\"Ult CC\"] = e4_s3[\"Earned Premium\"] * e4_s3[\"Selected Frequency\"]\n", + "e4_s3[\"Selected Severity\"] = [e4_sev_2007, e4_sev_2008]\n", + "e4_s3[\"Ult Claims\"] = e4_s3[\"Ult CC\"] * e4_s3[\"Selected Severity\"]\n", + "e4_s3[\"Reported Claims\"] = (e2_tri[\"Reported Claims\"] * 1000).latest_diagonal.to_frame()\n", + "e4_s3[\"Paid Claims\"] = (e2_tri[\"Paid Claims\"] * 1000).latest_diagonal.to_frame()\n", + "e4_s3[\"Case Outstanding\"] = e4_s3[\"Reported Claims\"] - e4_s3[\"Paid Claims\"]\n", + "e4_s3[\"IBNR\"] = e4_s3[\"Ult Claims\"] - e4_s3[\"Reported Claims\"]\n", + "e4_s3[\"Unpaid\"] = e4_s3[\"IBNR\"] + e4_s3[\"Case Outstanding\"]\n", "e4_s3.T" ] }, @@ -2054,19 +2250,18 @@ }, "outputs": [], "source": [ - "#Exhibit IV Sheet 1\n", - "assert np.all(e4_s1.iloc[4:9]['Trended Ult Freq'].values == np.array(['2.54%', '2.51%', '2.65%', '2.36%', '2.37%']))\n", - "#Exhibit IV Sheet 2\n", + "# Exhibit IV Sheet 1\n", + "assert np.all(\n", + " e4_s1.iloc[4:9][\"Trended Ult Freq\"].values\n", + " == np.array([\"2.54%\", \"2.51%\", \"2.65%\", \"2.36%\", \"2.37%\"])\n", + ")\n", + "# Exhibit IV Sheet 2\n", "assert np.allclose(\n", - " list(e4_sev.values()),\n", - " np.array([26669., 26720., 27254.]),\n", - " rtol=0.001\n", + " list(e4_sev.values()), np.array([26669.0, 26720.0, 27254.0]), rtol=0.001\n", ")\n", - "#Exhibit IV Sheet 3\n", + "# Exhibit IV Sheet 3\n", "assert np.allclose(\n", - " e4_s3.iloc[:,4].values,\n", - " np.array([30512152., 30140260.]),\n", - " rtol=0.001\n", + " e4_s3.iloc[:, 4].values, np.array([30512152.0, 30140260.0]), rtol=0.001\n", ")" ] }, @@ -2106,85 +2301,111 @@ }, "outputs": [], "source": [ - "#loading data and assumptions\n", - "e5_tri = cl.load_sample('friedland_gl_insurer')\n", + "# loading data and assumptions\n", + "e5_tri = cl.load_sample(\"friedland_gl_insurer\")\n", "e5_cnt_assumptions = {}\n", - "e5_cnt_assumptions['simple_5'] = {'n_periods':5, 'average':'simple'}\n", - "e5_cnt_assumptions['simple_3'] = {'n_periods':3, 'average':'simple'}\n", - "e5_cnt_assumptions['medial_5x1'] = {'n_periods':5, 'average':'simple','drop_high':1, 'drop_low':1}\n", - "e5_cnt_assumptions['volume_5'] = {'n_periods':5, 'average':'volume'}\n", - "e5_cnt_assumptions['volume_3'] = {'n_periods':3, 'average':'volume'}\n", + "e5_cnt_assumptions[\"simple_5\"] = {\"n_periods\": 5, \"average\": \"simple\"}\n", + "e5_cnt_assumptions[\"simple_3\"] = {\"n_periods\": 3, \"average\": \"simple\"}\n", + "e5_cnt_assumptions[\"medial_5x1\"] = {\n", + " \"n_periods\": 5,\n", + " \"average\": \"simple\",\n", + " \"drop_high\": 1,\n", + " \"drop_low\": 1,\n", + "}\n", + "e5_cnt_assumptions[\"volume_5\"] = {\"n_periods\": 5, \"average\": \"volume\"}\n", + "e5_cnt_assumptions[\"volume_3\"] = {\"n_periods\": 3, \"average\": \"volume\"}\n", "e5_disp_assumptions = {}\n", - "e5_disp_assumptions['simple_5'] = {'n_periods':5, 'average':'simple'}\n", - "e5_disp_assumptions['simple_3'] = {'n_periods':3, 'average':'simple'}\n", - "e5_disp_assumptions['medial_5x1'] = {'n_periods':5, 'average':'simple','drop_high':1, 'drop_low':1}\n", + "e5_disp_assumptions[\"simple_5\"] = {\"n_periods\": 5, \"average\": \"simple\"}\n", + "e5_disp_assumptions[\"simple_3\"] = {\"n_periods\": 3, \"average\": \"simple\"}\n", + "e5_disp_assumptions[\"medial_5x1\"] = {\n", + " \"n_periods\": 5,\n", + " \"average\": \"simple\",\n", + " \"drop_high\": 1,\n", + " \"drop_low\": 1,\n", + "}\n", "e5_trend_assumptions = {}\n", - "e5_trend_assumptions['all_years'] = {}\n", - "e5_trend_assumptions['latest_6'] = {'n_periods':6}\n", - "e5_trend_assumptions['latest_4'] = {'n_periods':4}\n", + "e5_trend_assumptions[\"all_years\"] = {}\n", + "e5_trend_assumptions[\"latest_6\"] = {\"n_periods\": 6}\n", + "e5_trend_assumptions[\"latest_4\"] = {\"n_periods\": 4}\n", "e5_sev_assumptions = {}\n", - "e5_sev_assumptions['simple_5'] = {'n_periods':5}\n", - "e5_sev_assumptions['simple_3'] = {'n_periods':3}\n", - "e5_sev_assumptions['medial_5x1'] = {'n_periods':5,'drop_high':1, 'drop_low':1}\n", + "e5_sev_assumptions[\"simple_5\"] = {\"n_periods\": 5}\n", + "e5_sev_assumptions[\"simple_3\"] = {\"n_periods\": 3}\n", + "e5_sev_assumptions[\"medial_5x1\"] = {\"n_periods\": 5, \"drop_high\": 1, \"drop_low\": 1}\n", "\n", - "#developing closed claim counts\n", - "e5_ccc_devs = average_dev(e5_tri['Closed Claim Counts'],e5_cnt_assumptions)\n", - "e5_ccc_selected = cl.TailConstant(tail = 1.100, projection_period = 0).fit_transform(e5_ccc_devs['volume_3'])\n", + "# developing closed claim counts\n", + "e5_ccc_devs = average_dev(e5_tri[\"Closed Claim Counts\"], e5_cnt_assumptions)\n", + "e5_ccc_selected = cl.TailConstant(tail=1.100, projection_period=0).fit_transform(\n", + " e5_ccc_devs[\"volume_3\"]\n", + ")\n", "e5_ccc_selected.ldf_ = e5_ccc_selected.ldf_.round(3)\n", "\n", - "#developing reported claim counts\n", - "e5_rcc_devs = average_dev(e5_tri['Reported Claim Counts'],e5_cnt_assumptions)\n", - "e5_rcc_selected = cl.TailConstant(tail = 1.0, projection_period = 0).fit_transform(e5_rcc_devs['volume_3'])\n", + "# developing reported claim counts\n", + "e5_rcc_devs = average_dev(e5_tri[\"Reported Claim Counts\"], e5_cnt_assumptions)\n", + "e5_rcc_selected = cl.TailConstant(tail=1.0, projection_period=0).fit_transform(\n", + " e5_rcc_devs[\"volume_3\"]\n", + ")\n", "e5_rcc_selected.ldf_ = e5_rcc_selected.ldf_.round(3)\n", "\n", - "#combining closed and reported claim counts\n", + "# combining closed and reported claim counts\n", "e5_ccc_cl = cl.Chainladder().fit(e5_ccc_selected)\n", "e5_rcc_cl = cl.Chainladder().fit(e5_rcc_selected)\n", - "e5_cc_ult = ((e5_ccc_cl.ultimate_ + e5_rcc_cl.ultimate_)/2)\n", + "e5_cc_ult = (e5_ccc_cl.ultimate_ + e5_rcc_cl.ultimate_) / 2\n", "\n", - "#calculating disposal rate and complete the closed count triangle\n", - "e5_drs = average_dr(e5_tri['Closed Claim Counts'],e5_cc_ult,e5_disp_assumptions)\n", - "e5_dr_selected = e5_drs['medial_5x1']\n", + "# calculating disposal rate and complete the closed count triangle\n", + "e5_drs = average_dr(e5_tri[\"Closed Claim Counts\"], e5_cc_ult, e5_disp_assumptions)\n", + "e5_dr_selected = e5_drs[\"medial_5x1\"]\n", "e5_dr_selected.disposal_rate_ = e5_dr_selected.disposal_rate_.round(3)\n", - "e5_dr_tri = e5_dr_selected.transform(e5_tri['Closed Claim Counts'],sample_weight = e5_cc_ult)\n", + "e5_dr_tri = e5_dr_selected.transform(\n", + " e5_tri[\"Closed Claim Counts\"], sample_weight=e5_cc_ult\n", + ")\n", "\n", - "#calculate and select incremental paid severity\n", - "e5_ipsev = e5_tri[\"Paid Claims\"].cum_to_incr() / e5_tri[\"Closed Claim Counts\"].cum_to_incr()\n", - "e5_regs = regs(e5_ipsev,e5_trend_assumptions)\n", - "e5_trend,e5_rsq = reg_outputs(e5_regs,e5_ipsev.development)\n", - "e5_regs_ex2001 = regs(e5_ipsev[e5_ipsev.origin>'2001'],{'all_years_ex_2001':{}})\n", - "e5_trend_ex2001,e5_rsq_ex2001 = reg_outputs(e5_regs_ex2001,e5_ipsev.development)\n", - "e5_sevs = average_sev(e5_ipsev.trend(0.05),e5_sev_assumptions)\n", - "e5_sevs_sel = e5_sevs['simple_3'].copy()\n", - "e5_sevs_sel.origin = ['2008']\n", + "# calculate and select incremental paid severity\n", + "e5_ipsev = (\n", + " e5_tri[\"Paid Claims\"].cum_to_incr() / e5_tri[\"Closed Claim Counts\"].cum_to_incr()\n", + ")\n", + "e5_regs = regs(e5_ipsev, e5_trend_assumptions)\n", + "e5_trend, e5_rsq = reg_outputs(e5_regs, e5_ipsev.development)\n", + "e5_regs_ex2001 = regs(e5_ipsev[e5_ipsev.origin > \"2001\"], {\"all_years_ex_2001\": {}})\n", + "e5_trend_ex2001, e5_rsq_ex2001 = reg_outputs(e5_regs_ex2001, e5_ipsev.development)\n", + "e5_sevs = average_sev(e5_ipsev.trend(0.05), e5_sev_assumptions)\n", + "e5_sevs_sel = e5_sevs[\"simple_3\"].copy()\n", + "e5_sevs_sel.origin = [\"2008\"]\n", "\n", - "#calculate and select tail paid severity\n", + "# calculate and select tail paid severity\n", "e5_incr_ccc = e5_tri[\"Closed Claim Counts\"].cum_to_incr()\n", "e5_incr_paid = e5_tri[\"Paid Claims\"].cum_to_incr()\n", "e5_incr_paid_trended = e5_incr_paid.trend(0.05)\n", "\n", - "#complete the incremental paid severity triangle\n", + "# complete the incremental paid severity triangle\n", "e5_ipsev_full = e5_ipsev.copy()\n", - "#extending the severity triangle to 108 months\n", - "e5_ipsev_full = cl.concat((e5_ipsev_full,e5_ipsev_full.latest_diagonal.rename(\"development\",[9999])),axis=3)\n", - "#setting the before 60 months to selected incremental\n", - "e5_ipsev_full.iloc[:,:,:,:5] = e5_sevs_sel.iloc[:,:,:,:5]\n", - "#setting the after 72 months to selected tail\n", - "e5_ipsev_full.iloc[:,:,:,5:] = (\n", + "# extending the severity triangle to 108 months\n", + "e5_ipsev_full = cl.concat(\n", + " (e5_ipsev_full, e5_ipsev_full.latest_diagonal.rename(\"development\", [9999])), axis=3\n", + ")\n", + "# setting the before 60 months to selected incremental\n", + "e5_ipsev_full.iloc[:, :, :, :5] = e5_sevs_sel.iloc[:, :, :, :5]\n", + "# setting the after 72 months to selected tail\n", + "e5_ipsev_full.iloc[:, :, :, 5:] = (\n", " e5_incr_paid_trended[e5_incr_paid_trended.development >= 72].sum().sum()\n", - " /\n", - " e5_incr_ccc[e5_incr_ccc.development >= 72].sum().sum() \n", + " / e5_incr_ccc[e5_incr_ccc.development >= 72].sum().sum()\n", ")\n", - "#setting the valuation_date to the future\n", + "# setting the valuation_date to the future\n", "e5_ipsev_full.valuation_date = pd.to_datetime(cl.options.ULT_VAL)\n", - "#trending back from latest accident year\n", - "e5_ipsev_full_detrended = e5_ipsev_full.trend(1/1.05-1,start='2008-12-31',end='2001-01-01')\n", - "#compositing the actual incremental severities with dtrended selected to complete the incremental paid severity triangle\n", - "e5_ipsev_full_complete = e5_ipsev[e5_ipsev.valuation <= e5_ipsev.valuation_date] + e5_ipsev_full_detrended[e5_ipsev_full_detrended.valuation > e5_ipsev.valuation_date]\n", + "# trending back from latest accident year\n", + "e5_ipsev_full_detrended = e5_ipsev_full.trend(\n", + " 1 / 1.05 - 1, start=\"2008-12-31\", end=\"2001-01-01\"\n", + ")\n", + "# compositing the actual incremental severities with dtrended selected to complete the incremental paid severity triangle\n", + "e5_ipsev_full_complete = (\n", + " e5_ipsev[e5_ipsev.valuation <= e5_ipsev.valuation_date]\n", + " + e5_ipsev_full_detrended[\n", + " e5_ipsev_full_detrended.valuation > e5_ipsev.valuation_date\n", + " ]\n", + ")\n", "\n", - "#complete the incremental paid triangle\n", + "# complete the incremental paid triangle\n", "e5_s11_ccc = e5_dr_tri.full_triangle_.cum_to_incr()\n", - "e5_s11_ip = e5_s11_ccc * e5_ipsev_full_complete\n" + "e5_s11_ip = e5_s11_ccc * e5_ipsev_full_complete" ] }, { @@ -2318,10 +2539,22 @@ }, "outputs": [], "source": [ - "e5_ccc_df = cl.model_diagnostics(e5_ccc_cl).to_frame(keepdims=True,implicit_axis=True).set_index('origin')\n", - "e5_rcc_df = cl.model_diagnostics(e5_rcc_cl).to_frame(keepdims=True,implicit_axis=True).set_index('origin')\n", - "e5_s3 = e5_ccc_df[['development','Latest','Ultimate']].rename(columns={'Latest':'Closed Claim Counts','Ultimate':'Ult Count Using CCC'})\n", - "e5_s3[['Reported Claim Counts','Ult Count Using RCC']] = e5_rcc_df[['Latest','Ultimate']]\n", + "e5_ccc_df = (\n", + " cl.model_diagnostics(e5_ccc_cl)\n", + " .to_frame(keepdims=True, implicit_axis=True)\n", + " .set_index(\"origin\")\n", + ")\n", + "e5_rcc_df = (\n", + " cl.model_diagnostics(e5_rcc_cl)\n", + " .to_frame(keepdims=True, implicit_axis=True)\n", + " .set_index(\"origin\")\n", + ")\n", + "e5_s3 = e5_ccc_df[[\"development\", \"Latest\", \"Ultimate\"]].rename(\n", + " columns={\"Latest\": \"Closed Claim Counts\", \"Ultimate\": \"Ult Count Using CCC\"}\n", + ")\n", + "e5_s3[[\"Reported Claim Counts\", \"Ult Count Using RCC\"]] = e5_rcc_df[\n", + " [\"Latest\", \"Ultimate\"]\n", + "]\n", "e5_s3[\"Selected Ult CC\"] = e5_cc_ult.round(0).latest_diagonal.to_frame()\n", "e5_s3" ] @@ -2360,11 +2593,15 @@ }, "outputs": [], "source": [ - "print('Part 1 - Disposal Rate Triangle')\n", - "nb_display(cl.DisposalRate().fit_transform(e5_tri['Closed Claim Counts'],sample_weight = e5_cc_ult).disposal_rate_tri.round(3))\n", - "print('PART 2 - Average Disposal Rate Factors')\n", + "print(\"Part 1 - Disposal Rate Triangle\")\n", + "nb_display(\n", + " cl.DisposalRate()\n", + " .fit_transform(e5_tri[\"Closed Claim Counts\"], sample_weight=e5_cc_ult)\n", + " .disposal_rate_tri.round(3)\n", + ")\n", + "print(\"PART 2 - Average Disposal Rate Factors\")\n", "nb_display(combine_disposal(e5_drs).round(3).to_frame())\n", - "print('PART 3 - Selected Disposal Rate Factors')\n", + "print(\"PART 3 - Selected Disposal Rate Factors\")\n", "nb_display(e5_dr_selected.disposal_rate_)" ] }, @@ -2402,9 +2639,9 @@ }, "outputs": [], "source": [ - "print('Closed Claim Counts')\n", - "nb_display(e5_tri['Closed Claim Counts'])\n", - "print('Projected Incremental Closed Claim Counts')\n", + "print(\"Closed Claim Counts\")\n", + "nb_display(e5_tri[\"Closed Claim Counts\"])\n", + "print(\"Projected Incremental Closed Claim Counts\")\n", "nb_display(e5_dr_tri.full_triangle_.cum_to_incr().round(0))" ] }, @@ -2442,13 +2679,13 @@ }, "outputs": [], "source": [ - "print('Paid Claims')\n", + "print(\"Paid Claims\")\n", "nb_display(e5_tri[\"Paid Claims\"])\n", - "print('Incremental Paid CLaims')\n", + "print(\"Incremental Paid CLaims\")\n", "nb_display(e5_tri[\"Paid Claims\"].cum_to_incr())\n", - "print('Incremental Closed Claim Counts')\n", + "print(\"Incremental Closed Claim Counts\")\n", "nb_display(e5_tri[\"Closed Claim Counts\"].cum_to_incr())\n", - "print('Incremental Paid Severities')\n", + "print(\"Incremental Paid Severities\")\n", "nb_display(e5_ipsev)" ] }, @@ -2486,12 +2723,12 @@ }, "outputs": [], "source": [ - "print('Incremental Paid Severities')\n", + "print(\"Incremental Paid Severities\")\n", "nb_display(e5_ipsev)\n", - "print('Annual Change based on Exponential Regression')\n", - "nb_display(pd.concat([e5_trend.round(3),e5_trend_ex2001.round(3)]))\n", - "print('Goodness of Fit Test of Exponential Regression (R-Squared)')\n", - "nb_display(pd.concat([e5_rsq.round(3),e5_rsq_ex2001.round(3)]))" + "print(\"Annual Change based on Exponential Regression\")\n", + "nb_display(pd.concat([e5_trend.round(3), e5_trend_ex2001.round(3)]))\n", + "print(\"Goodness of Fit Test of Exponential Regression (R-Squared)\")\n", + "nb_display(pd.concat([e5_rsq.round(3), e5_rsq_ex2001.round(3)]))" ] }, { @@ -2528,14 +2765,14 @@ }, "outputs": [], "source": [ - "print('Incremental Paid Severities')\n", + "print(\"Incremental Paid Severities\")\n", "nb_display(e5_ipsev)\n", - "print('Trended Incremental Paid Severities Assuming 5% Annual Trend')\n", + "print(\"Trended Incremental Paid Severities Assuming 5% Annual Trend\")\n", "nb_display(e5_ipsev.trend(0.05))\n", - "print('Average Trended Incremental Paid Severities')\n", + "print(\"Average Trended Incremental Paid Severities\")\n", "nb_display(combine_tri(e5_sevs).round(0).to_frame())\n", - "print('Selected Incremental Paid Severities')\n", - "nb_display(e5_sevs_sel[e5_sevs_sel.development<=60])" + "print(\"Selected Incremental Paid Severities\")\n", + "nb_display(e5_sevs_sel[e5_sevs_sel.development <= 60])" ] }, { @@ -2573,26 +2810,34 @@ "outputs": [], "source": [ "e5_s9 = pd.DataFrame(\n", - " data = [[\n", - " e5_incr_ccc[e5_incr_ccc.development >= 60].sum().sum(), \n", - " e5_incr_ccc[e5_incr_ccc.development >= 72].sum().sum()\n", - " ]],\n", - " index = ['Total Closed Claim Counts'],\n", - " columns = ['Age 60 & Older','Age 72 & Older']\n", - ")\n", - "e5_s9.loc['Total Trended Paid Claims'] = [\n", - " e5_incr_paid_trended[e5_incr_paid_trended.development >= 60].sum().sum(), \n", - " e5_incr_paid_trended[e5_incr_paid_trended.development >= 72].sum().sum()\n", + " data=[\n", + " [\n", + " e5_incr_ccc[e5_incr_ccc.development >= 60].sum().sum(),\n", + " e5_incr_ccc[e5_incr_ccc.development >= 72].sum().sum(),\n", + " ]\n", + " ],\n", + " index=[\"Total Closed Claim Counts\"],\n", + " columns=[\"Age 60 & Older\", \"Age 72 & Older\"],\n", + ")\n", + "e5_s9.loc[\"Total Trended Paid Claims\"] = [\n", + " e5_incr_paid_trended[e5_incr_paid_trended.development >= 60].sum().sum(),\n", + " e5_incr_paid_trended[e5_incr_paid_trended.development >= 72].sum().sum(),\n", + "]\n", + "e5_s9.loc[\"Estimated Trended Tail Severity\"] = e5_s9.iloc[1] / e5_s9.iloc[0]\n", + "e5_s9.loc[\"Estimated Incremental Trended Tail Severity\"] = e5_sevs_sel.iloc[\n", + " :, :, :, 4:6\n", + "].values.flatten()\n", + "e5_s9.loc[\"Selected Trended Paid Severity\"] = e5_ipsev_full.values[0, 0, -1, 4:6]\n", + "print(\"Incremental Closed Claim Counts\")\n", + "nb_display(e5_incr_ccc[e5_incr_ccc.development >= 60][e5_incr_ccc.origin <= \"2004\"])\n", + "print(\"Incremental Paid CLaims\")\n", + "nb_display(e5_incr_paid[e5_incr_paid.development >= 60][e5_incr_paid.origin <= \"2004\"])\n", + "print(\"Trended Incremental Paid Severities\")\n", + "nb_display(\n", + " e5_incr_paid_trended[e5_incr_paid_trended.development >= 60][\n", + " e5_incr_paid_trended.origin <= \"2004\"\n", " ]\n", - "e5_s9.loc['Estimated Trended Tail Severity'] = e5_s9.iloc[1] / e5_s9.iloc[0]\n", - "e5_s9.loc['Estimated Incremental Trended Tail Severity'] = e5_sevs_sel.iloc[:,:,:,4:6].values.flatten()\n", - "e5_s9.loc['Selected Trended Paid Severity'] = e5_ipsev_full.values[0,0,-1,4:6]\n", - "print('Incremental Closed Claim Counts')\n", - "nb_display(e5_incr_ccc[e5_incr_ccc.development >= 60][e5_incr_ccc.origin <= '2004'])\n", - "print('Incremental Paid CLaims')\n", - "nb_display(e5_incr_paid[e5_incr_paid.development >= 60][e5_incr_paid.origin <= '2004'])\n", - "print('Trended Incremental Paid Severities')\n", - "nb_display(e5_incr_paid_trended[e5_incr_paid_trended.development >= 60][e5_incr_paid_trended.origin <= '2004'])\n", + ")\n", "nb_display(e5_s9)" ] }, @@ -2630,11 +2875,13 @@ }, "outputs": [], "source": [ - "print('Incremental Paid Severities')\n", + "print(\"Incremental Paid Severities\")\n", "nb_display(e5_ipsev)\n", - "print('Selected Incremental Paid Severities')\n", - "nb_display(e5_ipsev_full[e5_ipsev_full.origin == '2008'])\n", - "print('Incremental Paid Severities Adjusted to Cost Level of Accident Year Assuming 5% Annual Trend Rate')\n", + "print(\"Selected Incremental Paid Severities\")\n", + "nb_display(e5_ipsev_full[e5_ipsev_full.origin == \"2008\"])\n", + "print(\n", + " \"Incremental Paid Severities Adjusted to Cost Level of Accident Year Assuming 5% Annual Trend Rate\"\n", + ")\n", "nb_display(e5_ipsev_full_complete)" ] }, @@ -2672,13 +2919,15 @@ }, "outputs": [], "source": [ - "print('Projected Incremental Closed Claim Counts')\n", + "print(\"Projected Incremental Closed Claim Counts\")\n", "nb_display(e5_s11_ccc.round(0))\n", - "print('Incremental Paid Severities Adjusted to Cost Level of Accident Year Assuming 5% Annual Trend Rate')\n", + "print(\n", + " \"Incremental Paid Severities Adjusted to Cost Level of Accident Year Assuming 5% Annual Trend Rate\"\n", + ")\n", "nb_display(e5_ipsev_full_complete)\n", - "print('Projected Incremental Paid Claims')\n", + "print(\"Projected Incremental Paid Claims\")\n", "nb_display(e5_s11_ip)\n", - "print('Projected Cumulative Paid Claims')\n", + "print(\"Projected Cumulative Paid Claims\")\n", "nb_display(e5_s11_ip.incr_to_cum())" ] }, @@ -2717,9 +2966,9 @@ "outputs": [], "source": [ "summary_exh(\n", - " e5_tri['Reported Claims']/1000,\n", - " e5_tri['Paid Claims']/1000,\n", - " e5_s11_ip.incr_to_cum()/1000\n", + " e5_tri[\"Reported Claims\"] / 1000,\n", + " e5_tri[\"Paid Claims\"] / 1000,\n", + " e5_s11_ip.incr_to_cum() / 1000,\n", ")" ] }, @@ -2749,12 +2998,9 @@ "execution_count": 0, "metadata": { "application/vnd.databricks.v1+cell": { - "cellMetadata": { - "byteLimit": 2048000, - "rowLimit": 10000 - }, + "cellMetadata": {}, "inputWidgets": {}, - "nuid": "2a46eef8-f279-4172-b5e1-a23f079ad80e", + "nuid": "520b1f99-ec88-4331-8cdf-56f3bb0d36e2", "showTitle": false, "tableResultSettingsMap": {}, "title": "" @@ -2762,52 +3008,95 @@ }, "outputs": [], "source": [ - "#Exhibit V Sheet 1\n", - "assert np.all(e5_ccc_selected.cdf_.round(3).values == np.array([4.769, 2.199, 1.682, 1.390, 1.256, 1.174, 1.123, 1.100]))\n", - "#Exhibit V Sheet 2\n", - "assert np.all(e5_rcc_selected.cdf_.round(3).values == np.array([0.753, 0.746, 0.818, 0.897, 0.932, 0.968, 1.007, 1.000]))\n", - "#Exhibit V Sheet 3\n", - "assert np.all(e5_s3['Selected Ult CC'].values==np.array([873., 720., 626., 629., 588., 553, 438., 609.]))\n", - "#Exhibit V Sheet 4\n", - "assert np.all(e5_dr_selected.disposal_rate_.values == np.array([0.200, 0.433, 0.585, 0.710, 0.791, 0.862, 0.882, 0.912, 1.000]))\n", - "#Exhibit V Sheet 5\n", - "lhs = (e5_dr_tri.full_triangle_.cum_to_incr()-e5_tri['Closed Claim Counts'].cum_to_incr()).values.flatten()\n", - "rhs = np.array([\n", - " 77., \n", - " 24., 70., \n", - " 12., 18., 54., \n", - " 46., 13., 19., 57., \n", - " 52., 45., 13., 19., 56., \n", - " 76., 49., 43., 12., 18., 54., \n", - " 67., 55., 36., 31., 9., 13., 39., \n", - "140., 91., 75., 49., 43., 12., 18., 53.\n", - "])\n", + "# ruff: noqa: E241\n", + "# Exhibit V Sheet 1\n", + "assert np.all(\n", + " e5_ccc_selected.cdf_.round(3).values\n", + " == np.array([4.769, 2.199, 1.682, 1.390, 1.256, 1.174, 1.123, 1.100])\n", + ")\n", + "# Exhibit V Sheet 2\n", + "assert np.all(\n", + " e5_rcc_selected.cdf_.round(3).values\n", + " == np.array([0.753, 0.746, 0.818, 0.897, 0.932, 0.968, 1.007, 1.000])\n", + ")\n", + "# Exhibit V Sheet 3\n", + "assert np.all(\n", + " e5_s3[\"Selected Ult CC\"].values\n", + " == np.array([873.0, 720.0, 626.0, 629.0, 588.0, 553, 438.0, 609.0])\n", + ")\n", + "# Exhibit V Sheet 4\n", + "assert np.all(\n", + " e5_dr_selected.disposal_rate_.values\n", + " == np.array([0.200, 0.433, 0.585, 0.710, 0.791, 0.862, 0.882, 0.912, 1.000])\n", + ")\n", + "# Exhibit V Sheet 5\n", + "lhs = (\n", + " e5_dr_tri.full_triangle_.cum_to_incr() - e5_tri[\"Closed Claim Counts\"].cum_to_incr()\n", + ").values.flatten()\n", + "rhs = np.array(\n", + " [\n", + " 77., \n", + " 24., 70., \n", + " 12., 18., 54., \n", + " 46., 13., 19., 57., \n", + " 52., 45., 13., 19., 56., \n", + " 76., 49., 43., 12., 18., 54., \n", + " 67., 55., 36., 31., 9., 13., 39., \n", + " 140., 91., 75., 49., 43., 12., 18., 53.,\n", + " ]\n", + ")\n", "assert np.all(abs(lhs[~np.isnan(lhs)] - rhs) < 1)\n", - "#Exhibit V Sheet 7\n", - "assert np.all(e5_trend.loc['latest_4'].values[:7].round(3) == np.array([0.018, 0.016, 0.095, 0.050, 0.122, -0.294, -0.331]))\n", - "assert np.all(e5_rsq.loc['latest_6'].values[:7].round(3) == np.array([0.704, 0.000, 0.644, 0.722, 0.084, 0.190, 1.000]))\n", - "#Exhibit V Sheet 8\n", - "assert np.all(e5_sevs_sel.values[...,:5].round(0) == np.array([11259., 32980., 65523., 80544., 140802.]))\n", - "#Exhibit V Sheet 9\n", - "assert np.all(e5_s9.iloc[2].values.round(0) == np.array([144160., 175816.]))\n", - "#Exhibit V Sheet 10\n", - "lhs = e5_ipsev_full_detrended[e5_ipsev_full_detrended.valuation > e5_ipsev.valuation_date].round(0).values.flatten()\n", - "rhs = np.array([\n", - " 124949.,\n", - " 131196.,131196.,\n", - " 137756.,137756.,137756.,\n", - " 144644.,144644.,144644.,144644.,\n", - " 121630.,151876.,151876.,151876.,151876.,\n", - " 73056.,\t127711.,159470.,159470.,159470.,159470.,\n", - " 62403.,\t76709.,\t134097.,167443.,167443.,167443.,167443.,\n", - "32980., 65523.,\t80544.,\t140802.,175816.,175816.,175816.,175816.,\n", - "])\n", + "# Exhibit V Sheet 7\n", + "assert np.all(\n", + " e5_trend.loc[\"latest_4\"].values[:7].round(3)\n", + " == np.array([0.018, 0.016, 0.095, 0.050, 0.122, -0.294, -0.331])\n", + ")\n", + "assert np.all(\n", + " e5_rsq.loc[\"latest_6\"].values[:7].round(3)\n", + " == np.array([0.704, 0.000, 0.644, 0.722, 0.084, 0.190, 1.000])\n", + ")\n", + "# Exhibit V Sheet 8\n", + "assert np.all(\n", + " e5_sevs_sel.values[..., :5].round(0)\n", + " == np.array([11259.0, 32980.0, 65523.0, 80544.0, 140802.0])\n", + ")\n", + "# Exhibit V Sheet 9\n", + "assert np.all(e5_s9.iloc[2].values.round(0) == np.array([144160.0, 175816.0]))\n", + "# Exhibit V Sheet 10\n", + "lhs = (\n", + " e5_ipsev_full_detrended[e5_ipsev_full_detrended.valuation > e5_ipsev.valuation_date]\n", + " .round(0)\n", + " .values.flatten()\n", + ")\n", + "rhs = np.array(\n", + " [\n", + " 124949.,\n", + " 131196.,131196.,\n", + " 137756.,137756.,137756.,\n", + " 144644.,144644.,144644.,144644.,\n", + " 121630.,151876.,151876.,151876.,151876.,\n", + " 73056.,\t127711.,159470.,159470.,159470.,159470.,\n", + " 62403.,\t76709.,\t134097.,167443.,167443.,167443.,167443.,\n", + " 32980., 65523.,\t80544.,\t140802.,175816.,175816.,175816.,175816.,\n", + " ]\n", + ")\n", "assert np.all(lhs[~np.isnan(lhs)] == rhs)\n", - "#Exhibit V Sheet 11\n", + "# Exhibit V Sheet 11\n", "assert np.allclose(\n", - " e5_s11_ip.incr_to_cum().iloc[:,:,:,-1].values.round(0).flatten(),\n", - " np.array([39497433., 44743308., 35510981., 37766027., 42007442., 41113459., 32859080., 47109641.]),\n", - " rtol = .001\n", + " e5_s11_ip.incr_to_cum().iloc[:, :, :, -1].values.round(0).flatten(),\n", + " np.array(\n", + " [\n", + " 39497433.0,\n", + " 44743308.0,\n", + " 35510981.0,\n", + " 37766027.0,\n", + " 42007442.0,\n", + " 41113459.0,\n", + " 32859080.0,\n", + " 47109641.0,\n", + " ]\n", + " ),\n", + " rtol=0.001,\n", ")" ] }, @@ -2845,61 +3134,77 @@ }, "outputs": [], "source": [ - "#loading data and assumptions\n", - "e6_tri = e2_tri[e2_tri.origin >= '2001'][e2_tri.development <= 96]\n", + "# loading data and assumptions\n", + "e6_tri = e2_tri[e2_tri.origin >= \"2001\"][e2_tri.development <= 96]\n", "e6_disp_assumptions = {}\n", - "e6_disp_assumptions['simple_3'] = {'n_periods':3, 'average':'simple'}\n", - "e6_disp_assumptions['simple_2'] = {'n_periods':2, 'average':'simple'}\n", - "e6_disp_assumptions['medial_5x1'] = {'n_periods':5, 'average':'simple','drop_high':1, 'drop_low':1}\n", + "e6_disp_assumptions[\"simple_3\"] = {\"n_periods\": 3, \"average\": \"simple\"}\n", + "e6_disp_assumptions[\"simple_2\"] = {\"n_periods\": 2, \"average\": \"simple\"}\n", + "e6_disp_assumptions[\"medial_5x1\"] = {\n", + " \"n_periods\": 5,\n", + " \"average\": \"simple\",\n", + " \"drop_high\": 1,\n", + " \"drop_low\": 1,\n", + "}\n", "e6_sev_assumptions = {}\n", - "e6_sev_assumptions['simple_3'] = {'n_periods':3}\n", - "e6_sev_assumptions['simple_2'] = {'n_periods':2}\n", - "e6_sev_assumptions['medial_5x1'] = {'n_periods':5,'drop_high':1, 'drop_low':1}\n", + "e6_sev_assumptions[\"simple_3\"] = {\"n_periods\": 3}\n", + "e6_sev_assumptions[\"simple_2\"] = {\"n_periods\": 2}\n", + "e6_sev_assumptions[\"medial_5x1\"] = {\"n_periods\": 5, \"drop_high\": 1, \"drop_low\": 1}\n", "\n", - "#calculating disposal rate and complete the closed count triangle\n", - "e6_rcc_ult = e2_rcc_cl.ultimate_[e2_rcc_cl.ultimate_.origin >= '2001']\n", - "e6_drs = average_dr(e6_tri['Closed Claim Counts'],e6_rcc_ult,e6_disp_assumptions)\n", - "e6_dr_selected = e6_drs['simple_2']\n", + "# calculating disposal rate and complete the closed count triangle\n", + "e6_rcc_ult = e2_rcc_cl.ultimate_[e2_rcc_cl.ultimate_.origin >= \"2001\"]\n", + "e6_drs = average_dr(e6_tri[\"Closed Claim Counts\"], e6_rcc_ult, e6_disp_assumptions)\n", + "e6_dr_selected = e6_drs[\"simple_2\"]\n", "e6_dr_selected.disposal_rate_ = e6_dr_selected.disposal_rate_.round(3)\n", - "e6_dr_tri = e6_dr_selected.transform(e6_tri['Closed Claim Counts'],sample_weight = e6_rcc_ult)\n", + "e6_dr_tri = e6_dr_selected.transform(\n", + " e6_tri[\"Closed Claim Counts\"], sample_weight=e6_rcc_ult\n", + ")\n", "\n", - "#calculate and select incremental paid severity\n", - "#in a real world scenario, it's possible to calculate incremental paid severity like this\n", + "# calculate and select incremental paid severity\n", + "# in a real world scenario, it's possible to calculate incremental paid severity like this\n", "# e6_tri[\"Paid Claims\"].cum_to_incr() / e6_tri[\"Closed Claim Counts\"].cum_to_incr() * 1000\n", - "#however, the cumulative paid data in Friedland is only given in thousands\n", - "#therefore, we will directly use the incremental paid severity data as given\n", - "e6_ipsev = e6_tri['Incremental Paid Severity']\n", + "# however, the cumulative paid data in Friedland is only given in thousands\n", + "# therefore, we will directly use the incremental paid severity data as given\n", + "e6_ipsev = e6_tri[\"Incremental Paid Severity\"]\n", "e6_ipsev_adj = e6_ipsev.trend(0.05) * xyz_tort_adjustment.fit(e6_ipsev).olf_.values\n", - "e6_sevs = average_sev(e6_ipsev_adj,e6_sev_assumptions)\n", - "e6_sevs_sel = e6_sevs['simple_2'].copy()\n", - "e6_sevs_sel.origin = ['2008']\n", + "e6_sevs = average_sev(e6_ipsev_adj, e6_sev_assumptions)\n", + "e6_sevs_sel = e6_sevs[\"simple_2\"].copy()\n", + "e6_sevs_sel.origin = [\"2008\"]\n", "\n", - "#calculate and select tail paid severity\n", + "# calculate and select tail paid severity\n", "e6_incr_ccc = e6_tri[\"Closed Claim Counts\"].cum_to_incr()\n", "e6_incr_paid = e6_tri[\"Paid Claims\"].cum_to_incr() * 1000\n", "e6_incr_paid_trended = e6_ipsev_adj * e6_incr_ccc\n", "\n", "e6_ipsev_full = e6_ipsev_adj.copy()\n", - "#extending the severity triangle to 108 months\n", - "e6_ipsev_full = cl.concat((e6_ipsev_full,e6_ipsev_full.latest_diagonal.rename(\"development\",[9999])),axis=3)\n", - "#setting the before 72 months to selected incremental\n", - "e6_ipsev_full.iloc[:,:,:,:6] = e6_sevs_sel.iloc[:,:,:,:6]\n", - "#setting the after 86 months to selected tail\n", - "e6_ipsev_full.iloc[:,:,:,6:] = (\n", + "# extending the severity triangle to 108 months\n", + "e6_ipsev_full = cl.concat(\n", + " (e6_ipsev_full, e6_ipsev_full.latest_diagonal.rename(\"development\", [9999])), axis=3\n", + ")\n", + "# setting the before 72 months to selected incremental\n", + "e6_ipsev_full.iloc[:, :, :, :6] = e6_sevs_sel.iloc[:, :, :, :6]\n", + "# setting the after 86 months to selected tail\n", + "e6_ipsev_full.iloc[:, :, :, 6:] = (\n", " e6_incr_paid_trended[e6_incr_paid_trended.development >= 84].sum().sum()\n", - " /\n", - " e6_incr_ccc[e6_incr_ccc.development >= 84].sum().sum() \n", + " / e6_incr_ccc[e6_incr_ccc.development >= 84].sum().sum()\n", ")\n", - "#setting the valuation_date to the future\n", + "# setting the valuation_date to the future\n", "e6_ipsev_full.valuation_date = pd.to_datetime(cl.options.ULT_VAL)\n", - "#trending back from latest accident year\n", - "e6_ipsev_full_detrended = e6_ipsev_full.trend(1/1.05-1,start='2008-12-31',end='2001-01-01') / xyz_tort_adjustment.fit(e6_ipsev).olf_.values\n", - "#compositing the actual incremental severities with dtrended selected\n", - "e6_ipsev_full_complete = e6_ipsev[e6_ipsev.valuation <= e6_ipsev.valuation_date] + e6_ipsev_full_detrended[e6_ipsev_full_detrended.valuation > e6_ipsev.valuation_date]\n", + "# trending back from latest accident year\n", + "e6_ipsev_full_detrended = (\n", + " e6_ipsev_full.trend(1 / 1.05 - 1, start=\"2008-12-31\", end=\"2001-01-01\")\n", + " / xyz_tort_adjustment.fit(e6_ipsev).olf_.values\n", + ")\n", + "# compositing the actual incremental severities with dtrended selected\n", + "e6_ipsev_full_complete = (\n", + " e6_ipsev[e6_ipsev.valuation <= e6_ipsev.valuation_date]\n", + " + e6_ipsev_full_detrended[\n", + " e6_ipsev_full_detrended.valuation > e6_ipsev.valuation_date\n", + " ]\n", + ")\n", "\n", - "#complete the incremental paid triangle\n", + "# complete the incremental paid triangle\n", "e6_s7_ccc = e6_dr_tri.full_triangle_.cum_to_incr()\n", - "e6_s7_ip = e6_s7_ccc * e6_ipsev_full_complete / 1000\n" + "e6_s7_ip = e6_s7_ccc * e6_ipsev_full_complete / 1000" ] }, { @@ -2936,13 +3241,17 @@ }, "outputs": [], "source": [ - "print('Part 1 - Disposal Rate Triangle')\n", - "nb_display(cl.DisposalRate().fit_transform(e6_tri['Closed Claim Counts'],sample_weight = e6_rcc_ult).disposal_rate_tri.round(3))\n", + "print(\"Part 1 - Disposal Rate Triangle\")\n", + "nb_display(\n", + " cl.DisposalRate()\n", + " .fit_transform(e6_tri[\"Closed Claim Counts\"], sample_weight=e6_rcc_ult)\n", + " .disposal_rate_tri.round(3)\n", + ")\n", "\n", - "print('PART 2 - Average Disposal Rate Factors')\n", + "print(\"PART 2 - Average Disposal Rate Factors\")\n", "nb_display(combine_disposal(e6_drs).round(3).to_frame())\n", "\n", - "print('PART 3 - Selected Disposal Rate Factors')\n", + "print(\"PART 3 - Selected Disposal Rate Factors\")\n", "nb_display(e6_dr_selected.disposal_rate_)" ] }, @@ -2980,9 +3289,9 @@ }, "outputs": [], "source": [ - "print('Closed Claim Counts')\n", - "nb_display(e6_tri['Closed Claim Counts'])\n", - "print('Projected Incremental Closed Claim Counts')\n", + "print(\"Closed Claim Counts\")\n", + "nb_display(e6_tri[\"Closed Claim Counts\"])\n", + "print(\"Projected Incremental Closed Claim Counts\")\n", "nb_display(e6_dr_tri.full_triangle_.cum_to_incr().round(0))" ] }, @@ -3020,13 +3329,13 @@ }, "outputs": [], "source": [ - "print('Paid Claims')\n", + "print(\"Paid Claims\")\n", "nb_display(e6_tri[\"Paid Claims\"])\n", - "print('Incremental Paid CLaims')\n", + "print(\"Incremental Paid CLaims\")\n", "nb_display(e6_tri[\"Paid Claims\"].cum_to_incr())\n", - "print('Incremental Closed Claim Counts')\n", + "print(\"Incremental Closed Claim Counts\")\n", "nb_display(e6_tri[\"Closed Claim Counts\"].cum_to_incr())\n", - "print('Incremental Paid Severities')\n", + "print(\"Incremental Paid Severities\")\n", "nb_display(e6_ipsev)" ] }, @@ -3064,14 +3373,16 @@ }, "outputs": [], "source": [ - "print('Incremental Paid Severities')\n", + "print(\"Incremental Paid Severities\")\n", "nb_display(e6_ipsev)\n", - "print('Trended Incremental Paid Severities Assuming 5% Annual Trend and Adjusted for Tort Reform')\n", + "print(\n", + " \"Trended Incremental Paid Severities Assuming 5% Annual Trend and Adjusted for Tort Reform\"\n", + ")\n", "nb_display(e6_ipsev_adj)\n", - "print('Average Trended Incremental Paid Severities')\n", + "print(\"Average Trended Incremental Paid Severities\")\n", "nb_display(combine_tri(e6_sevs).round(0).to_frame())\n", - "print('Selected Incremental Paid Severities')\n", - "nb_display(e6_sevs_sel[e6_sevs_sel.development<=72])" + "print(\"Selected Incremental Paid Severities\")\n", + "nb_display(e6_sevs_sel[e6_sevs_sel.development <= 72])" ] }, { @@ -3108,27 +3419,35 @@ }, "outputs": [], "source": [ - "print('Incremental Closed Claim Counts')\n", - "nb_display(e6_incr_ccc[e6_incr_ccc.development >= 72][e6_incr_ccc.origin <= '2003'])\n", - "print('Incremental Paid CLaims')\n", - "nb_display(e6_incr_paid[e6_incr_paid.development >= 72][e6_incr_paid.origin <= '2003'])\n", - "print('Trended Incremental Paid Severities')\n", - "nb_display(e6_incr_paid_trended[e6_incr_paid_trended.development >= 72][e6_incr_paid_trended.origin <= '2003'])\n", - "e6_s5 = pd.DataFrame(\n", - " data = [[\n", - " e6_incr_ccc[e6_incr_ccc.development >= 72].sum().sum(), \n", - " e6_incr_ccc[e6_incr_ccc.development >= 84].sum().sum()\n", - " ]],\n", - " index = ['Total Closed Claim Counts'],\n", - " columns = ['Age 72 & Older','Age 84 & Older']\n", - ")\n", - "e6_s5.loc['Total Trended Paid Claims'] = [\n", - " e6_incr_paid_trended[e6_incr_paid_trended.development >= 72].sum().sum(), \n", - " e6_incr_paid_trended[e6_incr_paid_trended.development >= 84].sum().sum()\n", + "print(\"Incremental Closed Claim Counts\")\n", + "nb_display(e6_incr_ccc[e6_incr_ccc.development >= 72][e6_incr_ccc.origin <= \"2003\"])\n", + "print(\"Incremental Paid CLaims\")\n", + "nb_display(e6_incr_paid[e6_incr_paid.development >= 72][e6_incr_paid.origin <= \"2003\"])\n", + "print(\"Trended Incremental Paid Severities\")\n", + "nb_display(\n", + " e6_incr_paid_trended[e6_incr_paid_trended.development >= 72][\n", + " e6_incr_paid_trended.origin <= \"2003\"\n", " ]\n", - "e6_s5.loc['Estimated Trended Tail Severity'] = e6_s5.iloc[1] / e6_s5.iloc[0]\n", - "e6_s5.loc['Estimated Incremental Trended Tail Severity'] = e6_sevs_sel.iloc[:,:,:,5:7].values.flatten()\n", - "e6_s5.loc['Selected Trended Paid Severity'] = e6_ipsev_full.values[0,0,-1,5:7]\n", + ")\n", + "e6_s5 = pd.DataFrame(\n", + " data=[\n", + " [\n", + " e6_incr_ccc[e6_incr_ccc.development >= 72].sum().sum(),\n", + " e6_incr_ccc[e6_incr_ccc.development >= 84].sum().sum(),\n", + " ]\n", + " ],\n", + " index=[\"Total Closed Claim Counts\"],\n", + " columns=[\"Age 72 & Older\", \"Age 84 & Older\"],\n", + ")\n", + "e6_s5.loc[\"Total Trended Paid Claims\"] = [\n", + " e6_incr_paid_trended[e6_incr_paid_trended.development >= 72].sum().sum(),\n", + " e6_incr_paid_trended[e6_incr_paid_trended.development >= 84].sum().sum(),\n", + "]\n", + "e6_s5.loc[\"Estimated Trended Tail Severity\"] = e6_s5.iloc[1] / e6_s5.iloc[0]\n", + "e6_s5.loc[\"Estimated Incremental Trended Tail Severity\"] = e6_sevs_sel.iloc[\n", + " :, :, :, 5:7\n", + "].values.flatten()\n", + "e6_s5.loc[\"Selected Trended Paid Severity\"] = e6_ipsev_full.values[0, 0, -1, 5:7]\n", "nb_display(e6_s5)" ] }, @@ -3166,11 +3485,13 @@ }, "outputs": [], "source": [ - "print('Incremental Paid Severities')\n", + "print(\"Incremental Paid Severities\")\n", "nb_display(e6_ipsev)\n", - "print('Selected Incremental Paid Severities')\n", - "nb_display(e6_ipsev_full[e6_ipsev_full.origin == '2008'])\n", - "print('Incremental Paid Severities Adjusted to Cost Level of Accident Year Assuming 5% Annual Trend Rate and Tort Reform Adjustment')\n", + "print(\"Selected Incremental Paid Severities\")\n", + "nb_display(e6_ipsev_full[e6_ipsev_full.origin == \"2008\"])\n", + "print(\n", + " \"Incremental Paid Severities Adjusted to Cost Level of Accident Year Assuming 5% Annual Trend Rate and Tort Reform Adjustment\"\n", + ")\n", "nb_display(e6_ipsev_full_complete)" ] }, @@ -3208,13 +3529,15 @@ }, "outputs": [], "source": [ - "print('Projected Incremental Closed Claim Counts')\n", + "print(\"Projected Incremental Closed Claim Counts\")\n", "nb_display(e6_s7_ccc.round(0))\n", - "print('Incremental Paid Severities Adjusted to Cost Level of Accident Year Assuming 5% Annual Trend Rate and Tort Reform Adjustment')\n", + "print(\n", + " \"Incremental Paid Severities Adjusted to Cost Level of Accident Year Assuming 5% Annual Trend Rate and Tort Reform Adjustment\"\n", + ")\n", "nb_display(e6_ipsev_full_complete)\n", - "print('Projected Incremental Paid Claims')\n", + "print(\"Projected Incremental Paid Claims\")\n", "nb_display(e6_s7_ip)\n", - "print('Projected Cumulative Paid Claims')\n", + "print(\"Projected Cumulative Paid Claims\")\n", "nb_display(e6_s7_ip.incr_to_cum())" ] }, @@ -3252,11 +3575,7 @@ }, "outputs": [], "source": [ - "summary_exh(\n", - " e6_tri['Reported Claims'],\n", - " e6_tri['Paid Claims'],\n", - " e6_s7_ip.incr_to_cum()\n", - ")" + "summary_exh(e6_tri[\"Reported Claims\"], e6_tri[\"Paid Claims\"], e6_s7_ip.incr_to_cum())" ] }, { @@ -3264,12 +3583,9 @@ "execution_count": 0, "metadata": { "application/vnd.databricks.v1+cell": { - "cellMetadata": { - "byteLimit": 2048000, - "rowLimit": 10000 - }, + "cellMetadata": {}, "inputWidgets": {}, - "nuid": "e03f91fe-ff00-47ee-9994-3caf56a38ab1", + "nuid": "d192b52a-32a1-4b88-895c-7e6747278d7f", "showTitle": false, "tableResultSettingsMap": {}, "title": "" @@ -3277,44 +3593,58 @@ }, "outputs": [], "source": [ - "#Exhibit VI Sheet 1\n", - "assert np.all(e6_dr_selected.disposal_rate_.values == np.array([0.244, 0.572, 0.704, 0.814, 0.910, 0.952, 0.982, 0.994, 1.000]))\n", - "#Exhibit VI Sheet 2\n", - "lhs = (e6_dr_tri.full_triangle_.cum_to_incr()-e6_tri['Closed Claim Counts'].cum_to_incr()).values.flatten()\n", - "rhs = np.array([\n", - " 9., \n", - " 21., 10., \n", - " 39., 16., 8., \n", - " 109., 78., 31., 16., \n", - " 235., 103., 74., 29., 15., \n", - " 177., 155., 68., 48., 19., 10., \n", - " 160., 133., 116., 51., 36., 15., 7., \n", - "389., 156., 130., 114., 50., 36., 14., 7.\n", - "])\n", + "# Exhibit VI Sheet 1\n", + "assert np.all(\n", + " e6_dr_selected.disposal_rate_.values\n", + " == np.array([0.244, 0.572, 0.704, 0.814, 0.910, 0.952, 0.982, 0.994, 1.000])\n", + ")\n", + "# Exhibit VI Sheet 2\n", + "lhs = (\n", + " e6_dr_tri.full_triangle_.cum_to_incr() - e6_tri[\"Closed Claim Counts\"].cum_to_incr()\n", + ").values.flatten()\n", + "rhs = np.array(\n", + " [\n", + " 9., \n", + " 21., 10., \n", + " 39., 16., 8., \n", + " 109., 78., 31., 16., \n", + " 235., 103., 74., 29., 15., \n", + " 177., 155., 68., 48., 19., 10., \n", + " 160., 133., 116., 51., 36., 15., 7., \n", + " 389., 156., 130., 114., 50., 36., 14., 7.,\n", + " ]\n", + ")\n", "assert np.all(abs(lhs[~np.isnan(lhs)] - rhs) < 1)\n", - "#Exhibit VI Sheet 4\n", - "assert np.all(e6_sevs_sel.values[...,:6].round(0) == np.array([11807.,15165.,26043.,35183.,41908.,62206.]))\n", - "#Exhibit VI Sheet 5\n", - "assert np.all(e6_s5.iloc[4].values.round(0) == np.array([62206.,70432.]))\n", - "#Exhibit VI Sheet 6\n", - "lhs = e6_ipsev_full_detrended[e6_ipsev_full_detrended.valuation > e6_ipsev.valuation_date].values.flatten()\n", - "rhs = np.array([\n", - " 74709., \n", - " 78444., 78444., \n", - " 82367., 82367., 82367., \n", - " 76384., 86485., 86485., 86485., \n", - " 54032., 80203., 90809., 90809., 90809., \n", - " 42549., 50682., 75230., 85179., 85179., 85179., \n", - " 24803., 33508., 39912., 59244., 67079., 67079., 67079., \n", - "15165., 26043., 35183., 41908., 62206., 70432., 70432., 70432., \n", - "])\n", + "# Exhibit VI Sheet 4\n", + "assert np.all(\n", + " e6_sevs_sel.values[..., :6].round(0)\n", + " == np.array([11807.0, 15165.0, 26043.0, 35183.0, 41908.0, 62206.0])\n", + ")\n", + "# Exhibit VI Sheet 5\n", + "assert np.all(e6_s5.iloc[4].values.round(0) == np.array([62206.0, 70432.0]))\n", + "# Exhibit VI Sheet 6\n", + "lhs = e6_ipsev_full_detrended[\n", + " e6_ipsev_full_detrended.valuation > e6_ipsev.valuation_date\n", + "].values.flatten()\n", + "rhs = np.array(\n", + " [\n", + " 74709., \n", + " 78444., 78444., \n", + " 82367., 82367., 82367., \n", + " 76384., 86485., 86485., 86485., \n", + " 54032., 80203., 90809., 90809., 90809., \n", + " 42549., 50682., 75230., 85179., 85179., 85179., \n", + " 24803., 33508., 39912., 59244., 67079., 67079., 67079., \n", + " 15165., 26043., 35183., 41908., 62206., 70432., 70432., 70432., \n", + " ]\n", + ")\n", "assert np.allclose(lhs[~np.isnan(lhs)], rhs, atol=1)\n", - "#Exhibit VI Sheet 7\n", + "# Exhibit VI Sheet 7\n", "assert np.allclose(\n", - " e6_s7_ip.incr_to_cum().iloc[:,:,:,-1].values.round(0).flatten(),\n", - " np.array([39192.,46869.,44479.,71906.,71684.,49913.,31805.,29828.]),\n", - " rtol = .001\n", - ")\n" + " e6_s7_ip.incr_to_cum().iloc[:, :, :, -1].values.round(0).flatten(),\n", + " np.array([39192.0, 46869.0, 44479.0, 71906.0, 71684.0, 49913.0, 31805.0, 29828.0]),\n", + " rtol=0.001,\n", + ")" ] } ], From 9c11fad57714d1d5e5680769ad516a20f2275ed5 Mon Sep 17 00:00:00 2001 From: "henrydingliu@gmail.com" Date: Wed, 2 Sep 2026 22:44:35 +0000 Subject: [PATCH 3/3] ruff fix --- chainladder/utils/tests/test_utilities.py | 4 +- docs/friedland/chapter_11.ipynb | 151 ++++++++++++---------- 2 files changed, 82 insertions(+), 73 deletions(-) diff --git a/chainladder/utils/tests/test_utilities.py b/chainladder/utils/tests/test_utilities.py index 5f33c24e..f52249d2 100644 --- a/chainladder/utils/tests/test_utilities.py +++ b/chainladder/utils/tests/test_utilities.py @@ -189,8 +189,8 @@ def test_model_diagnostics_groupby(prism, atol): est = cl.Chainladder().fit(dev.transform(prism["Incurred"])) lhs = cl.model_diagnostics(est, groupby=['Line']) rhs = cl.model_diagnostics(cl.Chainladder().fit(dev.transform(prism["Incurred"].groupby('Line').sum()))) - assert np.allclose(lhs['Ultimate'].values, rhs['Ultimate'].values,atol=atol, equal_nan=True) - assert np.allclose(np.nan_to_num(lhs['IBNR'].values), np.nan_to_num(rhs['IBNR'].values),atol=atol, equal_nan=True) + assert np.allclose(lhs['Ultimate'].values, rhs['Ultimate'].values, atol=atol, equal_nan=True) + assert np.allclose(np.nan_to_num(lhs['IBNR'].values), np.nan_to_num(rhs['IBNR'].values), atol=atol, equal_nan=True) def test_concat_immutability(raa): diff --git a/docs/friedland/chapter_11.ipynb b/docs/friedland/chapter_11.ipynb index 4a270d4f..1fac8ff2 100644 --- a/docs/friedland/chapter_11.ipynb +++ b/docs/friedland/chapter_11.ipynb @@ -52,18 +52,17 @@ " date_format: str = \"{:%Y}\",\n", "):\n", " return (\n", - " exh_df.reset_index()\n", + " exh_df\n", + " .reset_index()\n", " .rename(columns={\"origin\": \"Accident Year\"})\n", " .style.hide(axis=\"index\")\n", " .set_properties(**{\"text-align\": \"right\"})\n", - " .format(\n", - " {\n", - " \"Accident Year\": date_format,\n", - " **{x: \"{:,.0f}\" for x in value_cols},\n", - " **{x: \"{:,.3f}\" for x in factor_cols},\n", - " **other_formats,\n", - " }\n", - " )\n", + " .format({\n", + " \"Accident Year\": date_format,\n", + " **{x: \"{:,.0f}\" for x in value_cols},\n", + " **{x: \"{:,.3f}\" for x in factor_cols},\n", + " **other_formats,\n", + " })\n", " )\n", "\n", "\n", @@ -376,12 +375,14 @@ "outputs": [], "source": [ "e1_ccc_df = (\n", - " cl.model_diagnostics(e1_ccc_cl)\n", + " cl\n", + " .model_diagnostics(e1_ccc_cl)\n", " .to_frame(keepdims=True, implicit_axis=True)\n", " .set_index(\"origin\")\n", ")\n", "e1_rcc_df = (\n", - " cl.model_diagnostics(e1_rcc_cl)\n", + " cl\n", + " .model_diagnostics(e1_rcc_cl)\n", " .to_frame(keepdims=True, implicit_axis=True)\n", " .set_index(\"origin\")\n", ")\n", @@ -571,7 +572,8 @@ "outputs": [], "source": [ "e1_rsev_ult_df = (\n", - " cl.model_diagnostics(e1_rsev_cl)\n", + " cl\n", + " .model_diagnostics(e1_rsev_cl)\n", " .to_frame(keepdims=True, implicit_axis=True)\n", " .set_index(\"origin\")\n", ")\n", @@ -632,8 +634,10 @@ }, "outputs": [], "source": [ - "e1_s8 = summary_exh(e1_tri['Reported Claims']/1000,e1_tri['Paid Claims']/1000,e1_ult)\n", - "format_exh(e1_s8,e1_s8.columns,[],{},\"{:%Y%m}\")" + "e1_s8 = summary_exh(\n", + " e1_tri[\"Reported Claims\"] / 1000, e1_tri[\"Paid Claims\"] / 1000, e1_ult\n", + ")\n", + "format_exh(e1_s8, e1_s8.columns, [], {}, \"{:%Y%m}\")" ] }, { @@ -825,19 +829,19 @@ }, "outputs": [], "source": [ - "print('PART 1 - Data Triangle')\n", - "nb_display(e2_tri['Closed Claim Counts'])\n", - "print('PART 2 - Age-to-Age Factors')\n", - "nb_display(e2_tri['Closed Claim Counts'].age_to_age.round(3))\n", - "print('PART 3 - Average Age-to-Age Factor')\n", + "print(\"PART 1 - Data Triangle\")\n", + "nb_display(e2_tri[\"Closed Claim Counts\"])\n", + "print(\"PART 2 - Age-to-Age Factors\")\n", + "nb_display(e2_tri[\"Closed Claim Counts\"].age_to_age.round(3))\n", + "print(\"PART 3 - Average Age-to-Age Factor\")\n", "nb_display(combine_ldf(e2_ccc_devs).round(3).to_frame().rename_axis(\"\"))\n", - "print('PART 4 - Selected Age-to-Age Factors')\n", - "print('Selected')\n", + "print(\"PART 4 - Selected Age-to-Age Factors\")\n", + "print(\"Selected\")\n", "nb_display(e2_ccc_selected.ldf_)\n", - "print('CDF to Ultimate')\n", + "print(\"CDF to Ultimate\")\n", "nb_display(e2_ccc_selected.cdf_.round(3))\n", - "print('Percent Closed')\n", - "nb_display(1/e2_ccc_selected.cdf_.round(3))" + "print(\"Percent Closed\")\n", + "nb_display(1 / e2_ccc_selected.cdf_.round(3))" ] }, { @@ -926,12 +930,14 @@ "outputs": [], "source": [ "e2_ccc_df = (\n", - " cl.model_diagnostics(e2_ccc_cl)\n", + " cl\n", + " .model_diagnostics(e2_ccc_cl)\n", " .to_frame(keepdims=True, implicit_axis=True)\n", " .set_index(\"origin\")\n", ")\n", "e2_rcc_df = (\n", - " cl.model_diagnostics(e2_rcc_cl)\n", + " cl\n", + " .model_diagnostics(e2_rcc_cl)\n", " .to_frame(keepdims=True, implicit_axis=True)\n", " .set_index(\"origin\")\n", ")\n", @@ -1085,7 +1091,8 @@ "outputs": [], "source": [ "e2_rsev_df = (\n", - " cl.model_diagnostics(e2_rsev_cl)\n", + " cl\n", + " .model_diagnostics(e2_rsev_cl)\n", " .to_frame(keepdims=True, implicit_axis=True)\n", " .set_index(\"origin\")\n", ")\n", @@ -1270,7 +1277,7 @@ "e3_sevavg_assumptions[\"latest_3\"] = {\"n_periods\": 3}\n", "\n", "# developing closed claim counts\n", - "# There is a typo in the text. We will use a tail of 1.002 to match the 84-ult factor'\n", + "# There is a typo in the text. We will use a tail of 1.002 to match the 84-ult factor\n", "e3_ccc_devs = average_dev(e3_tri[\"Closed Claim Counts\"], e3_cnt_assumptions)\n", "e3_ccc_selected = cl.TailConstant(tail=1.002, projection_period=0).fit_transform(\n", " e3_ccc_devs[\"volume_5\"]\n", @@ -1686,16 +1693,16 @@ }, "outputs": [], "source": [ - "print('PART 1 - Data Triangle')\n", + "print(\"PART 1 - Data Triangle\")\n", "nb_display(e3_rsev)\n", - "print('PART 2 - Age-to-Age Factors')\n", + "print(\"PART 2 - Age-to-Age Factors\")\n", "nb_display(e3_rsev.age_to_age.round(3))\n", - "print('PART 3 - Average Age-to-Age Factor')\n", + "print(\"PART 3 - Average Age-to-Age Factor\")\n", "nb_display(combine_ldf(e3_rsev_devs).round(3).to_frame().rename_axis(\"\"))\n", - "print('PART 4 - Selected Age-to-Age Factors')\n", - "print('Selected')\n", + "print(\"PART 4 - Selected Age-to-Age Factors\")\n", + "print(\"Selected\")\n", "nb_display(e3_rsev_selected.ldf_)\n", - "print('CDF to Ultimate')\n", + "print(\"CDF to Ultimate\")\n", "nb_display(e3_rsev_selected.cdf_.round(3))" ] }, @@ -1805,24 +1812,26 @@ }, "outputs": [], "source": [ - "e3_s9 = e3_s8[['Selected Ult Sev']].copy()\n", - "e3_s9['Sev Trend'] = e3_sev_trend.latest_diagonal.to_frame()\n", - "e3_s9['Trended Ult Sev'] = e3_sev_ult_trended.latest_diagonal.to_frame()\n", - "e3_s9[['Sev Trend']] = e3_s9[['Sev Trend']].round(3)\n", - "#using floating point offset to achieve standard rounding\n", - "e3_s9[['Trended Ult Sev']] = (e3_s9[['Trended Ult Sev']] + 1e-9).round(0)\n", - "nb_display(format_exh(\n", - " e3_s9.iloc[:-2,:],\n", - " [\n", - " 'Selected Ult Sev',\n", - " 'Trended Ult Sev',\n", - " ],\n", - " [\n", - " 'Sev Trend',\n", - " ],\n", - "))\n", - "print('Average Trended Severity at 2008 Cost Level')\n", - "for k,v in e3_sevs.items():\n", + "e3_s9 = e3_s8[[\"Selected Ult Sev\"]].copy()\n", + "e3_s9[\"Sev Trend\"] = e3_sev_trend.latest_diagonal.to_frame()\n", + "e3_s9[\"Trended Ult Sev\"] = e3_sev_ult_trended.latest_diagonal.to_frame()\n", + "e3_s9[[\"Sev Trend\"]] = e3_s9[[\"Sev Trend\"]].round(3)\n", + "# using floating point offset to achieve standard rounding\n", + "e3_s9[[\"Trended Ult Sev\"]] = (e3_s9[[\"Trended Ult Sev\"]] + 1e-9).round(0)\n", + "nb_display(\n", + " format_exh(\n", + " e3_s9.iloc[:-2, :],\n", + " [\n", + " \"Selected Ult Sev\",\n", + " \"Trended Ult Sev\",\n", + " ],\n", + " [\n", + " \"Sev Trend\",\n", + " ],\n", + " )\n", + ")\n", + "print(\"Average Trended Severity at 2008 Cost Level\")\n", + "for k, v in e3_sevs.items():\n", " print(f\"\\t{k:<20} \\t{v:,.0f}\")\n", "print(f\"Selected 2008 Severity \\t\\t{e3_sev_2008:,.0f}\")\n", "print(f\"Estimated 2007 Severity \\t{e3_sev_2007:,.0f}\")" @@ -2442,19 +2451,19 @@ }, "outputs": [], "source": [ - "print('PART 1 - Data Triangle')\n", - "nb_display(e5_tri['Closed Claim Counts'])\n", - "print('PART 2 - Age-to-Age Factors')\n", - "nb_display(e5_tri['Closed Claim Counts'].age_to_age.round(3))\n", - "print('PART 3 - Average Age-to-Age Factor')\n", + "print(\"PART 1 - Data Triangle\")\n", + "nb_display(e5_tri[\"Closed Claim Counts\"])\n", + "print(\"PART 2 - Age-to-Age Factors\")\n", + "nb_display(e5_tri[\"Closed Claim Counts\"].age_to_age.round(3))\n", + "print(\"PART 3 - Average Age-to-Age Factor\")\n", "nb_display(combine_ldf(e5_ccc_devs).round(3).to_frame().rename_axis(\"\"))\n", - "print('PART 4 - Selected Age-to-Age Factors')\n", - "print('Selected')\n", + "print(\"PART 4 - Selected Age-to-Age Factors\")\n", + "print(\"Selected\")\n", "nb_display(e5_ccc_selected.ldf_)\n", - "print('CDF to Ultimate')\n", + "print(\"CDF to Ultimate\")\n", "nb_display(e5_ccc_selected.cdf_.round(3))\n", - "print('Percent Closed')\n", - "nb_display((1/e5_ccc_selected.cdf_).round(3))" + "print(\"Percent Closed\")\n", + "nb_display((1 / e5_ccc_selected.cdf_).round(3))" ] }, { @@ -2491,18 +2500,18 @@ }, "outputs": [], "source": [ - "print('PART 1 - Data Triangle')\n", - "nb_display(e5_tri['Reported Claim Counts'])\n", - "print('PART 2 - Age-to-Age Factors')\n", - "nb_display(e5_tri['Reported Claim Counts'].age_to_age.round(3))\n", - "print('PART 3 - Average Age-to-Age Factor')\n", + "print(\"PART 1 - Data Triangle\")\n", + "nb_display(e5_tri[\"Reported Claim Counts\"])\n", + "print(\"PART 2 - Age-to-Age Factors\")\n", + "nb_display(e5_tri[\"Reported Claim Counts\"].age_to_age.round(3))\n", + "print(\"PART 3 - Average Age-to-Age Factor\")\n", "nb_display(combine_ldf(e5_rcc_devs).round(3).to_frame().rename_axis(\"\"))\n", - "print('PART 4 - Selected Age-to-Age Factors')\n", + "print(\"PART 4 - Selected Age-to-Age Factors\")\n", "nb_display(e5_rcc_selected.ldf_)\n", - "print('CDF to Ultimate')\n", + "print(\"CDF to Ultimate\")\n", "nb_display(e5_rcc_selected.cdf_.round(3))\n", - "print('Percent Reported')\n", - "nb_display((1/e5_rcc_selected.cdf_).round(3))" + "print(\"Percent Reported\")\n", + "nb_display((1 / e5_rcc_selected.cdf_).round(3))" ] }, {