Skip to content
Closed
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 .github/workflows/sync-main-to-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Sync main to docs/great-docs-prototype

on:
push:
branches: [main]

permissions:
contents: write

jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Merge main into docs/great-docs-prototype
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout docs/great-docs-prototype
git merge origin/main --no-edit -m "chore: sync main into docs/great-docs-prototype"
git push origin docs/great-docs-prototype
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated CI workflow file included in docs PR

Low Severity

The new sync-main-to-docs.yml workflow file is not mentioned anywhere in the PR description's "Files changed" table, which exclusively lists Python source files. This workflow — which auto-merges main into docs/great-docs-prototype on every push — is unrelated to adding doctest examples and appears to have been accidentally included in this documentation-focused PR.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ce197a6. Configure here.

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
51 changes: 51 additions & 0 deletions chainladder/adjustments/parallelogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,57 @@ class ParallelogramOLF(BaseEstimator, TransformerMixin, EstimatorIO):

olf_:
A triangle representation of the on-level factors

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.

.. testsetup::

import chainladder as cl
import pandas as pd

.. testcode::

rate_history = pd.DataFrame(
{"EffDate": ["2010-07-01"], "RateChange": [0.20]}
)
data = pd.DataFrame(
{
"Year": [2010, 2011, 2012, 2013, 2014],
"EarnedPremium": [10000] * 5,
}
)

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

"""

def __init__(
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
28 changes: 28 additions & 0 deletions chainladder/development/constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,34 @@ class DevelopmentConstant(DevelopmentBase):
The estimated loss development patterns
cdf_: Triangle
The estimated cumulative development patterns

Examples
--------
``patterns`` is interpreted as multiplicative link ratios when
``style='ldf'``; swapping in a flat manual ladder changes the fitted
pattern immediately.

.. testsetup::

import chainladder as cl

.. testcode::

tri = cl.load_sample("raa")
dev = cl.Development().fit(tri)
n = dev.ldf_.shape[3]
fitted = {(i + 1) * 12: float(dev.ldf_.values[0, 0, 0, i]) for i in range(n)}
flat = {(i + 1) * 12: 1.2 for i in range(n)}
const_fitted = cl.DevelopmentConstant(patterns=fitted, style="ldf").fit(tri)
const_flat = cl.DevelopmentConstant(patterns=flat, style="ldf").fit(tri)
print(round(float(const_flat.ldf_.values[0, 0, 0, 0]), 4))
print(round(float(const_fitted.ldf_.values[0, 0, 0, 0]), 6))

.. testoutput::

1.2
2.999359

"""

def __init__(self, patterns=None, style="ldf", callable_axis=0, groupby=None):
Expand Down
30 changes: 30 additions & 0 deletions chainladder/development/munich.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,36 @@ class MunichAdjustment(DevelopmentBase):
cdf_: Triangle
The estimated bivariate cumulative development patterns

Examples
--------
``fillna=True`` imputes missing paid/incurred amounts with simple
chainladder expectations so the bivariate regression can still run.

.. testsetup::

import chainladder as cl
import numpy as np

.. testcode::

mcl = cl.load_sample("mcl").copy()
arr = np.asarray(mcl.values, dtype=float, copy=True)
arr[0, 1, 0, 2] = np.nan
mcl.values = arr
dev = cl.Development().fit_transform(mcl)
try:
cl.MunichAdjustment(("paid", "incurred"), fillna=False).fit(dev)
print("no_error")
except ValueError:
print("ValueError")
filled = cl.MunichAdjustment(("paid", "incurred"), fillna=True).fit(dev)
print(round(float(filled.ldf_.values[0, 0, 0, 0]), 6))

.. testoutput::

ValueError
2.151329

"""

def __init__(self, paid_to_incurred=None, fillna=False):
Expand Down
Loading