Data Adjustments#

There are many useful data adjustments in reserving that are not necessarily direct IBNR models nor development factor selections. This module covers those implemented by chainladder. In all cases, these adjustments are most useful when used as part of a larger workflow.

BootstrapODPSample#

Simulations#

:class:BootstrapODPSample is a transformer that simulates new triangles according to the ODP Bootstrap model. That is both the index and column of the Triangle must be of unity length. Upon fitting the Estimator, the index will contain the individual simulations.

import chainladder as cl
import pandas as pd

raa = cl.load_sample('raa')
cl.BootstrapODPSample(n_sims=500).fit_transform(raa)
/home/docs/checkouts/readthedocs.org/user_builds/chainladder-python/envs/experimental/lib/python3.11/site-packages/chainladder/adjustments/bootstrap.py:284: UserWarning: 'where' used without 'out', expect unitialized memory in output. If this is intentional, use out=None.
  hat = xp.diagonal(xp.sqrt(xp.divide(1, abs(1 - hat), where=(1 - hat) != 0)))
Triangle Summary
Valuation: 1990-12
Grain: OYDY
Shape: (500, 1, 10, 10)
Index: [Total]
Columns: [values]

Note

The BootstrapODPSample can only apply to single triangles as it needs the index axis to be free to hold the different triangle simluations.

Dropping#

The Estimator has full dropping support consistent with the Development estimator. This allows for eliminating problematic values from the sampled residuals.

cl.BootstrapODPSample(n_sims=100, drop=[('1982', 12)]).fit_transform(raa)
/home/docs/checkouts/readthedocs.org/user_builds/chainladder-python/envs/experimental/lib/python3.11/site-packages/chainladder/adjustments/bootstrap.py:284: UserWarning: 'where' used without 'out', expect unitialized memory in output. If this is intentional, use out=None.
  hat = xp.diagonal(xp.sqrt(xp.divide(1, abs(1 - hat), where=(1 - hat) != 0)))
Triangle Summary
Valuation: 1990-12
Grain: OYDY
Shape: (100, 1, 10, 10)
Index: [Total]
Columns: [values]

Deterministic methods#

The class only simulates new triangles from which you can generate statistics about parameter and process uncertainty. This allows for converting the various deterministic IBNR methods into stochastic methods.

Like the Development estimators, The BootstrapODPSample allows for ommission of certain residuals from its sampling algorithm with a suite of “dropping” parameters. See :ref:Omitting Link Ratios<dropping>.

Examples#

[BootstrapODPSample Variability]

BootstrapODPSample Variability
BootstrapODPSample Variability

[Shapland, 2016]

BerquistSherman#

:class:BerquistSherman provides a mechanism of restating the inner diagonals of a triangle for changes in claims practices. These adjustments can materialize in case incurred and paid amounts as well as closed claims count development.

In all cases, the adjustments retain the unadjusted latest diagonal of the triangle. For the Incurred adjustment, an assumption of the trend rate in average open case reserves must be supplied. For the adjustments to paid amounts and closed claim counts, an estimator, such as Chainladder is needed to calulate ultimate reported count so that the disposal_rate_ of the model can be calculated.

Adjustments#

The BerquistSherman technique requires values for paid_amount, incurred_amount, reported_count, and closed_count to be available in your Triangle. Without these triangles, the BerquistSherman model cannot be used. If these conditions are satisfied, the BerquistSherman technique adjusts all triangles except the reported_count triangle.

The Estimator wraps all adjustments up in its adjusted_triangle_ property.

triangle = cl.load_sample('berqsherm').loc['MedMal']
berq = cl.BerquistSherman(
    paid_amount='Paid', incurred_amount='Incurred',
    reported_count='Reported', closed_count='Closed',
    trend=0.15).fit(triangle)

# Only Reported triangle is left unadjusted
(triangle / berq.adjusted_triangle_)['Reported']
12 24 36 48 60 72 84 96
1969 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000
1970 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000
1971 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000
1972 1.0000 1.0000 1.0000 1.0000 1.0000
1973 1.0000 1.0000 1.0000 1.0000
1974 1.0000 1.0000 1.0000
1975 1.0000 1.0000
1976 1.0000

Latest Diagonal#

Only the inner diagonals of the Triangle are adjusted. This allows the adjusted_triangle_ to be a drop in surrogate for the original.

(triangle / berq.adjusted_triangle_)['Paid']
12 24 36 48 60 72 84 96
1969 2.0714 3.3635 1.5968 1.3856 0.7545 1.2105 0.8862 1.0000
1970 0.7603 2.2084 1.2420 0.9964 0.8729 1.4994 1.0000
1971 1.7663 4.3464 1.7429 1.2899 0.7629 1.0000
1972 0.7129 2.3514 1.6228 1.2540 1.0000
1973 1.3234 1.6029 0.7186 1.0000
1974 1.4519 2.8518 1.0000
1975 1.3604 1.0000
1976 1.0000

Transform#

BerquistSherman is strictly a data adjustment to the Triangle and it does not attempt to estimate development patterns, tails, or ultimate values. Once transform is invoked on a Triangle, the adjusted_triangle_ takes the place of the existing Triangle.

Examples#

[BerquistSherman Adjustment]

BerquistSherman Adjustment
BerquistSherman Adjustment

[Friedland, 2010]

ParallelogramOLF#

The :class:ParallelogramOLF estimator is used to on-level a Triangle using the parallogram technique. It requires a “rate history” and supports both vertical line estimates as well as the more common effective date estimates.

../_images/onlevel.PNG

Rate History#

The Estimator requires a rate history that has, at a minimum, the rate changes and date-like columns reflecting the corresponding effective dates of the rate changes.

rate_history = pd.DataFrame({
    'EffDate': ['2016-07-15', '2017-03-01', '2018-01-01', '2019-10-31'],
    'RateChange': [0.02, 0.05, -.03, 0.1]})
rate_history
EffDate RateChange
0 2016-07-15 0.02
1 2017-03-01 0.05
2 2018-01-01 -0.03
3 2019-10-31 0.10

The ParallelogramOLF maps the rate history using the change_col and date_col arguments. Once mapped, the transformer provides an olf_ property representing the on-level factors from the parallelogram on-leveling technique. When used as a transformer, it retains your Triangle as it is, but then adds the olf_ property to your Triangle.

data = pd.DataFrame({
    'Year': [2016, 2017, 2018, 2019, 2020],
    'EarnedPremium': [10_000]*5})
prem_tri = cl.Triangle(data, origin='Year', columns='EarnedPremium', cumulative = True)
prem_tri = cl.ParallelogramOLF(rate_history, change_col='RateChange', date_col='EffDate').fit_transform(prem_tri)
prem_tri.olf_
2020
2016 1.0185
2017 1.0011
2018 0.9854
2019 1.0000
2020 1.0000

Input to CapeCod#

This estimator can be used within other estimators that depend on on-leveling, such as the :class:CapeCod method. This is accomplished by passing a ParallelogramOLF transformed Triangle to the CapeCod estimator.

Examples#

[CapeCod Onleveling]

CapeCod Onleveling
CapeCod Onleveling

Trend#

The :class:Trend estimator is a convenience estimator that allows for compound trends to be used in other estimators that have a trend assumption. This enables more complex trend assumptions to be used.

ppauto_loss = cl.load_sample('clrd').groupby('LOB').sum().loc['ppauto', 'CumPaidLoss']
ppauto_prem = cl.load_sample('clrd').groupby('LOB').sum() \
                .loc['ppauto']['EarnedPremDIR'].latest_diagonal

# Simple trend
a = cl.CapeCod(trend=0.05).fit(ppauto_loss, sample_weight=ppauto_prem).ultimate_.sum()

# Equivalent using a Trend Estimator. This allows us to convert to more complex trends
b = cl.CapeCod().fit(cl.Trend(.05).fit_transform(ppauto_loss), sample_weight=ppauto_prem).ultimate_.sum()
a == b
np.True_

Multipart Trend#

A multipart trend can be achieved if passing a list of trends and corresponding dates. Dates should be represented as a list of tuples (start, end).

The default start and end dates for a Triangle are its valuation_date and its earliest origin date.

ppauto_loss = cl.load_sample('clrd').groupby('LOB').sum().loc['ppauto', 'CumPaidLoss']
cl.Trend(
    trends=[.05, .03],
    dates=[('1997-12-31', '1995-01-01'),('1995-01-01', '1992-07-01')]
).fit(ppauto_loss).trend_.round(2)
12 24 36 48 60 72 84 96 108 120
1988 1.2400 1.2400 1.2400 1.2400 1.2400 1.2400 1.2400 1.2400 1.2400 1.2400
1989 1.2400 1.2400 1.2400 1.2400 1.2400 1.2400 1.2400 1.2400 1.2400
1990 1.2400 1.2400 1.2400 1.2400 1.2400 1.2400 1.2400 1.2400
1991 1.2400 1.2400 1.2400 1.2400 1.2400 1.2400 1.2400
1992 1.2300 1.2300 1.2300 1.2300 1.2300 1.2300
1993 1.1900 1.1900 1.1900 1.1900 1.1900
1994 1.1600 1.1600 1.1600 1.1600
1995 1.1000 1.1000 1.1000
1996 1.0500 1.0500
1997 1.0000