From 1ed4ae2995852d86327f7bb0fc44093ba377a0bd Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Tue, 21 May 2024 17:28:29 -0500 Subject: [PATCH 01/10] REFACTOR: Remove unused import (copy). --- chainladder/core/triangle.py | 1 - 1 file changed, 1 deletion(-) diff --git a/chainladder/core/triangle.py b/chainladder/core/triangle.py index c8852045..b4205b0d 100644 --- a/chainladder/core/triangle.py +++ b/chainladder/core/triangle.py @@ -4,7 +4,6 @@ import pandas as pd import numpy as np -import copy import warnings from packaging import version from chainladder.core.base import TriangleBase From caa56f9ecfd4ab9d0bdf60cde18804a33f7b8368 Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Tue, 21 May 2024 17:29:27 -0500 Subject: [PATCH 02/10] REFACTOR: Add a more specific import statement. See PEP 8: E722 do not use bare 'except'. --- chainladder/core/triangle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainladder/core/triangle.py b/chainladder/core/triangle.py index b4205b0d..7efe3753 100644 --- a/chainladder/core/triangle.py +++ b/chainladder/core/triangle.py @@ -15,7 +15,7 @@ try: import dask.bag as db -except: +except ImportError: db = None From 1c47d6562e8083d8993da9903b5cfaed80d0a9ba Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Tue, 21 May 2024 17:45:21 -0500 Subject: [PATCH 03/10] REFACTOR: Add type hints. --- chainladder/core/triangle.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/chainladder/core/triangle.py b/chainladder/core/triangle.py index 7efe3753..e9f81c48 100644 --- a/chainladder/core/triangle.py +++ b/chainladder/core/triangle.py @@ -1,6 +1,7 @@ # 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 pandas as pd import numpy as np @@ -18,6 +19,10 @@ except ImportError: db = None +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from pandas import DataFrame + class Triangle(TriangleBase): """ @@ -107,17 +112,17 @@ class Triangle(TriangleBase): def __init__( self, - data=None, - origin=None, - development=None, - columns=None, - index=None, - origin_format=None, - development_format=None, - cumulative=None, + data: DataFrame = None, + origin: str | list = None, + development: str | list = None, + columns: str | list = None, + index: str | list | None = None, + origin_format: str = None, + development_format: str = None, + cumulative: bool = None, array_backend=None, pattern=False, - trailing=True, + trailing: bool = True, *args, **kwargs ): From cafc42947b60ca8fe271317dfbe1d2b7637f6bd9 Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Wed, 22 May 2024 17:13:51 -0500 Subject: [PATCH 04/10] STYLE: Simple style fixes. --- chainladder/core/triangle.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/chainladder/core/triangle.py b/chainladder/core/triangle.py index e9f81c48..cbf56643 100644 --- a/chainladder/core/triangle.py +++ b/chainladder/core/triangle.py @@ -19,7 +19,11 @@ except ImportError: db = None -from typing import TYPE_CHECKING +from typing import ( + Optional, + TYPE_CHECKING +) + if TYPE_CHECKING: from pandas import DataFrame @@ -31,7 +35,7 @@ class Triangle(TriangleBase): Parameters ---------- data: DataFrame - A single dataframe that contains columns represeting all other + A single dataframe that contains columns representing all other arguments to the Triangle constructor origin: str or list A representation of the accident, reporting or more generally the @@ -115,7 +119,7 @@ def __init__( data: DataFrame = None, origin: str | list = None, development: str | list = None, - columns: str | list = None, + columns: Optional[str | list] = None, index: str | list | None = None, origin_format: str = None, development_format: str = None, @@ -196,8 +200,9 @@ def __init__( if cumulative is None: warnings.warn( """ - The cumulative property of your triangle is not set. This may result in - undesirable behavior. In a future release this will result in an error.""" + The cumulative property of your triangle is not set. This may result in + undesirable behavior. In a future release this will result in an error. + """ ) self.is_cumulative = cumulative From ecd2ae6e2342ee46846a5957ebb8881ade9dacb9 Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Wed, 22 May 2024 17:15:49 -0500 Subject: [PATCH 05/10] REFACTOR: Explicitly mark optional arguments. --- chainladder/core/triangle.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/chainladder/core/triangle.py b/chainladder/core/triangle.py index cbf56643..955ad82b 100644 --- a/chainladder/core/triangle.py +++ b/chainladder/core/triangle.py @@ -116,14 +116,14 @@ class Triangle(TriangleBase): def __init__( self, - data: DataFrame = None, - origin: str | list = None, - development: str | list = None, + data: Optional[DataFrame] = None, + origin: Optional[str | list] = None, + development: Optional[str | list] = None, columns: Optional[str | list] = None, - index: str | list | None = None, - origin_format: str = None, - development_format: str = None, - cumulative: bool = None, + index: Optional[str | list] = None, + origin_format: Optional[str] = None, + development_format: Optional[str] = None, + cumulative: Optional[bool] = None, array_backend=None, pattern=False, trailing: bool = True, From 8c1d0570bc2285f1985633357d8aa3de696eddae Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Thu, 23 May 2024 08:21:47 -0500 Subject: [PATCH 06/10] REFACTOR: Add type hinting for _input_validation method. --- chainladder/core/base.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/chainladder/core/base.py b/chainladder/core/base.py index f3e9401a..83448704 100644 --- a/chainladder/core/base.py +++ b/chainladder/core/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 pandas as pd from packaging import version @@ -19,6 +21,11 @@ from chainladder import options from chainladder.utils.utility_functions import num_to_nan, concat +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pandas import DataFrame + class TriangleBase( TriangleIO, TriangleDisplay, TriangleSlicer, TriangleDunders, TrianglePandas, Common @@ -30,10 +37,22 @@ def shape(self): return self.values.shape @staticmethod - def _input_validation(data, index, columns, origin, development): + def _input_validation( + data: DataFrame, + index: str | list, + columns: str | list, + origin: str | list, + development: str | list + ) -> tuple[ + None | list, + None | list, + None | list, + None | list + ]: + """Validate/sanitize inputs""" - def str_to_list(arg): + def str_to_list(arg: str | list) -> None | list: if arg is None: return if type(arg) in [str, pd.Period]: @@ -417,7 +436,7 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): else: raise NotImplementedError() - def _interchange_dataframe(self, data): + def _interchange_dataframe(self, data) -> DataFrame: """ Convert an object supporting the __dataframe__ protocol to a pandas DataFrame. Requires pandas version > 1.5.2. From c3512fffc8e4cf4340cc0b3e0396a0e2c6df5719 Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Thu, 23 May 2024 08:22:02 -0500 Subject: [PATCH 07/10] REFACTOR: Add annotations. --- chainladder/core/triangle.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/chainladder/core/triangle.py b/chainladder/core/triangle.py index 955ad82b..aa3b117c 100644 --- a/chainladder/core/triangle.py +++ b/chainladder/core/triangle.py @@ -130,12 +130,18 @@ def __init__( *args, **kwargs ): + + # If data are present, validate the dimensions. if data is None: 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 + data=data, + index=index, + columns=columns, + origin=origin, + development=development ) self.columns_label = columns @@ -143,7 +149,7 @@ def __init__( # Handle any ultimate vectors in triangles separately data, ult = self._split_ult(data, index, columns, origin, development) - # Conform origins and developments to datetimes and determine lowest grains + # Conform origins and developments to datetimes and determine the lowest grains origin_date = self._to_datetime(data, origin, format=origin_format).rename( "__origin__" ) From e22f8b3558850563ed25aec312d14179607bd48f Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Thu, 23 May 2024 17:11:12 -0500 Subject: [PATCH 08/10] REFACTOR: Add DataFrameXchg annotations. --- chainladder/core/base.py | 4 ++-- chainladder/core/triangle.py | 33 +++++++++++++++++++++++++-------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/chainladder/core/base.py b/chainladder/core/base.py index 83448704..05d0da5f 100644 --- a/chainladder/core/base.py +++ b/chainladder/core/base.py @@ -25,7 +25,7 @@ if TYPE_CHECKING: from pandas import DataFrame - + from pandas.core.interchange.dataframe_protocol import DataFrame as DataFrameXchg class TriangleBase( TriangleIO, TriangleDisplay, TriangleSlicer, TriangleDunders, TrianglePandas, Common @@ -436,7 +436,7 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): else: raise NotImplementedError() - def _interchange_dataframe(self, data) -> DataFrame: + def _interchange_dataframe(self, data: DataFrameXchg) -> DataFrame: """ Convert an object supporting the __dataframe__ protocol to a pandas DataFrame. Requires pandas version > 1.5.2. diff --git a/chainladder/core/triangle.py b/chainladder/core/triangle.py index aa3b117c..ebc874ee 100644 --- a/chainladder/core/triangle.py +++ b/chainladder/core/triangle.py @@ -26,6 +26,7 @@ if TYPE_CHECKING: from pandas import DataFrame + from pandas.core.interchange.dataframe_protocol import DataFrame as DataFrameXchg class Triangle(TriangleBase): @@ -34,9 +35,12 @@ class Triangle(TriangleBase): Parameters ---------- - data: DataFrame + data: DataFrame or DataFrameXchg A single dataframe that contains columns representing all other - arguments to the Triangle constructor + arguments to the Triangle constructor. If using pandas version > 1.5.2, + one may supply a DataFrame-like object (referred to as DataFrameXchg) + supporting the __dataframe__ protocol, which will then be converted to + a pandas DataFrame. origin: str or list A representation of the accident, reporting or more generally the origin period of the triangle that will map to the Origin dimension @@ -116,7 +120,7 @@ class Triangle(TriangleBase): def __init__( self, - data: Optional[DataFrame] = None, + data: Optional[DataFrame | DataFrameXchg] = None, origin: Optional[str | list] = None, development: Optional[str | list] = None, columns: Optional[str | list] = None, @@ -144,11 +148,18 @@ def __init__( development=development ) - self.columns_label = columns - self.origin_label = origin + # Store dimension metadata. + self.columns_label: list = columns + self.origin_label: list = origin - # Handle any ultimate vectors in triangles separately - data, ult = self._split_ult(data, index, columns, origin, development) + # Handle any ultimate vectors in triangles separately. + data, ult = self._split_ult( + data=data, + index=index, + columns=columns, + origin=origin, + development=development + ) # Conform origins and developments to datetimes and determine the lowest grains origin_date = self._to_datetime(data, origin, format=origin_format).rename( "__origin__" @@ -293,7 +304,13 @@ def __init__( self.valuation_date = pd.Timestamp(options.ULT_VAL) @staticmethod - def _split_ult(data, index, columns, origin, development): + def _split_ult( + data: DataFrame, + index: list, + columns: list, + origin: list, + development: list + ): """Deal with triangles with ultimate values""" ult = None if ( From 47472b6de30c5e6594b0be45db96a856533278ef Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Thu, 23 May 2024 17:11:59 -0500 Subject: [PATCH 09/10] CHORE: Remove unused imports. --- chainladder/core/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/chainladder/core/base.py b/chainladder/core/base.py index 05d0da5f..aa5acb94 100644 --- a/chainladder/core/base.py +++ b/chainladder/core/base.py @@ -19,7 +19,6 @@ from chainladder.core.io import TriangleIO from chainladder.core.common import Common from chainladder import options -from chainladder.utils.utility_functions import num_to_nan, concat from typing import TYPE_CHECKING From e7e214554627b748cff694b1a2981a58b73957fa Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Fri, 24 May 2024 07:56:05 -0500 Subject: [PATCH 10/10] DOCS: Add annotations. --- chainladder/core/base.py | 12 ++++++++++-- chainladder/core/triangle.py | 8 ++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/chainladder/core/base.py b/chainladder/core/base.py index aa5acb94..61683366 100644 --- a/chainladder/core/base.py +++ b/chainladder/core/base.py @@ -20,7 +20,10 @@ from chainladder.core.common import Common from chainladder import options -from typing import TYPE_CHECKING +from typing import ( + Optional, + TYPE_CHECKING +) if TYPE_CHECKING: from pandas import DataFrame @@ -237,7 +240,12 @@ def nan_triangle(self): return nan_triangle @staticmethod - def _to_datetime(data, fields, period_end=False, format=None): + def _to_datetime( + data: DataFrame, + fields: list, + period_end: bool = False, + format: Optional[str] = None + ): """For tabular form, this will take a set of data column(s) and return a single date array. This function heavily relies on pandas, but does two additional things: diff --git a/chainladder/core/triangle.py b/chainladder/core/triangle.py index ebc874ee..28f6128f 100644 --- a/chainladder/core/triangle.py +++ b/chainladder/core/triangle.py @@ -161,7 +161,11 @@ def __init__( development=development ) # Conform origins and developments to datetimes and determine the lowest grains - origin_date = self._to_datetime(data, origin, format=origin_format).rename( + origin_date = self._to_datetime( + data=data, + fields=origin, + format=origin_format + ).rename( "__origin__" ) self.origin_grain = self._get_grain( @@ -310,7 +314,7 @@ def _split_ult( columns: list, origin: list, development: list - ): + ) -> tuple[DataFrame, Triangle]: """Deal with triangles with ultimate values""" ult = None if (