diff --git a/chainladder/development/constant.py b/chainladder/development/constant.py index e223c25b..b423c109 100644 --- a/chainladder/development/constant.py +++ b/chainladder/development/constant.py @@ -3,6 +3,7 @@ # file, You can obtain one at https://mozilla.org/MPL/2.0/. from chainladder.development.base import DevelopmentBase import pandas as pd +import numpy as np class DevelopmentConstant(DevelopmentBase): @@ -61,7 +62,7 @@ def fit(self, X, y=None, sample_weight=None): obj = obj.iloc[..., :1, :-1]*0+1 if callable(self.patterns): if self.callable_axis == 0: - ldf = obj.index.apply(self.patterns, axis=1) + ldf = obj.index.apply(self.patterns, axis=1) ldf = ( pd.concat(ldf.apply(pd.DataFrame, index=[0]).values, axis=0) .fillna(1)[obj.ddims].values) @@ -75,7 +76,11 @@ def fit(self, X, y=None, sample_weight=None): else: raise ValueError('callable axis needs to be 0 or 1') else: - ldf = xp.array([float(self.patterns[item]) for item in obj.ddims]) + extra_dims = [x for x in self.patterns.keys() if x > np.max(obj.ddims)] + if extra_dims: + obj.values = xp.concatenate([obj.values]+[obj.iloc[...,-1:].values]*len(extra_dims), -1) + obj.ddims = np.concatenate((obj.ddims, extra_dims), 0,) + ldf = xp.array([float(self.patterns.get(item,1)) for item in obj.ddims]) ldf = ldf[None, None, None, :] if self.style == "cdf": ldf = xp.concatenate((ldf[..., :-1] / ldf[..., 1:], ldf[..., -1:]), -1) diff --git a/chainladder/development/tests/test_constant.py b/chainladder/development/tests/test_constant.py index 1c84fc41..fc541c34 100644 --- a/chainladder/development/tests/test_constant.py +++ b/chainladder/development/tests/test_constant.py @@ -55,4 +55,245 @@ def paid_cdfs(x): with pytest.raises(ValueError): xerror = cl.DevelopmentConstant(patterns=paid_cdfs, callable_axis=2, style='cdf').fit(agway) lhs = cl.DevelopmentConstant(patterns=paid_cdfs, callable_axis=1, style='cdf').fit(agway).cdf_ - assert np.all(abs(lhs.values[0,:,0,:]-patterns.values[:,:-1]) < atol) \ No newline at end of file + assert np.all(abs(lhs.values[0,:,0,:]-patterns.values[:,:-1]) < atol) + +def test_constant_pattern_no_tail(): + reported_patterns = { + 12: 4.0, + 24: 2.9, + 36: 1.8, + 48: 1.4, + 60: 1.2, + 72: 1.1, + 84: 1.03, + 96: 1.02, + # 108: 1.005, + } + auto_bi = cl.load_sample("friedland_auto_bi_insurer") + reported_BI_claim = cl.DevelopmentConstant( + patterns=reported_patterns, style="cdf" + ).fit_transform(auto_bi["Reported Claims"]) + + assert np.all( + np.round(reported_BI_claim.cdf_.to_frame().values.flatten(), 6) + == np.array([4.0, 2.9, 1.8, 1.4, 1.2, 1.1, 1.03, 1.02]) + ) + + +def test_constant_pattern_has_tail(): + reported_patterns = { + 12: 4.0, + 24: 2.9, + 36: 1.8, + 48: 1.4, + 60: 1.2, + 72: 1.1, + 84: 1.03, + 96: 1.02, + 108: 1.005, + } + auto_bi = cl.load_sample("friedland_auto_bi_insurer") + reported_BI_claim = cl.DevelopmentConstant( + patterns=reported_patterns, style="cdf" + ).fit_transform(auto_bi["Reported Claims"]) + + assert np.all( + np.round(reported_BI_claim.cdf_.to_frame().values.flatten(), 6) + == np.array([4.0, 2.9, 1.8, 1.4, 1.2, 1.1, 1.03, 1.02, 1.005]) + ) + + +def test_constant_pattern_exact_cdf(raa): + reported_patterns = { + 12: 1.1, + 24: 1.1, + 36: 1.1, + 48: 1.1, + 60: 1.1, + 72: 1.1, + 84: 1.1, + 96: 1.1, + 108: 1.1, + 120: 1.1, + } + + result = cl.DevelopmentConstant( + patterns=reported_patterns, style="cdf" + ).fit_transform(raa) + + assert np.all( + np.round(result.cdf_.to_frame().values.flatten(), 6) + == np.array([1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1]) + ) + + +def test_constant_pattern_exact_ldf(raa): + reported_patterns = { + 12: 1.1, + 24: 1.1, + 36: 1.1, + 48: 1.1, + 60: 1.1, + 72: 1.1, + 84: 1.1, + 96: 1.1, + 108: 1.1, + 120: 1.1, + } + + result = cl.DevelopmentConstant( + patterns=reported_patterns, style="ldf" + ).fit_transform(raa) + + assert np.all( + np.round(result.cdf_.to_frame().values.flatten(), 6) + == np.array( + [ + 2.593742, + 2.357948, + 2.143589, + 1.948717, + 1.771561, + 1.61051, + 1.4641, + 1.331, + 1.21, + 1.1, + ] + ) + ) + + +def test_constant_pattern_short_cdf(raa): + reported_patterns = { + 12: 1.1, + 24: 1.1, + 36: 1.1, + 48: 1.1, + 60: 1.1, + 72: 1.1, + # 84: 1.1, + # 96: 1.1, + # 108: 1.1, + # 120: 1.1, + } + + result = cl.DevelopmentConstant( + patterns=reported_patterns, style="cdf" + ).fit_transform(raa) + + assert np.all( + np.round(result.cdf_.to_frame().values.flatten(), 6) + == np.array([1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.0, 1.0, 1.0]) + ) + + +def test_constant_pattern_short_ldf(raa): + reported_patterns = { + 12: 1.1, + 24: 1.1, + 36: 1.1, + 48: 1.1, + 60: 1.1, + 72: 1.1, + # 84: 1.1, + # 96: 1.1, + # 108: 1.1, + # 120: 1.1, + } + + result = cl.DevelopmentConstant( + patterns=reported_patterns, style="ldf" + ).fit_transform(raa) + + assert np.all( + np.round(result.cdf_.to_frame().values.flatten(), 6) + == np.array([1.771561, 1.61051, 1.4641, 1.331, 1.21, 1.1, 1.0, 1.0, 1.0]) + ) + + +def test_constant_pattern_long_cdf(raa): + reported_patterns = { + 12: 1.1, + 24: 1.1, + 36: 1.1, + 48: 1.1, + 60: 1.1, + 72: 1.1, + 84: 1.1, + 96: 1.1, + 108: 1.1, + 120: 1.1, + 132: 1.1, + } + + result = cl.DevelopmentConstant( + patterns=reported_patterns, style="cdf" + ).fit_transform(raa) + assert np.all( + np.round(result.cdf_.to_frame().values.flatten(), 6) + == np.array([1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1]) + ) + + +def test_constant_pattern_long_ldf(raa): + reported_patterns = { + 12: 1.1, + 24: 1.1, + 36: 1.1, + 48: 1.1, + 60: 1.1, + 72: 1.1, + 84: 1.1, + 96: 1.1, + 108: 1.1, + 120: 1.1, + 132: 1.1, + } + + result = cl.DevelopmentConstant( + patterns=reported_patterns, style="ldf" + ).fit_transform(raa) + + assert np.all( + np.round(result.cdf_.to_frame().values.flatten(), 6) + == np.array( + [ + 2.853117, + 2.593742, + 2.357948, + 2.143589, + 1.948717, + 1.771561, + 1.61051, + 1.4641, + 1.331, + 1.21, + 1.1 + ] + ) + ) + + +def test_constant_incr(): + raa_incr = cl.load_sample("raa").cum_to_incr() + reported_patterns = { + 12: 4.0, + 24: 2.9, + 36: 1.8, + 48: 1.4, + 60: 1.2, + 72: 1.1, + 84: 1.03, + 96: 1.02, + 108: 1.005, + } + + result = cl.DevelopmentConstant( + patterns=reported_patterns, style="cdf" + ).fit_transform(raa_incr) + + assert np.all( + np.round(result.cdf_.to_frame().values.flatten(), 6) + == np.array([4.0, 2.9, 1.8, 1.4, 1.2, 1.1, 1.03, 1.02, 1.005]) + ) \ No newline at end of file