Signal-ladder API renames, and two real defects the naming proposal missed (2.0.0) - #534
Merged
Merged
Conversation
Rename the permutation-null functions so their names state the question they answer, and retire the "discriminator" vocabulary from the sensory relate step. Scope is set by an adjudicated review: the proposal's section-5 "defect" is not one (the q2 gate is conjunctive with a FWER-controlled test), the deletion of permutation_column_null is dropped (all three reasons fail, and a calibration experiment measured it conservative), and two real naming defects it missed are fixed instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016vCXGaB7zcXSd7GXQoHJig
permutation_q2 -> check_predictive_signal, pipeline_null -> count_discoveries_under_null. Both old names now raise AttributeError naming their replacement, following the migration-helper pattern already used in univariate/metrics.py, on all three surfaces users import from. check_predictive_signal takes x and y first and makes fit_predict optional, defaulting to leave-one-out cross-validated PLS. The default deliberately receives the raw blocks so each fold derives its own centring and scaling constants, which is the trap the parameter documentation warns callers about. An out-of-sample null has to refit per fold per permutation, so the default costs n_perm * n_products fits; the docstring now says so with a figure rather than leaving the reader to discover it. count_discoveries_under_null renames empirical_fdr to null_to_observed_ratio and drops the [0, 1] clip. A ratio above 1 means shuffling found more than the real response did, which is the most informative reading the function can give, and clipping hid exactly that case behind a number that looked like a well-behaved rate. class_enrichment keeps its name; the module docstring now says why, so the boundary of the scheme is stated rather than left implicit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016vCXGaB7zcXSd7GXQoHJig
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…rted discriminate_observational becomes find_predictive_descriptors, and the "discriminator" vocabulary goes with it: result["discriminator"] becomes result["predictive_descriptors"], the discriminator_significant flag becomes is_predictive, and the discriminator= kwarg on relate_observational and analyze_descriptive becomes find_predictive=. The old function name raises AttributeError naming its replacement. Two defects fixed along the way, both of them naming errors that the rename made visible: - The docstring claimed the permutation p-value was Benjamini-Hochberg corrected across the whole family. It is not, and never was: the code builds a max-statistic (Westfall-Young) null, as its own inline comment says. _attach_fdr, the real BH helper, is only ever called from relate_observational. - The field carrying that value was named q_value, which is FDR vocabulary for a family-wise-error-adjusted p-value. Worse, q_value in the sibling associations list of the same result dict really is a BH q-value, so one key name meant two different quantities. It is now p_value_fwer. The docstring also now states what the correction does and does not cover: it is within an attribute, not across them, so on a panel of A attributes roughly alpha * A are expected to yield a spurious family. That gap is documented rather than closed here, because closing it changes results. The q2_cv > 0.0 gate is annotated as what it is: a cheap, deliberately uncalibrated pre-screen that decides whether the permutation loop is worth running, ANDed with the calibrated p-value and the jackknife flag. Passing it cannot make a descriptor a finding. permutation_column_null is kept and now cross-references its per-attribute counterpart in both directions, so the choice between block-level and per-attribute screening is stated rather than left to be guessed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016vCXGaB7zcXSd7GXQoHJig
Mechanical renames across test_multivariate_null.py, test_sensory.py and test_sensory_end_to_end.py, including the argument reorder now that check_predictive_signal takes x and y first. The q_value assertions needed splitting rather than replacing: the one on the descriptors table is now p_value_fwer, but the two on relate_observational's associations stay as q_value, because those really are Benjamini-Hochberg q-values. That the same key name meant two different quantities is the defect being fixed. New coverage: - The leave-one-out default separates signal from noise, matches an explicit equivalent callable, and clamps a silly n_components rather than raising. - Pre-scaling the blocks does not change the answer. This pins down a claim worth having in the suite: Q-squared is invariant to an affine transform of y, and the default re-derives constants per fold, so a caller who scales first is not quietly rewarded with an inflated score. - Every removed name raises AttributeError naming its replacement, on all the surfaces users import from, while an unrelated missing attribute still raises plainly. - null_to_observed_ratio equals the raw quotient, and on pure noise at least one draw exceeds 1. The old clipped empirical_fdr reported 1.0 for those draws, which is the information the clip was hiding. - permutation_column_null is still exported, with the reasons its proposed deletion failed recorded next to the assertion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016vCXGaB7zcXSd7GXQoHJig
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016vCXGaB7zcXSd7GXQoHJig
…cope permutation_nulls.rst now leads with the zero-argument form, since check_predictive_signal supplies a leave-one-out default, and says plainly that the blocks go in unscaled and why. It also gives the cost in fits rather than letting a reader discover it: n_perm refits per fold is twenty thousand fits at n_perm=999 on twenty products. sensory_panel.rst renames the "discriminator" section and, more usefully, adds what was missing: the max-statistic correction is within an attribute, not across attributes, so on a panel of A attributes roughly alpha*A are expected to throw up a spurious family. The page previously implied wider protection than the code provides. It now also points at permutation_column_null as the block-level screen, so the choice between the two is stated. Verified against the -W docs gate, which is where a stale :func: role would have failed CI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016vCXGaB7zcXSd7GXQoHJig
MAJOR: public names are removed without a deprecation cycle. CITATION.cff carries the identical version and today's date in this same commit. The changelog states the departure from CONTRIBUTING.md rather than leaving a reader to infer it. The two multivariate names were added in 1.76.0, one release and one day earlier, so warning shims would have carried dead weight to 2.0.0 for names nobody could have adopted. That is a judgement about this release, not a precedent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016vCXGaB7zcXSd7GXQoHJig
Codecov flagged two uncovered lines in multivariate/__init__.py: the __getattr__ fallback that handles a name which is not a rename. The test only exercised that path on _null. Worth covering rather than waving through. The helper intercepts every failed attribute lookup on the module, so its fallback is what stands between an ordinary typo and a confusing rename message pointing at an unrelated function. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016vCXGaB7zcXSd7GXQoHJig
CodeQL flagged process_improve.multivariate being imported with both 'import' and 'import from' in the same scope, and the coverage fix had just duplicated the pattern into a second test. Both now use the from-import form, which is the shape ruff had already settled on in the sibling sensory test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016vCXGaB7zcXSd7GXQoHJig
11 tasks
kgdunn
added a commit
that referenced
this pull request
Aug 30, 2026
…nstead of 2.0.0 (#535) * Make the signal-ladder renames backward compatible, and ship 1.77.0 The renames landed in #534 as a breaking 2.0.0. Nothing had been published, so this reworks them into the Announce phase the deprecation policy calls for: every old spelling still works and warns, and the release is a MINOR. Restored, each forwarding to its replacement with a DeprecationWarning naming it, per docs/development/deprecation_policy.rst: - permutation_q2, pipeline_null, discriminate_observational as function shims. The permutation_q2 shim keeps the old argument order and adapts to the new signature, since check_predictive_signal now takes the blocks first. - q_value and discriminator_significant on the descriptor records, emitted alongside p_value_fwer and is_predictive with identical values. - result.relate["discriminator"], referencing the same object as result.relate["predictive_descriptors"]. - The discriminator= keyword on relate_observational, analyze_descriptive and the sensory_analyze_descriptive tool input. Passing both spellings raises ValueError rather than silently picking one, following the desirability_weights precedent in experiments/optimization.py. - empirical_fdr, still clipped to [0, 1] exactly as before, so an existing caller reads the same number it always did. null_to_observed_ratio is the same quantity unclipped; the two only diverge once shuffling out-finds the real response, which is the case the old key was hiding. The shims take explicit signatures rather than **kwargs so help() and inspect.signature stay accurate, matching the reasoning behind _model_method elsewhere in the package. Verified that the package does not warn on itself: the default relate_observational path runs clean under -W error::DeprecationWarning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016vCXGaB7zcXSd7GXQoHJig * Include the restored empirical_fdr in the documented key set test_keys_are_as_documented asserts the exact returned key set, and bringing empirical_fdr back as a deprecated alias made it fail. Caught by CI on 3.13/ubuntu, not locally: after adding the key I re-ran only the -k Deprecated subset of the file rather than the whole file, so the one test that pins the full set never ran. The key is listed with a note saying it is deprecated and due out in 2.0.0, so removing it then fails this test rather than passing silently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016vCXGaB7zcXSd7GXQoHJig --------- Co-authored-by: Claude <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
permutation_q2->check_predictive_signal,pipeline_null->count_discoveries_under_null,discriminate_observational->find_predictive_descriptors. The "discriminator" vocabulary goes with them (result.relate["discriminator"]->["predictive_descriptors"],discriminator_significant->is_predictive, kwargdiscriminator=->find_predictive=, including on thesensory_analyze_descriptivetool schema). Removed names raiseAttributeErrornaming their replacement, following the precedent inunivariate/metrics.py.find_predictive_descriptorsclaimed in its docstring to apply Benjamini-Hochberg correction; it does not, and never did, the code builds a Westfall-Young max-statistic null as its own inline comment says. And the field carrying that value was namedq_value, FDR vocabulary for a family-wise-error-adjusted p-value, whileq_valuein the siblingassociationslist of the same result dict genuinely is a BH q-value. One key name, two quantities. It is nowp_value_fwer.check_predictive_signaltakesxandyfirst and makesfit_predictoptional, defaulting to leave-one-out cross-validated PLS on the raw blocks, so each fold derives its own constants rather than inheriting constants computed from the row it is predicting.empirical_fdr->null_to_observed_ratio, with the[0, 1]clip dropped. On pure noise the old field reported a tidy1.0for draws where shuffling actually out-found the real response, which is the single most informative reading the function can give. A test now pins that at least one such draw exceeds 1.Two things the proposal asked for that this does not do
predictable = q2_cv > 0.0is one of three conjunctive conditions atanalysis.py:456, ANDed with a FWER-controlled max-statistic p-value and a jackknife flag, so a loose gate cannot manufacture findings. Its real role is to skip the 199-refit permutation loop. Routing it through a permutation null would have cost roughly 20-46x on the defaultanalyze_descriptivepath to fix a problem that is not there. The gate is now documented as the deliberate pre-screen it is.permutation_column_nullis kept, not deleted. All three stated reasons fail: it answers a block-level question (one multi-response fit, one record per descriptor) that the per-attribute function does not; thevipnote it cited condemns counting VIP > 1 exceedances under response permutation, which is not what it does; and a calibration experiment measured it conservative (VIP 1.9%,cv_beta4.4% against a nominal 5%). It andfind_predictive_descriptorsnow cross-reference each other so the choice between them is explicit.The cross-attribute multiplicity gap is real (the max-statistic family is rebuilt per attribute, so nothing corrects across them) and is documented, not closed in this PR, since closing it changes results.
This is a breaking release with no deprecation cycle, departing from
CONTRIBUTING.md. Deliberate and recorded in the CHANGELOG: the two multivariate names shipped in 1.76.0, one release and one day earlier, so shims would have carried dead weight to 2.0.0 for names nobody could have adopted.Test plan
uv run pytestgreen: 2978 passed, 3 skipped, coverage 94.45% against the 92% gaten_components; pre-scaling the blocks does not change the answer; every removed name raises with its replacement on all import surfaces;null_to_observed_ratioequals the raw quotient and exceeds 1 on noise;permutation_column_nullis still exporteduv run ruff check .anduv run ruff format --check .both passuv run mypy src/process_improvepasses, 153 filesuv run sphinx-build docs docs/_build/html -b html -Wsucceeds, which is where a stale:func:role would have failed CIChecklist
pyproject.tomlto2.0.0(MAJOR: public names removed), withCITATION.cffset to the identical version and date in the same commitruff check .passesCHANGELOG.mdupdated