Skip to content

[FIX] Use premium * ELR for ClarkLDF Cape Cod fitted incrementals - #1520

Open
scks1 wants to merge 4 commits into
casact:mainfrom
scks1:fix-clark-capecod-scale
Open

scks1 wants to merge 4 commits into
casact:mainfrom
scks1:fix-clark-capecod-scale

Conversation

@scks1

@scks1 scks1 commented Nov 1, 2026 •

Copy link
Copy Markdown

Summary of Changes

ClarkLDF fitted the right Cape Cod parameters but computed incremental_fits_ from the wrong expected ultimate. For both methods it used the LDF-implied ultimate (latest diagonal × fitted CDF). Under Cape Cod the expected ultimate is premium × ELR. Because scale_ and norm_resid_ are derived from incremental_fits_, both were wrong for Cape Cod fits.

On the reproduction from #1501 (Clark's genins example, with premium 10,000,000 + 400,000·i):

omega theta ELR scale
Clark (2003), pp. 67–68 1.4476 48.02 0.5978 61,577
before 1.4476 48.02 0.5978 53,822
after 1.4476 48.02 0.5978 61,577

The fix is in chainladder/development/clark.py: when sample_weight is given, the expected ultimate behind incremental_fits_ is now sample_weight × elr_. The LDF method path is unchanged, and its scale (65,029) already matched the paper (p. 61).

Related GitHub Issue(s)

Fixes #1501

AI/LLM Usage

This change was written with Claude Opus 5.5 (Claude Code), working under my direction. It localised the bug, wrote the fix and tests, and drafted this description. I reviewed the results before submitting.

Two follow-up commits were made with Árbol, an AI assistant running on Muse, also under my direction, and I reviewed them: relative test tolerances with the p. 61 citation, and the changes from the review (page citations, the comparison with the paper's Table 2.1, and removing a redundant backend conversion).

Additional Context for Reviewers

Typo in Clark's paper. Clark's table prints 2,182,708 for the 1991 origin at age 48, where genins (the Taylor-Ashe data) has 2,218,270. The sample data is correct, so it is left unchanged. The test fixture edits that one cell so the tests can be checked against the paper's published results, the same way the reproduction in #1501 does.

Tests. New file chainladder/development/tests/test_clark.py:

  • test_clark_ldf_scale: the LDF scale is 65,029 (paper p. 61).
  • test_clark_cape_cod_scale: the Cape Cod ELR is 0.5978 (paper p. 67) and the scale is 61,577 (p. 68), using the assert from [BUG] Incorrect scale parameter computed with ClarkLDF Cape Cod method #1501.
  • test_clark_cape_cod_incremental_fits: the 55 fitted incrementals match the paper's Table 2.1 (p. 80).
  • test_clark_cape_cod_sparse_premium: a premium triangle on the sparse backend gives the same Cape Cod fit.

The Cape Cod scale and incremental-fit tests fail on main and pass with this change. The LDF and sparse-premium tests pass on both, as expected. The paper comparisons use a 0.1% relative tolerance, because the optimizer's stopping point varies across Python and library versions and moves the scale by a few units; the pre-fix Cape Cod scale is about 13% away. Full suite: 1428 passed, 2 skipped. ruff check and ruff format --check are clean. I also ran the clrd Cape Cod examples from the docstring, with and without groupby="LOB". They give the same ELRs as before and correctly shaped incremental_fits_ / scale_.

Submitter's Checklist

  • I have reviewed and am adhering to the standards outlined in the project Governing Doc.
  • The PR subject title summarizes the changes, with one proper prefix ([FIX], [FEAT], [DOCS], [TST], [CHORE], or [BRK]).
  • I am a human (not a bot), and this PR form is written by a human.

Reviewer's Checklist

  • The implementation addresses the associated issue(s).
  • The implementation is appropriate, maintainable, and follows ARCHITECTURE.md.
  • PR subject title has the proper prefix and the subject is appropriate.
  • Relevant issue(s) are linked.
  • AI/LLM usage is disclosed and appropriate.
  • Documentation and tests are appropriate.
  • CI tests passed, or any failures are acceptable.
  • Leave a comment with the final recommendation (e.g. approve as is, request a secondary review, or flag an area for more review).

🤖 Generated with Claude Code

ClarkLDF built incremental_fits_ from the LDF-implied ultimate (latest
diagonal times the fitted CDF) for both methods. Under Cape Cod the
expected ultimate is premium * ELR, so scale_ and norm_resid_ were wrong
for Cape Cod fits. On Clark's (2003) genins example the scale was 53,822
instead of the paper's 61,577.

Add regression tests against the paper's published LDF and Cape Cod
scale parameters, editing the one genins cell that is mistyped in the
paper.

Fixes casact#1501

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Pyright Type Completeness

View the full pyright --verifytypes output for this commit

Project (full chainladder package, at this PR's head): 16.2% of exported symbols fully typed (250 / 1539)

Known Ambiguous Unknown Total
Project (head) 250 106 1183 1539

Other symbols referenced but not exported by chainladder: 13

Known Ambiguous Unknown Total
Other (head) 3 1 9 13

Symbols without documentation:

  • Functions without docstring: 340
  • Functions without default param: 0
  • Classes without docstring: 8

Patch (exported symbols added or changed by this PR): 42.9% fully typed (3 / 7)

Known Ambiguous Unknown Total
Patch 3 0 4 7
Patch symbol details
Symbol Status Change
chainladder.development.tests.test_clark.PAPER_RTOL ✅ known new
chainladder.development.tests.test_clark.clark_genins ✅ known new
chainladder.development.tests.test_clark.clark_premium ✅ known new
chainladder.development.tests.test_clark.test_clark_cape_cod_incremental_fits ❌ unknown new
chainladder.development.tests.test_clark.test_clark_cape_cod_scale ❌ unknown new
chainladder.development.tests.test_clark.test_clark_cape_cod_sparse_premium ❌ unknown new
chainladder.development.tests.test_clark.test_clark_ldf_scale ❌ unknown new

@codecov

codecov Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.38%. Comparing base (2ea53be) to head (b47481a).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1520      +/-   ##
==========================================
+ Coverage   94.36%   94.38%   +0.01%     
==========================================
  Files          97       98       +1     
  Lines        6198     6216      +18     
  Branches      798      799       +1     
==========================================
+ Hits         5849     5867      +18     
  Misses        225      225              
  Partials      124      124              
Flag Coverage Δ
unittests 94.38% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@henrydingliu

Copy link
Copy Markdown
Member

@scks1 thanks for putting in the PR! please address the unittest fails.

ClarkLDF's optimizer stops at slightly different points across Python
versions and numpy/scipy/pandas builds. On Python 3.11 and on Python 3.14
with pandas 3, the fitted scales were 65,030.03 (LDF) and 61,579.31
(Cape Cod), outside the abs=1 tolerance, and a local run that reproduced
those values had the ELR inside abs=1e-4 by 0.0000038. Compare all three
fitted values with the paper at rel=1e-3. With the Cape Cod fix reverted,
the Cape Cod scale is 53,824 and both Cape Cod tests still fail.

The LDF test now cites p. 61, where Clark gives the 65,029 scale.

Developed with an AI assistant (Árbol, running Muse).
@scks1

scks1 commented Nov 2, 2026

Copy link
Copy Markdown
Author

Thanks! The failures were in the new tests, not the fix: ClarkLDF's optimizer stops at slightly different points across Python and library versions, so on 3.11 and on 3.14 with pandas 3 the scales came out at 65,030.03 and 61,579.31, just outside the ±1 tolerance. The paper comparisons now use a 0.1% relative tolerance, which the pre-fix Cape Cod scale (about 53,822) still fails, and the LDF test cites p. 61, where Clark gives 65,029. (This follow-up, commit and reply, was made with Árbol, an AI assistant, under my direction.)

_set_fit_groups already moves a sparse sample_weight to numpy, so the
array_backend check before set_backend('numpy') only mattered for cupy
and left a branch the test suite could not reach. Convert
unconditionally and add a test that a sparse premium triangle gives the
same Cape Cod fit as a dense one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

def test_clark_cape_cod_scale(clark_genins, clark_premium):
"""
Cape Cod scale parameter matches Clark (2003), p. 68. The fitted

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ELR is from p. 67

assert model.scale_.iloc[0, 0] == pytest.approx(61_577, rel=PAPER_RTOL)


def test_clark_cape_cod_incremental_fits(clark_genins, clark_premium):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is the capecod incrementals available from the paper for us to hardcode into this test?

Comment thread chainladder/development/clark.py Outdated
if sample_weight:
# Cape Cod: the expected ultimate is premium * ELR, not the
# LDF-implied ultimate from the latest diagonal.
premium = sample_weight.set_backend("numpy").values

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

_set_fit_group already set sample_weight backend to numpy

The Cape Cod scale test now cites p. 67 for the ELR and p. 68 for the
scale.

test_clark_cape_cod_incremental_fits compares incremental_fits_ with
the 55 fitted values Clark prints in Table 2.1 (p. 80), at the same 0.1%
relative tolerance as the other paper comparisons. With the Cape Cod fix
reverted, every cell is 2.5% to 16.1% away and the test fails.

_set_fit_groups already puts sample_weight on the numpy backend, so the
Cape Cod ultimate reads sample_weight.values directly, as the solver
does.

Developed with an AI assistant (Árbol, running Muse).
@scks1

scks1 commented Nov 3, 2026

Copy link
Copy Markdown
Author

Thanks for the review. All three are addressed in 19115ab:

  • ELR page: the Cape Cod scale test now cites p. 67 for the ELR and p. 68 for the scale.
  • Paper incrementals: yes, Table 2.1 (p. 80) prints the fitted increment for all 55 cells, so test_clark_cape_cod_incremental_fits now compares incremental_fits_ with those values at the same 0.1% relative tolerance. Locally the largest gap is 0.021%, the same on Python 3.11, 3.12 and 3.13; with the fix reverted, every cell is 2.5% to 16.1% off and the test fails.
  • Backend: removed the set_backend("numpy") call, since _set_fit_groups has already converted sample_weight. I kept test_clark_cape_cod_sparse_premium, which now covers a sparse premium going through _set_fit_groups; happy to drop it if you'd rather not carry it.

(The commit and this reply were made with Árbol, an AI assistant, under my direction.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Incorrect scale parameter computed with ClarkLDF Cape Cod method

2 participants