diff --git a/chainladder/adjustments/disposal.py b/chainladder/adjustments/disposal.py index 4c6be658..cd4e811d 100644 --- a/chainladder/adjustments/disposal.py +++ b/chainladder/adjustments/disposal.py @@ -1,13 +1,19 @@ # This Source Code Form is subject to the terms of the Mozilla Public # 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/. +from __future__ import annotations -from chainladder.methods import Chainladder +from chainladder.methods import Chainladder, MethodBase from chainladder.development import DevelopmentBase import numpy as np import copy from chainladder.utils import TriangleWeight, concat -from chainladder import Triangle + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from chainladder.core import Triangle + class DisposalRate(DevelopmentBase): """ @@ -192,18 +198,22 @@ def fit( """ if sample_weight is None: raise ValueError("sample_weight is required.") - #convert to numpy + #validate dimensions of sample weight + MethodBase().validate_weight(X, sample_weight) + #set backeneds to numpy if X.array_backend == "sparse": - X = X.set_backend("numpy").incr_to_cum() + X = X.set_backend("numpy") else: - X = X.copy().incr_to_cum() + X = X.copy() if sample_weight.array_backend == "sparse": ult = sample_weight.set_backend("numpy") else: ult = sample_weight.copy() - #get backend + #calculate disposal rate triangle self.xp = X.get_array_module() + self.X_ = X.sort_index() self.disposal_rate_tri = X / ult.values + #get weights for estimation tw = TriangleWeight( n_periods = self.n_periods, drop_high = self.drop_high, @@ -214,16 +224,17 @@ def fit( preserve = self.preserve, drop = self.drop ) - if hasattr(X, "w_"): - self.w_ = tw.fit(X=self.disposal_rate_tri * X.w_).w_.values + if hasattr(self.X_, "w_"): + self.w_ = tw.fit(X=self.disposal_rate_tri * self.X_.w_).w_.values else: self.w_ = tw.fit(X=self.disposal_rate_tri).w_.values #calculate factors - super().fit(ult.values,X.values,self.w_) + super().fit(ult.values,self.X_.values,self.w_) #keep attributes self.disposal_ = self._param_property(self.disposal_rate_tri,self.params_.slope_[...,0][..., None, :]) - self.disposal_ = concat((self.disposal_,(X.latest_diagonal*0 + 1).iloc[:,:,0,:].rename("development", [9999])),axis=3) + self.disposal_ = concat((self.disposal_,(self.X_.latest_diagonal*0 + 1).iloc[:,:,0,:].rename("development", [9999])),axis=3) self.disposal_.is_cumulative = True + #pattern multiples from tail and additive adds from head self.disposal_.is_pattern = False self.incr_disposal_ = self.disposal_.cum_to_incr() self.incr_disposal_.is_pattern = True @@ -253,10 +264,13 @@ def transform( if sample_weight is None: raise ValueError("sample_weight is required.") X_new = copy.deepcopy(X) + #validate dimensions of sample weight + MethodBase().validate_weight(X, sample_weight) + #align backeneds + X_new.ultimate_ = sample_weight.set_backend(self.X_.array_backend).latest_diagonal X_new.disposal_rate_tri = self.disposal_rate_tri X_new.disposal_ = self.disposal_ X_new.incr_disposal_ = self.incr_disposal_ - X_new.ultimate_ = sample_weight.latest_diagonal ibnr_pct = 1 - X_new.disposal_.align_pattern(X_new.disposal_rate_tri) run_off = X_new.incr_disposal_ / ibnr_pct * X_new.ibnr_ run_off = run_off[run_off.valuation > X_new.valuation_date] diff --git a/chainladder/methods/base.py b/chainladder/methods/base.py index 7ad3117e..6fcf0537 100644 --- a/chainladder/methods/base.py +++ b/chainladder/methods/base.py @@ -1,6 +1,8 @@ # This Source Code Form is subject to the terms of the Mozilla Public # 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/. +from __future__ import annotations + import numpy as np import pandas as pd import warnings @@ -10,6 +12,10 @@ from chainladder.core.io import EstimatorIO from chainladder.core.common import Common +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from chainladder.core import Triangle class MethodBase(BaseEstimator, EstimatorIO, Common): @@ -138,7 +144,14 @@ def _include_process_variance(self): process_var = None return process_var - def validate_weight(self, X, sample_weight): + @staticmethod + def validate_weight( + X: Triangle, + sample_weight: Triangle + ) -> None: + ''' + Checks that the a aprior has valid dimensions + ''' if ( sample_weight and X.shape[:-1] != sample_weight.shape[:-1]