Skip to content

Stop fit() mutating constructor parameters in PCA / PLS / OPLS (#505) - #527

Merged
kgdunn merged 6 commits into
mainfrom
claude/process-improve-library-issues-hr7xxk-clone
Aug 29, 2026
Merged

Stop fit() mutating constructor parameters in PCA / PLS / OPLS (#505)#527
kgdunn merged 6 commits into
mainfrom
claude/process-improve-library-issues-hr7xxk-clone

Conversation

@kgdunn

@kgdunn kgdunn commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • Fixes PCA / PLS / OPLS mutate constructor parameters inside fit(), breaking the sklearn clone contract that cross_validate depends on #505: fit() no longer writes the resolved component count (or, in PLS, the resolved missing-data settings) back onto constructor parameters. The resolved value is the new fitted attribute n_components_; n_components stays exactly what the user passed, including None, so get_params/clone/PLS.cross_validate resamples fit the requested configuration and refitting one instance on differently shaped data re-derives the clamp each time. Clamping logic and its SpecificationWarning are unchanged, per the issue's non-goals.
  • Internal post-fit readers migrated to n_components_: the shared Hotelling's T2 limit mixin and ellipse_coordinates in _base.py, spe_limit in _limits.py, the plot pre-checks in plots.py (new _fitted_n_components helper that also handles the TPLS _parent wiring), PCA's hotellings_t2, and PLS prediction_interval. MBPCA/MBPLS now also set n_components_ in fit() so those shared helpers read one attribute across all four estimators.
  • OPLS (which has no n_components constructor parameter) exposes the fitted count only as n_components_ and maps the old attribute name to a helpful rename message via its existing _ATTRIBUTE_RENAMES machinery.

All four acceptance criteria from the issue are covered by new tests in tests/test_sklearn_compat.py: get_params() round-trip after fit (including None), clone() reproduces the request, refit on different shapes resolves independently, and a spy on PLS.fit pins that every cross_validate sub-model is constructed with the user's requested n_components.

Version 1.73.0 (MINOR: post-fit meaning of n_components changes; assumes queued PR #524 merges first, trivial rebase otherwise).

Test plan

  • New PCA / PLS / OPLS mutate constructor parameters inside fit(), breaking the sklearn clone contract that cross_validate depends on #505 acceptance tests in tests/test_sklearn_compat.py (20 passed)
  • uv run pytest tests/test_multivariate.py tests/test_multivariate_opls.py tests/test_sklearn_compat.py tests/test_multivariate_contribution_plots.py tests/test_audit_regressions_multivariate.py --no-cov: 269 passed, 2 pre-existing skips
  • uv run pytest tests/test_multiblock_reference.py tests/test_multiblock_oracles.py tests/test_multivariate_tpls_display.py tests/test_multivariate_adaptive.py tests/test_multivariate_tpls_feature_importance.py --no-cov: 154 passed
  • ruff check and ruff format --check clean; mypy src/process_improve shows only the pre-existing mcp_server.py FastMCP error unrelated to this diff

Checklist

  • Version bumped in pyproject.toml (PATCH for fixes/docs/config, MINOR for new features)
  • Tests added or updated where relevant
  • ruff check . passes
  • CHANGELOG.md updated

Generated by Claude Code

claude added 4 commits August 29, 2026 00:17
…ing the clone contract

PCA, PLS, and OPLS wrote the resolved component count (and PLS the
resolved missing-data settings) back onto their constructor parameters
inside fit(). sklearn's contract requires __init__ arguments to stay
verbatim, because get_params/clone/GridSearchCV treat them as the
requested configuration: PLS.cross_validate clones the fitted parent, so
every resample silently inherited the clamped component count and the
mutated missing-data settings, and PCA(n_components=None) became a
concrete int after one fit, freezing the first dataset's clamp.

The resolved count is now the fitted attribute n_components_, set only
in fit(); the parameter is untouched. Internal post-fit readers (the T2
limit mixin, ellipse_coordinates, spe_limit, the plot pre-checks, PLS
prediction intervals) read n_components_, MBPCA/MBPLS expose the same
fitted attribute for those shared helpers, and OPLS maps the old
attribute name to a rename message. Clamping logic and its
SpecificationWarning are unchanged.

Fixes #505.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU
The typecheck gate is red on main itself: the unpinned mcp extra now
resolves to mcp 2.x, where FastMCP was renamed MCPServer, so mypy fails
on mcp_server.py on every branch. This ports the migrated server and the
mcp>=2.0 floor verbatim from #528; the diff no-ops once #528 merges.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU

kgdunn commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

The typecheck failure here is not this PR's: it is red on main's own latest runs. The unpinned mcp extra now resolves to mcp 2.x, where FastMCP was renamed MCPServer, so mypy fails on src/process_improve/mcp_server.py everywhere. The fix is PR #528; its migrated mcp_server.py and mcp>=2.0 floor are ported verbatim into this branch so CI can go green in any merge order. The ported files disappear from this diff once #528 merges.


Generated by Claude Code

@codecov

codecov Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/process_improve/multivariate/plots.py 87.50% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Codecov flagged the TPLS-parent delegation, the plain-integer fallback,
and the unfitted-model raise as uncovered on the patch; pin all four
resolution paths directly.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C3XXbJkuSYH9fMryLqNcU
…e-library-issues-hr7xxk-clone

# Conflicts:
#	CHANGELOG.md
#	CITATION.cff
#	pyproject.toml
@kgdunn
kgdunn merged commit e7f94b0 into main Aug 29, 2026
22 of 23 checks passed
@kgdunn
kgdunn deleted the claude/process-improve-library-issues-hr7xxk-clone branch August 29, 2026 06:54
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.

PCA / PLS / OPLS mutate constructor parameters inside fit(), breaking the sklearn clone contract that cross_validate depends on

2 participants