You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Migrate IPWClassicalMeanEstimator onto the engine pattern - #513
Moves the validation and computation of IPWClassicalMeanEstimator into a new internal IPWClassicalMeanEngine, so estimate becomes a thin shell calling the engine once, like the other migrated estimators. The engine can now be reused by an IPW-based monitor and by ASIMeanEstimator.
As a side effect, estimate now raises a ValueError when y and sampling_probability differ in length or when y has fewer than 2 labeled values (previously an opaque indexing error or a NaN standard error).
1. Behaviour change in a refactor: new _validate_min_samples check
glide/engines/ipw_classical.py:24
_validate_min_samples(y[labeled_mask], "y") makes IPWClassicalMeanEstimator.estimate reject inputs it previously accepted. Example: y=[5.0, nan, nan] with sampling_probability=[0.5, 0.5, 0.5] has one label. Before this PR, estimate returned the IPW mean, with std from ddof=1 over all n_samples rows (the zero-filled unlabeled rows included). Now it raises ValueError("'y' must have at least 2 non-NaN values; got 1.").
Under IPW the variance is computed over the weighted terms of all rows, so the relevant requirement is n_samples >= 2, not the labeled count. The check looks copied from ClassicalMeanEngine, where it is correct. Callers with very small label budgets now crash. The Raises docstring changed, but CHANGELOG.md was not updated for this user-visible change.
Suggested fix: drop the check, or keep it deliberately and document it in CHANGELOG.md.
2. Lost test coverage of zero-probability row filtering
tests/unit/engines/test_ipw_classical.py:28
test_preprocess_valid_output uses all-0.5 probabilities and no zero-probability row, so the row-filtering branch is never asserted. CLAUDE.md says to put edge-case rows (a zero-probability row, a NaN label) into the single valid-output example. The delegation test mocks _get_non_zero_mask to all-True, so nothing else covers it. The old estimator test did (sampling_probability=[0.5, 0.5, 0.0, 0.5]). If non_zero_mask were dropped or inverted, the test would still pass.
3. Unused labeled_indicator in the dataset
glide/engines/ipw_classical.py:28
_compute_mean_and_std never uses labeled_indicator. Its only consumer is the estimator, which unpacks _, labeled_indicator, _ and calls int(labeled_indicator.sum()) to get n_labeled. The .astype(float) is unnecessary, since a boolean array sums fine. The 3-tuple dataset is harder to read than the plain array of the sibling ClassicalMeanEngine. A simpler design would be a 2-tuple dataset, or a boolean mask.
4. Estimator tests are weak guards
tests/unit/estimators/test_ipw_classical.py:33
test_estimate_delegates builds the expected dataset by calling estimator._engine._preprocess outside the mock block, so the wraps= mocks compare outputs of the code under test with each other. CLAUDE.md asks for hardcoded expected values. If _preprocess returned a wrong dataset, the test would still pass. test_init_sets_engine only asserts isinstance and adds little over the estimate tests.
5. Dead plumbing: no-op _fit_tuning_parameter call (minor)
glide/estimators/ipw_classical.py:77
estimate calls _fit_tuning_parameter(dataset, power_tuning=False), which always returns None, and passes that None to _compute_mean_and_std. ClassicalMeanEstimator skips the tuning step and passes None directly, so the two estimators are inconsistent. Harmless, but the extra call only exists to satisfy the protocol.
6. _preprocess signature differs from the protocol (minor, low severity)
glide/engines/ipw_classical.py:14
_preprocess(self, y, sampling_probability) takes positional parameters, while MeanEstimationEngine in base.py declares _preprocess(self, **fields). This could trip ty or generic keyword-based callers (e.g. a monitor façade). The existing PPI and classical engines have the same shape, so this is probably acceptable.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
classicalLinked to classical inferencerefactoringImprove structure with no functional change
2 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Moves the validation and computation of
IPWClassicalMeanEstimatorinto a new internalIPWClassicalMeanEngine, soestimatebecomes a thin shell calling the engine once, like the other migrated estimators. The engine can now be reused by an IPW-based monitor and byASIMeanEstimator.As a side effect,
estimatenow raises aValueErrorwhenyandsampling_probabilitydiffer in length or whenyhas fewer than 2 labeled values (previously an opaque indexing error or aNaNstandard error).Closes #506
Checklist
Quality gates that must be satisfied before requesting a review:
CONTRIBUTING.mdmake lintpassesmake type-checkpassesmake testspassesmake coveragereports 100% coveragemake test-notebookspassesmake doc)CHANGELOG.mdupdated if the change is user-facingLLM usage
Disclose if an LLM was used in writing this PR:
🤖 Generated with Claude Code