Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions chainladder/adjustments/disposal.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,3 +339,28 @@ def fit_transform(self, X, y=None, sample_weight=None):
Triangle with new full_triangle_
"""
return self.fit(X, y, sample_weight).transform(X, sample_weight=sample_weight)

def _param_property(self, X: Triangle, params: np.ndarray) -> Triangle:
"""
Private method to wrap a numpy array into a Triangle

Uses and overrides DevelopmentBase._param_property.

Parameters
----------
X : Triangle
The Triangle object that we want to place the parameter into

params : np.ndarray
The parammeter we want to turn into a Triangle

Returns
-------
Triangle
A Triangle that mimics X but has params values
"""
obj = super()._param_property(X, params)
obj.values = params
obj.is_disposal_rate = True

return obj
2 changes: 2 additions & 0 deletions chainladder/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from chainladder.utils.sparse import sp

from chainladder.adjustments.disposal import DisposalMixin
from chainladder.development.incremental import IncrementalMixin

from typing import Optional, TYPE_CHECKING

Expand All @@ -50,6 +51,7 @@ class TriangleBase(
Common,
ABC,
DisposalMixin,
IncrementalMixin,
):
"""This class handles the initialization of a triangle"""

Expand Down
9 changes: 1 addition & 8 deletions chainladder/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def has_ldf(self):

@property
def has_zeta(self):
if hasattr(self, "zeta_"):
if hasattr(self, "_zeta_"):
return True
else:
return False
Expand Down Expand Up @@ -159,13 +159,6 @@ def pct_unreported_(self):
)
return 1 - 1 / self.cdf_

@property
def cum_zeta_(self):
if not self.has_zeta:
x = self.__class__.__name__
raise AttributeError("'" + x + "' object has no attribute 'cum_zeta_'")
return self.zeta_.incr_to_cum()

@property
def ibnr_(self):
"""
Expand Down
4 changes: 2 additions & 2 deletions chainladder/core/tests/test_triangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -3200,7 +3200,7 @@ def test_has_zeta_true(raa: Triangle) -> None:
None
"""
fitted = cl.IncrementalAdditive().fit(raa, sample_weight=raa.latest_diagonal)
assert fitted.has_zeta is True
assert fitted.has_zeta


def test_has_zeta_false(raa: Triangle) -> None:
Expand All @@ -3216,7 +3216,7 @@ def test_has_zeta_false(raa: Triangle) -> None:
-------
None
"""
assert cl.Development().fit(raa).has_zeta is False
assert not cl.Development().fit(raa).has_zeta


def test_cum_zeta_raises_when_no_zeta(raa: Triangle) -> None:
Expand Down
69 changes: 53 additions & 16 deletions chainladder/core/triangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,17 @@ def is_disposal_rate(self) -> bool:
def is_disposal_rate(self, is_dr: bool) -> None:
self._is_disposal_rate = is_dr

@property
def is_additive(self) -> bool:
"""
Indicates whether the Triangle holds a normal additive ratio (which goes from tail to head)
"""
return getattr(self, "_is_additive", False)

@is_additive.setter
def is_additive(self, is_add: bool) -> None:
self._is_additive = is_add

def align_pattern(
self, X: Triangle, sample_weight: Triangle | None = None
) -> Triangle:
Expand Down Expand Up @@ -1377,20 +1388,30 @@ def incr_to_cum(self, inplace=False):
if inplace:
xp = self.get_array_module()
if not self.is_cumulative:
if self.is_pattern & (not self.is_disposal_rate):
if hasattr(self, "is_additive") and self.is_additive:
values = xp.nan_to_num(self.values[..., ::-1])
values = num_to_value(values, 0)
self.values = (
xp.cumsum(values, -1)[..., ::-1] * self.nan_triangle
)
else:
values = xp.nan_to_num(self.values[..., ::-1])
values = num_to_value(values, 1)
values = xp.cumprod(values, -1)[..., ::-1]
self.values = values * self.nan_triangle
values = num_to_value(values, self.get_array_module(values).nan)
else:
# fmt: off
if (
self.is_pattern
and (not self.is_additive)
and (not self.is_disposal_rate)
): # for multiplicative ldf triangles (mult from left to right)
# fmt: on
values = xp.nan_to_num(self.values[..., ::-1])
values = num_to_value(values, 1)
values = xp.cumprod(values, -1)[..., ::-1]
self.values = values * self.nan_triangle
values = num_to_value(values, self.get_array_module(values).nan)
# fmt: off
elif (
self.is_pattern
and self.is_additive
and (not self.is_disposal_rate)
): # for additive factor triangles (sum from left to right)
# fmt: on
values = xp.nan_to_num(self.values[..., ::-1])
values = num_to_value(values, 0)
self.values = (xp.cumsum(values, -1)[..., ::-1] * self.nan_triangle)
else: # for normal value triangles
# also for disposal rate triangles (sum from right to left)
if self.array_backend not in ["sparse", "dask"]:
self.values = (
xp.cumsum(xp.nan_to_num(self.values), 3)
Expand Down Expand Up @@ -1466,7 +1487,13 @@ def cum_to_incr(self, inplace=False):
if inplace:
v = self.valuation_date
if self.is_cumulative or self.is_cumulative is None:
if self.is_pattern & (not self.is_disposal_rate):
# fmt: off
if (
self.is_pattern
and (not self.is_additive)
and (not self.is_disposal_rate)
): # for multiplicative ldf triangles (div from left to right)
# fmt: on
xp = self.get_array_module()
self.values = num_to_value(xp.nan_to_num(self.values), 1)
diff = self.iloc[..., :-1] / self.iloc[..., 1:].values
Expand All @@ -1478,7 +1505,17 @@ def cum_to_incr(self, inplace=False):
axis=3,
)
self.values = self.values * self.nan_triangle
else:
# fmt: fn
elif (
self.is_pattern
and self.is_additive
and (not self.is_disposal_rate)
): # for additive factor triangles (diff from left to right)
# fmt: on
diff = self.iloc[..., :-1] - self.iloc[..., 1:].values
self = concat((diff, self.iloc[..., -1]), axis=3)
Comment thread
cursor[bot] marked this conversation as resolved.
else: # for normal value triangles
# also for disposal rate triangles (diff from right to left)
diff = self.iloc[..., 1:] - self.iloc[..., :-1].values
self = concat((self.iloc[..., 0], diff), axis=3)
self.is_cumulative = False
Expand Down
3 changes: 0 additions & 3 deletions chainladder/development/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,11 +409,8 @@ def _param_property(X: Triangle, params: np.ndarray) -> Triangle:
from chainladder import options

obj: Triangle = X[X.origin == X.origin.min()]
obj.values = params
obj.valuation_date = pd.to_datetime(options.ULT_VAL)
obj.is_pattern = True
obj.is_additive = True
obj.is_cumulative = False
obj.virtual_columns.columns = {}
obj._set_slicers()
return obj
25 changes: 18 additions & 7 deletions chainladder/development/development.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from __future__ import annotations

import numpy as np
import pandas as pd
import warnings

from chainladder.development.base import DevelopmentBase
Expand Down Expand Up @@ -482,16 +481,28 @@ def transform(self, X):
return X_new

def _param_property(self, X, params, idx):
from chainladder import options
"""
Private method to wrap a numpy array into a Triangle

Uses and overrides DevelopmentBase._param_property.

Parameters
----------
X : Triangle
The Triangle object that we want to place the parameter into

params : np.ndarray
The parammeter we want to turn into a Triangle

obj = X[X.origin == X.origin.min()]
Returns
-------
Triangle
A Triangle that mimics X but has params values
"""
obj = super()._param_property(X, params)
xp = X.get_array_module()
obj.values = xp.ones(obj.shape)[..., :-1] * params[..., idx : idx + 1, :]
obj.ddims = X.link_ratio.ddims
obj.valuation_date = pd.to_datetime(options.ULT_VAL)
obj.is_pattern = True
obj.is_cumulative = False
obj.virtual_columns.columns = {}
obj._set_slicers()

return obj
82 changes: 80 additions & 2 deletions chainladder/development/incremental.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,66 @@
# 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

from chainladder.development import DevelopmentBase
from chainladder.utils import TriangleWeight
import numpy as np
import copy
import warnings

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from chainladder.core import Triangle

class IncrementalAdditive(DevelopmentBase):

class IncrementalMixin:
"""
This class provides attributes for the IncrementalAdditive development method
and transformed `Triangle`
"""

@property
def zeta_(self) -> Triangle:
"""
Gets the estimated incremental additive factors
"""
if not hasattr(self, "_zeta_"):
raise AttributeError("`zeta_` has not been estimated.")
return self._zeta_

@zeta_.setter
def zeta_(self, value: Triangle) -> None:
"""
Sets zeta_
"""
obj = copy.deepcopy(value)
obj.is_pattern = True
obj.is_additive = True
obj.is_cumulative = False
self._zeta_ = obj

@property
def cum_zeta_(self) -> Triangle:
"""
Gets the estimated cumulative additive factors
"""
return self.zeta_.incr_to_cum()

@cum_zeta_.setter
def cum_zeta_(self, value: Triangle) -> None:
"""
Sets cum_zeta_
"""
obj = copy.deepcopy(value)
obj.is_pattern = True
obj.is_additive = True
obj.is_cumulative = True
self._zeta_ = obj.cum_to_incr()


class IncrementalAdditive(DevelopmentBase, IncrementalMixin):
"""
The Incremental Additive Method.

Expand Down Expand Up @@ -311,11 +364,36 @@ def transform(self, X):
for item in [
"ldf_",
"w_",
"zeta_",
"_zeta_",
"incremental_",
"tri_zeta",
"fit_zeta_",
"sample_weight",
]:
X_new.__dict__[item] = self.__dict__[item]
return X_new

def _param_property(self, X, params):
"""
Private method to wrap a numpy array into a Triangle

Uses and overrides DevelopmentBase._param_property.

Parameters
----------
X : Triangle
The Triangle object that we want to place the parameter into

params : np.ndarray
The parammeter we want to turn into a Triangle

Returns
-------
Triangle
A Triangle that mimics X but has params values
"""
obj = super()._param_property(X, params)
obj.values = params
obj.is_additive = True

return obj
Loading
Loading