fix(pp): index top peaks by region in normalize_peaks - #240
Conversation
|
thanks to @UCDNJJ for noticing!! |
Sorting the thresholded column gives positions within that column, which are offset from region indices by the number of regions dropped ahead of them. Those positions were used to index the full region matrix, so the Gini scores deciding the low-Gini subset were read off unrelated regions whenever a region fell at or below peak_threshold. That in effect skipped the broad/specific test: the peaks averaged for the weight were the right top peaks, but were never checked for being broad. Conversely the same wrong indices were stored, so the returned region DataFrame listed genuinely broad regions that were not top peaks. Carry the kept region indices through the top-k selection instead. Weights move ~1% on average (2.8% max) on the mouse cortex tutorial data.
A cell type with no selected peaks got top_k_mean 0, so its weight was max_mean/0 = inf, or nan for every cell type at once when none had any. Both went straight into .X behind a numpy RuntimeWarning. Reachable without any sparsity by raising gini_std_threshold past the point where no top peak still counts as broad. On the mouse cortex tutorial data that cliff sits at 2.5, next to the 2.0 in the docstring example, and its position depends on the dataset's Gini distribution.
f994654 to
a1b0ee7
Compare
|
still have to update the tutorial notebook |
There was a problem hiding this comment.
Pull request overview
Fixes correctness issues in crested.pp.normalize_peaks where peak indices were mis-mapped after thresholding (causing Gini-based broad/specific filtering and returned peak lists to be computed from unrelated regions), and hardens normalization by raising when a cell type ends up with no selected peaks (instead of silently writing inf/nan into .X).
Changes:
- Correctly carries original region indices through top-k selection when
peak_thresholdfilters regions prior to sorting. - Raises a clear
ValueErrorwhen no peaks pass top-k + Gini selection for one or more cell types. - Adds closed-form tests covering the indexing bug and the new error behavior; documents both changes in the changelog.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/crested/pp/_normalization.py |
Fixes index mapping for top-k selection; adds an explicit error when normalization weights would be undefined. |
tests/test_pp.py |
Adds analytic regression tests for correct top-peak/Gini coupling and for raising on empty selections. |
docs/changelog.md |
Documents the indexing bugfix, the new error behavior, and the added regression test. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The guard test only exercised the case where no top peak counts as broad, which leaves top_indices non-empty. Add the two routes that empty it: a cell type entirely at or below peak_threshold, and top_k_percent rounding the selection to zero. Both stay a clean shape-(0,) reduction over the cell type axis and reach the intended error, rather than tripping numpy's zero-size reduction as a PR review suggested. Matching on the message distinguishes the two, since numpy raises ValueError for that as well.
Rerun after the normalize_peaks indexing fix. The returned region DataFrame now lists the cell types' top peaks rather than arbitrary broad regions, and the weights plot reflects the corrected values.
|
ready to merge if all agree |
| "text": [ | ||
| "Downloading file 'data/mouse_biccn/bigwigs_cut_sites.tar.gz' from 'https://resources.aertslab.org/CREsted/data/mouse_biccn/bigwigs_cut_sites.tar.gz' to '/home/VIB.LOCAL/niklas.kempynck/.cache/crested'.\n", |
There was a problem hiding this comment.
avoid exact paths to our storage. Just remove notebook output cells that include these.
|
I find the code a bit confusing to follow and the comments are very verbose as always with generated stuff, so I'll clean up the comments a bit |
|
Had a look over it and adjusted some stuff, here's a summary:
Here's a version of my reworked inner filtering step with annotations of which indices belong to which, so that you can check my logic: I checked my changes and they return identical values to Niklas' rework: |
casblaauw
left a comment
There was a problem hiding this comment.
Changes made manually
|
thanks cas! |
A bit of a concerning indexing bug that luckily does not have huge effects.
Sorting the thresholded column gives positions within that column, which are
offset from region indices by the number of regions dropped ahead of them. Those
positions were used to index the full region matrix, so the Gini scores deciding
the low-Gini subset were read off unrelated regions whenever a region fell at or
below
peak_threshold.That in effect skipped the broad/specific test: the peaks averaged for the weight
were the right top peaks, but were never checked for being broad. Conversely the
same wrong indices were stored, so the returned region DataFrame listed genuinely
broad regions that were not top peaks.
Carry the kept region indices through the top-k selection instead.
Weights move ~1% on average (2.8% max) on the mouse cortex tutorial data.
Second commit:
normalize_peaksnow raises when a cell type ends up with noselected peaks, instead of giving it an
infweight (ornanfor all of them, ifit happens to every cell type) and writing that into
.X. Reachable by raisinggini_std_thresholdpast the point where no top peak still counts as broad, whichis dataset dependent — 2.5 on the mouse cortex tutorial data, right next to the
2.0 in the docstring example.