From e7744c55139679a9b15b98070a4d5c290d345379 Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Wed, 10 Apr 2024 20:57:18 -0700 Subject: [PATCH] Began work on trendconstant --- chainladder/adjustments/trend.py | 103 +++++++++++++++++++++++++++++-- chainladder/core/triangle.py | 31 +++++++--- 2 files changed, 120 insertions(+), 14 deletions(-) diff --git a/chainladder/adjustments/trend.py b/chainladder/adjustments/trend.py index b54e7e9d..1c5367fe 100644 --- a/chainladder/adjustments/trend.py +++ b/chainladder/adjustments/trend.py @@ -57,17 +57,112 @@ def fit(self, X, y=None, sample_weight=None): dates = [dates] if type(dates) is not list else dates if type(dates[0]) is not tuple: raise AttributeError( - 'Dates must be specified as a tuple of start and end dates') + "Dates must be specified as a tuple of start and end dates" + ) self.trend_ = X.copy() for i, trend in enumerate(trends): self.trend_ = self.trend_.trend( - trend, self.axis, - start=dates[i][0], end=dates[i][1]) + trend, self.axis, start=dates[i][0], end=dates[i][1] + ) self.trend_ = self.trend_ / X return self def transform(self, X, y=None, sample_weight=None): - """ If X and self are of different shapes, align self to X, else + """If X and self are of different shapes, align self to X, else + return self. + + Parameters + ---------- + X: Triangle + The triangle to be transformed + + Returns + ------- + X_new: New triangle with transformed attributes. + """ + X_new = X.copy() + triangles = ["trend_"] + for item in triangles: + setattr(X_new, item, getattr(self, item)) + X_new._set_slicers() + return X_new + + +class TrendConstant(BaseEstimator, TransformerMixin, EstimatorIO): + """ + Estimator to create and apply trend factors to a Triangle object. Allows + for compound trends as well as storage of the trend matrix to be used in + other estimators. + + Parameters + ---------- + + trends: list-like + The list containing the annual trends expressed as a decimal. For example, + 5% decrease should be stated as -0.05 + effective_date: list of date-likes + A list-like of (start, end) dates to correspond to the `trend` list. + axis: str (options: [‘origin’, ‘valuation’]) + The axis on which to apply the trend + + Attributes + ---------- + + trend_: + A triangle representation of the trend factors + + """ + + def __init__(self, trends=0.0, effective_date=None, axis="origin"): + self.trends = trends + self.effective_date = effective_date + self.axis = axis + + def fit(self, X, y=None, sample_weight=None): + """Fit the model with X. + + Parameters + ---------- + X: Triangle-like + Data to which the model will be applied. + y: Ignored + sample_weight: Ignored + + Returns + ------- + self: object + Returns the instance itself. + """ + if type(self.trends): + trend_list = self.trends + else: + trend_list = [self.trends] + + if self.dates is None: + dates = [(None, None)] + else: + dates = self.dates + + if type(dates) is not list: + dates = [dates] + else: + dates = dates + + if type(dates[0]) is not tuple: + raise AttributeError( + "Dates must be specified as a tuple of start and end dates" + ) + + self.trend_ = X.copy() + for i, trend in enumerate(trend_list): + self.trend_ = self.trend_.trend( + trend, self.axis, start=dates[i][0], end=dates[i][1] + ) + self.trend_ = self.trend_ / X + return self + + def transform(self, X, y=None, sample_weight=None): + """If X and self are of different shapes, align self to X, else return self. Parameters diff --git a/chainladder/core/triangle.py b/chainladder/core/triangle.py index 9b6ad873..1be73ab9 100644 --- a/chainladder/core/triangle.py +++ b/chainladder/core/triangle.py @@ -125,7 +125,7 @@ def __init__( return elif not isinstance(data, pd.DataFrame) and hasattr(data, "__dataframe__"): data = self._interchange_dataframe(data) - + index, columns, origin, development = self._input_validation( data, index, columns, origin, development ) @@ -276,7 +276,7 @@ def __init__( self.ddims = obj.ddims self.values = obj.values self.valuation_date = pd.Timestamp(options.ULT_VAL) - + @staticmethod def _split_ult(data, index, columns, origin, development): """Deal with triangles with ultimate values""" @@ -330,17 +330,25 @@ def origin(self): if self.is_pattern and len(self.odims) == 1: return pd.Series(["(All)"]) else: - freq = {"Y": "Y" if float('.'.join(pd.__version__.split('.')[:-1])) < 2.2 else "A", - "S": "2Q", "H": "2Q"}.get( - self.origin_grain, self.origin_grain - ) + freq = { + "Y": ( + "Y" + if float(".".join(pd.__version__.split(".")[:-1])) < 2.2 + else "A" + ), + "S": "2Q", + "H": "2Q", + }.get(self.origin_grain, self.origin_grain) freq = freq if freq == "M" else freq + "-" + self.origin_close return pd.DatetimeIndex(self.odims, name="origin").to_period(freq=freq) @origin.setter def origin(self, value): self._len_check(self.origin, value) - freq = {"Y": "Y" if float('.'.join(pd.__version__.split('.')[:-1])) < 2.2 else "A", "S": "2Q"}.get(self.origin_grain, self.origin_grain) + freq = { + "Y": "Y" if float(".".join(pd.__version__.split(".")[:-1])) < 2.2 else "A", + "S": "2Q", + }.get(self.origin_grain, self.origin_grain) freq = freq if freq == "M" else freq + "-" + self.origin_close value = pd.PeriodIndex(list(value), freq=freq) self.odims = value.to_timestamp().values @@ -693,9 +701,11 @@ def grain(self, grain="", trailing=False, inplace=False): obj.origin_close = origin_period_end d_start = pd.Period( obj.valuation[0], - freq=dgrain_old - if dgrain_old == "M" - else dgrain_old + obj.origin.freqstr[-4:], + freq=( + dgrain_old + if dgrain_old == "M" + else dgrain_old + obj.origin.freqstr[-4:] + ), ).to_timestamp(how="s") if len(obj.ddims) > 1 and obj.origin.to_timestamp(how="s")[0] != d_start: addl_ts = ( @@ -763,6 +773,7 @@ def trend( Triangle updated with multiplicative trend applied. """ + print("in trend") if axis not in ["origin", "valuation", 2, -2]: raise ValueError( "Only origin and valuation axes are supported for trending"