diff --git a/chainladder/workflow/tests/test_voting.py b/chainladder/workflow/tests/test_voting.py index cda3672fa..c748daeb1 100644 --- a/chainladder/workflow/tests/test_voting.py +++ b/chainladder/workflow/tests/test_voting.py @@ -9,6 +9,7 @@ if TYPE_CHECKING: from chainladder import Triangle + @pytest.fixture def triangle_data(): clrd = cl.load_sample("clrd")[["CumPaidLoss", "EarnedPremDIR"]] @@ -31,11 +32,14 @@ def estimators(): list_weight = [[[1, 2, 3]] * 4 + [[0, 0.5, 0.5]] * 3 + [[0, 0, 1]] * 3] -callable_weight = lambda origin: np.where( - origin.year < 1992, - (1, 2, 3), - np.where(origin.year > 1994, (0, 0, 1), (0, 0.5, 0.5)), -) + +def callable_weight(origin): + return np.where( + origin.year < 1992, + (1, 2, 3), + np.where(origin.year > 1994, (0, 0, 1), (0, 0.5, 0.5)), + ) + dict_weight = { "1992": (0, 0.5, 0.5), @@ -54,14 +58,16 @@ def weights(request): def test_voting_ultimate(triangle_data, estimators, weights): bcl_ult = ( - cl.Chainladder() + cl + .Chainladder() .fit( triangle_data["CumPaidLoss"].sum(), ) .ultimate_ ) bf_ult = ( - cl.BornhuetterFerguson() + cl + .BornhuetterFerguson() .fit( triangle_data["CumPaidLoss"].sum(), sample_weight=triangle_data["EarnedPremDIR"].sum().latest_diagonal, @@ -69,7 +75,8 @@ def test_voting_ultimate(triangle_data, estimators, weights): .ultimate_ ) cc_ult = ( - cl.CapeCod() + cl + .CapeCod() .fit( triangle_data["CumPaidLoss"].sum(), sample_weight=triangle_data["EarnedPremDIR"].sum().latest_diagonal, @@ -78,7 +85,8 @@ def test_voting_ultimate(triangle_data, estimators, weights): ) vot_ult = ( - cl.VotingChainladder( + cl + .VotingChainladder( estimators=estimators, weights=weights, default_weighting=(1, 2, 3) ) .fit( @@ -118,15 +126,13 @@ def test_different_backends(triangle_data, estimators, weights): ) assert ( abs( - ( - model.predict( - triangle_data["CumPaidLoss"].sum().set_backend("sparse"), - sample_weight=triangle_data["EarnedPremDIR"] - .sum() - .latest_diagonal.set_backend("sparse"), - ).ultimate_.sum() - - model.ultimate_.sum() - ) + model.predict( + triangle_data["CumPaidLoss"].sum().set_backend("sparse"), + sample_weight=triangle_data["EarnedPremDIR"] + .sum() + .latest_diagonal.set_backend("sparse"), + ).ultimate_.sum() + - model.ultimate_.sum() ) < 1 ) @@ -139,7 +145,8 @@ def test_weight_broadcasting(triangle_data, estimators, weights): max_dim_weights = np.array(mid_dim_weights * 132) min_dim_ult = ( - cl.VotingChainladder(estimators=estimators, weights=weights) + cl + .VotingChainladder(estimators=estimators, weights=weights) .fit( triangle_data["CumPaidLoss"], sample_weight=triangle_data["EarnedPremDIR"].latest_diagonal, @@ -147,7 +154,8 @@ def test_weight_broadcasting(triangle_data, estimators, weights): .ultimate_.sum() ) mid_dim_ult = ( - cl.VotingChainladder(estimators=estimators, weights=mid_dim_weights) + cl + .VotingChainladder(estimators=estimators, weights=mid_dim_weights) .fit( triangle_data["CumPaidLoss"], sample_weight=triangle_data["EarnedPremDIR"].latest_diagonal, @@ -155,7 +163,8 @@ def test_weight_broadcasting(triangle_data, estimators, weights): .ultimate_.sum() ) max_dim_ult = ( - cl.VotingChainladder(estimators=estimators, weights=max_dim_weights) + cl + .VotingChainladder(estimators=estimators, weights=max_dim_weights) .fit( triangle_data["CumPaidLoss"], sample_weight=triangle_data["EarnedPremDIR"].latest_diagonal, @@ -191,34 +200,31 @@ def test_voting(raa): ] ).all() -def test_tri_sel(clrd:Triangle) -> None: - ''' + +def test_tri_sel(clrd: Triangle) -> None: + """ starter test for the TriangleSelector class - ''' + """ tri = clrd.sum() - assert tri['CumPaidLoss'] == cl.TriangleSelector('CumPaidLoss').fit_transform(tri) + assert tri["CumPaidLoss"] == cl.TriangleSelector("CumPaidLoss").fit_transform(tri) + -def test_mismatching_tri_sel(clrd:Triangle) -> None: - ''' +def test_mismatching_tri_sel(clrd: Triangle) -> None: + """ checking that an error is raised when different number of columns are specified by estimators in a VotingChainladder - ''' - tri = clrd.groupby('LOB').sum().loc['othliab'] + """ + tri = clrd.groupby("LOB").sum().loc["othliab"] pipe_p = cl.Pipeline( steps=[ - ('tri_sel', cl.TriangleSelector('CumPaidLoss')), - ('dev', cl.Development()), - ('model', cl.Chainladder()) - ] - ) - pipe_i = cl.Pipeline( - steps=[ - ('dev', cl.Development()), - ('model', cl.Chainladder()) + ("tri_sel", cl.TriangleSelector("CumPaidLoss")), + ("dev", cl.Development()), + ("model", cl.Chainladder()), ] ) + pipe_i = cl.Pipeline(steps=[("dev", cl.Development()), ("model", cl.Chainladder())]) - estimators = [('incurred', pipe_i), ('paid', pipe_p)] + estimators = [("incurred", pipe_i), ("paid", pipe_p)] weights = np.array([[0.5, 0.5]] * 4 + [[0.75, 0.25]] * 3 + [[1, 0]] * 3) vot = cl.VotingChainladder(estimators=estimators, weights=weights) with pytest.raises(ValueError): - vot.fit(tri) \ No newline at end of file + vot.fit(tri) diff --git a/chainladder/workflow/tests/test_workflow.py b/chainladder/workflow/tests/test_workflow.py index 5d8fb1bbb..31dbc6027 100644 --- a/chainladder/workflow/tests/test_workflow.py +++ b/chainladder/workflow/tests/test_workflow.py @@ -1,6 +1,7 @@ import chainladder as cl import pytest + def test_grid(clrd): # Load Data medmal_paid = clrd.groupby("LOB").sum().loc["medmal"]["CumPaidLoss"] @@ -25,7 +26,8 @@ def test_grid(clrd): grid.fit(medmal_paid, benk__sample_weight=medmal_prem) assert ( grid.results_["IBNR"][0] - == cl.Benktander(n_iters=250, apriori=1) + == cl + .Benktander(n_iters=250, apriori=1) .fit( cl.TailCurve().fit_transform(cl.Development().fit_transform(medmal_paid)), sample_weight=medmal_prem, @@ -36,23 +38,34 @@ def test_grid(clrd): @pytest.fixture def tri(clrd): - tri = clrd.groupby('LOB').sum()[['CumPaidLoss', 'IncurLoss', 'EarnedPremDIR']] - tri['CaseIncurredLoss'] = tri['IncurLoss'] - tri['CumPaidLoss'] + tri = clrd.groupby("LOB").sum()[["CumPaidLoss", "IncurLoss", "EarnedPremDIR"]] + tri["CaseIncurredLoss"] = tri["IncurLoss"] - tri["CumPaidLoss"] return tri -dev = [cl.Development, cl.ClarkLDF, cl.Trend, cl.IncrementalAdditive, - lambda : cl.MunichAdjustment(paid_to_incurred=('CumPaidLoss', 'CaseIncurredLoss')), - lambda :cl.CaseOutstanding(paid_to_incurred=('CumPaidLoss', 'CaseIncurredLoss'))] + +dev = [ + cl.Development, + cl.ClarkLDF, + cl.Trend, + cl.IncrementalAdditive, + lambda: cl.MunichAdjustment(paid_to_incurred=("CumPaidLoss", "CaseIncurredLoss")), + lambda: cl.CaseOutstanding(paid_to_incurred=("CumPaidLoss", "CaseIncurredLoss")), +] tail = [cl.TailCurve, cl.TailConstant, cl.TailBondy, cl.TailClark] -ibnr = [cl.Chainladder, cl.BornhuetterFerguson, - lambda : cl.Benktander(n_iters=2), cl.CapeCod] +ibnr = [ + cl.Chainladder, + cl.BornhuetterFerguson, + lambda: cl.Benktander(n_iters=2), + cl.CapeCod, +] + -@pytest.mark.parametrize('dev', dev) -@pytest.mark.parametrize('tail', tail) -@pytest.mark.parametrize('ibnr', ibnr) +@pytest.mark.parametrize("dev", dev) +@pytest.mark.parametrize("tail", tail) +@pytest.mark.parametrize("ibnr", ibnr) def test_pipeline(tri, dev, tail, ibnr): - X = tri[['CumPaidLoss', 'CaseIncurredLoss']] - sample_weight = tri['EarnedPremDIR'].latest_diagonal - cl.Pipeline( - steps=[('dev', dev()), ('tail', tail()), ('ibnr', ibnr())] - ).fit_predict(X, sample_weight=sample_weight).ibnr_.sum('origin').sum('columns').sum() + X = tri[["CumPaidLoss", "CaseIncurredLoss"]] + sample_weight = tri["EarnedPremDIR"].latest_diagonal + cl.Pipeline(steps=[("dev", dev()), ("tail", tail()), ("ibnr", ibnr())]).fit_predict( + X, sample_weight=sample_weight + ).ibnr_.sum("origin").sum("columns").sum() diff --git a/chainladder/workflow/voting.py b/chainladder/workflow/voting.py index bd0d8f870..95e3cdc53 100644 --- a/chainladder/workflow/voting.py +++ b/chainladder/workflow/voting.py @@ -11,7 +11,7 @@ from sklearn.base import ( BaseEstimator, clone, - TransformerMixin + TransformerMixin, ) from sklearn.ensemble._base import _fit_single_estimator, _BaseHeterogeneousEnsemble from sklearn.ensemble._voting import _BaseVoting @@ -417,8 +417,10 @@ def _get_ultimate(self, X, sample_weight=None): f" and {X.shape[1]} for X." ) weights = self.weights_ - - ultimates = [est.predict(X, sample_weight).ultimate_ for est in self.estimators_] + + ultimates = [ + est.predict(X, sample_weight).ultimate_ for est in self.estimators_ + ] shape_check = list(set([ult.shape for ult in ultimates])) if len(shape_check) > 1: @@ -426,16 +428,15 @@ def _get_ultimate(self, X, sample_weight=None): "Estimators returning ultimate_ of different shapes," "likely due to a mis-specified TriangleSelector transformer in the pipeline" ) - #weights are broadcasted to the shape of X. However ultimate_ does not always take the shape of X - #use shape_check to redim weights - ultimate = sum( - [ - ultimates[i] * weights[...,:shape_check[0][1], :, i, :] - for i, _ in enumerate(ultimates) - ] - ) / weights[...,:shape_check[0][1], :, :, :].sum(axis=-2) + # weights are broadcasted to the shape of X. However ultimate_ does not always take the shape of X + # use shape_check to redim weights + ultimate = sum([ + ultimates[i] * weights[..., : shape_check[0][1], :, i, :] + for i, _ in enumerate(ultimates) + ]) / weights[..., : shape_check[0][1], :, :, :].sum(axis=-2) return ultimate + class TriangleSelector( BaseEstimator, TransformerMixin, @@ -454,7 +455,7 @@ class TriangleSelector( Examples -------- Actuaries commonly uses both incurred and paid losses, or both reported and closed counts for estimating ultimate loss or ultimate count. We can use this helper class to create a singular VotingChainladder pipeline that weighs between incurred/paid methods, or reported/closed methods. - + .. testsetup:: import chainladder as cl @@ -497,11 +498,12 @@ class TriangleSelector( 1996 672721.951809 1997 664061.404455 """ - def __init__(self, col:str): + + def __init__(self, col: str): self.col = col - def fit(self, X:Triangle, y:None=None): + def fit(self, X: Triangle, y: None = None): return self - def transform(self, X:Triangle): - return X[[self.col]] \ No newline at end of file + def transform(self, X: Triangle): + return X[[self.col]] diff --git a/pyproject.toml b/pyproject.toml index dc203be0a..7651cddfb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -125,9 +125,6 @@ select = ["E2", "E4", "E7", "E9", "F", "B018", "UP034", "N802"] "chainladder/development/tests/rtest_munich.py" = ["E266", "E722", "F401"] "chainladder/methods/tests/rtest_mack.py" = ["E266", "E722", "F401"] "chainladder/tails/tests/rtest_exponential.py" = ["E722", "F401"] -"chainladder/workflow/tests/test_voting.py" = ["E231", "E731", "UP034"] -"chainladder/workflow/tests/test_workflow.py" = ["E203", "E241"] -"chainladder/workflow/voting.py" = ["E231", "E252", "E265"] "docs/friedland/chapter_10.ipynb" = ["E731", "F841"] "docs/friedland/chapter_7_part_2.ipynb" = ["N802"] "docs/friedland/chapter_9.ipynb" = ["E731"]