From 92f7aa926877db49818a6bf37d4acb1c30010a9f Mon Sep 17 00:00:00 2001 From: "henrydingliu@gmail.com" Date: Tue, 15 Sep 2026 05:53:57 +0000 Subject: [PATCH 1/5] ruff format fixes --- chainladder/core/dunders.py | 134 +++++++++++++++++++++++++----------- chainladder/core/io.py | 30 +++++--- 2 files changed, 115 insertions(+), 49 deletions(-) diff --git a/chainladder/core/dunders.py b/chainladder/core/dunders.py index 22e27393..3ce217c3 100644 --- a/chainladder/core/dunders.py +++ b/chainladder/core/dunders.py @@ -6,10 +6,7 @@ import numpy as np import pandas as pd -from chainladder.utils.utility_functions import ( - num_to_nan, - concat -) +from chainladder.utils.utility_functions import num_to_nan, concat from chainladder.core.pandas import TriangleGroupBy from chainladder.utils.sparse import sp @@ -25,13 +22,14 @@ if TYPE_CHECKING: from typing import Any + class TriangleDunders: - """ Class that implements the dunder (double underscore) methods for the - Triangle class + """ + Class that implements the dunder (double underscore) methods for the Triangle class """ def _validate_arithmetic(self, other: Any) -> tuple: - """ Common functionality BEFORE arithmetic operations """ + """Common functionality BEFORE arithmetic operations""" if isinstance(other, TriangleDunders): obj, other = self._compatibility_check(self, other) if obj.is_pattern != other.is_pattern: @@ -43,17 +41,17 @@ def _validate_arithmetic(self, other: Any) -> tuple: if isinstance(other, TriangleDunders): other = other.values else: - if isinstance(other, np.ndarray) and self.array_backend != 'numpy': + if isinstance(other, np.ndarray) and self.array_backend != "numpy": obj = self.copy() other = obj.get_array_module().array(other) - elif isinstance(other, sp.COO) and self.array_backend != 'sparse': - obj = self.set_backend('sparse') + elif isinstance(other, sp.COO) and self.array_backend != "sparse": + obj = self.set_backend("sparse") else: obj = self.copy() return obj, other def _arithmetic_cleanup(self, obj): - """ Common functionality AFTER arithmetic operations """ + """Common functionality AFTER arithmetic operations""" obj.values = obj.values * obj.get_array_module().nan_to_num(obj.nan_triangle) obj.values = num_to_nan(obj.values) return obj @@ -62,10 +60,9 @@ def _compatibility_check(self, x, y): from chainladder.utils.utility_functions import set_common_backend x, y = set_common_backend([x, y]) - if ( - x.origin_grain != y.origin_grain - or (x.development_grain != y.development_grain and - min(x.shape[-1], y.shape[-1]) > 1) + if x.origin_grain != y.origin_grain or ( + x.development_grain != y.development_grain + and min(x.shape[-1], y.shape[-1]) > 1 ): raise ValueError( "Triangle arithmetic requires both triangles to be the same grain." @@ -83,7 +80,9 @@ def _prep_index(self, x, y): return x, y if x.kdims.shape[0] == y.kdims.shape[0] == 1 and x.key_labels != y.key_labels: kdims = x.kdims if len(x.key_labels) > len(y.key_labels) else y.kdims - key_labels = x.key_labels if len(x.key_labels) > len(y.key_labels) else y.key_labels + key_labels = ( + x.key_labels if len(x.key_labels) > len(y.key_labels) else y.key_labels + ) x.kdims = y.kdims = kdims x.key_labels = y.key_labels = key_labels return x, y @@ -97,7 +96,11 @@ def _prep_index(self, x, y): if x_labels != y_labels or x.kdims.shape[0] != y.kdims.shape[0]: x = x.groupby(list(common)) y = y.groupby(list(common)) - elif x.kdims.shape[0] > 1 and not np.array_equal(x.kdims, y.kdims) and not x.index.equals(y.index): + elif ( + x.kdims.shape[0] > 1 + and not np.array_equal(x.kdims, y.kdims) + and not x.index.equals(y.index) + ): x = x.sort_index() try: y = y.loc[x.index] @@ -107,7 +110,12 @@ def _prep_index(self, x, y): return x, y else: - raise ValueError('Index broadcasting is ambiguous between ' + str(x_labels) + ' and ' + str(y_labels)) + raise ValueError( + 'Index broadcasting is ambiguous between ' + + str(x_labels) + + ' and ' + + str(y_labels) + ) def _prep_columns(self, x, y): if len(x.columns) == 1 and len(y.columns) > 1: @@ -118,9 +126,9 @@ def _prep_columns(self, x, y): y.vdims = x.vdims elif x.shape[1] == y.shape[1] and np.array_equal(x.columns, y.columns): return x, y - else: + else: # Find columns to add to each triangle - cols_to_add_to_x = [col for col in y.columns if col not in x.columns] + cols_to_add_to_x = [col for col in y.columns if col not in x.columns] cols_to_add_to_y = [col for col in x.columns if col not in y.columns] # Start with case with no new columns, y simply has a different order @@ -145,7 +153,8 @@ def _prep_origin_development(self, obj, other): xp = obj.get_array_module() is_broadcastable = all( (m == n) or (m == 1) or (n == 1) - for m, n in zip(obj.shape[-2:][::-1], other.shape[-2:][::-1])) + for m, n in zip(obj.shape[-2:][::-1], other.shape[-2:][::-1]) + ) if (len(obj.odims), len(obj.ddims)) == (len(other.odims), len(other.ddims)): if np.all(obj.ddims != other.ddims) and len(obj.ddims) > 1: is_broadcastable = False @@ -191,10 +200,20 @@ def _prep_origin_development(self, obj, other): ldl = int(np.where(~d_arr0 == 1)[0].min()) ldh = int(np.where(~d_arr0 == 1)[0].max() + 1) if obj.array_backend != "sparse": - other_arr = xp.zeros((other.shape[0], other.shape[1], len(odims), len(ddims))) + other_arr = xp.zeros(( + other.shape[0], + other.shape[1], + len(odims), + len(ddims) + )) other_arr[:] = xp.nan other_arr[:, :, rol:roh, rdl:rdh] = other.values - obj_arr = xp.zeros((self.shape[0], self.shape[1], len(odims), len(ddims))) + obj_arr = xp.zeros(( + self.shape[0], + self.shape[1], + len(odims), + len(ddims) + )) obj_arr[:] = xp.nan obj_arr[:, :, lol:loh, ldl:ldh] = obj.values else: @@ -203,8 +222,18 @@ def _prep_origin_development(self, obj, other): other_arr.coords[3] = other_arr.coords[3] + rdl obj_arr.coords[2] = obj_arr.coords[2] + lol obj_arr.coords[3] = obj_arr.coords[3] + ldl - other_arr.shape = (other.shape[0], other.shape[1], len(odims), len(ddims)) - obj_arr.shape = (self.shape[0], self.shape[1], len(odims), len(ddims)) + other_arr.shape = ( + other.shape[0], + other.shape[1], + len(odims), + len(ddims) + ) + obj_arr.shape = ( + self.shape[0], + self.shape[1], + len(odims), + len(ddims) + ) obj.odims = np.array(odims.index) if isinstance(obj.ddims, pd.DatetimeIndex): obj.ddims = pd.DatetimeIndex(ddims.index) @@ -216,15 +245,20 @@ def _prep_origin_development(self, obj, other): @staticmethod def _slice_or_nan(obj, other, k): - """ Return a broadcastable slice or NaNs for missing slices """ + """Return a broadcastable slice or NaNs for missing slices""" if k in obj.groups.indices.keys(): return obj.obj.iloc[obj.groups.indices[k]] else: other = other.obj.iloc[other.groups.indices[k]] new_obj = obj.obj.iloc[:1] * 0 labels = list(set(other.key_labels).intersection(set(new_obj.key_labels))) - new_idx = other.index.set_index(labels).join( - new_obj.index.set_index(labels)).reset_index() + new_idx = ( + other + .index + .set_index(labels) + .join(new_obj.index.set_index(labels)) + .reset_index() + ) new_idx = new_idx[new_obj.key_labels].iloc[-1:] new_obj.kdims = new_idx.values new_obj.key_labels = list(new_idx.columns) @@ -232,16 +266,20 @@ def _slice_or_nan(obj, other, k): @staticmethod def _get_key_union(obj, other): - return set(list(obj.groups.indices.keys()) + - list(other.groups.indices.keys())) + # fmt: off + return set( + list(obj.groups.indices.keys()) + + list(other.groups.indices.keys()) + ) + # fmt: on def _arithmetic_mapper(self, obj, other, f): - """ Use Dask if available, otherwise basic list comprehension """ - if db and obj.obj.array_backend == 'sparse': + """Use Dask if available, otherwise basic list comprehension""" + if db and obj.obj.array_backend == "sparse": _warn_dask_parallel_deprecated() bag = db.from_sequence(self._get_key_union(obj, other)) bag = bag.map(f, self, obj, other) - c = bag.compute(scheduler='threads') + c = bag.compute(scheduler="threads") else: c = [f(k, self, obj, other) for k in self._get_key_union(obj, other)] return concat(c, 0).sort_index() @@ -281,9 +319,13 @@ def __add__(self, other): """ obj, other = self._validate_arithmetic(other) if isinstance(obj, TriangleGroupBy): + def f(k, self, obj, other): + # fmt: off return (self._slice_or_nan(obj, other, k) + self._slice_or_nan(other, obj, k)) + # fmt: on + obj = self._arithmetic_mapper(obj, other, f) else: xp = obj.get_array_module() @@ -326,9 +368,13 @@ def __sub__(self, other): """ obj, other = self._validate_arithmetic(other) if isinstance(obj, TriangleGroupBy): + def f(k, self, obj, other): + # fmt: off return (self._slice_or_nan(obj, other, k) - self._slice_or_nan(other, obj, k)) + # fmt: on + obj = self._arithmetic_mapper(obj, other, f) else: xp = obj.get_array_module() @@ -400,9 +446,13 @@ def __mul__(self, other): """ obj, other = self._validate_arithmetic(other) if isinstance(obj, TriangleGroupBy): + def f(k, self, obj, other): + # fmt: off return (self._slice_or_nan(obj, other, k) * self._slice_or_nan(other, obj, k)) + # fmt: on + obj = self._arithmetic_mapper(obj, other, f) else: obj.values = obj.values * other @@ -414,9 +464,13 @@ def __rmul__(self, other): def __pow__(self, other): obj, other = self._validate_arithmetic(other) if isinstance(obj, TriangleGroupBy): + def f(k, self, obj, other): + # fmt: off return (self._slice_or_nan(obj, other, k) ** self._slice_or_nan(other, obj, k)) + # fmt: on + obj = self._arithmetic_mapper(obj, other, f) else: xp = obj.get_array_module() @@ -484,9 +538,13 @@ def __truediv__(self, other: Any): """ obj, other = self._validate_arithmetic(other) if isinstance(obj, TriangleGroupBy): + def f(k, self, obj, other): + # fmt: off return (self._slice_or_nan(obj, other, k) / self._slice_or_nan(other, obj, k)) + # fmt: on + obj = self._arithmetic_mapper(obj, other, f) else: obj.values = obj.values / other @@ -504,12 +562,10 @@ def __eq__(self, other): from chainladder import options backend = options.ARRAY_PRIORITY[ - min( - [ - options.ARRAY_PRIORITY.index(x) - for x in [self.array_backend, other.array_backend] - ] - ) + min([ + options.ARRAY_PRIORITY.index(x) + for x in [self.array_backend, other.array_backend] + ]) ] x, y = self.set_backend(backend), other.set_backend(backend) xp = x.get_array_module() diff --git a/chainladder/core/io.py b/chainladder/core/io.py index 6f6836a6..c41d7f16 100644 --- a/chainladder/core/io.py +++ b/chainladder/core/io.py @@ -1,6 +1,7 @@ """ Support Triangle I/O capabilities. """ + # 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/. @@ -13,7 +14,8 @@ class TriangleIO: def to_pickle(self, path, protocol=None): - """ Serializes triangle object to pickle. + """ + Serializes triangle object to pickle. Parameters ---------- @@ -53,7 +55,8 @@ def to_pickle(self, path, protocol=None): dill.dump(self, pkl) def to_json(self): - """ Serializes triangle object to json format + """ + Serializes triangle object to json format Returns ------- @@ -88,8 +91,13 @@ def to_json(self): "is_pattern": self.is_pattern, "columns": list(self.columns), } - out = self.cum_to_incr().dev_to_val().to_frame( - keepdims=True, origin_as_datetime=True).fillna(0) + out = ( + self + .cum_to_incr() + .dev_to_val() + .to_frame(keepdims=True, origin_as_datetime=True) + .fillna(0) + ) x = out.reset_index().to_json(orient="split", date_unit="ns") json_dict = {"metadata": json.dumps(metadata), "data": x} sub_tris = [k for k, v in vars(self).items() if isinstance(v, TriangleIO)] @@ -99,17 +107,18 @@ def to_json(self): dfs = [k for k, v in vars(self).items() if isinstance(v, pd.DataFrame)] json_dict["dfs"] = {df: getattr(self, df).to_json() for df in dfs} dfs = [k for k, v in vars(self).items() if isinstance(v, pd.Series)] - json_dict["dfs"].update( - {df: getattr(self, df).to_frame().to_json() for df in dfs} - ) + json_dict["dfs"].update({ + df: getattr(self, df).to_frame().to_json() for df in dfs + }) return json.dumps(json_dict) class EstimatorIO: - """ Class intended to allow persistence of estimator objects """ + """Class intended to allow persistence of estimator objects""" def to_pickle(self, path, protocol=None): - """ Serializes triangle object to pickle. + """ + Serializes triangle object to pickle. Parameters ---------- @@ -149,7 +158,8 @@ def to_pickle(self, path, protocol=None): dill.dump(self, pkl) def to_json(self): - """ Serializes triangle object to json format + """ + Serializes triangle object to json format Returns ------- From 6e07472e393430898d76c72aac4eeac2c32604c1 Mon Sep 17 00:00:00 2001 From: "henrydingliu@gmail.com" Date: Tue, 15 Sep 2026 05:56:58 +0000 Subject: [PATCH 2/5] more ruff fixes --- chainladder/core/dunders.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/chainladder/core/dunders.py b/chainladder/core/dunders.py index 3ce217c3..8b49e626 100644 --- a/chainladder/core/dunders.py +++ b/chainladder/core/dunders.py @@ -111,9 +111,9 @@ def _prep_index(self, x, y): else: raise ValueError( - 'Index broadcasting is ambiguous between ' + "Index broadcasting is ambiguous between " + str(x_labels) - + ' and ' + + "" and "" + str(y_labels) ) @@ -204,7 +204,7 @@ def _prep_origin_development(self, obj, other): other.shape[0], other.shape[1], len(odims), - len(ddims) + len(ddims), )) other_arr[:] = xp.nan other_arr[:, :, rol:roh, rdl:rdh] = other.values @@ -212,7 +212,7 @@ def _prep_origin_development(self, obj, other): self.shape[0], self.shape[1], len(odims), - len(ddims) + len(ddims), )) obj_arr[:] = xp.nan obj_arr[:, :, lol:loh, ldl:ldh] = obj.values @@ -226,14 +226,9 @@ def _prep_origin_development(self, obj, other): other.shape[0], other.shape[1], len(odims), - len(ddims) - ) - obj_arr.shape = ( - self.shape[0], - self.shape[1], - len(odims), - len(ddims) + len(ddims), ) + obj_arr.shape = (self.shape[0], self.shape[1], len(odims), len(ddims)) obj.odims = np.array(odims.index) if isinstance(obj.ddims, pd.DatetimeIndex): obj.ddims = pd.DatetimeIndex(ddims.index) @@ -253,8 +248,7 @@ def _slice_or_nan(obj, other, k): new_obj = obj.obj.iloc[:1] * 0 labels = list(set(other.key_labels).intersection(set(new_obj.key_labels))) new_idx = ( - other - .index + other.index .set_index(labels) .join(new_obj.index.set_index(labels)) .reset_index() From d1915273a81993c589f5cfbb2b0d951585ea2c08 Mon Sep 17 00:00:00 2001 From: "henrydingliu@gmail.com" Date: Tue, 15 Sep 2026 14:14:10 +0000 Subject: [PATCH 3/5] fixing typo --- chainladder/core/dunders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainladder/core/dunders.py b/chainladder/core/dunders.py index 8b49e626..e53414f8 100644 --- a/chainladder/core/dunders.py +++ b/chainladder/core/dunders.py @@ -113,7 +113,7 @@ def _prep_index(self, x, y): raise ValueError( "Index broadcasting is ambiguous between " + str(x_labels) - + "" and "" + + " and " + str(y_labels) ) From d9fb20d19aeaeafbaa727a199c30c38a29078f00 Mon Sep 17 00:00:00 2001 From: "henrydingliu@gmail.com" Date: Tue, 15 Sep 2026 17:11:31 +0000 Subject: [PATCH 4/5] adding D213 per #1277 --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index b5edad14..ac16743f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -108,6 +108,7 @@ extend-exclude = ["docs/getting_started/online_sandbox/sandbox_workbook_blank.ip select = [ "B018", "BLE001", + "D213", "E2", "E4", "E7", From c4a9383c42f9709ee0c2166a57666729ee1df4eb Mon Sep 17 00:00:00 2001 From: "henrydingliu@gmail.com" Date: Tue, 15 Sep 2026 20:42:12 +0000 Subject: [PATCH 5/5] ruff fixes --- chainladder/adjustments/bootstrap.py | 3 +- chainladder/adjustments/disposal.py | 6 ++- chainladder/adjustments/parallelogram.py | 6 ++- .../adjustments/tests/test_parallelogram.py | 3 +- chainladder/adjustments/trend.py | 6 ++- chainladder/core/base.py | 18 ++++--- chainladder/core/common.py | 18 ++++--- chainladder/core/dunders.py | 9 ++-- chainladder/core/pandas.py | 51 ++++++++++++------- 9 files changed, 80 insertions(+), 40 deletions(-) diff --git a/chainladder/adjustments/bootstrap.py b/chainladder/adjustments/bootstrap.py index 101b84dd..a2592971 100644 --- a/chainladder/adjustments/bootstrap.py +++ b/chainladder/adjustments/bootstrap.py @@ -344,7 +344,8 @@ def _get_hetero_adjustment(self): pass 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 diff --git a/chainladder/adjustments/disposal.py b/chainladder/adjustments/disposal.py index 71d1c1eb..fcd3a568 100644 --- a/chainladder/adjustments/disposal.py +++ b/chainladder/adjustments/disposal.py @@ -285,7 +285,8 @@ def fit(self, X: Triangle, y: None = None, sample_weight: Triangle | None = None return self def transform(self, X: Triangle, sample_weight: Triangle | None = None) -> Triangle: - """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 @@ -318,7 +319,8 @@ def transform(self, X: Triangle, sample_weight: Triangle | None = None) -> Trian return X_new def fit_transform(self, X, y=None, sample_weight=None): - """Fit and return transformed full_triangle_ based on the Disposal Rate + """ + Fit and return transformed full_triangle_ based on the Disposal Rate Parameters ---------- diff --git a/chainladder/adjustments/parallelogram.py b/chainladder/adjustments/parallelogram.py index 91545714..09a85f09 100644 --- a/chainladder/adjustments/parallelogram.py +++ b/chainladder/adjustments/parallelogram.py @@ -262,7 +262,8 @@ def __init__( self.cumulative = cumulative def fit(self, X, y=None, sample_weight=None): - """Fit the model with X. + """ + Fit the model with X. Parameters ---------- @@ -332,7 +333,8 @@ def _combine_duplicate_dates(self, r): return (r.groupby(self.date_col)[self.change_col].prod() - 1).reset_index() 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 diff --git a/chainladder/adjustments/tests/test_parallelogram.py b/chainladder/adjustments/tests/test_parallelogram.py index 8a8c4310..9e74c461 100644 --- a/chainladder/adjustments/tests/test_parallelogram.py +++ b/chainladder/adjustments/tests/test_parallelogram.py @@ -457,7 +457,8 @@ def test_cumulative_rejects_non_positive(): def test_cumulative_factor_predates_window(): - """A factor in force before the triangle window is honored, not dropped. + """ + A factor in force before the triangle window is honored, not dropped. The earliest factor's effective date (2000) predates the triangle's lookback window (origins start 2003), so it must still apply to the first diff --git a/chainladder/adjustments/trend.py b/chainladder/adjustments/trend.py index 610b5228..425b0f47 100644 --- a/chainladder/adjustments/trend.py +++ b/chainladder/adjustments/trend.py @@ -163,7 +163,8 @@ def __init__(self, trends=0.0, dates=None, axis="origin"): self.axis = axis def fit(self, X, y=None, sample_weight=None): - """Fit the model with X. + """ + Fit the model with X. Parameters ---------- @@ -193,7 +194,8 @@ def fit(self, X, y=None, sample_weight=None): 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 diff --git a/chainladder/core/base.py b/chainladder/core/base.py index b86100af..ac89fc13 100644 --- a/chainladder/core/base.py +++ b/chainladder/core/base.py @@ -55,7 +55,8 @@ class TriangleBase( @property def shape(self): - """The 4-D shape of the Triangle: ``(index, columns, origin, development)``. + """ + The 4-D shape of the Triangle: ``(index, columns, origin, development)``. Examples -------- @@ -79,7 +80,8 @@ def shape(self): @property def dimensionality(self): - """The dimensionality of the Triangle. + """ + The dimensionality of the Triangle. Returns ``'empty'`` for a Triangle instantiated without data (e.g. ``cl.Triangle()``), ``'single'`` for a Triangle holding a @@ -107,7 +109,8 @@ def dimensionality(self): @property def empty(self): - """Whether the Triangle contains any data. + """ + Whether the Triangle contains any data. Mirrors ``pandas.DataFrame.empty``. Returns ``True`` for a Triangle instantiated without data (e.g. ``cl.Triangle()``), whose ``values`` @@ -425,7 +428,8 @@ def is_ultimate(self) -> bool: @property def nan_triangle(self): - """Given the current triangle shape and valuation, it determines the + """ + Given the current triangle shape and valuation, it determines the appropriate placement of NANs in the triangle for future valuations. This becomes useful when managing array arithmetic. @@ -775,7 +779,8 @@ def __array_function__(self, func, types, args, kwargs): return HANDLED_FUNCTIONS[func](*args, **kwargs) def compute(self, *args, **kwargs): - """Materialize a lazy dask-backed Triangle. + """ + Materialize a lazy dask-backed Triangle. When ``values`` is a dask array, compute it and update ``array_backend`` to match the resulting array type. Returns ``self`` @@ -819,7 +824,8 @@ def _get_axis_value(self, axis): def is_chainladder(estimator): - """Return True if the given estimator is a chainladder based method. + """ + Return True if the given estimator is a chainladder based method. Parameters ---------- estimator : object diff --git a/chainladder/core/common.py b/chainladder/core/common.py index dd949553..8f421cb5 100644 --- a/chainladder/core/common.py +++ b/chainladder/core/common.py @@ -89,7 +89,8 @@ def has_zeta(self): @property def cdf_(self): - """Cumulative development factors, ``ldf_`` converted with ``incr_to_cum``. + """ + Cumulative development factors, ``ldf_`` converted with ``incr_to_cum``. Examples -------- @@ -117,7 +118,8 @@ def cdf_(self): @property def pct_reported_(self): - """Percentage of ultimate reported (or paid) at each development age, + """ + Percentage of ultimate reported (or paid) at each development age, equal to the inverse of the cumulative development factor. Examples @@ -146,8 +148,10 @@ def pct_reported_(self): @property def pct_unreported_(self): - """Percentage of ultimate still unreported (or unpaid) at each - development age, equal to ``1 - 1 / cdf_``.""" + """ + Percentage of ultimate still unreported (or unpaid) at each + development age, equal to ``1 - 1 / cdf_``. + """ if not self.has_ldf: x = self.__class__.__name__ raise AttributeError( @@ -164,7 +168,8 @@ def cum_zeta_(self): @property def ibnr_(self): - """Outstanding development to ultimate: ``ultimate_`` minus the latest + """ + Outstanding development to ultimate: ``ultimate_`` minus the latest diagonal (or the origin total, for incremental triangles). Examples @@ -227,7 +232,8 @@ def full_triangle_(self): return _get_full_triangle(X, self.ultimate_, X.is_cumulative) def pipe(self, func, *args, **kwargs): - """Apply ``func(self, *args, **kwargs)``. + """ + Apply ``func(self, *args, **kwargs)``. Parameters ---------- diff --git a/chainladder/core/dunders.py b/chainladder/core/dunders.py index 526a4200..0d00a090 100644 --- a/chainladder/core/dunders.py +++ b/chainladder/core/dunders.py @@ -282,7 +282,8 @@ def _arithmetic_mapper(self, obj, other, f): return concat(c, 0).sort_index() def __add__(self, other): - """Element-wise addition. + """ + Element-wise addition. Examples -------- @@ -333,7 +334,8 @@ def __radd__(self, other): return self if other == 0 else self.__add__(other) def __sub__(self, other): - """Element-wise subtraction. + """ + Element-wise subtraction. Examples -------- @@ -411,7 +413,8 @@ def __abs__(self): return obj def __mul__(self, other): - """Element-wise multiplication. + """ + Element-wise multiplication. Examples -------- diff --git a/chainladder/core/pandas.py b/chainladder/core/pandas.py index 4b7237ba..5d0a8508 100644 --- a/chainladder/core/pandas.py +++ b/chainladder/core/pandas.py @@ -70,7 +70,8 @@ def to_frame( keepdims: bool = False, implicit_axis: bool = False, ) -> DataFrame | Series: - """Converts a triangle to a pandas.DataFrame. + """ + Converts a triangle to a pandas.DataFrame. Parameters ---------- @@ -531,7 +532,8 @@ def dropna(self) -> Triangle: return obj def fillna(self, value: int | float | ndarray, inplace: bool = False) -> Triangle: - """Fill nan with 'value' by axis. + """ + Fill nan with 'value' by axis. Parameters ---------- @@ -563,7 +565,8 @@ def fillna(self, value: int | float | ndarray, inplace: bool = False) -> Triangl return new_obj def fillzero(self, inplace: bool = False) -> Triangle: - """Fill nan with 0 by axis. separate function from fillna() because fillna(0) isn't working. + """ + Fill nan with 0 by axis. separate function from fillna() because fillna(0) isn't working. Parameters ---------- @@ -590,7 +593,8 @@ def fillzero(self, inplace: bool = False) -> Triangle: return new_obj def ffill(self, axis: int | str = 3) -> Triangle: - """Forward-fill missing values along an axis. + """ + Forward-fill missing values along an axis. Only cells within the observed triangle (see ``nan_triangle``) are filled; a cell that has not yet been valued is left as-is regardless @@ -703,7 +707,8 @@ def _validate_contiguous_drop( axis_name: str, errors: str, ) -> np.ndarray: - """Validate and return boolean keep mask for dropping contiguous edge periods. + """ + Validate and return boolean keep mask for dropping contiguous edge periods. Parameters ---------- @@ -749,7 +754,8 @@ def drop( level: int | str | None = None, errors: str = "raise", ) -> Triangle: - """Drop specified labels from rows or columns. + """ + Drop specified labels from rows or columns. Remove labels by specifying label names and corresponding axis, or by specifying directly ``index``, ``columns``, ``origin``, or @@ -1008,7 +1014,8 @@ def rename( axis: Literal["index", "columns", "origin", "development"] | int, value: str | list | dict, ) -> Triangle: - """Alter Triangle axes labels. + """ + Alter Triangle axes labels. Parameters ---------- @@ -1052,7 +1059,8 @@ def rename( return cast("Triangle", cast(object, self)) def astype(self, dtype, inplace=True) -> Triangle: - """Copy of the array, cast to a specified type. + """ + Copy of the array, cast to a specified type. Parameters ---------- @@ -1070,7 +1078,8 @@ def astype(self, dtype, inplace=True) -> Triangle: return cast("Triangle", obj) def head(self, n: int = 5) -> Triangle: - """Return the first ``n`` triangles along the index axis. + """ + Return the first ``n`` triangles along the index axis. Parameters ---------- @@ -1084,7 +1093,8 @@ def head(self, n: int = 5) -> Triangle: return self.iloc[:n] def tail(self, n: int = 5) -> Triangle: - """Return the last ``n`` triangles along the index axis. + """ + Return the last ``n`` triangles along the index axis. Parameters ---------- @@ -1098,7 +1108,8 @@ def tail(self, n: int = 5) -> Triangle: return self.iloc[-n:] def sort_index(self, *args, **kwargs) -> Triangle: - """Sort Triangle rows by index labels. + """ + Sort Triangle rows by index labels. Returns ------- @@ -1110,7 +1121,8 @@ def sort_index(self, *args, **kwargs) -> Triangle: return self.iloc[sorted_index.index] def exp(self) -> Triangle: - """Return the exponential of each element. + """ + Return the exponential of each element. Returns ------- @@ -1119,7 +1131,8 @@ def exp(self) -> Triangle: return self.get_array_module().exp(self) def log(self) -> Triangle: - """Return the natural logarithm of each element. + """ + Return the natural logarithm of each element. Returns ------- @@ -1128,7 +1141,8 @@ def log(self) -> Triangle: return self.get_array_module().log(self) def minimum(self, other: Triangle | int | float) -> Triangle: - """Element-wise minimum of this Triangle and another operand. + """ + Element-wise minimum of this Triangle and another operand. See :func:`chainladder.minimum` for parameters, usage, and examples. @@ -1136,14 +1150,16 @@ def minimum(self, other: Triangle | int | float) -> Triangle: return self.get_array_module().minimum(self, other) def maximum(self, other: Triangle | int | float) -> Triangle: - """Element-wise maximum of this Triangle and another operand. + """ + Element-wise maximum of this Triangle and another operand. See :func:`chainladder.maximum` for parameters, usage, and examples. """ return self.get_array_module().maximum(self, other) def sqrt(self) -> Triangle: - """Return the non-negative square root of each element. + """ + Return the non-negative square root of each element. Returns ------- @@ -1152,7 +1168,8 @@ def sqrt(self) -> Triangle: return self.get_array_module().sqrt(self) def round(self, decimals: int = 0) -> Triangle: - """Round each element to the given number of decimal places. + """ + Round each element to the given number of decimal places. Uses banker's rounding (round half to even). For example, ``(8.5).round(0)`` returns 8, not 9. For conventional rounding,