Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/casact/chainladder-python
Browse files Browse the repository at this point in the history
…into 497
  • Loading branch information
kennethshsu committed Mar 26, 2024
2 parents 9c741dd + 0a0d0cb commit 52eec77
Show file tree
Hide file tree
Showing 16 changed files with 1,761 additions and 17 deletions.
27 changes: 26 additions & 1 deletion chainladder/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# 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/.
import pandas as pd
from packaging import version

import numpy as np
from chainladder.utils.cupy import cp
from chainladder.utils.sparse import sp
Expand Down Expand Up @@ -402,7 +404,30 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
return obj
else:
raise NotImplementedError()


def _interchange_dataframe(self, data):
"""
Convert an object supporting the __dataframe__ protocol to a pandas DataFrame.
Requires pandas version > 1.5.2.
Parameters
----------
data :
Dataframe object supporting the __dataframe__ protocol.
Returns
-------
out: pd.DataFrame
A pandas DataFrame.
"""
# Check if pandas version is greater than 1.5.2
if version.parse(pd.__version__) >= version.parse("1.5.2"):
return pd.api.interchange.from_dataframe(data)

else:
# Raise an error prompting the user to upgrade pandas
raise NotImplementedError("Your version of pandas does not support the DataFrame interchange API. "
"Please upgrade pandas to a version greater than 1.5.2 to use this feature.")

def __array_function__(self, func, types, args, kwargs):
from chainladder.utils.utility_functions import concat

Expand Down
36 changes: 36 additions & 0 deletions chainladder/core/tests/test_triangle.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import chainladder as cl
import pandas as pd
import polars as pl
import numpy as np
import copy
import pytest
Expand Down Expand Up @@ -745,6 +746,9 @@ def test_halfyear_development():
["2012-01-01", "2013-12-31", "incurred", 200.0],
]

df_polars = pl.DataFrame(data)
df_polars.columns = ["origin", "val_date", "idx", "value"]

assert (
type(
cl.Triangle(
Expand All @@ -757,3 +761,35 @@ def test_halfyear_development():
)
)
) == cl.Triangle

assert (
type(
cl.Triangle(
data=df_polars,
index="idx",
columns="value",
origin="origin",
development="val_date",
cumulative=True,
)
)
) == cl.Triangle

assert (
cl.Triangle(
data=pd.DataFrame(data, columns=["origin", "val_date", "idx", "value"]),
index="idx",
columns="value",
origin="origin",
development="val_date",
cumulative=True,
) ==
cl.Triangle(
data=df_polars,
index="idx",
columns="value",
origin="origin",
development="val_date",
cumulative=True,
)
)
8 changes: 5 additions & 3 deletions chainladder/core/triangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ def __init__(
):
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
)
Expand Down Expand Up @@ -274,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"""
Expand Down Expand Up @@ -804,7 +806,7 @@ def broadcast_axis(self, axis, value):
return self

def copy(self):
X = Triangle()
X = object.__new__(self.__class__)
X.__dict__.update(vars(self))
X._set_slicers()
X.values = X.values.copy()
Expand Down
4 changes: 2 additions & 2 deletions chainladder/methods/chainladder.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def fit(self, X, y=None, sample_weight=None):
return self

def predict(self, X, sample_weight=None):
"""Predicts the Benktander ultimate on a new triangle **X**
"""Predicts the chainladder ultimate on a new triangle **X**
Parameters
----------
Expand All @@ -61,7 +61,7 @@ def predict(self, X, sample_weight=None):
Returns
-------
X_new: Triangle
Loss data with Benktander ultimate applied
Loss data with chainladder ultimate applied
"""
X_new = super().predict(X, sample_weight)
X_new.ultimate_ = self._get_ultimate(X_new, sample_weight)
Expand Down
4 changes: 2 additions & 2 deletions chainladder/methods/expectedloss.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def __init__(self, apriori=1.0, apriori_sigma=0.0, random_state=None):
self.random_state = random_state

def fit(self, X, y=None, sample_weight=None):
"""Applies the Bornhuetter-Ferguson technique to triangle **X**
"""Applies the Benktander technique to triangle **X**
Parameters
----------
Expand All @@ -60,7 +60,7 @@ def fit(self, X, y=None, sample_weight=None):
return self

def predict(self, X, sample_weight=None):
"""Predicts the Bornhuetter-Ferguson ultimate on a new triangle **X**
"""Predicts the Benktander ultimate on a new triangle **X**
Parameters
----------
Expand Down
4 changes: 2 additions & 2 deletions chainladder/methods/tests/test_predict.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def test_misaligned_index2(clrd):
b = bcc.predict(clrd.iloc[150], sample_weight=w.iloc[150]).ultimate_.sum().sum()
assert abs(a - b) < 1e-5

def align_cdfs():
def test_align_cdfs():
ld = cl.load_sample('raa').latest_diagonal*0+40000
model = cl.BornhuetterFerguson().fit(cl.load_sample('raa'), sample_weight=ld)
a = model.ultimate_.iloc[..., :4, :]
Expand All @@ -127,6 +127,6 @@ def align_cdfs():
assert a == b


def check_val_tri_cl(raa):
def test_check_val_tri_cl(raa):
model = cl.Chainladder().fit(raa.dev_to_val())
assert model.predict(raa.latest_diagonal).ultimate_ == model.ultimate_
1 change: 1 addition & 0 deletions ci/environment-latest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ channels:

dependencies:
- conda-forge::pandas>=2.0
- polars
- scikit-learn>=0.23
- sparse>=0.9
- numba
Expand Down
1 change: 1 addition & 0 deletions ci/environment-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ channels:

dependencies:
- pandas>=0.23
- polars>=0.16.2
- scikit-learn>=0.23
- sparse>=0.9
- numba
Expand Down
2 changes: 1 addition & 1 deletion conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def prism(request):
@pytest.fixture
def prism_dense(request):
if request.param == "sparse_only_run":
cl.options.set_option('ARRAY_BACKEND', 'numpy')
cl.options.set_option('ARRAY_BACKEND', 'sparse')
else:
cl.options.set_option('ARRAY_BACKEND', 'numpy')
return cl.load_sample('prism').sum()
Expand Down
2 changes: 1 addition & 1 deletion docs/gallery/plot_benktander.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"metadata": {},
"source": [
"This example demonstrates the relationship between the Chainladder and\n",
"BornhuetterFerguson methods by way fo the Benktander model. Each is a\n",
"BornhuetterFerguson methods by way of the Benktander model. Each is a\n",
"special case of the Benktander model where ``n_iters = 1`` for BornhuetterFerguson\n",
"and as ``n_iters`` approaches infinity yields the chainladder. As ``n_iters``\n",
"increases the apriori selection becomes less relevant regardless of initial\n",
Expand Down
2 changes: 1 addition & 1 deletion docs/user_guide/adjustments.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@
"vertical line estimates as well as the more common effective date estimates.\n",
"\n",
"```{eval-rst}\n",
".. image:: images/onlevel.PNG\n",
".. image:: ../images/onlevel.PNG\n",
"```\n",
"\n",
"### Rate History\n",
Expand Down
2 changes: 1 addition & 1 deletion docs/user_guide/development.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -5459,7 +5459,7 @@
"which assumes no ``valuation`` covariate, the PTF framework allows for this.\n",
"\n",
"```{eval-rst}\n",
".. figure:: images/prob_trend_family.PNG\n",
".. figure:: ../images/prob_trend_family.PNG\n",
" :align: center\n",
" :scale: 40%\n",
"```\n",
Expand Down
1,681 changes: 1,678 additions & 3 deletions docs/user_guide/triangle.ipynb

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions docs/user_guide/workflow.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -1653,6 +1653,8 @@
"Using `GridSearch` for scenario testing is entirely optional. You can write\n",
"your own looping mechanisms to achieve the same result. For example:\n",
"\n",
"<!-- TODO: what is supposed to be showing here? -->\n",
"\n",
"```{eval-rst}\n",
".. figure:: /auto_examples/images/sphx_glr_plot_capecod_001.png\n",
" :target: ../auto_examples/plot_capecod.html\n",
Expand Down
1 change: 1 addition & 0 deletions environment-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies:
- ipykernel

- pandas
- polars
- scikit-learn
- sparse
- numba
Expand Down
1 change: 1 addition & 0 deletions environment-docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies:
- git

- pandas
- polars
- scikit-learn
- sparse
- numba
Expand Down

0 comments on commit 52eec77

Please sign in to comment.