From e9a0dd84a93eecc31c34f6dd0efcc033dabc17bf Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Fri, 4 Sep 2026 19:45:31 -0500 Subject: [PATCH] [FIX] Apply Ruff fix to chainladder/tails. --- chainladder/tails/base.py | 52 ++++++++++++++++++++------------ chainladder/tails/bondy.py | 5 ++-- chainladder/tails/curve.py | 61 +++++++++++++++++++------------------- pyproject.toml | 3 -- 4 files changed, 67 insertions(+), 54 deletions(-) diff --git a/chainladder/tails/base.py b/chainladder/tails/base.py index 6243aeb12..daa968a7a 100644 --- a/chainladder/tails/base.py +++ b/chainladder/tails/base.py @@ -7,8 +7,10 @@ class TailBase(DevelopmentBase): - """ Base class for all tail methods. Tail objects are equivalent - to development objects with an additional set of tail statistics""" + """ + Base class for all tail methods. Tail objects are equivalent + to development objects with an additional set of tail statistics + """ def fit(self, X, y=None, sample_weight=None): obj = X.copy() @@ -16,14 +18,20 @@ def fit(self, X, y=None, sample_weight=None): obj = Development().fit_transform(obj) xp = obj.ldf_.get_array_module() m = int(self.projection_period / 12) - self._ave_period = {"Y": (1 * m, 12), "Q": (4 * m, 3), "M": (12 * m, 1), "S": (2 * m, 6)}[ - obj.development_grain - ] + self._ave_period = { + "Y": (1 * m, 12), + "Q": (4 * m, 3), + "M": (12 * m, 1), + "S": (2 * m, 6), + }[obj.development_grain] t_ddims = [ (item + 1) * self._ave_period[1] + obj.ldf_.ddims[-1] - for item in range(self._ave_period[0]+1) + for item in range(self._ave_period[0] + 1) ] - ddims = np.concatenate((obj.ldf_.ddims, t_ddims), 0,) + ddims = np.concatenate( + (obj.ldf_.ddims, t_ddims), + 0, + ) self.ldf_ = obj.ldf_.copy() tail = xp.ones(self.ldf_.shape)[..., -1:] tail = xp.repeat(tail, self._ave_period[0] + 1, -1) @@ -35,7 +43,7 @@ def fit(self, X, y=None, sample_weight=None): self.sigma_.values = xp.concatenate((self.sigma_.values, zeros), -1) self.std_err_ = getattr(obj, "std_err_").copy() self.std_err_.values = xp.concatenate((self.std_err_.values, zeros), -1) - self.sigma_.ddims = self.std_err_.ddims = self.ldf_.ddims[:obj.shape[2]] + self.sigma_.ddims = self.std_err_.ddims = self.ldf_.ddims[: obj.shape[2]] self.sigma_._set_slicers() self.std_err_._set_slicers() if hasattr(obj, "average_"): @@ -46,7 +54,8 @@ def fit(self, X, y=None, sample_weight=None): return self def transform(self, X): - """ 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 @@ -75,15 +84,15 @@ def _get_tail_prediction(self, tail_ldf): return tail def _get_initial_ldf(self, xp, tail): - """ Quadratic series expansion solution to return seed LDF for tail""" + """Quadratic series expansion solution to return seed LDF for tail""" arr = self.decay ** xp.arange(1000) - a = xp.sum(arr ** 2) + a = xp.sum(arr**2) b = xp.sum(arr) c = -xp.log(tail) - return (-b + xp.sqrt(b ** 2 - 4 * a * c)) / (2 * a) + return (-b + xp.sqrt(b**2 - 4 * a * c)) / (2 * a) def _apply_decay(self, X, tail, attach_idx=None): - """ Created Tail vector with decay over time. """ + """Created Tail vector with decay over time.""" xp = self.ldf_.get_array_module() if attach_idx: decay_range = self.ldf_.shape[-1] - attach_idx @@ -106,17 +115,21 @@ def _apply_decay(self, X, tail, attach_idx=None): return self def _get_tail_stats(self, X): - """ Method to approximate the tail sigma using + """ + Method to approximate the tail sigma using log-linear extrapolation applied to tail average period """ from chainladder.utils.utility_functions import num_to_nan - if not hasattr(X, 'sigma_'): + + if not hasattr(X, "sigma_"): self.sigma_ = None self.std_err_ = None else: time_pd = self._get_tail_weighted_time_period(X) xp = X.sigma_.get_array_module() - reg = WeightedRegression(axis=3, xp=xp).fit(None, xp.log(X.sigma_.values), None) + reg = WeightedRegression(axis=3, xp=xp).fit( + None, xp.log(X.sigma_.values), None + ) sigma_ = xp.exp(time_pd * reg.slope_ + reg.intercept_) y = X.std_err_.values y = num_to_nan(y) @@ -125,7 +138,7 @@ def _get_tail_stats(self, X): if self.tail_.values.flatten().sum() / xp.prod(self.tail_.shape) == 1.0: # If no tail, assume no variation sigma_ = sigma_ * 0 - std_err_ = std_err_* 0 + std_err_ = std_err_ * 0 self.sigma_.values = xp.concatenate( (self.sigma_.values[..., :-1], sigma_[..., -1:]), axis=-1 ) @@ -134,7 +147,8 @@ def _get_tail_stats(self, X): ) def _get_tail_weighted_time_period(self, X): - """ Method to approximate the weighted-average development age of tail + """ + Method to approximate the weighted-average development age of tail using log-linear extrapolation Returns: float32 @@ -158,7 +172,7 @@ def _tail_(self): == self.cdf_.development.iloc[-1 - self._ave_period[0]] ] if np.all(df.values.min(axis=2) == df.values.max(axis=2)): - df = df.iloc[..., 0, :].to_frame(origin_as_datetime = False) + df = df.iloc[..., 0, :].to_frame(origin_as_datetime=False) return df @property diff --git a/chainladder/tails/bondy.py b/chainladder/tails/bondy.py index 5fe76a756..2120984a2 100644 --- a/chainladder/tails/bondy.py +++ b/chainladder/tails/bondy.py @@ -5,7 +5,7 @@ import pandas as pd from scipy.optimize import least_squares from chainladder.tails import TailBase -from chainladder.development import DevelopmentBase, Development +from chainladder.development import Development class TailBondy(TailBase): @@ -150,7 +150,8 @@ def fit(self, X, y=None, sample_weight=None): else: earliest_age = X.ddims[ int( - self.earliest_age / ({"Y": 12, "S": 6, "Q": 3, "M": 1}[X.development_grain]) + self.earliest_age + / ({"Y": 12, "S": 6, "Q": 3, "M": 1}[X.development_grain]) ) - 1 ] diff --git a/chainladder/tails/curve.py b/chainladder/tails/curve.py index 2dbd52d7b..71d2a5f15 100644 --- a/chainladder/tails/curve.py +++ b/chainladder/tails/curve.py @@ -128,30 +128,27 @@ class TailCurve(TailBase): """ def __init__( - self, - curve="exponential", - fit_period=(None, None), - extrap_periods=100, - errors="ignore", - attachment_age=None, - reg_threshold=(1.00001, None), - projection_period=12 + self, + curve="exponential", + fit_period=(None, None), + extrap_periods=100, + errors="ignore", + attachment_age=None, + reg_threshold=(1.00001, None), + projection_period=12, ): # validate arguments - if curve not in [ - 'exponential', - 'inverse_power', - 'weibull' - ]: - raise ValueError("Invalid curve type specified. Accepted values are 'exponential', 'inverse_power' and 'weibull'.") + if curve not in ["exponential", "inverse_power", "weibull"]: + raise ValueError( + "Invalid curve type specified. Accepted values are 'exponential', 'inverse_power' and 'weibull'." + ) - if errors not in [ - 'ignore', - 'raise' - ]: - raise ValueError("Invalid value argument supplied to the errors parameter. Accepted values are 'raise' " - "and 'ignore'.") + if errors not in ["ignore", "raise"]: + raise ValueError( + "Invalid value argument supplied to the errors parameter. Accepted values are 'raise' " + "and 'ignore'." + ) self.curve = curve self.fit_period = fit_period self.extrap_periods = extrap_periods @@ -175,11 +172,10 @@ def fit(self, X, y=None, sample_weight=None): self : object Returns the instance itself. """ - from chainladder.utils.utility_functions import num_to_nan X = X.copy() xp = X.get_array_module() - if type(self.fit_period) == slice: + if type(self.fit_period) is slice: warnings.warn( "Slicing for fit_period is deprecated and will be removed. Please use a tuple (start_age, end_age)." ) @@ -210,19 +206,22 @@ def fit(self, X, y=None, sample_weight=None): if self.reg_threshold[0] is None: warnings.warn( "Lower threshold for ldfs not set. Lower threshold will be set to 1.0 to ensure" - "valid inputs for regression.") + "valid inputs for regression." + ) lower_threshold = 1 elif self.reg_threshold[0] < 1: warnings.warn( "Lower threshold for ldfs set too low (<1). Lower threshold will be set to 1.0 to ensure " - "valid inputs for regression.") + "valid inputs for regression." + ) lower_threshold = 1 else: lower_threshold = self.reg_threshold[0] if self.reg_threshold[1] is not None: if self.reg_threshold[1] <= lower_threshold: warnings.warn( - "Can't set upper threshold for ldfs below lower threshold. Upper threshold will be set to 'None'.") + "Can't set upper threshold for ldfs below lower threshold. Upper threshold will be set to 'None'." + ) upper_threshold = None else: upper_threshold = self.reg_threshold[1] @@ -276,15 +275,17 @@ def _predict_tail(self, extrapolate): if self.curve == "exponential": tail_ldf = xp.exp(self._slope_ * extrapolate + self._intercept_) if self.curve == "inverse_power": - tail_ldf = xp.exp(self._intercept_) * (extrapolate ** self._slope_) + tail_ldf = xp.exp(self._intercept_) * (extrapolate**self._slope_) if self.curve == "weibull": - tail_ldf = 1/(1-xp.exp(-xp.exp(self._intercept_) - * extrapolate**self._slope_))-1 + tail_ldf = ( + 1 / (1 - xp.exp(-xp.exp(self._intercept_) * extrapolate**self._slope_)) + - 1 + ) return self._get_tail_prediction(tail_ldf) @property def slope_(self): - """ Does not work with munich """ + """Does not work with munich""" rows = self.ldf_.index.set_index(self.ldf_.key_labels).index return pd.DataFrame( self._slope_[..., 0, 0], index=rows, columns=self.ldf_.vdims @@ -292,7 +293,7 @@ def slope_(self): @property def intercept_(self): - """ Does not work with munich """ + """Does not work with munich""" rows = self.ldf_.index.set_index(self.ldf_.key_labels).index return pd.DataFrame( self._intercept_[..., 0, 0], index=rows, columns=self.ldf_.vdims diff --git a/pyproject.toml b/pyproject.toml index 72211897d..dc203be0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -124,9 +124,6 @@ select = ["E2", "E4", "E7", "E9", "F", "B018", "UP034", "N802"] "chainladder/development/tests/rtest_development.py" = ["E266", "E722", "F401"] "chainladder/development/tests/rtest_munich.py" = ["E266", "E722", "F401"] "chainladder/methods/tests/rtest_mack.py" = ["E266", "E722", "F401"] -"chainladder/tails/base.py" = ["E226", "E251"] -"chainladder/tails/bondy.py" = ["F401"] -"chainladder/tails/curve.py" = ["E226", "E721", "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"]