Skip to content

[FEAT] warn when CapeCod.predict infers the grain rather than regrouping silently - #1306

Open
ppcvote wants to merge 1 commit into
casact:mainfrom
ppcvote:feat/capecod-warn-inferred-grain
Open

[FEAT] warn when CapeCod.predict infers the grain rather than regrouping silently#1306
ppcvote wants to merge 1 commit into
casact:mainfrom
ppcvote:feat/capecod-warn-inferred-grain

Conversation

@ppcvote

@ppcvote ppcvote commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Summary of Changes

CapeCod.predict re-estimates the apriori at the fitted grain whenever sample_weight carries index levels the fitted apriori_ does not, and returns apriori_ at that grain rather than the caller's. On clrd that means a 775 row triangle goes in and a 6 row apriori_ comes back, with nothing said about it. This says it:

UserWarning: sample_weight has index levels the fitted apriori does not (GRNAME),
so the apriori is re-estimated at the fitted grain ['LOB']. apriori_ is returned at
that grain, not the one passed in. Pass groupby to CapeCod to state this explicitly.

One thing worth flagging, because it decides the shape of the check. CapeCod(groupby=...) reaches the same branch: on clrd, CapeCod(groupby="LOB").fit(tri, sample_weight=prem) leaves apriori_ at ['LOB'] while sample_weight is at ['GRNAME', 'LOB'], so the level difference is non-empty and the branch fires. Warning there would fire on someone who already said what they wanted, which is the quickest way to get a warning suppressed wholesale. So the warning is scoped to self.groupby is None: it points out an inferred grain and never a declared one. To be explicit about what that means at the edges, groupby is a statement of what to keep, so a level it does not name is collapsed by the declaration rather than by inference and stays quiet: with groupby="LOB" and a prediction triangle at ['GRNAME', 'LOB', 'Foo'], apriori_ comes back at ['LOB'] with no warning, which is what was asked for. Going the other way, a level that happens to carry a single value still warns, because apriori_ still comes back at different key_labels from the ones passed in.

Related GitHub Issue(s)

The warning discussed in #1274. The Chainladder.predict shape changes from that same thread are separate and tracked in #1288.

Additional Context for Reviewers

  • Three tests. Removing the warning fails the first; removing the self.groupby is None guard fails the second. Both halves are covered rather than just the happy path.
  • pytest chainladder passes: 1142 passed, 7 skipped.
  • ruff check --force-exclude --config lint.per-file-ignores={} passes on both files. ruff format --check reports exactly what it reported on main before this change, line for line, so the added code introduces nothing of its own.
  • test_capecod_predict1 and test_capecod_predict2, the [BUG] CapeCod predict at different index grain #400 regression tests, now emit the warning, as does the test added in [FIX] CapeCod.predict discards the fitted apriori when the prediction data has exactly one extra index level #1275. That is the behaviour the issue is about and they still pass. Running the file with -W error::UserWarning is a quick way to see exactly which tests take the inferred path.
  • The predict docstring example uses ukmotor at a single grain, so the level difference is empty and it neither warns nor changes its documented output.

I have not touched the docstrings. Say the word if you would like the behaviour written up there as well.

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: 1142 passed, 7 skipped. No documentation changes in this PR, so I did not run the docs doctest build; the one docstring the change could reach is the CapeCod.predict ukmotor example, which I ran directly and which still reproduces its documented output.

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 it on #1274.

AI disclosure, per the AI Usage Policy: I used Claude Code on this. It ran the reproductions, established that CapeCod(groupby=...) reaches the same branch, wrote the tests and the before and after comparisons. I reviewed the diff and the test file myself, and the suite was run locally on my machine.

…ing silently

predict() re-estimates the apriori at the fitted grain whenever sample_weight
carries index levels the fitted apriori_ does not, and returns apriori_ at that
grain rather than the caller's. A 775 row triangle goes in and a 6 row apriori_
comes back with nothing said about it.

The warning is scoped to self.groupby is None. CapeCod(groupby=...) reaches the
same branch, but there the grain was asked for rather than inferred, and warning
someone who was explicit is the fastest way to get a warning suppressed.

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

github-actions Bot commented Sep 6, 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 / 1373)

Known Ambiguous Unknown Total
Project (head) 204 111 1058 1373

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 / 3)

Known Ambiguous Unknown Total
Patch 0 0 3 3
Patch symbol details
Symbol Status Change
chainladder.methods.tests.test_capecod.test_capecod_predict_does_not_warn_at_a_matching_grain ❌ unknown new
chainladder.methods.tests.test_capecod.test_capecod_predict_does_not_warn_when_groupby_is_explicit ❌ unknown new
chainladder.methods.tests.test_capecod.test_capecod_predict_warns_when_the_grain_is_inferred ❌ unknown new

@codecov

codecov Bot commented Sep 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.71%. Comparing base (91a942f) to head (d0e29c1).

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

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.

1 participant