diff --git a/chainladder/methods/capecod.py b/chainladder/methods/capecod.py index 4eebf830..c3b1fb9d 100644 --- a/chainladder/methods/capecod.py +++ b/chainladder/methods/capecod.py @@ -2,7 +2,6 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. import warnings -import numpy as np from chainladder.methods import Benktander @@ -322,7 +321,7 @@ def predict(self, X, sample_weight=None): X_new = X.copy() _, X_new.ldf_ = self.intersection(X_new, self.ldf_) # If model was fit at a higher grain, then need to aggregate predicted aprioris too - if len(set(sample_weight.key_labels) - set(self.apriori_.key_labels)) > 1: + if len(set(sample_weight.key_labels) - set(self.apriori_.key_labels)) > 0: apriori_, detrended_apriori_ = self._get_capecod_aprioris( X_new.groupby(self.apriori_.key_labels).sum(), sample_weight.groupby(self.apriori_.key_labels).sum()) diff --git a/chainladder/methods/tests/test_capecod.py b/chainladder/methods/tests/test_capecod.py index 55e05f96..d7744aff 100644 --- a/chainladder/methods/tests/test_capecod.py +++ b/chainladder/methods/tests/test_capecod.py @@ -14,7 +14,7 @@ def test_struhuss(): def test_groupby(clrd): - clrd = clrd[clrd['LOB']=='comauto'] + clrd = clrd[clrd['LOB'] == 'comauto'] # But only the top 10 get their own CapeCod aprioris. Smaller companies get grouped together top_10 = clrd['EarnedPremDIR'].groupby('GRNAME').sum().latest_diagonal top_10 = top_10.loc[..., '1997', :].to_frame(origin_as_datetime=True).nlargest(10) @@ -25,7 +25,7 @@ def test_groupby(clrd): # All companies share the same development factors regardless of size X = cl.Development().fit(clrd['CumPaidLoss'].sum()).transform(clrd['CumPaidLoss']) - sample_weight=clrd['EarnedPremDIR'].latest_diagonal + sample_weight = clrd['EarnedPremDIR'].latest_diagonal a = cl.CapeCod(groupby='Top 10', decay=0.98, trend=0.02).fit(X, sample_weight=sample_weight).ibnr_.groupby('Top 10').sum().sort_index() b = cl.CapeCod(decay=0.98, trend=0.02).fit(X.groupby('Top 10').sum(), sample_weight=sample_weight.groupby('Top 10').sum()).ibnr_.sort_index() xp = a.get_array_module() @@ -35,8 +35,8 @@ def test_groupby(clrd): def test_capecod_zero_tri(raa): premium = raa.latest_diagonal * 0 + 50000 - raa.at['Total','values','1987',48] = 0 - assert cl.CapeCod().fit(raa, sample_weight=premium).ultimate_.loc[:,:,'1987'].sum() > 0 + raa.at['Total', 'values', '1987', 48] = 0 + assert cl.CapeCod().fit(raa, sample_weight=premium).ultimate_.loc[:, :, '1987'].sum() > 0 def test_capecod_predict1(prism): @@ -83,4 +83,25 @@ def test_capecod_predict2(prism): pred1 = pipe1.named_steps.model.ultimate_.sum() pred2 = pipe2.predict(prism['Paid'], sample_weight=prism['reportedCount'].sum('development')).ultimate_.sum() - assert np.nan_to_num(abs(pred1 - pred2).values).sum() <= 1e-6 \ No newline at end of file + assert np.nan_to_num(abs(pred1 - pred2).values).sum() <= 1e-6 + + +def test_capecod_predict_one_extra_index_level(clrd): + """github issue #1265 + + predict() aggregates the prediction data up to the grain the model was fit + at. test_capecod_predict2 covers that path with prism, whose triangle has + five index levels more than the fitted model. This covers the case of a + single extra level, which clrd gives. + """ + tri = clrd["CumPaidLoss"] + sample_weight = clrd["EarnedPremDIR"].latest_diagonal + + model = cl.CapeCod().fit( + tri.groupby("LOB").sum(), sample_weight=sample_weight.groupby("LOB").sum() + ) + pred = model.predict(tri, sample_weight=sample_weight) + + assert set(sample_weight.key_labels) - set(model.apriori_.key_labels) == {"GRNAME"} + assert np.allclose(pred.apriori_.values, model.apriori_.values) + assert abs(pred.ultimate_.sum().sum() - model.ultimate_.sum().sum()) < 1e-6 diff --git a/pyproject.toml b/pyproject.toml index 8094fbe7..77985eb1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -136,11 +136,9 @@ select = ["E2", "E4", "E7", "E9", "F", "B018", "UP034", "N802"] "chainladder/development/tests/test_glm.py" = ["E225", "E226", "E231"] "chainladder/development/tests/test_incremental.py" = ["F841"] "chainladder/methods/base.py" = ["E231", "F401", "N802"] -"chainladder/methods/capecod.py" = ["F401"] "chainladder/methods/mack.py" = ["E265", "F401"] "chainladder/methods/tests/rtest_mack.py" = ["E266", "E722", "F401"] "chainladder/methods/tests/test_benktander.py" = ["E231", "E251"] -"chainladder/methods/tests/test_capecod.py" = ["E225", "E231"] "chainladder/methods/tests/test_mack.py" = ["E226", "E231", "E251", "E265"] "chainladder/methods/tests/test_predict.py" = ["E231", "E265"] "chainladder/tails/base.py" = ["E226", "E251"]