From 9829c16511d2d1f21111fd1ae9c15435170e53b1 Mon Sep 17 00:00:00 2001 From: Ethan Kang Date: Thu, 13 Aug 2026 09:38:46 -0700 Subject: [PATCH 1/3] docs: add Triangle shape/empty/compute doctest examples (#704) Co-authored-by: Cursor --- chainladder/core/base.py | 110 ++++++++++++++++++ .../autosummary/class_inherited.rst | 5 +- 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/chainladder/core/base.py b/chainladder/core/base.py index bdb3b8806..5f7705e17 100644 --- a/chainladder/core/base.py +++ b/chainladder/core/base.py @@ -60,6 +60,26 @@ class TriangleBase( @property def shape(self): + """The 4-D shape of the Triangle: ``(index, columns, origin, development)``. + + Examples + -------- + A single-triangle sample such as RAA has one index and one column. + + .. testsetup:: + + import chainladder as cl + + .. testcode:: + + print(cl.load_sample('raa').shape) + print(cl.load_sample('clrd').shape) + + .. testoutput:: + + (1, 1, 10, 10) + (775, 6, 10, 10) + """ return self.values.shape @property @@ -69,6 +89,24 @@ def dimensionality(self): Returns ``'empty'`` for a Triangle instantiated without data (e.g. ``cl.Triangle()``), ``'single'`` for a Triangle holding a single triangle, and ``'multi'`` for a multidimensional Triangle. + + Examples + -------- + .. testsetup:: + + import chainladder as cl + + .. testcode:: + + print(cl.Triangle().dimensionality) + print(cl.load_sample('raa').dimensionality) + print(cl.load_sample('clrd').dimensionality) + + .. testoutput:: + + empty + single + multi """ return self._dimensionality @@ -79,6 +117,22 @@ def empty(self): Mirrors ``pandas.DataFrame.empty``. Returns ``True`` for a Triangle instantiated without data (e.g. ``cl.Triangle()``), whose ``values`` have not been populated, and ``False`` otherwise. + + Examples + -------- + .. testsetup:: + + import chainladder as cl + + .. testcode:: + + print(cl.Triangle().empty) + print(cl.load_sample('raa').empty) + + .. testoutput:: + + True + False """ return self._dimensionality == "empty" @@ -367,6 +421,25 @@ def nan_triangle(self): """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. + + Examples + -------- + The most recent origin is only observed at age 12, so later lags in + that row of ``nan_triangle`` are missing. + + .. testsetup:: + + import chainladder as cl + + .. testcode:: + + import numpy as np + mask = np.isnan(cl.load_sample('raa').nan_triangle[-1]) + print(mask.tolist()) + + .. testoutput:: + + [False, True, True, True, True, True, True, True, True, True] """ xp = self.get_array_module() if self.is_pattern or self.is_ultimate: @@ -527,6 +600,23 @@ def get_array_module( ------- The backend module. For example, if the backend is numpy, it will return the "np" that you would get if you ran the statement, "import numpy as np". + + Examples + -------- + .. testsetup:: + + import chainladder as cl + + .. testcode:: + + raa = cl.load_sample('raa') + print(raa.get_array_module().__name__) + print(raa.set_backend('sparse').get_array_module().__name__) + + .. testoutput:: + + numpy + sparse """ backend: str = ( @@ -666,6 +756,26 @@ def compute(self, *args, **kwargs): Returns ------- Triangle + + Examples + -------- + Numpy-backed Triangles are already materialized, so ``compute`` + returns the same object. + + .. testsetup:: + + import chainladder as cl + + .. testcode:: + + raa = cl.load_sample('raa') + print(raa.compute() is raa) + print(raa.compute() == raa) + + .. testoutput:: + + True + True """ if hasattr(self.values, "chunks"): obj = self.copy() diff --git a/docs/_templates/autosummary/class_inherited.rst b/docs/_templates/autosummary/class_inherited.rst index ee45f6cc1..187a35f44 100644 --- a/docs/_templates/autosummary/class_inherited.rst +++ b/docs/_templates/autosummary/class_inherited.rst @@ -2,8 +2,11 @@ .. currentmodule:: {{ module }} +{% set documented_attrs = ['shape', 'empty', 'dimensionality', 'nan_triangle'] %} +{% set hidden_attrs = attributes | reject('in', documented_attrs) | list %} + .. autoclass:: {{ objname }} :members: :inherited-members: :undoc-members: - :exclude-members: set_fit_request, set_predict_request, set_score_request, set_transform_request, {{ attributes | join(', ') }} + :exclude-members: set_fit_request, set_predict_request, set_score_request, set_transform_request{% if hidden_attrs %}, {{ hidden_attrs | join(', ') }}{% endif %} From cd30689003b7da1ccbb689cc25f99ab9b48806a3 Mon Sep 17 00:00:00 2001 From: Ethan Kang Date: Thu, 13 Aug 2026 13:11:23 -0700 Subject: [PATCH 2/3] Tighten TriangleBase examples from review on nan_triangle, get_array_module, and compute. Print the nan mask as-is, show numpy/sparse module identity, and document compute with a dask code sample instead of a numpy no-op doctest. Co-authored-by: Cursor --- chainladder/core/base.py | 53 +++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/chainladder/core/base.py b/chainladder/core/base.py index 5f7705e17..cbc029720 100644 --- a/chainladder/core/base.py +++ b/chainladder/core/base.py @@ -424,8 +424,7 @@ def nan_triangle(self): Examples -------- - The most recent origin is only observed at age 12, so later lags in - that row of ``nan_triangle`` are missing. + Observed cells are ``1`` and future valuations are missing. .. testsetup:: @@ -433,13 +432,20 @@ def nan_triangle(self): .. testcode:: - import numpy as np - mask = np.isnan(cl.load_sample('raa').nan_triangle[-1]) - print(mask.tolist()) + print(cl.load_sample('raa').nan_triangle) .. testoutput:: - [False, True, True, True, True, True, True, True, True, True] + [[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.] + [ 1. 1. 1. 1. 1. 1. 1. 1. 1. nan] + [ 1. 1. 1. 1. 1. 1. 1. 1. nan nan] + [ 1. 1. 1. 1. 1. 1. 1. nan nan nan] + [ 1. 1. 1. 1. 1. 1. nan nan nan nan] + [ 1. 1. 1. 1. 1. nan nan nan nan nan] + [ 1. 1. 1. 1. nan nan nan nan nan nan] + [ 1. 1. 1. nan nan nan nan nan nan nan] + [ 1. 1. nan nan nan nan nan nan nan nan] + [ 1. nan nan nan nan nan nan nan nan nan]] """ xp = self.get_array_module() if self.is_pattern or self.is_ultimate: @@ -603,20 +609,26 @@ def get_array_module( Examples -------- + The returned module is the same object as ``numpy`` or ``sparse``, + matching the Triangle's ``array_backend``. + .. testsetup:: import chainladder as cl .. testcode:: + import numpy as np + import sparse as sp + raa = cl.load_sample('raa') - print(raa.get_array_module().__name__) - print(raa.set_backend('sparse').get_array_module().__name__) + print(raa.get_array_module() is np) + print(raa.set_backend('sparse').get_array_module() is sp) .. testoutput:: - numpy - sparse + True + True """ backend: str = ( @@ -759,23 +771,14 @@ def compute(self, *args, **kwargs): Examples -------- - Numpy-backed Triangles are already materialized, so ``compute`` - returns the same object. - - .. testsetup:: + Numpy- and sparse-backed Triangles are already materialized. + ``compute`` exists to realize a lazy dask array. The dask backend is + deprecated and optional, so that path is shown as a code sample: - import chainladder as cl + .. code-block:: python - .. testcode:: - - raa = cl.load_sample('raa') - print(raa.compute() is raa) - print(raa.compute() == raa) - - .. testoutput:: - - True - True + tri = cl.load_sample('raa').set_backend('dask') + tri = tri.compute() """ if hasattr(self.values, "chunks"): obj = self.copy() From e048d013e9d0631bae3fe8882b3e78cdb7f33072 Mon Sep 17 00:00:00 2001 From: Ethan Kang Date: Thu, 13 Aug 2026 20:51:18 -0700 Subject: [PATCH 3/3] Fix E721 in TriangleBase, show a fake compute() output, and align the autosummary template. The ruff workflow lints touched files with per-file ignores cleared. The template now unions the documented Triangle attrs and arithmetic dunders so #1208, #1212, and #1213 do not clobber each other on merge. Co-authored-by: Cursor --- chainladder/core/base.py | 22 +++++++++---------- .../autosummary/class_inherited.rst | 3 ++- pyproject.toml | 1 - 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/chainladder/core/base.py b/chainladder/core/base.py index cbc029720..b9d099d54 100644 --- a/chainladder/core/base.py +++ b/chainladder/core/base.py @@ -220,13 +220,11 @@ def _aggregate_data( columns: list ): """Summarize dataframe to the level specified in axes""" - if type(data) != pd.DataFrame: - # A non-pandas input that reaches this branch is a Dask dataframe. - # Only the Dask backend is deprecated, so gate the warning on the - # data's module rather than warning for every pandas subclass that - # also takes this path. stacklevel=3 points the warning at the - # user's Triangle(...) call (warn -> this method -> - # Triangle.__init__ -> user). + if not isinstance(data, pd.DataFrame): + # Non-pandas inputs (typically a Dask dataframe) take this path. + # Warn only when the object's module is dask. stacklevel=3 points + # the warning at the user's Triangle(...) call (warn -> this + # method -> Triangle.__init__ -> user). if type(data).__module__.split(".")[0] == "dask": warnings.warn( _deprecated_backend_message("dask"), @@ -480,7 +478,7 @@ def _to_datetime( target: Series = target_field # If the target field is a period, convert to timestamp. period_end is a boolean that if true, # means that the timestamp should be the end of the period. - if type(target.iloc[0]) == pd.Period: + if isinstance(target.iloc[0], pd.Period): return target.dt.to_timestamp(how={1: "e", 0: "s"}[period_end]) else: datetime_arg: np.ndarray = target_field.unique() @@ -775,10 +773,12 @@ def compute(self, *args, **kwargs): ``compute`` exists to realize a lazy dask array. The dask backend is deprecated and optional, so that path is shown as a code sample: - .. code-block:: python + .. code-block:: pycon - tri = cl.load_sample('raa').set_backend('dask') - tri = tri.compute() + >>> tri = cl.load_sample('raa').set_backend('dask') + >>> tri = tri.compute() + >>> tri.array_backend + 'numpy' """ if hasattr(self.values, "chunks"): obj = self.copy() diff --git a/docs/_templates/autosummary/class_inherited.rst b/docs/_templates/autosummary/class_inherited.rst index 187a35f44..41fc413c4 100644 --- a/docs/_templates/autosummary/class_inherited.rst +++ b/docs/_templates/autosummary/class_inherited.rst @@ -2,11 +2,12 @@ .. currentmodule:: {{ module }} -{% set documented_attrs = ['shape', 'empty', 'dimensionality', 'nan_triangle'] %} +{% set documented_attrs = ['loc', 'iloc', 'at', 'iat', 'shape', 'empty', 'dimensionality', 'nan_triangle'] %} {% set hidden_attrs = attributes | reject('in', documented_attrs) | list %} .. autoclass:: {{ objname }} :members: :inherited-members: :undoc-members: + :special-members: __add__, __sub__, __mul__, __truediv__ :exclude-members: set_fit_request, set_predict_request, set_score_request, set_transform_request{% if hidden_attrs %}, {{ hidden_attrs | join(', ') }}{% endif %} diff --git a/pyproject.toml b/pyproject.toml index 815c125f6..5bf087895 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,7 +114,6 @@ select = ["E4", "E7", "E9", "F"] "chainladder/adjustments/tests/test_berqsherm.py" = ["F841"] "chainladder/adjustments/tests/test_disposal.py" = ["F841"] "chainladder/adjustments/trend.py" = ["F401"] -"chainladder/core/base.py" = ["E721"] "chainladder/core/common.py" = ["F401"] "chainladder/core/correlation.py" = ["E741"] "chainladder/core/dunders.py" = ["E721", "E722", "F841"]