Skip to content

[FIX] CapeCod.predict discards the fitted apriori when the prediction data has exactly one extra index level - #1275

Merged
henrydingliu merged 3 commits into
casact:mainfrom
ppcvote:fix/capecod-predict-grain-1265
Sep 5, 2026
Merged

[FIX] CapeCod.predict discards the fitted apriori when the prediction data has exactly one extra index level#1275
henrydingliu merged 3 commits into
casact:mainfrom
ppcvote:fix/capecod-predict-grain-1265

Conversation

@ppcvote

@ppcvote ppcvote commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary of Changes

CapeCod.predict() regroups the prediction data up to the grain the model was fit at, and decides whether to do so by counting the index levels sample_weight carries that apriori_ does not. The count required more than one, so the ordinary case of a single extra level fell through to the ungrouped branch and recomputed the apriori from the prediction data instead of using the fitted one.

import chainladder as cl

clrd = cl.load_sample("clrd")
tri, prem = clrd["CumPaidLoss"], clrd["EarnedPremDIR"].latest_diagonal

model = cl.CapeCod().fit(tri.groupby("LOB").sum(),
                         sample_weight=prem.groupby("LOB").sum())
model.predict(tri, sample_weight=prem)

The fitted comauto apriori is 0.5689995797. Before this change predict() returned 1.2516635774, and all 775 rows differed from the value fitted for their LOB. After it, every row carries its fitted apriori and expectation_ is exactly premium times that apriori across all 775 rows.

test_capecod_predict2 already covers this code path, but it uses prism, whose triangle carries five index levels more than the fitted model, so it stays on the grouped branch. The added test uses clrd, which carries exactly one.

Related GitHub Issue(s)

Fixes #1265. The API question that thread raised is split out into #1274.

Additional Context for Reviewers

  • pytest chainladder passes: 1130 passed, 7 skipped.
  • ruff check and ruff format --check under the CI flags report the same eight findings on these two files before and after the change, so the added lines introduce none of their own.
  • The new test fails on the unpatched line and passes on the patched one.
  • The predict() docstring example (ukmotor) is unaffected: it fits and predicts at the same grain, so the level difference is empty and neither branch changes. Its documented output still reproduces exactly.

Declarations

I am adhering to the standards in the Governing Doc. I am a human contributor, not a bot, and I opened this because @henrydingliu asked for the > 0 fix in #1265.

AI disclosure, per the AI Usage Policy: I used Claude Code on this. It reproduced the bug against clrd, ran the before and after comparison, and drafted the regression test. The one-character fix itself is the one @henrydingliu and I settled on in #1265 before any code was written. I reviewed the diff and the test myself, and the suite was run locally on my machine.

Checklist

  • I passed tests locally for both code (uv run pytest) and documentation changes (uv run --directory docs jb build . --builder=custom --custom-builder=doctest)

    pytest locally: 1130 passed, 7 skipped. There are no documentation changes in this PR, so I did not run the docs doctest build. The only docstring the change could reach is the CapeCod.predict ukmotor example, and I ran that directly: it still reproduces its documented output exactly. CI's Doctest job passes on this branch.

@github-actions

github-actions Bot commented Sep 3, 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): 14.9% of exported symbols fully typed (204 / 1370)

Known Ambiguous Unknown Total
Project (head) 204 111 1055 1370

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: 325
  • Functions without default param: 0
  • Classes without docstring: 10

Patch (exported symbols added or changed by this PR): 0.0% fully typed (0 / 1)

Known Ambiguous Unknown Total
Patch 0 0 1 1
Patch symbol details
Symbol Status Change
chainladder.methods.tests.test_capecod.test_capecod_predict_one_extra_index_level ❌ unknown new

@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.69%. Comparing base (acd0bdb) to head (f036ba7).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1275      +/-   ##
==========================================
- Coverage   91.69%   91.69%   -0.01%     
==========================================
  Files          96       96              
  Lines        5457     5456       -1     
  Branches      702      702              
==========================================
- Hits         5004     5003       -1     
  Misses        328      328              
  Partials      125      125              
Flag Coverage Δ
unittests 91.69% <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

henrydingliu commented Sep 3, 2026

Copy link
Copy Markdown
Member

looks good! would you mind picking up the couple ruff linter fixes?

ppcvote and others added 2 commits September 5, 2026 20:40
… level is added

predict() regroups the prediction data up to the grain the model was fit at, and
decides whether to do so by counting the index levels sample_weight carries that
apriori_ does not. The count required more than one, so the ordinary case of a
single extra level fell through to the ungrouped branch and recomputed the
apriori from the prediction data.

The existing casact#400 regression test uses prism, which has five extra levels, so it
exercises the grouped branch and never saw this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`ruff check --config lint.per-file-ignores={}` now passes on both. The numpy
import in capecod.py was unused; the rest is whitespace in existing tests.

`ruff format` is left alone: it would rewrite 105 of the 107 lines in
test_capecod.py, and six of the eight files in chainladder/methods are
unformatted on main, so that looks like a separate decision.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ppcvote
ppcvote force-pushed the fix/capecod-predict-grain-1265 branch from 45dd61e to b46e378 Compare September 5, 2026 12:45
The table's own comment says to remove entries as files are cleaned up. Both
are dead now: ruff check passes on capecod.py and test_capecod.py with the
project config as well as with per-file-ignores cleared.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ppcvote

ppcvote commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Done. ruff check --force-exclude --config lint.per-file-ignores={} now passes on both files: the numpy import in capecod.py was unused, the rest was whitespace in existing tests. I also dropped the two per-file-ignores entries for these files, since the table's comment says to remove them as files are cleaned up and both are dead now. Rebased onto current main.

I left ruff format alone. It touches 40 of the 107 lines in test_capecod.py and grows it to 132, and six of the eight files under chainladder/methods are unformatted on main, so it looked like a separate decision rather than something to slip into this PR. Say the word if you would rather have it here.

@henrydingliu
henrydingliu merged commit 91a942f into casact:main Sep 5, 2026
11 of 12 checks passed
@henrydingliu

Copy link
Copy Markdown
Member

thanks again!

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.

CapeCod.predict discards the fitted apriori when the model was fit on exactly one fewer index level

2 participants