-
Notifications
You must be signed in to change notification settings - Fork 916
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for maintain_order param in joins #17698
base: branch-25.02
Are you sure you want to change the base?
Changes from all commits
056ca24
b952e15
0d702fe
e07cef8
3005745
2695735
01007fb
e01aa2e
84830b6
7238d16
5df0303
08b3a83
6022db5
1daa8c6
e38d5a1
712146b
ad44798
807de8f
6f1741f
6a10590
14e2508
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -1139,9 +1139,6 @@ def __init__( | |||||||||||||||||||||||||||||||||||||||||
self.options = options | ||||||||||||||||||||||||||||||||||||||||||
self.children = (left, right) | ||||||||||||||||||||||||||||||||||||||||||
self._non_child_args = (self.left_on, self.right_on, self.options) | ||||||||||||||||||||||||||||||||||||||||||
# TODO: Implement maintain_order | ||||||||||||||||||||||||||||||||||||||||||
if options[5] != "none": | ||||||||||||||||||||||||||||||||||||||||||
raise NotImplementedError("maintain_order not implemented yet") | ||||||||||||||||||||||||||||||||||||||||||
if any( | ||||||||||||||||||||||||||||||||||||||||||
isinstance(e.value, expr.Literal) | ||||||||||||||||||||||||||||||||||||||||||
for e in itertools.chain(self.left_on, self.right_on) | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -1195,6 +1192,7 @@ def _reorder_maps( | |||||||||||||||||||||||||||||||||||||||||
right_rows: int, | ||||||||||||||||||||||||||||||||||||||||||
rg: plc.Column, | ||||||||||||||||||||||||||||||||||||||||||
right_policy: plc.copying.OutOfBoundsPolicy, | ||||||||||||||||||||||||||||||||||||||||||
maintain_order: Literal["none", "left", "right", "left_right", "right_left"], | ||||||||||||||||||||||||||||||||||||||||||
) -> list[plc.Column]: | ||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||
Reorder gather maps to satisfy polars join order restrictions. | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -1213,31 +1211,52 @@ def _reorder_maps( | |||||||||||||||||||||||||||||||||||||||||
Right gather map | ||||||||||||||||||||||||||||||||||||||||||
right_policy | ||||||||||||||||||||||||||||||||||||||||||
Nullify policy for right map | ||||||||||||||||||||||||||||||||||||||||||
maintain_order | ||||||||||||||||||||||||||||||||||||||||||
Which DataFrame row order to preserve | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
Returns | ||||||||||||||||||||||||||||||||||||||||||
------- | ||||||||||||||||||||||||||||||||||||||||||
list of reordered left and right gather maps. | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
Notes | ||||||||||||||||||||||||||||||||||||||||||
----- | ||||||||||||||||||||||||||||||||||||||||||
For a left join, the polars result preserves the order of the | ||||||||||||||||||||||||||||||||||||||||||
left keys, and is stable wrt the right keys. For all other | ||||||||||||||||||||||||||||||||||||||||||
joins, there is no order obligation. | ||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||
dt = plc.interop.to_arrow(plc.types.SIZE_TYPE) | ||||||||||||||||||||||||||||||||||||||||||
init = plc.interop.from_arrow(pa.scalar(0, type=dt)) | ||||||||||||||||||||||||||||||||||||||||||
step = plc.interop.from_arrow(pa.scalar(1, type=dt)) | ||||||||||||||||||||||||||||||||||||||||||
left_order = plc.copying.gather( | ||||||||||||||||||||||||||||||||||||||||||
plc.Table([plc.filling.sequence(left_rows, init, step)]), lg, left_policy | ||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||
right_order = plc.copying.gather( | ||||||||||||||||||||||||||||||||||||||||||
plc.Table([plc.filling.sequence(right_rows, init, step)]), rg, right_policy | ||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||
if maintain_order in {"none", "left_right", "right_left"}: | ||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue/question: If we have no obligation There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes you're correct, I'll also need to update the other tests in the suite since polars defaults to "none" . So I'll add |
||||||||||||||||||||||||||||||||||||||||||
left_order = plc.copying.gather( | ||||||||||||||||||||||||||||||||||||||||||
plc.Table([plc.filling.sequence(left_rows, init, step)]), | ||||||||||||||||||||||||||||||||||||||||||
lg, | ||||||||||||||||||||||||||||||||||||||||||
left_policy, | ||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||
right_order = plc.copying.gather( | ||||||||||||||||||||||||||||||||||||||||||
plc.Table([plc.filling.sequence(right_rows, init, step)]), | ||||||||||||||||||||||||||||||||||||||||||
rg, | ||||||||||||||||||||||||||||||||||||||||||
right_policy, | ||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||
elif maintain_order == "left": | ||||||||||||||||||||||||||||||||||||||||||
left_order = plc.copying.gather( | ||||||||||||||||||||||||||||||||||||||||||
plc.Table([plc.filling.sequence(left_rows, init, step)]), | ||||||||||||||||||||||||||||||||||||||||||
lg, | ||||||||||||||||||||||||||||||||||||||||||
left_policy, | ||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||
elif maintain_order == "right": | ||||||||||||||||||||||||||||||||||||||||||
right_order = plc.copying.gather( | ||||||||||||||||||||||||||||||||||||||||||
plc.Table([plc.filling.sequence(right_rows, init, step)]), | ||||||||||||||||||||||||||||||||||||||||||
rg, | ||||||||||||||||||||||||||||||||||||||||||
right_policy, | ||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||
if maintain_order == "left": | ||||||||||||||||||||||||||||||||||||||||||
sort_keys = left_order.columns() | ||||||||||||||||||||||||||||||||||||||||||
elif maintain_order == "right": | ||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the reviewer: This PR needs more work, but I'm opening it up for review so I can get some help handling a special case: full right joins. Specifically, the case where the test fails is when there are unmatched keys in the left dataframe. Any advice on how to handle this? Example: left = pl.LazyFrame(
{
"a": [1, 2, 3, 1, None],
"b": [1, 2, 3, 4, 5],
"c": [2, 3, 4, 5, 6],
}
)
right = pl.LazyFrame(
{
"a": [1, 4, 3, 7, None, None, 1],
"c": [2, 3, 4, 5, 6, 7, 8],
"d": [6, None, 7, 8, -1, 2, 4],
}
)
q = left.join(right, on=pl.col("a"), how="full", maintain_order="right")
q.collect(engine="gpu") The dataframe differ at column "a"
The |
||||||||||||||||||||||||||||||||||||||||||
sort_keys = right_order.columns() | ||||||||||||||||||||||||||||||||||||||||||
elif maintain_order in {"none", "left_right"}: | ||||||||||||||||||||||||||||||||||||||||||
sort_keys = left_order.columns() + right_order.columns() | ||||||||||||||||||||||||||||||||||||||||||
elif maintain_order == "right_left": | ||||||||||||||||||||||||||||||||||||||||||
sort_keys = right_order.columns() + left_order.columns() | ||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+1225
to
+1254
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we avoid repetition here by just immediately making the |
||||||||||||||||||||||||||||||||||||||||||
return plc.sorting.stable_sort_by_key( | ||||||||||||||||||||||||||||||||||||||||||
plc.Table([lg, rg]), | ||||||||||||||||||||||||||||||||||||||||||
plc.Table([*left_order.columns(), *right_order.columns()]), | ||||||||||||||||||||||||||||||||||||||||||
[plc.types.Order.ASCENDING, plc.types.Order.ASCENDING], | ||||||||||||||||||||||||||||||||||||||||||
[plc.types.NullOrder.AFTER, plc.types.NullOrder.AFTER], | ||||||||||||||||||||||||||||||||||||||||||
plc.Table(sort_keys), | ||||||||||||||||||||||||||||||||||||||||||
[plc.types.Order.ASCENDING] * len(sort_keys), | ||||||||||||||||||||||||||||||||||||||||||
[plc.types.NullOrder.AFTER] * len(sort_keys), | ||||||||||||||||||||||||||||||||||||||||||
).columns() | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
@classmethod | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -1257,7 +1276,7 @@ def do_evaluate( | |||||||||||||||||||||||||||||||||||||||||
right: DataFrame, | ||||||||||||||||||||||||||||||||||||||||||
) -> DataFrame: | ||||||||||||||||||||||||||||||||||||||||||
"""Evaluate and return a dataframe.""" | ||||||||||||||||||||||||||||||||||||||||||
how, join_nulls, zlice, suffix, coalesce, _ = options | ||||||||||||||||||||||||||||||||||||||||||
how, join_nulls, zlice, suffix, coalesce, maintain_order = options | ||||||||||||||||||||||||||||||||||||||||||
if how == "cross": | ||||||||||||||||||||||||||||||||||||||||||
# Separate implementation, since cross_join returns the | ||||||||||||||||||||||||||||||||||||||||||
# result, not the gather maps | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -1300,11 +1319,16 @@ def do_evaluate( | |||||||||||||||||||||||||||||||||||||||||
left, right = right, left | ||||||||||||||||||||||||||||||||||||||||||
left_on, right_on = right_on, left_on | ||||||||||||||||||||||||||||||||||||||||||
lg, rg = join_fn(left_on.table, right_on.table, null_equality) | ||||||||||||||||||||||||||||||||||||||||||
if how == "left" or how == "right": | ||||||||||||||||||||||||||||||||||||||||||
# Order of left table is preserved | ||||||||||||||||||||||||||||||||||||||||||
lg, rg = cls._reorder_maps( | ||||||||||||||||||||||||||||||||||||||||||
left.num_rows, lg, left_policy, right.num_rows, rg, right_policy | ||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||
# Reorder maps based on maintain_order | ||||||||||||||||||||||||||||||||||||||||||
lg, rg = cls._reorder_maps( | ||||||||||||||||||||||||||||||||||||||||||
left.num_rows, | ||||||||||||||||||||||||||||||||||||||||||
lg, | ||||||||||||||||||||||||||||||||||||||||||
left_policy, | ||||||||||||||||||||||||||||||||||||||||||
right.num_rows, | ||||||||||||||||||||||||||||||||||||||||||
rg, | ||||||||||||||||||||||||||||||||||||||||||
right_policy, | ||||||||||||||||||||||||||||||||||||||||||
maintain_order, | ||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+1322
to
+1331
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||
if coalesce and how == "inner": | ||||||||||||||||||||||||||||||||||||||||||
right = right.discard_columns(right_on.column_names_set) | ||||||||||||||||||||||||||||||||||||||||||
left = DataFrame.from_table( | ||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or accept
"none"
but just return the input maps immediately.