Skip to content

nearest_ranges: honour preserve_input_order for direction="upstream"/"downstream" - #175

Open
youdie006 wants to merge 1 commit into
pyranges:masterfrom
youdie006:fix/169-nearest-preserve-order-directional
Open

nearest_ranges: honour preserve_input_order for direction="upstream"/"downstream"#175
youdie006 wants to merge 1 commit into
pyranges:masterfrom
youdie006:fix/169-nearest-preserve-order-directional

Conversation

@youdie006

Copy link
Copy Markdown

Fixes #169.

nearest_ranges(direction="upstream"/"downstream") returns rows grouped by strand regardless of preserve_input_order -- the option is silently inert on a directional query. direction="any" is unaffected.

Repro (interleaved-strand query)

direction preserve_input_order result Id order index
any True / False a, b, c, d 0, 1, 2, 3
upstream True a, c, b, d 0, 2, 1, 3 <- grouped by strand
upstream False a, c, b, d 0, 2, 1, 3
downstream True a, c, b, d 0, 2, 1, 3
downstream False a, c, b, d 0, 2, 1, 3

The two preserve_input_order values give identical output for the directional cases, and the returned index [0, 2, 1, 3] is not the input index [0, 1, 2, 3], which matters because PyRanges otherwise preserves the input index.

Root cause

The docstring promises preserve_input_order (default True) "preserve[s] the original input order in the result", and direction="any" delivers exactly that -- it calls RangeFrame.nearest_ranges once, where the flag maps to ruranges' sort_output and rows come back in self's positional input order.

The directional path has to split self by strand (each strand searches the opposite coordinate direction), then recombines with a plain pd.concat(per_strand). The flag is forwarded into each half, so order holds within a strand, but the concat appends every forward row then every reverse row and nothing restores the interleaving between them.

Fix

Carry each row's position in self through the split and stable-sort the concatenated frame back into input order when preserve_input_order is set -- mirroring the ruranges sort_output positional-order semantics the direction="any" branch already uses, so the ordering rule is now consistent across all directions (and consistent with the ordering standardization shared with polaranges in #165). The index alone can't stand in for the position, since k > 1 duplicates it and the caller's index may be non-unique, so a positional order column rides through the split. preserve_input_order=False is unchanged (still grouped, faster). This is user-visible for directional queries and may deserve its own release note.

Tests

Adds a regression test on the interleaved-strand frame from the issue: directional queries with preserve_input_order=True now match direction="any" (input order + original index), direction="any" stays correct, and preserve_input_order=False stays grouped. RED on current master (a, c, b, d / [0, 2, 1, 3]), GREEN after the fix. One direction='downstream' doctest updated to the corrected input order.

Reported and diagnosed by @marco-mariotti.

This change was prepared with AI assistance (Claude).

nearest_ranges(direction="upstream"/"downstream") returned rows grouped by
strand regardless of preserve_input_order - the option was silently inert on a
directional query, while direction="any" honoured it.

The directional path splits self by strand (each strand searches the opposite
coordinate direction) then recombines with a plain pd.concat(per_strand), which
appends every forward row then every reverse row and never restores the
interleaving. Carry each row's position in self through the split and stable-sort
the concatenated frame back into input order when preserve_input_order is set,
mirroring the ruranges sort_output positional-order semantics the "any" branch
already uses (and the ordering standardization shared with polaranges in pyranges#165).
The index alone can't stand in for position, since k > 1 duplicates it and the
caller's index may be non-unique, so a positional order column rides through the
split. preserve_input_order=False is unchanged (still grouped).

Fixes pyranges#169.
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.

nearest_ranges: preserve_input_order is ignored for direction="upstream"/"downstream"

1 participant