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
37 changes: 37 additions & 0 deletions chainladder/adjustments/berqsherm.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,43 @@ class BerquistSherman(BaseEstimator, TransformerMixin, EstimatorIO):
Two-period Exponential intercept parameters
b_: Triangle
Two-period Exponential slope parameters

Examples
--------
``trend`` tilts the case-adequacy adjustment before ``Incurred`` is rebuilt;
on the ``MedMal`` slice the column totals move materially between ``0%``
and ``15%`` annual drift.

.. testsetup::

import chainladder as cl
import numpy as np

.. testcode::

tri = cl.load_sample("berqsherm").loc["MedMal"]
base = cl.BerquistSherman(
paid_amount="Paid",
incurred_amount="Incurred",
reported_count="Reported",
closed_count="Closed",
trend=0.0,
).fit(tri)
tilted = cl.BerquistSherman(
paid_amount="Paid",
incurred_amount="Incurred",
reported_count="Reported",
closed_count="Closed",
trend=0.15,
).fit(tri)
print(round(float(np.nansum(base.adjusted_triangle_["Incurred"].values)), 2))
print(round(float(np.nansum(tilted.adjusted_triangle_["Incurred"].values)), 2))

.. testoutput::

1407473237.41
1126985253.66

"""

def __init__(
Expand Down
35 changes: 35 additions & 0 deletions chainladder/adjustments/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,41 @@ class BootstrapODPSample(DevelopmentBase):
A set of triangles represented by each simulation
scale_:
The scale parameter to be used in generating process risk
Examples
--------
``n_periods`` is forwarded to the internal ``Development`` fit, which
changes the Pearson scale, while ``hat_adj`` toggles the residual
standardization used before resampling.
.. testsetup::
import chainladder as cl
.. testcode::
tri = cl.load_sample("raa")
hat = cl.BootstrapODPSample(
n_sims=5, random_state=42, hat_adj=True
).fit(tri)
nohat = cl.BootstrapODPSample(
n_sims=5, random_state=42, hat_adj=False
).fit(tri)
short_hist = cl.BootstrapODPSample(
n_sims=5, random_state=42, n_periods=3
).fit(tri)
print(round(float(hat.scale_), 6))
print(round(float(short_hist.scale_), 6))
print(round(float(hat.resampled_triangles_.mean().values[0, 0, 0, 0]), 4))
print(round(float(nohat.resampled_triangles_.mean().values[0, 0, 0, 0]), 4))
.. testoutput::
983.635027
322.397502
1455.5201
1532.5693
"""

def __init__(
Expand Down
130 changes: 44 additions & 86 deletions chainladder/adjustments/parallelogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@ class ParallelogramOLF(BaseEstimator, TransformerMixin, EstimatorIO):
----------

rate_history: pd.DataFrame
A DataFrame with two columns: one containing the effective dates of the rate
changes and the other containing the rate changes expressed as a decimal.
For example, 5% decrease should be stated as -0.05.
A DataFrame with
change_col: str
The column containing the rate changes expressed as a decimal. For example,
5% decrease should be stated as -0.05.
5% decrease should be stated as -0.05
date_col: str
A list-like set of effective dates corresponding to each of the changes.
A list-like set of effective dates corresponding to each of the changes
approximation_grain: str {"M", "D"} (default="M")
The resolution of the internal calendar spacing used to calculate on-level
factors can be set to monthly (`'M'`) or daily (`'D'`). Under each
Expand All @@ -36,7 +34,7 @@ class ParallelogramOLF(BaseEstimator, TransformerMixin, EstimatorIO):
origin periods.
policy_length: int (default=12)
The length of the policy in months.
vertical_line: bool (default=False)
vertical_line:
Rates are typically stated on an effective date basis and premiums on
and earned basis. By default, this argument is False and produces
parallelogram OLFs. If True, Parallelograms become squares. This is
Expand All @@ -51,93 +49,53 @@ class ParallelogramOLF(BaseEstimator, TransformerMixin, EstimatorIO):

Examples
--------
``policy_length`` sets the earning window used in the parallelogram
geometry; a longer policy smooths rate changes over more months and
shifts the first on-level factor.

Premium vectors are expressed as a Triangle object. This example shows how to create and apply on-level factors to a Triangle object with one rate change.

.. testsetup::
.. testsetup::

import chainladder as cl
import pandas as pd

.. testcode::
.. testcode::

import pandas as pd
import numpy as np

xyz = cl.load_sample("xyz")
olf = (
cl.ParallelogramOLF(
rate_history=pd.DataFrame(
{
"EffDate": ["2001-07-01"],
"RateChange": [0.20],
}
),
change_col="RateChange",
date_col="EffDate",
)
.fit_transform(xyz["Premium"])
.olf_
rate_history = pd.DataFrame(
{"EffDate": ["2010-07-01"], "RateChange": [0.20]}
)
xyz["Leveled Premium"] = xyz["Premium"] * olf
print(np.round(xyz["Leveled Premium"], 0))

.. testoutput::

12 24 36 48 60 72 84 96 108 120 132
1998 NaN NaN 24000.0 24000.0 24000.0 24000.0 24000.0 24000.0 24000.0 24000.0 24000.0
1999 NaN 37800.0 37800.0 37800.0 37800.0 37800.0 37800.0 37800.0 37800.0 37800.0 NaN
2000 54000.0 54000.0 54000.0 54000.0 54000.0 54000.0 54000.0 54000.0 54000.0 NaN NaN
2001 58537.0 58537.0 58537.0 58537.0 58537.0 58537.0 58537.0 58537.0 NaN NaN NaN
2002 62485.0 62485.0 62485.0 62485.0 62485.0 62485.0 62485.0 NaN NaN NaN NaN
2003 69175.0 69175.0 69175.0 69175.0 69175.0 69175.0 NaN NaN NaN NaN NaN
2004 99322.0 99322.0 99322.0 99322.0 99322.0 NaN NaN NaN NaN NaN NaN
2005 138151.0 138151.0 138151.0 138151.0 NaN NaN NaN NaN NaN NaN NaN
2006 107578.0 107578.0 107578.0 NaN NaN NaN NaN NaN NaN NaN NaN
2007 62438.0 62438.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN
2008 47797.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

Of course, we can have multiple rate changes, or assuems that policies are 24 months
long with `policy_length`.
We can also get more accurate OLFs by using the `approximation_grain`
argument to set the resolution of the internal calendar spacing used to
calculate on-level factors.

.. testcode::

xyz = cl.load_sample("xyz")
olf = (
cl.ParallelogramOLF(
rate_history=pd.DataFrame(
{
"EffDate": ["2001-07-01", "2023-10-01"],
"RateChange": [0.20, -0.05],
}
),
change_col="RateChange",
date_col="EffDate",
policy_length=24,
approximation_grain="D",
)
.fit_transform(xyz["Premium"])
.olf_
data = pd.DataFrame(
{
"Year": [2010, 2011, 2012, 2013, 2014],
"EarnedPremium": [10000] * 5,
}
)
xyz["Leveled Premium"] = xyz["Premium"] * olf
print(np.round(xyz["Leveled Premium"], 0))

.. testoutput::

12 24 36 48 60 72 84 96 108 120 132
1998 NaN NaN 24000.0 24000.0 24000.0 24000.0 24000.0 24000.0 24000.0 24000.0 24000.0
1999 NaN 37800.0 37800.0 37800.0 37800.0 37800.0 37800.0 37800.0 37800.0 37800.0 NaN
2000 54000.0 54000.0 54000.0 54000.0 54000.0 54000.0 54000.0 54000.0 54000.0 NaN NaN
2001 59247.0 59247.0 59247.0 59247.0 59247.0 59247.0 59247.0 59247.0 NaN NaN NaN
2002 66720.0 66720.0 66720.0 66720.0 66720.0 66720.0 66720.0 NaN NaN NaN NaN
2003 69891.0 69891.0 69891.0 69891.0 69891.0 69891.0 NaN NaN NaN NaN NaN
2004 99322.0 99322.0 99322.0 99322.0 99322.0 NaN NaN NaN NaN NaN NaN
2005 138151.0 138151.0 138151.0 138151.0 NaN NaN NaN NaN NaN NaN NaN
2006 107578.0 107578.0 107578.0 NaN NaN NaN NaN NaN NaN NaN NaN
2007 62438.0 62438.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN
2008 47797.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

def prem():
return cl.Triangle(
data, origin="Year", columns="EarnedPremium", cumulative=True
)

olf_12 = cl.ParallelogramOLF(
rate_history,
change_col="RateChange",
date_col="EffDate",
policy_length=12,
approximation_grain="M",
).fit_transform(prem())
olf_24 = cl.ParallelogramOLF(
rate_history,
change_col="RateChange",
date_col="EffDate",
policy_length=24,
approximation_grain="M",
).fit_transform(prem())
print(round(float(olf_12.olf_.values[0, 0, 0, 0]), 6))
print(round(float(olf_24.olf_.values[0, 0, 0, 0]), 6))

.. testoutput::

1.170732
1.185185

"""

Expand Down
72 changes: 72 additions & 0 deletions chainladder/adjustments/trend.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,78 @@ class Trend(BaseEstimator, TransformerMixin, EstimatorIO):
trend_:
A triangle representation of the trend factors

Examples
--------
The same annual decimal trend is applied along ``origin`` or
``valuation`` axes, producing different factor surfaces.

.. testsetup::

import chainladder as cl
import numpy as np

.. testcode::

tri = cl.load_sample("raa")
origin = cl.Trend(0.05, axis="origin").fit(tri)
val = cl.Trend(0.05, axis="valuation").fit(tri)
print(round(float(origin.trend_.values[0, 0, 2, 3]), 6))
print(round(float(val.trend_.values[0, 0, 2, 3]), 6))

.. testoutput::

1.4071
1.215506

Multiple ``trends`` with paired ``dates`` compound only across the
windows you specify, so the factors need not match a single flat trend.

.. testcode::

tri = cl.load_sample("raa")
flat = cl.Trend(0.10, axis="origin").fit(tri)
piece = cl.Trend(
trends=[0.05, 0.05],
dates=[(None, "1985"), ("1985", None)],
axis="origin",
).fit(tri)
print(round(float(flat.trend_.values[0, 0, 0, 0]), 6))
print(round(float(piece.trend_.values[0, 0, 0, 0]), 6))

.. testoutput::

2.357948
1.551328

``trend_`` holds the compounded factor surface; ``transform`` applies it
so a downstream ``CapeCod`` can be run with ``trend=0`` while still
reflecting the staged annual assumptions.

.. testcode::

tr = cl.load_sample("clrd")[["CumPaidLoss", "EarnedPremDIR"]].sum()
t_step = cl.Trend(
trends=[0.04, 0.02],
dates=[(None, "1995"), ("1995", None)],
axis="origin",
).fit(tr["CumPaidLoss"])
paid_leveled = t_step.transform(tr["CumPaidLoss"])
ibnr = (
cl.CapeCod()
.fit(
paid_leveled,
sample_weight=tr["EarnedPremDIR"].latest_diagonal,
)
.ibnr_
)
print(round(float(t_step.trend_.values[0, 0, 2, 3]), 6))
print(int(round(float(np.nansum(ibnr.values)), 0)))

.. testoutput::

1.21562
29278236

"""

def __init__(self, trends=0.0, dates=None, axis="origin"):
Expand Down
57 changes: 57 additions & 0 deletions chainladder/core/correlation.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,34 @@ class DevelopmentCorrelation:
confidence_interval: tuple
Range within which ``t_expectation`` must fall for independence assumption
to be significant.

Examples
--------
``p_critical`` sets how wide the acceptance band is for the Spearman
composite statistic; tightening it can flip ``t_critical`` even when the
point estimate is unchanged.

.. testsetup::

import chainladder as cl

.. testcode::

tri = cl.load_sample("raa")
loose = cl.DevelopmentCorrelation(tri, p_critical=0.5)
tight = cl.DevelopmentCorrelation(tri, p_critical=0.99)
print(bool(loose.t_critical.iloc[0, 0]))
print(bool(tight.t_critical.iloc[0, 0]))
print(round(float(loose.confidence_interval[0]), 6))
print(round(float(tight.confidence_interval[0]), 6))

.. testoutput::

False
True
-0.127467
-0.002369

"""

def __init__(self, triangle, p_critical: float = 0.5):
Expand Down Expand Up @@ -171,6 +199,35 @@ class ValuationCorrelation:
The expected value of Z.
z_variance : Triangle or DataFrame
The variance value of Z.

Examples
--------
``total=True`` follows Mack (1993) and returns ``DataFrame`` summaries;
``total=False`` follows Mack (1997) and keeps a ``Triangle`` of
valuation-year diagnostics.

.. testsetup::

import chainladder as cl
import numpy as np

.. testcode::

tri = cl.load_sample("raa")
agg = cl.ValuationCorrelation(tri, p_critical=0.1, total=True)
yearly = cl.ValuationCorrelation(tri, p_critical=0.1, total=False)
print(type(agg.z_critical).__name__)
print(type(yearly.z_critical).__name__)
print(yearly.z_critical.shape)
print(int(np.nansum(yearly.z_critical.values)))

.. testoutput::

DataFrame
Triangle
(1, 1, 1, 9)
0

"""

def __init__(self, triangle: Triangle, p_critical: float = 0.1, total: bool = True):
Expand Down
Loading
Loading