Skip to content

fix(compose): pass target_aware to optionally supervised representations - #131

Open
ChrisW09 wants to merge 1 commit into
mainfrom
fix/96-optional-target-aware
Open

ChrisW09 wants to merge 1 commit into
mainfrom
fix/96-optional-target-aware

Conversation

@ChrisW09

@ChrisW09 ChrisW09 commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #96.

A custom representation registered with supervision="optional" never received the Preprocessor's target_aware. It kept its own default while y was still passed to fit, so Preprocessor(target_aware=False) could fit it on y (target leakage in a configuration chosen to be target-free), and target_aware=True was ignored by a class that defaults to False.

Root cause

_placement_kwargs in pretab/compose/factory.py returned {} for every spec without placement_strategies, before it reached the target_usage == "optional" branch that injects target_aware. register_representation defaults placement_strategies to (), so only the built-in optional methods, which all declare placement strategies, received target_aware.

Fix

  • _placement_kwargs handles target_usage == "optional" first: every optional spec receives target_aware, and placement_strategy only when the spec declares placement strategies. The no-strategies early return now applies only to the other kinds of spec, so the built-in methods get exactly the same keyword arguments as before.
  • register_representation raises TypeError when a class registered as "optional" (declared on the class or passed as supervision) has no target_aware constructor parameter (_accepts_target_aware), because the Preprocessor could not switch its use of y off.

Behaviour changes

  • A registered optional representation always receives the Preprocessor's target_aware (default True), whatever its own default. A class that defaults to target_aware=False therefore becomes target-aware under the default Preprocessor(), and its lineage reports uses_target=True.
  • register_representation(..., supervision="optional") raises TypeError for a class without a target_aware constructor parameter. load_entry_point_representations skips such a plugin with a ConfigWarning, as it does for any plugin that fails to register.
  • Built-in methods are unchanged.

Documentation

  • docs/tutorials/custom_representation.md (supervision entry): an "optional" class must take a target_aware constructor parameter; Preprocessor sets it from its own target_aware, and registration rejects a class without one.
  • Docstrings: BaseRepresentation (supervision), register_representation (supervision, and placement_strategies: Preprocessor passes placement_strategy only to a method that declares strategies), _placement_kwargs.
  • docs/core_concepts/target_awareness.md already defines optional as "Uses the target only when target_aware=True", which now also holds for registered classes, and is unchanged.

Tests

  • test_placement_optional_without_strategies_still_forwards_target_aware: an optional spec without placement strategies gets {"target_aware": False} or {"target_aware": True} and no placement_strategy. Fails on main, passes here.
  • test_register_optional_representation_follows_preprocessor_target_aware[_SplitAtTarget, _SplitAtTargetOffByDefault]: for a class defaulting to True and one defaulting to False, the fitted step's target_aware, whether its fit used y, and the lineage uses_target follow the Preprocessor's target_aware. Fails on main, passes here.
  • test_register_optional_representation_requires_target_aware_parameter: registering an optional class without target_aware, declared on the class or passed as supervision="optional", raises TypeError and registers nothing. Fails on main, passes here.

Reproduction

Output of the script from #96 on this branch:

Preprocessor(target_aware=False) -> step.target_aware=False, fit used y: False
Preprocessor(target_aware=True) -> step.target_aware=True, fit used y: True

Checks

  • just test: 1442 passed, 59 skipped, 7 xfailed; coverage 93.62% (gate 90%).
  • Minimum dependencies (CI job: Python 3.10, numpy 1.24.4, pandas 2.0.3, scipy 1.10.1, scikit-learn 1.6.0): 1429 passed, 72 skipped, 7 xfailed; scripts/quickstart.py passes.
  • just docs: strict build succeeds.
  • just check: all pre-commit and pre-push hooks pass (ruff format, ruff lint, prettier, file hygiene, pyright with 0 errors).
  • Import direction between subpackages is unchanged.

Merge notes

This change was previously part of #97, which is closed in favour of one pull request per issue.

The Preprocessor injected target_aware only into methods that declare placement
strategies, and register_representation defaults placement_strategies to ().
A representation registered with supervision="optional" therefore kept its own
target_aware default while y was still forwarded to it: with target_aware=False
it could fit on y (target leakage despite the "y-free fit" contract), and with
target_aware=True a class defaulting to False ignored it and lineage reported
uses_target=False.

target_aware is now passed to every optional method, and placement_strategy only
to methods that declare strategies, so built-ins are unchanged. As the docs say
an optional representation uses y only when target_aware=True,
register_representation now rejects an optional class without a target_aware
constructor parameter with a TypeError instead of letting it see y unchecked.

Fixes #96

This branch has not been deployed

No deployments
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.

Preprocessor(target_aware=False) still fits registered supervision="optional" representations on y

1 participant