Skip to content

fix: #1162; preserving order of obs after _filter_table_by_elements - #1193

Open
LucaMarconato wants to merge 13 commits into
mainfrom
fix/reorder-obs-after-relational-query
Open

fix: #1162; preserving order of obs after _filter_table_by_elements#1193
LucaMarconato wants to merge 13 commits into
mainfrom
fix/reorder-obs-after-relational-query

Conversation

@LucaMarconato

Copy link
Copy Markdown
Member

Closes #1162

@LucaMarconato

Copy link
Copy Markdown
Member Author

The fix implements a suggestion from @jan-glx "in the filter function, add row numbers, do the join, then sort by those row numbers, then drop".

Please either @timtreis or @jan-glx review; whoever does it first 😁

@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.44%. Comparing base (7944486) to head (0fed6d7).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1193      +/-   ##
==========================================
+ Coverage   92.40%   92.44%   +0.03%     
==========================================
  Files          52       52              
  Lines        7872     7886      +14     
==========================================
+ Hits         7274     7290      +16     
+ Misses        598      596       -2     
Files with missing lines Coverage Δ
src/spatialdata/_core/query/relational_query.py 95.89% <100.00%> (+0.57%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@LucaMarconato

Copy link
Copy Markdown
Member Author

For the maintainers: I created and added the label "todo-add-extra-comments-on-release-notes", so when we auto-generate release notes we remember to add a comment mentioning potential order implications to users.

…1162)

Move the row-order fix from `_filter_table_by_elements` into
`_left_join_spatialelement_table`.

Our left_join with match `match_rows="no"` is actually a semi-join for the table --
exactly what we need for filter. A semi-join should preserve order, so fixing it there.
( By sorting the table indices, already used for subsetting after all grouped
operations. (which caused the reordering).

@jan-glx jan-glx left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks correct, but I think now upstream fix is the way to go.

Comment thread src/spatialdata/_core/query/relational_query.py Outdated
assert list(sdata["table"].obs.columns) == ["region", "instance_id", "label"]


def test_filter_table_non_annotating(full_sdata):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test LGTM

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expanded them, please can you re-review?

del table.obs[order_col]
if filtered is None or len(filtered) == 0:
return None
filtered = filtered[np.argsort(filtered.obs[order_col].to_numpy()), :].copy()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks correct, but probably upstream fix is more efficient (no double copy)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the upstream fix; I extended it to the inner join.

@jan-glx

jan-glx commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

In d61c0ca I proposed an alternative solution that restores the row order for the table already in the semi join performed in _left_join_spatialelement_table

This reverts the temporary-obs-column stash/restore approach introduced in
828faab, restoring the plain `how="left"` call. The workaround mutated
`table.obs` during filtering; the following commit reintroduces the #1162 fix
at the join level instead, leaving this helper untouched.
Fix the row-order scrambling in `_left_join_spatialelement_table`: the join
groups matching table rows by region, which does not preserve `table.obs`
order when a table annotates multiple interleaved regions. For
`match_rows="no"` (a semi-join) there is no element-driven ordering to honor,
so restore the original table row order at the source.
@jan-glx
jan-glx force-pushed the fix/reorder-obs-after-relational-query branch from d61c0ca to 1e3c764 Compare August 26, 2026 09:02
@LucaMarconato
LucaMarconato marked this pull request as draft August 26, 2026 16:55
if match_rows == "right":
warnings.warn("Matching rows 'right' is not supported for 'left' join.", UserWarning, stacklevel=2)
warnings.warn(
"Matching rows 'right' is not supported for 'left' join; it will be treated as 'no'.",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Truth be told, for left join we could support the right match_rows, and for right join we could support the left match_rows. But we can skip it for now (since also it was not supported before this PR), and eventually do it in the future.

For left_exclusive, right match_rows does not make sense, so it is not supported. Same for right_exclusive: left match_rows does not make sense there.

@LucaMarconato
LucaMarconato marked this pull request as ready for review August 27, 2026 12:04
Comment on lines +1010 to +1012
# TODO: make this test more interesting by adding indices 5, 4 to "a" and 4, 6 to "b"
# by design, "left_exclusive" never returns a table (only filtered elements), regardless of
# match_rows or whether anything was actually excluded.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do in this PR or leave for the future. It is beyond the scope of the original fix anyway.

@LucaMarconato
LucaMarconato requested a review from jan-glx August 27, 2026 12:04
@LucaMarconato

LucaMarconato commented Aug 27, 2026

Copy link
Copy Markdown
Member Author

Thanks @jan-glx for the review. I upstreamed the fix as suggested and added extensive tests for all the join and match_rows combinations for tables with interleaved annotations of the elements.

In doing so I found out that:

  • the reported bug was affecting also the inner join
  • I improved the disclosure to users of unsupported join x match_rows combinations (via clear warning messages)
  • found and fixed a bug for match_rows = 'no' for 'inner' and 'right' join, which was not leaving the index order untouched
  • found and fixed a bug in _left_exclusive_join_spatialelement_table that was leading to an exception.

I kindly ask you for a re-review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Relational query reorders table obs by region in 0.8

3 participants