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 workflow file included in docs-only PR

Medium Severity

A new CI workflow sync-main-to-docs.yml was added that automatically merges main into docs/great-docs-prototype on every push to main. This file is not mentioned in the PR description, which exclusively describes doctest and Sphinx directive changes. The workflow triggers on all pushes to main (not just docs-related ones), will fail without error handling if the target branch doesn't exist or merge conflicts arise, and represents a significant infrastructure change that warrants its own dedicated review.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1df94ec. Configure here.

29 changes: 29 additions & 0 deletions chainladder/development/barnzehn.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,35 @@ class BarnettZehnwirth(TweedieGLM):
gamma: list of int
iota: list of int

Examples
--------
A patsy ``formula`` and the built-in ``alpha`` / ``gamma`` / ``iota`` PTF
specification can both fit the same triangle; the leading fitted
coefficient differs because the design matrices differ.

.. testsetup::

import chainladder as cl

.. testcode::

import numpy as np

tri = cl.load_sample("abc")
m_formula = cl.BarnettZehnwirth(
formula="C(origin)+C(development)"
).fit(tri)
m_ptf = cl.BarnettZehnwirth(
alpha=[0, 5], gamma=[0, 2, 5], iota=[0, 7, 11]
).fit(tri)
print(float(np.round(m_formula.coef_.values.flatten()[0], 3)))
print(float(np.round(m_ptf.coef_.values.flatten()[0], 3)))

.. testoutput::

11.837
12.151

"""

def __init__(self, drop=None,drop_valuation=None,formula=None, response=None, alpha=None, gamma=None, iota=None):
Expand Down
52 changes: 52 additions & 0 deletions chainladder/development/clark.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,58 @@ class ClarkLDF(DevelopmentBase):
norm_resid_: Triangle
The "Normalized" Residuals of the model according to Clark.

Examples
--------
``growth`` selects the incremental curve; the first LDF cell moves slightly
between ``loglogistic`` (default) and ``weibull``.

.. testsetup::

import chainladder as cl

.. testcode::

import numpy as np

tri = cl.load_sample("ukmotor")
m_log = cl.ClarkLDF(growth="loglogistic").fit(tri)
m_wei = cl.ClarkLDF(growth="weibull").fit(tri)
print(float(np.round(m_log.ldf_.values[0, 0, 0, 0], 6)))
print(float(np.round(m_wei.ldf_.values[0, 0, 0, 0], 6)))

.. testoutput::

1.917318
1.911706

Passing ``sample_weight`` switches to Cape Cod: ``method_`` becomes
``cape_cod`` and ``elr_`` is estimated.

.. testcode::

tri = cl.load_sample("ukmotor")
m = cl.ClarkLDF().fit(tri, sample_weight=tri * 0 + 1e7)
print(m.method_)
print(float(np.round(m.elr_.values[0, 0], 6)))

.. testoutput::

cape_cod
0.002002

``groupby`` pools index levels before fitting so one parameter set is
returned per group (here, line of business on ``clrd``).

.. testcode::

clrd = cl.load_sample("clrd").groupby("LOB")[["IncurLoss"]].sum()
m = cl.ClarkLDF(groupby="LOB").fit(clrd)
print(m.theta_.shape)

.. testoutput::

(6, 1)

"""

def __init__(
Expand Down
42 changes: 42 additions & 0 deletions chainladder/development/glm.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,48 @@ class TweedieGLM(DevelopmentBase):
----------
model_: sklearn.Pipeline
A scikit-learn Pipeline of the GLM

Examples
--------
``design_matrix`` controls which patsy terms enter the GLM; dropping
``C(origin)`` changes the first fitted LDF.

.. testsetup::

import chainladder as cl

.. testcode::

import numpy as np

tri = cl.load_sample("genins")
m_full = cl.TweedieGLM(
power=1, design_matrix="C(development) + C(origin)"
).fit(tri)
m_dev = cl.TweedieGLM(power=1, design_matrix="C(development)").fit(tri)
print(float(np.round(m_full.ldf_.values[0, 0, 0, 0], 4)))
print(float(np.round(m_dev.ldf_.values[0, 0, 0, 0], 4)))

.. testoutput::

3.491
3.5085

``power`` and ``link`` select the Tweedie family; a Normal GLM
(``power=0`` with ``link='identity'``) yields a different pattern.

.. testcode::

import numpy as np

tri = cl.load_sample("genins")
m = cl.TweedieGLM(power=0, link="identity").fit(tri)
print(float(np.round(m.ldf_.values[0, 0, 0, 0], 2)))

.. testoutput::

2.31

"""

def __init__(self, design_matrix='C(development) + C(origin)',
Expand Down
41 changes: 41 additions & 0 deletions chainladder/development/incremental.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,47 @@ class IncrementalAdditive(DevelopmentBase):
A triangle of full incremental values.


Examples
--------
Basic fit on ``ia_sample`` with exposure on the latest diagonal.

.. testsetup::

import chainladder as cl

.. testcode::

tri = cl.load_sample("ia_sample")
model = cl.IncrementalAdditive().fit(
tri["loss"], sample_weight=tri["exposure"].latest_diagonal
)
print(model.ldf_.shape)

.. testoutput::

(1, 1, 6, 5)

``future_trend`` (when non-zero) changes extrapolated incrementals in the
lower triangle even when ``trend`` is held at zero; here the summed
fitted incrementals increase.

.. testcode::

import numpy as np

tri = cl.load_sample("ia_sample")
loss = tri["loss"]
sw = tri["exposure"].latest_diagonal
m0 = cl.IncrementalAdditive(trend=0, future_trend=0).fit(loss, sample_weight=sw)
m1 = cl.IncrementalAdditive(trend=0, future_trend=0.1).fit(loss, sample_weight=sw)
print(float(np.round(np.nansum(m0.incremental_.values), 1)))
print(float(np.round(np.nansum(m1.incremental_.values), 1)))

.. testoutput::

30988.1
33360.1

"""

def __init__(
Expand Down
75 changes: 75 additions & 0 deletions chainladder/development/learning.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,81 @@ class DevelopmentML(DevelopmentBase):
The estimated loss development patterns.
cdf_: Triangle
The estimated cumulative development patterns.

Examples
--------
``fit_incrementals`` toggles whether the pipeline fits on incrementals
versus cumulatives, which shifts the implied ``ldf_``.

.. testsetup::

import chainladder as cl

.. testcode::

import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline

from chainladder.utils.utility_functions import PatsyFormula

tri = cl.load_sample("genins")
pipe = Pipeline(
steps=[
("design_matrix", PatsyFormula("C(development)")),
("model", LinearRegression(fit_intercept=False)),
]
)
m_incr = cl.DevelopmentML(
pipe, y_ml=[tri.columns[0]], fit_incrementals=True
).fit(tri)
m_cum = cl.DevelopmentML(
pipe, y_ml=[tri.columns[0]], fit_incrementals=False
).fit(tri)
print(float(np.round(m_incr.ldf_.values[0, 0, 0, 0], 4)))
print(float(np.round(m_cum.ldf_.values[0, 0, 0, 0], 4)))

.. testoutput::

3.508
3.515

With ``weighted_step='model'``, ``sample_weight`` is forwarded into the
final regressor; squaring the triangle as a crude weight changes the first
LDF versus an unweighted fit.

.. testcode::

import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline

from chainladder.utils.utility_functions import PatsyFormula

tri = cl.load_sample("genins")
pipe = Pipeline(
steps=[
("design_matrix", PatsyFormula("C(development)")),
("model", LinearRegression(fit_intercept=False)),
]
)
m0 = cl.DevelopmentML(
pipe, y_ml=[tri.columns[0]], fit_incrementals=False
).fit(tri)
m1 = cl.DevelopmentML(
pipe,
y_ml=[tri.columns[0]],
fit_incrementals=False,
weighted_step="model",
).fit(tri, sample_weight=tri * tri)
print(float(np.round(m0.ldf_.values[0, 0, 0, 0], 4)))
print(float(np.round(m1.ldf_.values[0, 0, 0, 0], 4)))

.. testoutput::

3.515
3.4459

"""

def __init__(self, estimator_ml=None, y_ml=None, autoregressive=False,
Expand Down
31 changes: 25 additions & 6 deletions chainladder/methods/mack.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,17 @@ class MackChainladder(Chainladder):
which combines the deterministic chainladder estimate with Mack's
stochastic standard error.

.. testsetup:
.. testsetup::

import chainladder as cl

.. testcode:
.. testcode::

tr = cl.load_sample('ukmotor')
model = cl.MackChainladder().fit(tr)
print(model.summary_)

.. testoutput:
.. testoutput::

Latest IBNR Ultimate Mack Std Err
2007 12690.0 NaN 12690.000000 NaN
Expand All @@ -71,14 +71,30 @@ class MackChainladder(Chainladder):
:class:`Chainladder`. Mack's contribution is the stochastic standard error
in the rightmost column, which can be aggregated across origins.

.. testcode:
.. testcode::

print(model.total_mack_std_err_)

.. testoutput:
.. testoutput::

columns values
(Total,) 1424.531543

Mack's total error depends on how ``ldf_`` and ``sigma_`` were produced.
Here the same triangle is pre-smoothed with :class:`Development` using
``average='simple'`` instead of the default volume weights before fitting
``MackChainladder``, which raises the aggregate Mack standard error.

.. testcode::

tr = cl.load_sample("ukmotor")
tr_simple = cl.Development(average="simple").fit_transform(tr)
print(cl.MackChainladder().fit(tr_simple).total_mack_std_err_)

.. testoutput::

columns values
(Total,) 1591.603339
"""

def fit(self, X, y=None, sample_weight=None):
Expand Down Expand Up @@ -217,7 +233,7 @@ def full_std_err_(self):
model = cl.MackChainladder().fit(tr)
print(model.full_std_err_)

.. testoutput
.. testoutput::

12 24 36 48 60 72 84
2007 0.047826 0.040745 0.031412 0.010337 0.001431 0.001523 0.0
Expand Down Expand Up @@ -268,6 +284,7 @@ def total_process_risk_(self):
--------

.. testsetup::

import chainladder as cl

.. testcode::
Expand Down Expand Up @@ -340,9 +357,11 @@ def mack_std_err_(self):
error per origin.

.. testsetup::

import chainladder as cl

.. testcode::

tr = cl.load_sample('ukmotor')
model = cl.MackChainladder().fit(tr)
print(model.mack_std_err_.iloc[..., -3:, -3:])
Expand Down
Loading