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
86 changes: 46 additions & 40 deletions chainladder/workflow/tests/test_voting.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
if TYPE_CHECKING:
from chainladder import Triangle


@pytest.fixture
def triangle_data():
clrd = cl.load_sample("clrd")[["CumPaidLoss", "EarnedPremDIR"]]
Expand All @@ -31,11 +32,14 @@ def estimators():

list_weight = [[[1, 2, 3]] * 4 + [[0, 0.5, 0.5]] * 3 + [[0, 0, 1]] * 3]

callable_weight = lambda origin: np.where(
origin.year < 1992,
(1, 2, 3),
np.where(origin.year > 1994, (0, 0, 1), (0, 0.5, 0.5)),
)

def callable_weight(origin):
return np.where(
origin.year < 1992,
(1, 2, 3),
np.where(origin.year > 1994, (0, 0, 1), (0, 0.5, 0.5)),
)


dict_weight = {
"1992": (0, 0.5, 0.5),
Expand All @@ -54,22 +58,25 @@ def weights(request):

def test_voting_ultimate(triangle_data, estimators, weights):
bcl_ult = (
cl.Chainladder()
cl
.Chainladder()
.fit(
triangle_data["CumPaidLoss"].sum(),
)
.ultimate_
)
bf_ult = (
cl.BornhuetterFerguson()
cl
.BornhuetterFerguson()
.fit(
triangle_data["CumPaidLoss"].sum(),
sample_weight=triangle_data["EarnedPremDIR"].sum().latest_diagonal,
)
.ultimate_
)
cc_ult = (
cl.CapeCod()
cl
.CapeCod()
.fit(
triangle_data["CumPaidLoss"].sum(),
sample_weight=triangle_data["EarnedPremDIR"].sum().latest_diagonal,
Expand All @@ -78,7 +85,8 @@ def test_voting_ultimate(triangle_data, estimators, weights):
)

vot_ult = (
cl.VotingChainladder(
cl
.VotingChainladder(
estimators=estimators, weights=weights, default_weighting=(1, 2, 3)
)
.fit(
Expand Down Expand Up @@ -118,15 +126,13 @@ def test_different_backends(triangle_data, estimators, weights):
)
assert (
abs(
(
model.predict(
triangle_data["CumPaidLoss"].sum().set_backend("sparse"),
sample_weight=triangle_data["EarnedPremDIR"]
.sum()
.latest_diagonal.set_backend("sparse"),
).ultimate_.sum()
- model.ultimate_.sum()
)
model.predict(
triangle_data["CumPaidLoss"].sum().set_backend("sparse"),
sample_weight=triangle_data["EarnedPremDIR"]
.sum()
.latest_diagonal.set_backend("sparse"),
).ultimate_.sum()
- model.ultimate_.sum()
)
< 1
)
Expand All @@ -139,23 +145,26 @@ def test_weight_broadcasting(triangle_data, estimators, weights):
max_dim_weights = np.array(mid_dim_weights * 132)

min_dim_ult = (
cl.VotingChainladder(estimators=estimators, weights=weights)
cl
.VotingChainladder(estimators=estimators, weights=weights)
.fit(
triangle_data["CumPaidLoss"],
sample_weight=triangle_data["EarnedPremDIR"].latest_diagonal,
)
.ultimate_.sum()
)
mid_dim_ult = (
cl.VotingChainladder(estimators=estimators, weights=mid_dim_weights)
cl
.VotingChainladder(estimators=estimators, weights=mid_dim_weights)
.fit(
triangle_data["CumPaidLoss"],
sample_weight=triangle_data["EarnedPremDIR"].latest_diagonal,
)
.ultimate_.sum()
)
max_dim_ult = (
cl.VotingChainladder(estimators=estimators, weights=max_dim_weights)
cl
.VotingChainladder(estimators=estimators, weights=max_dim_weights)
.fit(
triangle_data["CumPaidLoss"],
sample_weight=triangle_data["EarnedPremDIR"].latest_diagonal,
Expand Down Expand Up @@ -191,34 +200,31 @@ def test_voting(raa):
]
).all()

def test_tri_sel(clrd:Triangle) -> None:
'''

def test_tri_sel(clrd: Triangle) -> None:
"""
starter test for the TriangleSelector class
'''
"""
tri = clrd.sum()
assert tri['CumPaidLoss'] == cl.TriangleSelector('CumPaidLoss').fit_transform(tri)
assert tri["CumPaidLoss"] == cl.TriangleSelector("CumPaidLoss").fit_transform(tri)


def test_mismatching_tri_sel(clrd:Triangle) -> None:
'''
def test_mismatching_tri_sel(clrd: Triangle) -> None:
"""
checking that an error is raised when different number of columns are specified by estimators in a VotingChainladder
'''
tri = clrd.groupby('LOB').sum().loc['othliab']
"""
tri = clrd.groupby("LOB").sum().loc["othliab"]
pipe_p = cl.Pipeline(
steps=[
('tri_sel', cl.TriangleSelector('CumPaidLoss')),
('dev', cl.Development()),
('model', cl.Chainladder())
]
)
pipe_i = cl.Pipeline(
steps=[
('dev', cl.Development()),
('model', cl.Chainladder())
("tri_sel", cl.TriangleSelector("CumPaidLoss")),
("dev", cl.Development()),
("model", cl.Chainladder()),
]
)
pipe_i = cl.Pipeline(steps=[("dev", cl.Development()), ("model", cl.Chainladder())])

estimators = [('incurred', pipe_i), ('paid', pipe_p)]
estimators = [("incurred", pipe_i), ("paid", pipe_p)]
weights = np.array([[0.5, 0.5]] * 4 + [[0.75, 0.25]] * 3 + [[1, 0]] * 3)
vot = cl.VotingChainladder(estimators=estimators, weights=weights)
with pytest.raises(ValueError):
vot.fit(tri)
vot.fit(tri)
45 changes: 29 additions & 16 deletions chainladder/workflow/tests/test_workflow.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import chainladder as cl
import pytest


def test_grid(clrd):
# Load Data
medmal_paid = clrd.groupby("LOB").sum().loc["medmal"]["CumPaidLoss"]
Expand All @@ -25,7 +26,8 @@ def test_grid(clrd):
grid.fit(medmal_paid, benk__sample_weight=medmal_prem)
assert (
grid.results_["IBNR"][0]
== cl.Benktander(n_iters=250, apriori=1)
== cl
.Benktander(n_iters=250, apriori=1)
.fit(
cl.TailCurve().fit_transform(cl.Development().fit_transform(medmal_paid)),
sample_weight=medmal_prem,
Expand All @@ -36,23 +38,34 @@ def test_grid(clrd):

@pytest.fixture
def tri(clrd):
tri = clrd.groupby('LOB').sum()[['CumPaidLoss', 'IncurLoss', 'EarnedPremDIR']]
tri['CaseIncurredLoss'] = tri['IncurLoss'] - tri['CumPaidLoss']
tri = clrd.groupby("LOB").sum()[["CumPaidLoss", "IncurLoss", "EarnedPremDIR"]]
tri["CaseIncurredLoss"] = tri["IncurLoss"] - tri["CumPaidLoss"]
return tri

dev = [cl.Development, cl.ClarkLDF, cl.Trend, cl.IncrementalAdditive,
lambda : cl.MunichAdjustment(paid_to_incurred=('CumPaidLoss', 'CaseIncurredLoss')),
lambda :cl.CaseOutstanding(paid_to_incurred=('CumPaidLoss', 'CaseIncurredLoss'))]

dev = [
cl.Development,
cl.ClarkLDF,
cl.Trend,
cl.IncrementalAdditive,
lambda: cl.MunichAdjustment(paid_to_incurred=("CumPaidLoss", "CaseIncurredLoss")),
lambda: cl.CaseOutstanding(paid_to_incurred=("CumPaidLoss", "CaseIncurredLoss")),
]
tail = [cl.TailCurve, cl.TailConstant, cl.TailBondy, cl.TailClark]
ibnr = [cl.Chainladder, cl.BornhuetterFerguson,
lambda : cl.Benktander(n_iters=2), cl.CapeCod]
ibnr = [
cl.Chainladder,
cl.BornhuetterFerguson,
lambda: cl.Benktander(n_iters=2),
cl.CapeCod,
]


@pytest.mark.parametrize('dev', dev)
@pytest.mark.parametrize('tail', tail)
@pytest.mark.parametrize('ibnr', ibnr)
@pytest.mark.parametrize("dev", dev)
@pytest.mark.parametrize("tail", tail)
@pytest.mark.parametrize("ibnr", ibnr)
def test_pipeline(tri, dev, tail, ibnr):
X = tri[['CumPaidLoss', 'CaseIncurredLoss']]
sample_weight = tri['EarnedPremDIR'].latest_diagonal
cl.Pipeline(
steps=[('dev', dev()), ('tail', tail()), ('ibnr', ibnr())]
).fit_predict(X, sample_weight=sample_weight).ibnr_.sum('origin').sum('columns').sum()
X = tri[["CumPaidLoss", "CaseIncurredLoss"]]
sample_weight = tri["EarnedPremDIR"].latest_diagonal
cl.Pipeline(steps=[("dev", dev()), ("tail", tail()), ("ibnr", ibnr())]).fit_predict(
X, sample_weight=sample_weight
).ibnr_.sum("origin").sum("columns").sum()
34 changes: 18 additions & 16 deletions chainladder/workflow/voting.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from sklearn.base import (
BaseEstimator,
clone,
TransformerMixin
TransformerMixin,
)
from sklearn.ensemble._base import _fit_single_estimator, _BaseHeterogeneousEnsemble
from sklearn.ensemble._voting import _BaseVoting
Expand Down Expand Up @@ -417,25 +417,26 @@ def _get_ultimate(self, X, sample_weight=None):
f" and {X.shape[1]} for X."
)
weights = self.weights_

ultimates = [est.predict(X, sample_weight).ultimate_ for est in self.estimators_]

ultimates = [
est.predict(X, sample_weight).ultimate_ for est in self.estimators_
]

shape_check = list(set([ult.shape for ult in ultimates]))
if len(shape_check) > 1:
raise ValueError(
"Estimators returning ultimate_ of different shapes,"
"likely due to a mis-specified TriangleSelector transformer in the pipeline"
)
#weights are broadcasted to the shape of X. However ultimate_ does not always take the shape of X
#use shape_check to redim weights
ultimate = sum(
[
ultimates[i] * weights[...,:shape_check[0][1], :, i, :]
for i, _ in enumerate(ultimates)
]
) / weights[...,:shape_check[0][1], :, :, :].sum(axis=-2)
# weights are broadcasted to the shape of X. However ultimate_ does not always take the shape of X
# use shape_check to redim weights
ultimate = sum([
ultimates[i] * weights[..., : shape_check[0][1], :, i, :]
for i, _ in enumerate(ultimates)
]) / weights[..., : shape_check[0][1], :, :, :].sum(axis=-2)
return ultimate


class TriangleSelector(
BaseEstimator,
TransformerMixin,
Expand All @@ -454,7 +455,7 @@ class TriangleSelector(
Examples
--------
Actuaries commonly uses both incurred and paid losses, or both reported and closed counts for estimating ultimate loss or ultimate count. We can use this helper class to create a singular VotingChainladder pipeline that weighs between incurred/paid methods, or reported/closed methods.

.. testsetup::

import chainladder as cl
Expand Down Expand Up @@ -497,11 +498,12 @@ class TriangleSelector(
1996 672721.951809
1997 664061.404455
"""
def __init__(self, col:str):

def __init__(self, col: str):
self.col = col

def fit(self, X:Triangle, y:None=None):
def fit(self, X: Triangle, y: None = None):
return self

def transform(self, X:Triangle):
return X[[self.col]]
def transform(self, X: Triangle):
return X[[self.col]]
3 changes: 0 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,6 @@ select = ["E2", "E4", "E7", "E9", "F", "B018", "UP034", "N802"]
"chainladder/development/tests/rtest_munich.py" = ["E266", "E722", "F401"]
"chainladder/methods/tests/rtest_mack.py" = ["E266", "E722", "F401"]
"chainladder/tails/tests/rtest_exponential.py" = ["E722", "F401"]
"chainladder/workflow/tests/test_voting.py" = ["E231", "E731", "UP034"]
"chainladder/workflow/tests/test_workflow.py" = ["E203", "E241"]
"chainladder/workflow/voting.py" = ["E231", "E252", "E265"]
"docs/friedland/chapter_10.ipynb" = ["E731", "F841"]
"docs/friedland/chapter_7_part_2.ipynb" = ["N802"]
"docs/friedland/chapter_9.ipynb" = ["E731"]
Expand Down
Loading