Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
121 changes: 106 additions & 15 deletions chainladder/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
)

if TYPE_CHECKING:
from numpy.typing import ArrayLike
from chainladder.core.typing import TriangleLike


Expand Down Expand Up @@ -95,6 +94,27 @@ def has_zeta(self):

@property
def cdf_(self):
"""Cumulative development factors, ``ldf_`` converted with ``incr_to_cum``.

Examples
--------
After fitting a development estimator, ``cdf_`` is the cumulative
product of the selected LDFs, including the tail if one was applied.

.. testsetup::

import chainladder as cl

.. testcode::

import numpy as np
cdf = cl.Development().fit_transform(cl.load_sample('raa')).cdf_
print(np.round(cdf.values[0, 0, 0, :3], 4).tolist())

.. testoutput::

[8.9202, 2.974, 1.8318]
"""
if not self.has_ldf:
x = self.__class__.__name__
raise AttributeError("'" + x + "' object has no attribute 'cdf_'")
Expand All @@ -103,7 +123,27 @@ def cdf_(self):
@property
def pct_reported_(self):
"""Percentage of ultimate reported (or paid) at each development age,
equal to the inverse of the cumulative development factor."""
equal to the inverse of the cumulative development factor.

Examples
--------
At 12 months, RAA volume-weighted development implies about 11% of
ultimate is reported.

.. testsetup::

import chainladder as cl

.. testcode::

import numpy as np
pct = cl.Development().fit_transform(cl.load_sample('raa')).pct_reported_
print(np.round(pct.values[0, 0, 0, :3], 4).tolist())

.. testoutput::

[0.1121, 0.3362, 0.5459]
"""
if not self.has_ldf:
x = self.__class__.__name__
raise AttributeError("'" + x + "' object has no attribute 'pct_reported_'")
Expand All @@ -127,6 +167,27 @@ def cum_zeta_(self):

@property
def ibnr_(self):
"""Outstanding development to ultimate: ``ultimate_`` minus the latest
diagonal (or the origin total, for incremental triangles).

Examples
--------
Chainladder IBNR is zero for the oldest origin once that year is fully
developed, and largest for the youngest origin.

.. testsetup::

import chainladder as cl

.. testcode::

ibnr = cl.Chainladder().fit(cl.load_sample('raa')).ibnr_
print(ibnr.to_frame(origin_as_datetime=False).round(2).iloc[:, 0].tolist())

.. testoutput::

[nan, 153.95, 617.37, 1636.14, 2746.74, 3649.1, 5435.3, 10907.19, 10649.98, 16339.44]
"""
if not hasattr(self, "ultimate_"):
x = self.__class__.__name__
raise AttributeError("'" + x + "' object has no attribute 'ibnr_'")
Expand Down Expand Up @@ -189,20 +250,29 @@ def pipe(self, func, *args, **kwargs):
--------
Keep development periods from 48 onward:

>>> import chainladder as cl
>>> raa = cl.load_sample('raa')
>>> raa.pipe(lambda tri: tri.loc[..., 48:])
.. testsetup::

import chainladder as cl

.. testcode::

raa = cl.load_sample('raa')
print(raa.pipe(lambda tri: tri.loc[..., 48:]))

.. testoutput::
:options: +NORMALIZE_WHITESPACE

48 60 72 84 96 108 120
1981 11805.0 13539.0 16181.0 18009.0 18608.0 18662.0 18834.0
1982 10666.0 13782.0 15599.0 15496.0 16169.0 16704.0 NaN
1983 16141.0 18735.0 22214.0 22863.0 23466.0 NaN NaN
1984 21266.0 23425.0 26083.0 27067.0 NaN NaN NaN
1985 22169.0 25955.0 26180.0 NaN NaN NaN NaN
1986 12935.0 15852.0 NaN NaN NaN NaN NaN
1987 12314.0 NaN NaN NaN NaN NaN NaN
1988 NaN NaN NaN NaN NaN NaN NaN
1989 NaN NaN NaN NaN NaN NaN NaN
1990 NaN NaN NaN NaN NaN NaN NaN
1981 11805.0 13539.0 16181.0 18009.0 18608.0 18662.0 18834.0
1982 10666.0 13782.0 15599.0 15496.0 16169.0 16704.0 NaN
1983 16141.0 18735.0 22214.0 22863.0 23466.0 NaN NaN
1984 21266.0 23425.0 26083.0 27067.0 NaN NaN NaN
1985 22169.0 25955.0 26180.0 NaN NaN NaN NaN
1986 12935.0 15852.0 NaN NaN NaN NaN NaN
1987 12314.0 NaN NaN NaN NaN NaN NaN
1988 NaN NaN NaN NaN NaN NaN NaN
1989 NaN NaN NaN NaN NaN NaN NaN
1990 NaN NaN NaN NaN NaN NaN NaN
"""
return func(self, *args, **kwargs)

Expand Down Expand Up @@ -230,6 +300,27 @@ def set_backend(
Returns
-------
Triangle with updated array_backend

Examples
--------
``set_backend`` returns a new Triangle unless ``inplace=True``.

.. testsetup::

import chainladder as cl

.. testcode::

raa = cl.load_sample('raa')
print(raa.array_backend)
print(raa.set_backend('sparse').array_backend)
print(raa.array_backend)

.. testoutput::

numpy
sparse
numpy
"""
# Warn once, at the public entry point, so stacklevel=2 points at the
# user's call site rather than an internal recursive call. The _warn
Expand Down
34 changes: 21 additions & 13 deletions chainladder/core/triangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
from numpy.typing import ArrayLike
from pandas._libs.tslibs.timestamps import Timestamp # noqa
from pandas.core.interchange.dataframe_protocol import DataFrame as DataFrameXchg
from sparse import COO


class Triangle(TriangleBase):
Expand Down Expand Up @@ -133,6 +132,11 @@ class Triangle(TriangleBase):
Transpose index and columns of object. Only available when Triangle is
convertible to DataFrame.

See Also
--------
Development : Fitted development patterns, including ``ldf_`` and ``cdf_``.
Chainladder : Fitted chainladder results, including ``ultimate_`` and ``ibnr_``.

Examples
--------

Expand Down Expand Up @@ -442,7 +446,7 @@ def __init__(
# If data are present, validate the dimensions.
if data is None:
return
elif type(data) == dict:
elif isinstance(data, dict):
data = pd.DataFrame(data)
elif not isinstance(data, pd.DataFrame) and hasattr(data, "__dataframe__"):
data = self._interchange_dataframe(data)
Expand Down Expand Up @@ -510,11 +514,11 @@ def __init__(

# Ensure that origin_date values represent the beginning of the period.
# i.e., 1990 means the start of 1990.
origin_date: Series = to_period(origin_date,self.origin_grain).dt.to_timestamp(how="s")
origin_date: Series = to_period(origin_date, self.origin_grain).dt.to_timestamp(how="s")

# Ensure that development_date values represent the end of the period.
# i.e., 1990 means the end of 1990 assuming annual development periods.
development_date: Series = to_period(development_date,self.development_grain).dt.to_timestamp(how="e")
development_date: Series = to_period(development_date, self.development_grain).dt.to_timestamp(how="e")

# Aggregate dates to the origin/development grains.
data_agg: DataFrame = self._aggregate_data(
Expand Down Expand Up @@ -924,7 +928,7 @@ def is_val_tri(self):

True
"""
return type(self.ddims) == pd.DatetimeIndex
return isinstance(self.ddims, pd.DatetimeIndex)

@property
def is_full(self) -> bool:
Expand Down Expand Up @@ -1033,7 +1037,7 @@ def is_disposal_rate(self) -> bool:
def is_disposal_rate(self, is_dr: bool) -> None:
self._is_disposal_rate = is_dr

def align_pattern(self, X:Triangle, sample_weight:Triangle|None=None) -> Triangle:
def align_pattern(self, X: Triangle, sample_weight: Triangle | None = None) -> Triangle:
"""
Vertically align a selected pattern to origin period latest diagonal. Triangle must be a selected pattern.

Expand Down Expand Up @@ -1370,9 +1374,14 @@ def incr_to_cum(self, inplace=False):
else:
values = xp.nan_to_num(self.values)
nan_triangle = xp.nan_to_num(self.nan_triangle)
l1 = lambda i: values[..., 0 : i + 1]
l2 = lambda i: l1(i) * nan_triangle[..., i : i + 1]
l3 = lambda i: l2(i).sum(3, keepdims=True)
def l1(i):
return values[..., 0 : i + 1]

def l2(i):
return l1(i) * nan_triangle[..., i : i + 1]

def l3(i):
return l2(i).sum(3, keepdims=True)
if db:
_warn_dask_parallel_deprecated()
bag = db.from_sequence(range(self.shape[-1]))
Expand Down Expand Up @@ -1432,7 +1441,6 @@ def cum_to_incr(self, inplace=False):
if self.is_pattern & (not self.is_disposal_rate):
xp = self.get_array_module()
self.values = xp.nan_to_num(self.values)
values = num_to_value(self.values, 1)
Comment thread
cursor[bot] marked this conversation as resolved.
diff = self.iloc[..., :-1] / self.iloc[..., 1:].values
self = concat(
(
Expand Down Expand Up @@ -1484,7 +1492,7 @@ def _val_dev(self, sign, inplace=False):
)
ddims = np.max([np.max(obj.values.coords[-1]) + 1, ddims])
obj.values.shape = tuple(list(obj.shape[:-1]) + [ddims])
if options.AUTO_SPARSE == False or backend == "cupy":
if not options.AUTO_SPARSE or backend == "cupy":
obj = obj.set_backend(backend)
else:
obj = obj._auto_sparse()
Expand Down Expand Up @@ -1812,11 +1820,11 @@ def grain(self, grain="", trailing=False, inplace=False):
).to_timestamp(how="s")

if dgrain_old == "S":
d_start = d_start + pd.DateOffset(months=-3)
d_start = d_start + pd.DateOffset(months=-3)

if len(obj.ddims) > 1 and obj.origin.to_timestamp(how="s")[0] != d_start:
addl_ts = (
pd.period_range(obj.odims[0], obj.valuation[0], freq=dgrain_old.replace("S","2Q"))[
pd.period_range(obj.odims[0], obj.valuation[0], freq=dgrain_old.replace("S", "2Q"))[
:-1
]
.to_timestamp()
Expand Down
19 changes: 11 additions & 8 deletions chainladder/development/development.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ class Development(DevelopmentBase):
The estimated loss development patterns
cdf_: Triangle
The estimated cumulative development patterns
pct_reported_: Triangle
The estimated percent of ultimate reported (or paid) at each
development age
sigma_: Triangle
Sigma of the ldf regression
std_err_: Triangle
Expand Down Expand Up @@ -396,14 +399,14 @@ def fit(self, X: TriangleLike, y: None = None, sample_weight: None = None):
link_ratio: ArrayLike = y / x

tw = TriangleWeight(
n_periods = self.n_periods,
drop_high = self.drop_high,
drop_low = self.drop_low,
drop_above = self.drop_above,
drop_below = self.drop_below,
drop_valuation = self.drop_valuation,
preserve = self.preserve,
drop = self.drop
n_periods=self.n_periods,
drop_high=self.drop_high,
drop_low=self.drop_low,
drop_above=self.drop_above,
drop_below=self.drop_below,
drop_valuation=self.drop_valuation,
preserve=self.preserve,
drop=self.drop
)

if hasattr(X, "w_v2_"):
Expand Down
5 changes: 4 additions & 1 deletion docs/_templates/autosummary/class.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

.. currentmodule:: {{ module }}

{% set documented_attrs = ['cdf_', 'ibnr_', 'pct_reported_'] %}
{% set hidden_attrs = attributes | reject('in', documented_attrs) | list %}

.. autoclass:: {{ objname }}
: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 %}

{% set inherited = [] %}
{% for method in methods %}
Expand Down
6 changes: 5 additions & 1 deletion docs/_templates/autosummary/class_inherited.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

.. currentmodule:: {{ module }}

{% 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:
:exclude-members: set_fit_request, set_predict_request, set_score_request, set_transform_request, {{ attributes | join(', ') }}
: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 %}
3 changes: 0 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ select = ["E2", "E4", "E7", "E9", "F"]
"chainladder/adjustments/tests/test_disposal.py" = ["E226", "E231", "E241", "E251", "E265", "F841"]
"chainladder/adjustments/trend.py" = ["F401"]
"chainladder/core/base.py" = ["E221", "E231", "E721"]
"chainladder/core/common.py" = ["F401"]
"chainladder/core/correlation.py" = ["E741"]
"chainladder/core/display.py" = ["E203", "E252"]
"chainladder/core/dunders.py" = ["E721", "E722", "F841"]
Expand All @@ -134,12 +133,10 @@ select = ["E2", "E4", "E7", "E9", "F"]
"chainladder/core/tests/test_grain.py" = ["E265", "F401", "F841"]
"chainladder/core/tests/test_slicing.py" = ["E203", "E225"]
"chainladder/core/tests/test_triangle.py" = ["E201", "E221", "E225", "E226", "E231", "E241", "E251", "E265", "E712", "E721", "F811", "F841"]
"chainladder/core/triangle.py" = ["E222", "E227", "E231", "E252", "E712", "E721", "E731", "F401", "F841"]
"chainladder/development/barnzehn.py" = ["E201", "E202", "E231", "E251", "E275"]
"chainladder/development/base.py" = ["E712", "F401", "F841"]
"chainladder/development/clark.py" = ["E721", "E731"]
"chainladder/development/constant.py" = ["E712"]
"chainladder/development/development.py" = ["E251"]
"chainladder/development/glm.py" = ["E225", "E231", "E251", "F401"]
"chainladder/development/incremental.py" = ["E226", "E231", "E251", "E265", "E721", "F401"]
"chainladder/development/learning.py" = ["E225", "E226", "E231", "E265", "E711", "F401"]
Expand Down
Loading