From acedad083cc671b2d7f8f4725f8827ee3dd2336b Mon Sep 17 00:00:00 2001 From: "henrydingliu@gmail.com" Date: Tue, 26 May 2026 02:10:13 +0000 Subject: [PATCH 01/17] Adding summary to methods --- chainladder/methods/base.py | 32 +++++++++++++++ chainladder/methods/benktander.py | 6 +++ chainladder/methods/bornferg.py | 2 + chainladder/methods/capecod.py | 2 + chainladder/methods/chainladder.py | 3 ++ chainladder/methods/expectedloss.py | 2 + chainladder/methods/tests/test_predict.py | 50 +++++++++++------------ 7 files changed, 72 insertions(+), 25 deletions(-) diff --git a/chainladder/methods/base.py b/chainladder/methods/base.py index 26bf84fb..9f2f6e26 100644 --- a/chainladder/methods/base.py +++ b/chainladder/methods/base.py @@ -65,6 +65,38 @@ def latest_diagonal(self): else: return self.X_.sum('development') + @property + def summary(self): + return self._get_summary(self.X_,self.ultimate_) + + def _get_summary(self,X,ult): + #columns for melt + ids = X.key_labels + ['origin','development','valuation'] + #create dataframe for amount + amount = X.latest_diagonal.to_frame(implicit_axis=True,keepdims=True).reset_index().melt(id_vars=ids,var_name = 'column',value_name='actual') + amount_index = X.key_labels + ['origin','column'] + amount = amount[amount_index + ['development','actual']] + amount.set_index(amount_index,inplace=True) + #create dataframe for ultimate + ultimate = ult.to_frame(implicit_axis=True,keepdims=True).reset_index().melt(id_vars=ids,var_name = 'column',value_name='ultimate') + #columns for melt for dev factors + dev_ids = X.ldf_.key_labels + ['origin','development','valuation'] + #create dataframe for ldf + ldf = X.ldf_.to_frame(implicit_axis=True,keepdims=True).reset_index().melt(id_vars=dev_ids,var_name = 'column',value_name='ldf') + dev_factor_index = X.ldf_.key_labels + ['development','column'] + ldf = ldf[dev_factor_index + ['ldf']] + ldf.set_index(dev_factor_index,inplace=True) + #create dataframe for cdf + cdf = X.cdf_.to_frame(implicit_axis=True,keepdims=True).reset_index().melt(id_vars=dev_ids,var_name = 'column',value_name='cdf') + cdf = cdf[dev_factor_index + ['cdf']] + cdf.set_index(dev_factor_index,inplace=True) + #assemble full summary. start from ultimate, as some methods (e.g. BF) return an ultimate without any actual amount + output = ultimate.drop(columns=['development','valuation']) + output = output.join(amount,amount_index,how='left') + output = output.join(ldf,on=dev_factor_index,how='left') + output = output.join(cdf,on=dev_factor_index,how='left') + return output[X.key_labels + ['column','origin','development','actual','ldf','cdf','ultimate']] + def fit(self, X, y=None, sample_weight=None): """Applies the chainladder technique to triangle **X** diff --git a/chainladder/methods/benktander.py b/chainladder/methods/benktander.py index 6681d5b8..0318f827 100644 --- a/chainladder/methods/benktander.py +++ b/chainladder/methods/benktander.py @@ -36,6 +36,9 @@ class Benktander(MethodBase): The ultimate losses per the method ibnr_: Triangle The IBNR per the method + summary: Pandas Dataframe + a summary exhibit that contains actual, ldf, cdf, and ultimate + Examples -------- @@ -243,9 +246,12 @@ def predict(self, X, sample_weight=None): 2012 15914.716737 2013 17193.715555 """ + if sample_weight is None: + raise ValueError("sample_weight is required.") X_new = super().predict(X, sample_weight) X_new.expectation_ = self._get_benktander_aprioris(X, sample_weight) X_new.ultimate_ = self._get_ultimate(X_new, X_new.expectation_) + X_new.summary = self._get_summary(X_new,X_new.ultimate_) X_new.n_iters = self.n_iters X_new.apriori = self.apriori return X_new diff --git a/chainladder/methods/bornferg.py b/chainladder/methods/bornferg.py index 695a6f49..4b035711 100644 --- a/chainladder/methods/bornferg.py +++ b/chainladder/methods/bornferg.py @@ -36,6 +36,8 @@ class BornhuetterFerguson(Benktander): The ultimate losses per the method ibnr_: Triangle The IBNR per the method + summary: Pandas Dataframe + a summary exhibit that contains actual, ldf, cdf, and ultimate Examples -------- diff --git a/chainladder/methods/capecod.py b/chainladder/methods/capecod.py index 0d132d82..9b877518 100644 --- a/chainladder/methods/capecod.py +++ b/chainladder/methods/capecod.py @@ -54,6 +54,8 @@ class CapeCod(Benktander): The trended apriori vector developed by the Cape Cod Method detrended_apriori_: The detrended apriori vector developed by the Cape Cod Method + summary: Pandas Dataframe + a summary exhibit that contains actual, ldf, cdf, and ultimate Examples -------- diff --git a/chainladder/methods/chainladder.py b/chainladder/methods/chainladder.py index 893933f3..c80a2a32 100644 --- a/chainladder/methods/chainladder.py +++ b/chainladder/methods/chainladder.py @@ -26,6 +26,8 @@ class Chainladder(MethodBase): full_triangle_: The ultimates back-filled to each development period in **X** retaining the known data + summary: Pandas Dataframe + a summary exhibit that contains actual, ldf, cdf, and ultimate Examples -------- @@ -188,6 +190,7 @@ def predict(self, X, sample_weight=None): """ X_new = super().predict(X, sample_weight) X_new.ultimate_ = self._get_ultimate(X_new, sample_weight) + X_new.summary = self._get_summary(X_new,X_new.ultimate_) return X_new def _get_ultimate(self, X, sample_weight=None): diff --git a/chainladder/methods/expectedloss.py b/chainladder/methods/expectedloss.py index 44444a2e..e8a8de89 100644 --- a/chainladder/methods/expectedloss.py +++ b/chainladder/methods/expectedloss.py @@ -33,6 +33,8 @@ class ExpectedLoss(Benktander): The ultimate losses per the method ibnr_: Triangle The IBNR per the method + summary: Pandas Dataframe + a summary exhibit that contains actual, ldf, cdf, and ultimate Examples -------- diff --git a/chainladder/methods/tests/test_predict.py b/chainladder/methods/tests/test_predict.py index 6e9b852f..dbf6b331 100644 --- a/chainladder/methods/tests/test_predict.py +++ b/chainladder/methods/tests/test_predict.py @@ -1,5 +1,6 @@ import chainladder as cl import pandas as pd +import numpy as np import pytest raa = cl.load_sample("RAA") @@ -8,37 +9,36 @@ apriori = cl_ult * 0 + (float(cl_ult.sum()) / 10) # Mean Chainladder Ultimate apriori_1989 = apriori[apriori.origin < "1990"] +@pytest.mark.parametrize( + "estimators", + [ + cl.CapeCod, + cl.BornhuetterFerguson, + cl.ExpectedLoss, + cl.Benktander, + cl.Chainladder + ], +) +def test_predict_and_weights(estimators,atol): + est = estimators().fit(raa_1989, sample_weight=apriori_1989) + pred = est.predict(raa, sample_weight=apriori) + assert pred + assert np.all(abs(est.summary['actual'].values - raa_1989.latest_diagonal.values.reshape(-1)) < atol) + assert np.all(abs(pred.summary['actual'].values - raa.latest_diagonal.values.reshape(-1)) < atol) + assert np.all(abs(est.summary['ultimate'].values - est.ultimate_.values.reshape(-1)) < atol) + assert np.all(abs(pred.summary['ultimate'].values - pred.ultimate_.values.reshape(-1)) < atol) + #Test validation of sample_weight requirement. Should raise a value error if no weight is supplied. + if estimators != cl.Chainladder: + with pytest.raises(ValueError): + estimators().fit(raa_1989) + with pytest.raises(ValueError): + estimators().fit(raa_1989, sample_weight=apriori_1989).predict(raa) -def test_cc_predict(): - cc = cl.CapeCod().fit(raa_1989, sample_weight=apriori_1989) - assert cc.predict(raa, sample_weight=apriori) - -def test_bf_predict(): - bf = cl.BornhuetterFerguson().fit(raa_1989, sample_weight=apriori_1989) - assert bf.predict(raa, sample_weight=apriori) - -def test_el_predict(): - bf = cl.ExpectedLoss().fit(raa_1989, sample_weight=apriori_1989) - assert bf.predict(raa, sample_weight=apriori) def test_mack_predict(): mack = cl.MackChainladder().fit(raa_1989) assert mack.predict(raa_1989) -def test_capecod_fit_weight(): - """ - Test validation of sample_weight requirement. Should raise a value error if no weight is supplied. - """ - with pytest.raises(ValueError): - cl.CapeCod().fit(raa_1989) - -def test_capecod_predict_weight(): - """ - Test validation of sample_weight requirement. Should raise a value error if no weight is supplied. - """ - with pytest.raises(ValueError): - cc = cl.CapeCod().fit(raa_1989, sample_weight=apriori_1989) - cc.predict(raa) def test_bs_random_state_predict(clrd): tri = clrd.groupby("LOB").sum().loc["wkcomp", ["CumPaidLoss", "EarnedPremNet"]] From 12072f37922b55ff632b855f4774420aa9e07ae9 Mon Sep 17 00:00:00 2001 From: "henrydingliu@gmail.com" Date: Tue, 26 May 2026 03:06:06 +0000 Subject: [PATCH 02/17] bugbot fix --- chainladder/methods/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainladder/methods/base.py b/chainladder/methods/base.py index 9f2f6e26..e8885252 100644 --- a/chainladder/methods/base.py +++ b/chainladder/methods/base.py @@ -73,7 +73,7 @@ def _get_summary(self,X,ult): #columns for melt ids = X.key_labels + ['origin','development','valuation'] #create dataframe for amount - amount = X.latest_diagonal.to_frame(implicit_axis=True,keepdims=True).reset_index().melt(id_vars=ids,var_name = 'column',value_name='actual') + amount = X.incr_to_cum().latest_diagonal.to_frame(implicit_axis=True,keepdims=True).reset_index().melt(id_vars=ids,var_name = 'column',value_name='actual') amount_index = X.key_labels + ['origin','column'] amount = amount[amount_index + ['development','actual']] amount.set_index(amount_index,inplace=True) From e90bbd406b9f19c0a489bdccaf66fca9421c2788 Mon Sep 17 00:00:00 2001 From: "henrydingliu@gmail.com" Date: Tue, 26 May 2026 03:39:43 +0000 Subject: [PATCH 03/17] bugbot fix --- chainladder/methods/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainladder/methods/base.py b/chainladder/methods/base.py index e8885252..9eb6de58 100644 --- a/chainladder/methods/base.py +++ b/chainladder/methods/base.py @@ -92,7 +92,7 @@ def _get_summary(self,X,ult): cdf.set_index(dev_factor_index,inplace=True) #assemble full summary. start from ultimate, as some methods (e.g. BF) return an ultimate without any actual amount output = ultimate.drop(columns=['development','valuation']) - output = output.join(amount,amount_index,how='left') + output = output.join(amount,on=amount_index,how='left') output = output.join(ldf,on=dev_factor_index,how='left') output = output.join(cdf,on=dev_factor_index,how='left') return output[X.key_labels + ['column','origin','development','actual','ldf','cdf','ultimate']] From 4c8105064220a82b96f904b334d9ccb9ed72578c Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Mon, 25 May 2026 22:56:14 -0700 Subject: [PATCH 04/17] Update sandbox_workbook_blank.ipynb --- .../online_sandbox/sandbox_workbook_blank.ipynb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/getting_started/online_sandbox/sandbox_workbook_blank.ipynb b/docs/getting_started/online_sandbox/sandbox_workbook_blank.ipynb index f98b7079..e2248110 100644 --- a/docs/getting_started/online_sandbox/sandbox_workbook_blank.ipynb +++ b/docs/getting_started/online_sandbox/sandbox_workbook_blank.ipynb @@ -697,7 +697,7 @@ "id": "bdb08c81-5921-4c41-ad63-96168ffd48b7", "metadata": {}, "source": [ - "MackChainladder also has a `summary_` attribute." + "MackChainladder also has a `mack_summary_` attribute." ] }, { @@ -707,7 +707,7 @@ "metadata": {}, "outputs": [], "source": [ - "__fill_in_code__.summary_" + "__fill_in_code__.mack_summary_" ] }, { @@ -726,15 +726,15 @@ "outputs": [], "source": [ "plt.bar(\n", - " mcl_mod.summary_.to_frame().index.year,\n", - " mcl_mod.summary_.to_frame()[__fill_in_code__],\n", + " mcl_mod.mack_summary_.to_frame().index.year,\n", + " mcl_mod.mack_summary_.to_frame()[__fill_in_code__],\n", " label=\"Reported\",\n", ")\n", "plt.bar(\n", - " mcl_mod.summary_.to_frame().index.year,\n", - " mcl_mod.summary_.to_frame()[__fill_in_code__],\n", - " bottom=mcl_mod.summary_.to_frame()[__fill_in_code__],\n", - " yerr=mcl_mod.summary_.to_frame()[__fill_in_code__],\n", + " mcl_mod.mack_summary_.to_frame().index.year,\n", + " mcl_mod.mack_summary_.to_frame()[__fill_in_code__],\n", + " bottom=mcl_mod.mack_summary_.to_frame()[__fill_in_code__],\n", + " yerr=mcl_mod.mack_summary_.to_frame()[__fill_in_code__],\n", " label=\"IBNR\",\n", ")\n", "plt.legend(loc=\"upper left\")" From 633a13cef3eff0514e8d0cec91365f194634d977 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Mon, 25 May 2026 22:56:52 -0700 Subject: [PATCH 05/17] Update plot_mack.ipynb --- docs/gallery/plot_mack.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gallery/plot_mack.ipynb b/docs/gallery/plot_mack.ipynb index 3c582451..ecfc3500 100644 --- a/docs/gallery/plot_mack.ipynb +++ b/docs/gallery/plot_mack.ipynb @@ -170,7 +170,7 @@ "dev = cl.Development(average='volume')\n", "mack.fit(dev.fit_transform(data))\n", "\n", - "plot_data = mack.summary_.to_frame(origin_as_datetime=False)\n", + "plot_data = mack.mack_summary_.to_frame(origin_as_datetime=False)\n", "plot_data" ] }, From a63d9f5c5a935a10df355128e967587921b8884f Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Mon, 25 May 2026 22:58:30 -0700 Subject: [PATCH 06/17] Update stochastic-tutorial.ipynb --- .../tutorials/stochastic-tutorial.ipynb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/getting_started/tutorials/stochastic-tutorial.ipynb b/docs/getting_started/tutorials/stochastic-tutorial.ipynb index 903ed71b..93a278e8 100644 --- a/docs/getting_started/tutorials/stochastic-tutorial.ipynb +++ b/docs/getting_started/tutorials/stochastic-tutorial.ipynb @@ -2008,7 +2008,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "With the `summary_` method, we can more easily look at the result of the `MackChainladder` model." + "With the `mack_summary_` method, we can more easily look at the result of the `MackChainladder` model." ] }, { @@ -2020,7 +2020,7 @@ } }, "source": [ - "mack.summary_" + "mack.mack_summary_" ], "outputs": [ { @@ -2148,15 +2148,15 @@ }, "source": [ "plt.bar(\n", - " mack.summary_.to_frame(origin_as_datetime=True).index.year,\n", - " mack.summary_.to_frame(origin_as_datetime=True)[\"Latest\"],\n", + " mack.mack_summary_.to_frame(origin_as_datetime=True).index.year,\n", + " mack.mack_summary_.to_frame(origin_as_datetime=True)[\"Latest\"],\n", " label=\"Paid\",\n", ")\n", "plt.bar(\n", - " mack.summary_.to_frame(origin_as_datetime=True).index.year,\n", - " mack.summary_.to_frame(origin_as_datetime=True)[\"IBNR\"],\n", - " bottom=mack.summary_.to_frame(origin_as_datetime=True)[\"Latest\"],\n", - " yerr=mack.summary_.to_frame(origin_as_datetime=True)[\"Mack Std Err\"],\n", + " mack.mack_summary_.to_frame(origin_as_datetime=True).index.year,\n", + " mack.mack_summary_.to_frame(origin_as_datetime=True)[\"IBNR\"],\n", + " bottom=mack.mack_summary_.to_frame(origin_as_datetime=True)[\"Latest\"],\n", + " yerr=mack.mack_summary_.to_frame(origin_as_datetime=True)[\"Mack Std Err\"],\n", " label=\"Reserves\",\n", ")\n", "plt.legend(loc=\"upper left\")\n", From 3c93489f347fbad62fb0c8d8e9271c5ac5afb756 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Mon, 25 May 2026 22:59:48 -0700 Subject: [PATCH 07/17] Update sandbox_workbook_filled.ipynb --- .../online_sandbox/sandbox_workbook_filled.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/getting_started/online_sandbox/sandbox_workbook_filled.ipynb b/docs/getting_started/online_sandbox/sandbox_workbook_filled.ipynb index 5f8b31f9..5ab12f08 100644 --- a/docs/getting_started/online_sandbox/sandbox_workbook_filled.ipynb +++ b/docs/getting_started/online_sandbox/sandbox_workbook_filled.ipynb @@ -4666,7 +4666,7 @@ "id": "bdb08c81-5921-4c41-ad63-96168ffd48b7", "metadata": {}, "source": [ - "MackChainladder also has a `summary_` attribute." + "MackChainladder also has a `mack_summary_` attribute." ] }, { @@ -4790,7 +4790,7 @@ } ], "source": [ - "mcl_mod.summary_" + "mcl_mod.mack_summary_" ] }, { From 19b6d31e3864385f401a6529f5672fbef0e66f18 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Mon, 25 May 2026 23:05:59 -0700 Subject: [PATCH 08/17] Update mack.py --- chainladder/methods/mack.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/chainladder/methods/mack.py b/chainladder/methods/mack.py index cfcf71e0..15845e07 100644 --- a/chainladder/methods/mack.py +++ b/chainladder/methods/mack.py @@ -27,8 +27,8 @@ class MackChainladder(Chainladder): full_triangle_: The ultimates back-filled to each development period in **X** retaining the known data - summary_: - summary of the model + mack_summary_: + summary of the mack model full_std_err_: The full standard error total_process_risk_: @@ -39,7 +39,9 @@ class MackChainladder(Chainladder): The total prediction error by origin period total_mack_std_err_: The total prediction error across all origin periods - + summary_: Pandas Dataframe + a summary exhibit that contains latest, ldf, cdf, and ultimate + Examples -------- Fit the Mack chainladder method and inspect the headline summary table, @@ -54,7 +56,7 @@ class MackChainladder(Chainladder): tr = cl.load_sample('ukmotor') model = cl.MackChainladder().fit(tr) - print(model.summary_) + print(model.mack_summary_) .. testoutput: @@ -401,7 +403,7 @@ def _get_total_mack_std_err_(self, obj): return pd.DataFrame(out, index=obj.index, columns=obj.columns) @property - def summary_(self): + def mack_summary_(self): """ Headline Mack summary table by origin: latest diagonal, IBNR, ultimate, and Mack standard error. @@ -423,7 +425,7 @@ def summary_(self): tr = cl.load_sample('ukmotor') model = cl.MackChainladder().fit(tr) - print(model.summary_) + print(model.mack_summary_) .. testoutput:: From 6863d05087635753da6911960e584908bdb050d0 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Mon, 25 May 2026 23:06:27 -0700 Subject: [PATCH 09/17] Update expectedloss.py --- chainladder/methods/expectedloss.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chainladder/methods/expectedloss.py b/chainladder/methods/expectedloss.py index e8a8de89..8d4ec67d 100644 --- a/chainladder/methods/expectedloss.py +++ b/chainladder/methods/expectedloss.py @@ -33,8 +33,8 @@ class ExpectedLoss(Benktander): The ultimate losses per the method ibnr_: Triangle The IBNR per the method - summary: Pandas Dataframe - a summary exhibit that contains actual, ldf, cdf, and ultimate + summary_: Pandas Dataframe + a summary exhibit that contains latest, ldf, cdf, and ultimate Examples -------- From 3d3ee6011d301655cd42756856a8b0cce409168e Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Mon, 25 May 2026 23:06:45 -0700 Subject: [PATCH 10/17] Update capecod.py --- chainladder/methods/capecod.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chainladder/methods/capecod.py b/chainladder/methods/capecod.py index 9b877518..db2364fb 100644 --- a/chainladder/methods/capecod.py +++ b/chainladder/methods/capecod.py @@ -54,8 +54,8 @@ class CapeCod(Benktander): The trended apriori vector developed by the Cape Cod Method detrended_apriori_: The detrended apriori vector developed by the Cape Cod Method - summary: Pandas Dataframe - a summary exhibit that contains actual, ldf, cdf, and ultimate + summary_: Pandas Dataframe + a summary exhibit that contains latest, ldf, cdf, and ultimate Examples -------- From 01be8fbc866a7b58e03b03300b2216aa2a883d1a Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Mon, 25 May 2026 23:07:04 -0700 Subject: [PATCH 11/17] Update bornferg.py --- chainladder/methods/bornferg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chainladder/methods/bornferg.py b/chainladder/methods/bornferg.py index 4b035711..a4c42c79 100644 --- a/chainladder/methods/bornferg.py +++ b/chainladder/methods/bornferg.py @@ -36,8 +36,8 @@ class BornhuetterFerguson(Benktander): The ultimate losses per the method ibnr_: Triangle The IBNR per the method - summary: Pandas Dataframe - a summary exhibit that contains actual, ldf, cdf, and ultimate + summary_: Pandas Dataframe + a summary exhibit that contains latest, ldf, cdf, and ultimate Examples -------- From 8aa4db417f351ef8ffe206f660f56a84ff57c58f Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Mon, 25 May 2026 23:07:39 -0700 Subject: [PATCH 12/17] Update chainladder.py --- chainladder/methods/chainladder.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chainladder/methods/chainladder.py b/chainladder/methods/chainladder.py index c80a2a32..48c90eec 100644 --- a/chainladder/methods/chainladder.py +++ b/chainladder/methods/chainladder.py @@ -26,8 +26,8 @@ class Chainladder(MethodBase): full_triangle_: The ultimates back-filled to each development period in **X** retaining the known data - summary: Pandas Dataframe - a summary exhibit that contains actual, ldf, cdf, and ultimate + summary_: Pandas Dataframe + a summary exhibit that contains latest, ldf, cdf, and ultimate Examples -------- @@ -190,7 +190,7 @@ def predict(self, X, sample_weight=None): """ X_new = super().predict(X, sample_weight) X_new.ultimate_ = self._get_ultimate(X_new, sample_weight) - X_new.summary = self._get_summary(X_new,X_new.ultimate_) + X_new.summary_ = self._get_summary(X_new,X_new.ultimate_) return X_new def _get_ultimate(self, X, sample_weight=None): From 5333439485c807698b486099bf275cf5e476eaf1 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Mon, 25 May 2026 23:08:12 -0700 Subject: [PATCH 13/17] Update benktander.py --- chainladder/methods/benktander.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chainladder/methods/benktander.py b/chainladder/methods/benktander.py index 0318f827..9a1da156 100644 --- a/chainladder/methods/benktander.py +++ b/chainladder/methods/benktander.py @@ -36,8 +36,8 @@ class Benktander(MethodBase): The ultimate losses per the method ibnr_: Triangle The IBNR per the method - summary: Pandas Dataframe - a summary exhibit that contains actual, ldf, cdf, and ultimate + summary_: Pandas Dataframe + a summary exhibit that contains latest, ldf, cdf, and ultimate Examples @@ -251,7 +251,7 @@ def predict(self, X, sample_weight=None): X_new = super().predict(X, sample_weight) X_new.expectation_ = self._get_benktander_aprioris(X, sample_weight) X_new.ultimate_ = self._get_ultimate(X_new, X_new.expectation_) - X_new.summary = self._get_summary(X_new,X_new.ultimate_) + X_new.summary_ = self._get_summary(X_new,X_new.ultimate_) X_new.n_iters = self.n_iters X_new.apriori = self.apriori return X_new From ca5f61e297129f3aee064489970e71bbf8560a8b Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Mon, 25 May 2026 23:09:52 -0700 Subject: [PATCH 14/17] Update base.py --- chainladder/methods/base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chainladder/methods/base.py b/chainladder/methods/base.py index 9eb6de58..23e0f1d3 100644 --- a/chainladder/methods/base.py +++ b/chainladder/methods/base.py @@ -66,16 +66,16 @@ def latest_diagonal(self): return self.X_.sum('development') @property - def summary(self): + def summary_(self): return self._get_summary(self.X_,self.ultimate_) def _get_summary(self,X,ult): #columns for melt ids = X.key_labels + ['origin','development','valuation'] #create dataframe for amount - amount = X.incr_to_cum().latest_diagonal.to_frame(implicit_axis=True,keepdims=True).reset_index().melt(id_vars=ids,var_name = 'column',value_name='actual') + amount = X.incr_to_cum().latest_diagonal.to_frame(implicit_axis=True,keepdims=True).reset_index().melt(id_vars=ids,var_name = 'column',value_name='latest') amount_index = X.key_labels + ['origin','column'] - amount = amount[amount_index + ['development','actual']] + amount = amount[amount_index + ['development','latest']] amount.set_index(amount_index,inplace=True) #create dataframe for ultimate ultimate = ult.to_frame(implicit_axis=True,keepdims=True).reset_index().melt(id_vars=ids,var_name = 'column',value_name='ultimate') @@ -95,7 +95,7 @@ def _get_summary(self,X,ult): output = output.join(amount,on=amount_index,how='left') output = output.join(ldf,on=dev_factor_index,how='left') output = output.join(cdf,on=dev_factor_index,how='left') - return output[X.key_labels + ['column','origin','development','actual','ldf','cdf','ultimate']] + return output[X.key_labels + ['column','origin','development','latest','ldf','cdf','ultimate']] def fit(self, X, y=None, sample_weight=None): """Applies the chainladder technique to triangle **X** From d13b0fad93f5cd133b71e41e0da28e9eefd3c11a Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Mon, 25 May 2026 23:10:20 -0700 Subject: [PATCH 15/17] Update test_mack.py --- chainladder/methods/tests/test_mack.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chainladder/methods/tests/test_mack.py b/chainladder/methods/tests/test_mack.py index c9adc020..1cb59111 100644 --- a/chainladder/methods/tests/test_mack.py +++ b/chainladder/methods/tests/test_mack.py @@ -8,10 +8,10 @@ def test_mack_to_triangle(): cl.Development().fit_transform(cl.load_sample("ABC")) ) ) - .summary_ + .mack_summary_ == cl.MackChainladder() .fit(cl.Development().fit_transform(cl.load_sample("ABC"))) - .summary_ + .mack_summary_ ) @@ -20,4 +20,4 @@ def test_mack_malformed(): b = a.iloc[:, :, :-1] x = cl.MackChainladder().fit(a) y = cl.MackChainladder().fit(b) - assert x.process_risk_.iloc[:,:,:-1] == y.process_risk_ \ No newline at end of file + assert x.process_risk_.iloc[:,:,:-1] == y.process_risk_ From 5d3c97d9514629256b61f390e74453b523b3441f Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Mon, 25 May 2026 23:11:26 -0700 Subject: [PATCH 16/17] Update test_predict.py --- chainladder/methods/tests/test_predict.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chainladder/methods/tests/test_predict.py b/chainladder/methods/tests/test_predict.py index dbf6b331..04f87a1a 100644 --- a/chainladder/methods/tests/test_predict.py +++ b/chainladder/methods/tests/test_predict.py @@ -23,8 +23,8 @@ def test_predict_and_weights(estimators,atol): est = estimators().fit(raa_1989, sample_weight=apriori_1989) pred = est.predict(raa, sample_weight=apriori) assert pred - assert np.all(abs(est.summary['actual'].values - raa_1989.latest_diagonal.values.reshape(-1)) < atol) - assert np.all(abs(pred.summary['actual'].values - raa.latest_diagonal.values.reshape(-1)) < atol) + assert np.all(abs(est.summary['latest'].values - raa_1989.latest_diagonal.values.reshape(-1)) < atol) + assert np.all(abs(pred.summary['latest'].values - raa.latest_diagonal.values.reshape(-1)) < atol) assert np.all(abs(est.summary['ultimate'].values - est.ultimate_.values.reshape(-1)) < atol) assert np.all(abs(pred.summary['ultimate'].values - pred.ultimate_.values.reshape(-1)) < atol) #Test validation of sample_weight requirement. Should raise a value error if no weight is supplied. @@ -186,4 +186,4 @@ def test_odd_shaped_triangle(): ) ult1 = cl.Chainladder().fit(cl.Development(average="volume").fit_transform(tr.grain("OYDQ"))).ultimate_.sum() ult2 = cl.Chainladder().fit(cl.Development(average="volume").fit_transform(tr)).ultimate_.grain("OYDQ").sum() - assert abs(ult1 - ult2) < 1e-5 \ No newline at end of file + assert abs(ult1 - ult2) < 1e-5 From 1c3db5c95e47c21c0679e9e34e9d869fdb18e7cc Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Mon, 25 May 2026 23:17:32 -0700 Subject: [PATCH 17/17] Update test_predict.py --- chainladder/methods/tests/test_predict.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/chainladder/methods/tests/test_predict.py b/chainladder/methods/tests/test_predict.py index 04f87a1a..cae9e5d8 100644 --- a/chainladder/methods/tests/test_predict.py +++ b/chainladder/methods/tests/test_predict.py @@ -23,16 +23,19 @@ def test_predict_and_weights(estimators,atol): est = estimators().fit(raa_1989, sample_weight=apriori_1989) pred = est.predict(raa, sample_weight=apriori) assert pred - assert np.all(abs(est.summary['latest'].values - raa_1989.latest_diagonal.values.reshape(-1)) < atol) - assert np.all(abs(pred.summary['latest'].values - raa.latest_diagonal.values.reshape(-1)) < atol) - assert np.all(abs(est.summary['ultimate'].values - est.ultimate_.values.reshape(-1)) < atol) - assert np.all(abs(pred.summary['ultimate'].values - pred.ultimate_.values.reshape(-1)) < atol) + assert np.all(abs(est.summary_['latest'].values - raa_1989.latest_diagonal.values.reshape(-1)) < atol) + assert np.all(abs(pred.summary_['latest'].values - raa.latest_diagonal.values.reshape(-1)) < atol) + assert np.all(abs(est.summary_['ultimate'].values - est.ultimate_.values.reshape(-1)) < atol) + assert np.all(abs(pred.summary_['ultimate'].values - pred.ultimate_.values.reshape(-1)) < atol) #Test validation of sample_weight requirement. Should raise a value error if no weight is supplied. if estimators != cl.Chainladder: with pytest.raises(ValueError): estimators().fit(raa_1989) with pytest.raises(ValueError): estimators().fit(raa_1989, sample_weight=apriori_1989).predict(raa) + else: + assert np.all(abs(est.summary_['ultimate'].values / est.summary_['latest'].values - est.summary_['cdf'].values) < atol) + assert np.all(abs(pred.summary_['ultimate'].values / pred.summary_['latest'].values - pred.summary_['cdf'].values) < atol) def test_mack_predict():