Skip to content

Give informed callers a narrow opt-out from the PLS uncentred-block warning - #575

Merged
kgdunn merged 3 commits into
mainfrom
claude/pls-uncentred-warning-opt-out-irqjas
Sep 13, 2026
Merged

kgdunn merged 3 commits into
mainfrom
claude/pls-uncentred-warning-opt-out-irqjas

Conversation

@kgdunn

@kgdunn kgdunn commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Summary

PLS(scale=False) fits no intercept, so an un-centred block displaces every prediction, and fit has warned about that since 1.77. The warning is correct and stays on by default. What was missing was a way to permit one deliberate un-centred fit. A caller proving that some other centring check fires had only simplefilter("ignore", SpecificationWarning), which also hides clamped component counts and NIPALS non-convergence, and under a filterwarnings = error policy the warning arrived as an exception indistinguishable from a failed fit.

Two escapes, narrowest first:

  • PLS(..., warn_on_uncentred=False) skips the check for that model alone. Stored verbatim in __init__ and read in fit(), so it survives clone() and reaches a fit that a Pipeline or a grid search constructs. No effect under scale=True, where the model centres both blocks itself. It changes only whether the warning is emitted, never the numbers.
  • UncentredDataWarning, a new SpecificationWarning subclass, is now the category the warning is raised under, so ignore::process_improve.multivariate.UncentredDataWarning leaves every other specification diagnostic in force. For when the model is built by code you do not own.

Both warning classes are now importable from process_improve.multivariate, so a filterwarnings entry never has to name the private _common module. The warning text itself names the flag, so a caller who hits this by surprise finds the remedy without searching.

import warnings
from process_improve.multivariate import PLS, UncentredDataWarning

warnings.simplefilter("error")                                    # as a strict pytest.ini does
PLS(n_components=2, scale=False, warn_on_uncentred=False).fit(X, Y_uncentred)   # permitted

with warnings.catch_warnings():                                   # when the flag is out of reach
    warnings.simplefilter("ignore", UncentredDataWarning)
    build_somebody_elses_pls_and_fit(X, Y_uncentred)

Backwards compatible: UncentredDataWarning subclasses SpecificationWarning, so existing filters, except SpecificationWarning handlers and pytest.warns(SpecificationWarning) assertions all keep matching. Only an exact-identity test (record[0].category is SpecificationWarning) changes behaviour, which is noted in the changelog.

Docs: a "Centring and the Missing Intercept" section in the PLS user guide (with the three opt-outs ordered narrowest first, so a reader reaches for the flag before the category filter and the category filter before blanket suppression); a Warnings section in the multivariate API reference; and a note in docs/development/error_handling.rst on when a diagnostic earns its own subclass, since the question will recur.

Test plan

  • Nine new tests in tests/test_multivariate_centring_traps.py::TestUncentredWarningOptOut, covering: the flag silencing the warning under simplefilter("error"); the flag not silencing a second, unrelated SpecificationWarning raised by the same fit call; the category filter as the wider opt-out; identical predictions with and without the flag; no-op under scale=True; get_params() / clone() round-trip; the default staying on; and the message naming the flag.
  • uv run pytest full suite: 3369 passed, 41 skipped (every skip is a dataset download blocked by this sandbox's proxy, unrelated to the change).
  • uv run python -O -m pytest tests/test_multivariate_centring_traps.py tests/test_multivariate.py: 252 passed, 2 skipped.
  • uv run ruff check . and uv run ruff format --check .: both clean.
  • uv run mypy src/process_improve: no issues in 163 source files.
  • Strict Sphinx build (-W --keep-going) of the touched pages: no warnings from any edited file. Notebook directories were excluded locally because pandoc is not installed in this sandbox; the only remaining warnings are intersphinx inventories the proxy blocks.
  • The issue's reproduce snippet: both opt-outs permit the fit, and the default still fires.

Checklist

  • Version bumped in pyproject.toml (MINOR: 1.93.1 → 1.94.0, new public class and constructor parameter), with CITATION.cff in step
  • Tests added or updated where relevant
  • ruff check . passes
  • CHANGELOG.md updated

🤖 Generated with Claude Code

https://claude.ai/code/session_01H7cg6VtfMGvqwCtgZBKwU2

PLS(scale=False) fits no intercept, so an un-centred block displaces every
prediction; fit() has warned about this since 1.77. The warning is correct and
stays on. What was missing was a way to permit one deliberate un-centred fit: a
caller proving that some other centring check fires had only
simplefilter("ignore", SpecificationWarning), which also hides clamped component
counts and NIPALS non-convergence, and under filterwarnings = error the warning
arrived as an exception indistinguishable from a failed fit.

Two escapes, narrowest first:

* PLS(..., warn_on_uncentred=False) skips the check for that model only. Stored
  verbatim in __init__ and read in fit(), so it survives clone() and reaches a
  fit that a Pipeline or grid search constructs. No effect under scale=True,
  where the model centres both blocks itself.
* UncentredDataWarning, a SpecificationWarning subclass, is now the category the
  warning is raised under, so "ignore::...UncentredDataWarning" leaves every
  other SpecificationWarning in force. Subclassing keeps existing filters and
  pytest.warns(SpecificationWarning) assertions matching.

Both warning classes are now importable from process_improve.multivariate, so
the filter idiom does not reach into a private module. The message names the
flag, so a caller who hits this by surprise finds the remedy in the text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7cg6VtfMGvqwCtgZBKwU2
- docs/user_guide/pls.rst: a "Centring and the Missing Intercept" section
  explaining why scale=False fits no intercept, and a table of the three
  opt-outs ordered narrowest first, so a reader reaches for the flag before the
  category filter and the category filter before silencing SpecificationWarning
  wholesale.
- docs/api/multivariate.rst: a Warnings section, so both classes are documented
  at the path a filterwarnings entry should name.
- docs/development/error_handling.rst: when to give a warning its own subclass,
  since the same question will come up the next time a diagnostic has a
  legitimate opt-out.
- Version 1.93.1 -> 1.94.0 (new public class and constructor parameter), with
  CITATION.cff and CHANGELOG.md in step.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7cg6VtfMGvqwCtgZBKwU2
@codecov

codecov Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@kgdunn
kgdunn merged commit 47685e1 into main Sep 13, 2026
14 checks passed
@kgdunn
kgdunn deleted the claude/pls-uncentred-warning-opt-out-irqjas branch September 13, 2026 21:43
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.

2 participants