From eea1aa8dec568adbdfcd82f8cceff478c867c4eb Mon Sep 17 00:00:00 2001 From: nameexhaustion Date: Fri, 15 Mar 2024 15:10:46 +1100 Subject: [PATCH 1/7] refactor `update_sorted_flag_before_append` --- .../src/chunked_array/ops/append.rs | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/crates/polars-core/src/chunked_array/ops/append.rs b/crates/polars-core/src/chunked_array/ops/append.rs index 73bebe686dd7..1d551626b524 100644 --- a/crates/polars-core/src/chunked_array/ops/append.rs +++ b/crates/polars-core/src/chunked_array/ops/append.rs @@ -19,8 +19,6 @@ where T: PolarsDataType, for<'a> T::Physical<'a>: TotalOrd, { - // TODO: attempt to maintain sortedness better in case of nulls. - // If either is empty, copy the sorted flag from the other. if ca.is_empty() { ca.set_sorted_flag(other.is_sorted_flag()); @@ -41,26 +39,39 @@ where } // Check the order is maintained. - let still_sorted = { - // To prevent potential quadratic append behavior we do not find - // the last non-null element in ca. - if let Some(left) = ca.last() { - if let Some(right_idx) = other.first_non_null() { - let right = other.get(right_idx).unwrap(); - if ca.is_sorted_ascending_flag() { - left.tot_le(&right) - } else { - left.tot_ge(&right) - } + let still_sorted = match ( + ca.null_count() != ca.len(), + other.null_count() != other.len(), + ) { + (false, false) => true, // all null + (false, true) => 1 + other.last_non_null().unwrap() == other.len(), // nulls first + (true, false) => ca.first_non_null().unwrap() == 0, // nulls last + (true, true) => { + // both arrays have non-null values + let l_idx = ca.last_non_null().unwrap(); + let r_idx = other.first_non_null().unwrap(); + + let l_val = unsafe { ca.value_unchecked(l_idx) }; + let r_val = unsafe { other.value_unchecked(r_idx) }; + + // compare values + let out = if ca.is_sorted_ascending_flag() { + l_val.tot_le(&r_val) } else { - // Right is only nulls, trivially sorted. - true - } - } else { - // Last element in left is null, pessimistically assume not sorted. - false - } + l_val.tot_ge(&r_val) + }; + + out && ( + // check null position + // the first 2 ensure there are no nulls in the center + (1 + l_idx == ca.len()) + && (r_idx == 0) + // this checks that if there are nulls, they are all on one side + && !(ca.first_non_null().unwrap() != 0 && 1 + other.last_non_null().unwrap() != other.len()) + ) + }, }; + if !still_sorted { ca.set_sorted_flag(IsSorted::Not); } From 85d32e3f8bd41a94003462c1f31e184c9717ddfe Mon Sep 17 00:00:00 2001 From: nameexhaustion Date: Fri, 15 Mar 2024 17:00:19 +1100 Subject: [PATCH 2/7] more refactor and add test case --- crates/polars-core/src/chunked_array/mod.rs | 12 +++ .../src/chunked_array/ops/append.rs | 94 ++++++++++-------- py-polars/tests/unit/operations/test_sort.py | 98 +++++++++++++++++++ 3 files changed, 163 insertions(+), 41 deletions(-) diff --git a/crates/polars-core/src/chunked_array/mod.rs b/crates/polars-core/src/chunked_array/mod.rs index a0612c5457ba..384d99008e7f 100644 --- a/crates/polars-core/src/chunked_array/mod.rs +++ b/crates/polars-core/src/chunked_array/mod.rs @@ -236,6 +236,12 @@ impl ChunkedArray { 0 }; + debug_assert!( + // If we are lucky this catches something. + unsafe { self.get_unchecked(out).is_some() }, + "incorrect sorted flag" + ); + Some(out) } else { first_non_null(self.iter_validities()) @@ -260,6 +266,12 @@ impl ChunkedArray { self.len() - self.null_count() - 1 }; + debug_assert!( + // If we are lucky this catches something. + unsafe { self.get_unchecked(out).is_some() }, + "incorrect sorted flag" + ); + Some(out) } else { last_non_null(self.iter_validities(), self.len()) diff --git a/crates/polars-core/src/chunked_array/ops/append.rs b/crates/polars-core/src/chunked_array/ops/append.rs index 1d551626b524..6f75e4ec812f 100644 --- a/crates/polars-core/src/chunked_array/ops/append.rs +++ b/crates/polars-core/src/chunked_array/ops/append.rs @@ -19,62 +19,74 @@ where T: PolarsDataType, for<'a> T::Physical<'a>: TotalOrd, { - // If either is empty, copy the sorted flag from the other. - if ca.is_empty() { - ca.set_sorted_flag(other.is_sorted_flag()); - return; - } - if other.is_empty() { - return; - } - - // Both need to be sorted, in the same order, if the order is maintained. - // TODO: rework sorted flags, ascending and descending are not mutually - // exclusive for all-equal/all-null arrays. - let ls = ca.is_sorted_flag(); - let rs = other.is_sorted_flag(); - if ls != rs || ls == IsSorted::Not || rs == IsSorted::Not { - ca.set_sorted_flag(IsSorted::Not); - return; - } + let is_sorted_any = |ca: &ChunkedArray| !matches!(ca.is_sorted_flag(), IsSorted::Not); - // Check the order is maintained. - let still_sorted = match ( + let sorted_flag = match ( ca.null_count() != ca.len(), other.null_count() != other.len(), ) { - (false, false) => true, // all null - (false, true) => 1 + other.last_non_null().unwrap() == other.len(), // nulls first - (true, false) => ca.first_non_null().unwrap() == 0, // nulls last + (false, false) => IsSorted::Ascending, + (false, true) => { + if is_sorted_any(other) && 1 + other.last_non_null().unwrap() == other.len() { + // nulls first + other.is_sorted_flag() + } else { + IsSorted::Not + } + }, + (true, false) => { + // nulls last + if is_sorted_any(ca) && ca.first_non_null().unwrap() == 0 { + ca.is_sorted_flag() + } else { + IsSorted::Not + } + }, (true, true) => { // both arrays have non-null values - let l_idx = ca.last_non_null().unwrap(); - let r_idx = other.first_non_null().unwrap(); + if ( + // we can ignore the sorted flag of other if there is only 1 non-null value + // ca : descending [3, 2] + // other : ascending [1] + // out : descending [3, 2, 1] + other.len() - other.null_count() > 1 + ) && ca.is_sorted_flag() != other.is_sorted_flag() + { + IsSorted::Not + } else { + let l_idx = ca.last_non_null().unwrap(); + let r_idx = other.first_non_null().unwrap(); - let l_val = unsafe { ca.value_unchecked(l_idx) }; - let r_val = unsafe { other.value_unchecked(r_idx) }; + let l_val = unsafe { ca.value_unchecked(l_idx) }; + let r_val = unsafe { other.value_unchecked(r_idx) }; - // compare values - let out = if ca.is_sorted_ascending_flag() { - l_val.tot_le(&r_val) - } else { - l_val.tot_ge(&r_val) - }; + // compare values + let keep_sorted = if ca.is_sorted_ascending_flag() { + l_val.tot_le(&r_val) + } else { + l_val.tot_ge(&r_val) + }; - out && ( - // check null position - // the first 2 ensure there are no nulls in the center - (1 + l_idx == ca.len()) + let keep_sorted = keep_sorted + & ( + // check null position + // the first 2 ensure there are no nulls in the center + (1 + l_idx == ca.len()) && (r_idx == 0) // this checks that if there are nulls, they are all on one side && !(ca.first_non_null().unwrap() != 0 && 1 + other.last_non_null().unwrap() != other.len()) - ) + ); + + if keep_sorted { + ca.is_sorted_flag() + } else { + IsSorted::Not + } + } }, }; - if !still_sorted { - ca.set_sorted_flag(IsSorted::Not); - } + ca.set_sorted_flag(sorted_flag); } impl ChunkedArray diff --git a/py-polars/tests/unit/operations/test_sort.py b/py-polars/tests/unit/operations/test_sort.py index a8e60ec192a9..f6defe5c5f64 100644 --- a/py-polars/tests/unit/operations/test_sort.py +++ b/py-polars/tests/unit/operations/test_sort.py @@ -796,3 +796,101 @@ def test_sorted_flag_14552() -> None: a = pl.concat([a, a], rechunk=False) assert not a.join(a, on="a", how="left")["a"].flags["SORTED_ASC"] + + +def test_sorted_flag_concat_15072() -> None: + def is_sorted_any(s: pl.Series) -> bool: + return s.flags["SORTED_ASC"] or s.flags["SORTED_DESC"] + + def is_not_sorted(s: pl.Series) -> bool: + return not is_sorted_any(s) + + # Both all-null + a = pl.Series("x", [None, None], dtype=pl.Int8) + b = pl.Series("x", [None, None], dtype=pl.Int8) + assert pl.concat((a, b)).flags["SORTED_ASC"] + + # left all-null, right 0 < null_count < len + a = pl.Series("x", [None, None], dtype=pl.Int8) + b = pl.Series("x", [1, 2, 1, None], dtype=pl.Int8) + + out = pl.concat((a, b.sort())) + assert out.to_list() == [None, None, None, 1, 1, 2] + assert out.flags["SORTED_ASC"] + + out = pl.concat((a, b.sort(descending=True))) + assert out.to_list() == [None, None, None, 2, 1, 1] + assert out.flags["SORTED_DESC"] + + out = pl.concat((a, b.sort(nulls_last=True))) + assert out.to_list() == [None, None, 1, 1, 2, None] + assert is_not_sorted(out) + + out = pl.concat((a, b.sort(nulls_last=True, descending=True))) + assert out.to_list() == [None, None, 2, 1, 1, None] + assert is_not_sorted(out) + + # left 0 < null_count < len, right all-null + a = pl.Series("x", [1, 2, 1, None], dtype=pl.Int8) + b = pl.Series("x", [None, None], dtype=pl.Int8) + + out = pl.concat((a.sort(), b)) + assert out.to_list() == [None, 1, 1, 2, None, None] + assert is_not_sorted(out) + + out = pl.concat((a.sort(descending=True), b)) + assert out.to_list() == [None, 2, 1, 1, None, None] + assert is_not_sorted(out) + + out = pl.concat((a.sort(nulls_last=True), b)) + assert out.to_list() == [1, 1, 2, None, None, None] + assert out.flags["SORTED_ASC"] + + out = pl.concat((a.sort(nulls_last=True, descending=True), b)) + assert out.to_list() == [2, 1, 1, None, None, None] + assert out.flags["SORTED_DESC"] + + # both 0 < null_count < len + assert pl.concat( + ( + pl.Series([None, 1]).set_sorted(), + pl.Series([2]).set_sorted(), + ) + ).flags["SORTED_ASC"] + + assert is_not_sorted( + pl.concat( + ( + pl.Series([None, 1]).set_sorted(), + pl.Series([2, None]).set_sorted(), + ) + ) + ) + + assert pl.concat( + ( + pl.Series([None, 2]).set_sorted(descending=True), + pl.Series([1]).set_sorted(descending=True), + ) + ).flags["SORTED_DESC"] + + assert is_not_sorted( + pl.concat( + ( + pl.Series([None, 2]).set_sorted(descending=True), + pl.Series([1, None]).set_sorted(descending=True), + ) + ) + ) + + # coerces sorted flag if there is only a single non-null + assert pl.concat( + (pl.Series([3, 2]).set_sorted(descending=True), pl.Series([1]).set_sorted()) + ).flags["SORTED_DESC"] + + assert pl.concat( + ( + pl.Series([3, 2]).set_sorted(descending=True), + pl.Series([1, None]).set_sorted(), + ) + ).flags["SORTED_DESC"] From a2cb24a1b43fad4b30e2a7cd67de7495dadd0b76 Mon Sep 17 00:00:00 2001 From: nameexhaustion Date: Fri, 15 Mar 2024 17:07:33 +1100 Subject: [PATCH 3/7] update test --- py-polars/tests/unit/operations/test_sort.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py-polars/tests/unit/operations/test_sort.py b/py-polars/tests/unit/operations/test_sort.py index f6defe5c5f64..55d80f38e6b1 100644 --- a/py-polars/tests/unit/operations/test_sort.py +++ b/py-polars/tests/unit/operations/test_sort.py @@ -885,12 +885,12 @@ def is_not_sorted(s: pl.Series) -> bool: # coerces sorted flag if there is only a single non-null assert pl.concat( - (pl.Series([3, 2]).set_sorted(descending=True), pl.Series([1]).set_sorted()) + (pl.Series([3, 2]).set_sorted(descending=True), pl.Series([1])) ).flags["SORTED_DESC"] assert pl.concat( ( pl.Series([3, 2]).set_sorted(descending=True), - pl.Series([1, None]).set_sorted(), + pl.Series([1, None]), ) ).flags["SORTED_DESC"] From 3453f4d3a881e13deaac97678516b2c457088506 Mon Sep 17 00:00:00 2001 From: nameexhaustion Date: Fri, 15 Mar 2024 18:47:17 +1100 Subject: [PATCH 4/7] simplify --- .../src/chunked_array/ops/append.rs | 45 +++++++++---------- py-polars/tests/unit/operations/test_sort.py | 12 ----- 2 files changed, 22 insertions(+), 35 deletions(-) diff --git a/crates/polars-core/src/chunked_array/ops/append.rs b/crates/polars-core/src/chunked_array/ops/append.rs index 6f75e4ec812f..cf70e08c9f0d 100644 --- a/crates/polars-core/src/chunked_array/ops/append.rs +++ b/crates/polars-core/src/chunked_array/ops/append.rs @@ -20,12 +20,15 @@ where for<'a> T::Physical<'a>: TotalOrd, { let is_sorted_any = |ca: &ChunkedArray| !matches!(ca.is_sorted_flag(), IsSorted::Not); + let is_not_sorted = |ca: &ChunkedArray| matches!(ca.is_sorted_flag(), IsSorted::Not); + // Note: Do not call (first|last)_non_null on an array here before checking + // it is sorted, otherwise it will lead to quadratic behavior. let sorted_flag = match ( ca.null_count() != ca.len(), other.null_count() != other.len(), ) { - (false, false) => IsSorted::Ascending, + (false, false) => IsSorted::Ascending, // all null (false, true) => { if is_sorted_any(other) && 1 + other.last_non_null().unwrap() == other.len() { // nulls first @@ -35,8 +38,8 @@ where } }, (true, false) => { - // nulls last if is_sorted_any(ca) && ca.first_non_null().unwrap() == 0 { + // nulls last ca.is_sorted_flag() } else { IsSorted::Not @@ -44,13 +47,9 @@ where }, (true, true) => { // both arrays have non-null values - if ( - // we can ignore the sorted flag of other if there is only 1 non-null value - // ca : descending [3, 2] - // other : ascending [1] - // out : descending [3, 2, 1] - other.len() - other.null_count() > 1 - ) && ca.is_sorted_flag() != other.is_sorted_flag() + if is_not_sorted(ca) + || is_not_sorted(other) + || ca.is_sorted_flag() != other.is_sorted_flag() { IsSorted::Not } else { @@ -60,22 +59,22 @@ where let l_val = unsafe { ca.value_unchecked(l_idx) }; let r_val = unsafe { other.value_unchecked(r_idx) }; - // compare values - let keep_sorted = if ca.is_sorted_ascending_flag() { - l_val.tot_le(&r_val) - } else { - l_val.tot_ge(&r_val) - }; + let keep_sorted = + // check null positions + // lhs does not end in nulls + (1 + l_idx == ca.len()) + // rhs does not start with nulls + && (r_idx == 0) + // if there are nulls, they are all on one end + && !(ca.first_non_null().unwrap() != 0 && 1 + other.last_non_null().unwrap() != other.len()); let keep_sorted = keep_sorted - & ( - // check null position - // the first 2 ensure there are no nulls in the center - (1 + l_idx == ca.len()) - && (r_idx == 0) - // this checks that if there are nulls, they are all on one side - && !(ca.first_non_null().unwrap() != 0 && 1 + other.last_non_null().unwrap() != other.len()) - ); + // compare values + && if ca.is_sorted_ascending_flag() { + l_val.tot_le(&r_val) + } else { + l_val.tot_ge(&r_val) + }; if keep_sorted { ca.is_sorted_flag() diff --git a/py-polars/tests/unit/operations/test_sort.py b/py-polars/tests/unit/operations/test_sort.py index 55d80f38e6b1..889ddae9fe5f 100644 --- a/py-polars/tests/unit/operations/test_sort.py +++ b/py-polars/tests/unit/operations/test_sort.py @@ -882,15 +882,3 @@ def is_not_sorted(s: pl.Series) -> bool: ) ) ) - - # coerces sorted flag if there is only a single non-null - assert pl.concat( - (pl.Series([3, 2]).set_sorted(descending=True), pl.Series([1])) - ).flags["SORTED_DESC"] - - assert pl.concat( - ( - pl.Series([3, 2]).set_sorted(descending=True), - pl.Series([1, None]), - ) - ).flags["SORTED_DESC"] From ab87b9b6a96cbae7a7175329c884453c12b7b8a0 Mon Sep 17 00:00:00 2001 From: nameexhaustion Date: Fri, 15 Mar 2024 18:54:59 +1100 Subject: [PATCH 5/7] simplify --- crates/polars-core/src/chunked_array/mod.rs | 19 +++++++++---------- .../src/chunked_array/ops/append.rs | 11 ++++------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/crates/polars-core/src/chunked_array/mod.rs b/crates/polars-core/src/chunked_array/mod.rs index 384d99008e7f..64fb025321b0 100644 --- a/crates/polars-core/src/chunked_array/mod.rs +++ b/crates/polars-core/src/chunked_array/mod.rs @@ -189,6 +189,11 @@ impl ChunkedArray { self.bit_settings.contains(Settings::SORTED_DSC) } + /// Whether `self` is sorted in any direction. + pub(crate) fn is_sorted_any(&self) -> bool { + self.is_sorted_ascending_flag() || self.is_sorted_descending_flag() + } + pub fn unset_fast_explode_list(&mut self) { self.bit_settings.remove(Settings::FAST_EXPLODE_LIST) } @@ -224,10 +229,7 @@ impl ChunkedArray { None } // We now know there is at least 1 non-null item in the array, and self.len() > 0 - else if matches!( - self.is_sorted_flag(), - IsSorted::Ascending | IsSorted::Descending - ) { + else if self.is_sorted_any() { let out = if unsafe { self.downcast_get_unchecked(0).is_null_unchecked(0) } { // nulls are all at the start self.null_count() @@ -238,7 +240,7 @@ impl ChunkedArray { debug_assert!( // If we are lucky this catches something. - unsafe { self.get_unchecked(out).is_some() }, + unsafe { self.get_unchecked(out) }.is_some(), "incorrect sorted flag" ); @@ -254,10 +256,7 @@ impl ChunkedArray { None } // We now know there is at least 1 non-null item in the array, and self.len() > 0 - else if matches!( - self.is_sorted_flag(), - IsSorted::Ascending | IsSorted::Descending - ) { + else if self.is_sorted_any() { let out = if unsafe { self.downcast_get_unchecked(0).is_null_unchecked(0) } { // nulls are all at the start self.len() - 1 @@ -268,7 +267,7 @@ impl ChunkedArray { debug_assert!( // If we are lucky this catches something. - unsafe { self.get_unchecked(out).is_some() }, + unsafe { self.get_unchecked(out) }.is_some(), "incorrect sorted flag" ); diff --git a/crates/polars-core/src/chunked_array/ops/append.rs b/crates/polars-core/src/chunked_array/ops/append.rs index cf70e08c9f0d..78cba5f3f628 100644 --- a/crates/polars-core/src/chunked_array/ops/append.rs +++ b/crates/polars-core/src/chunked_array/ops/append.rs @@ -19,9 +19,6 @@ where T: PolarsDataType, for<'a> T::Physical<'a>: TotalOrd, { - let is_sorted_any = |ca: &ChunkedArray| !matches!(ca.is_sorted_flag(), IsSorted::Not); - let is_not_sorted = |ca: &ChunkedArray| matches!(ca.is_sorted_flag(), IsSorted::Not); - // Note: Do not call (first|last)_non_null on an array here before checking // it is sorted, otherwise it will lead to quadratic behavior. let sorted_flag = match ( @@ -30,7 +27,7 @@ where ) { (false, false) => IsSorted::Ascending, // all null (false, true) => { - if is_sorted_any(other) && 1 + other.last_non_null().unwrap() == other.len() { + if other.is_sorted_any() && 1 + other.last_non_null().unwrap() == other.len() { // nulls first other.is_sorted_flag() } else { @@ -38,7 +35,7 @@ where } }, (true, false) => { - if is_sorted_any(ca) && ca.first_non_null().unwrap() == 0 { + if ca.is_sorted_any() && ca.first_non_null().unwrap() == 0 { // nulls last ca.is_sorted_flag() } else { @@ -47,8 +44,8 @@ where }, (true, true) => { // both arrays have non-null values - if is_not_sorted(ca) - || is_not_sorted(other) + if !ca.is_sorted_any() + || !other.is_sorted_any() || ca.is_sorted_flag() != other.is_sorted_flag() { IsSorted::Not From c10f788013c3a3544b5ef84e22c13a47cb5677c0 Mon Sep 17 00:00:00 2001 From: nameexhaustion Date: Fri, 15 Mar 2024 20:10:37 +1100 Subject: [PATCH 6/7] fix regression concat with empty --- .../src/chunked_array/ops/append.rs | 22 ++++++++++++++----- py-polars/tests/unit/operations/test_sort.py | 21 ++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/crates/polars-core/src/chunked_array/ops/append.rs b/crates/polars-core/src/chunked_array/ops/append.rs index 78cba5f3f628..d2749c2764bb 100644 --- a/crates/polars-core/src/chunked_array/ops/append.rs +++ b/crates/polars-core/src/chunked_array/ops/append.rs @@ -25,18 +25,30 @@ where ca.null_count() != ca.len(), other.null_count() != other.len(), ) { - (false, false) => IsSorted::Ascending, // all null + (false, false) => IsSorted::Ascending, (false, true) => { - if other.is_sorted_any() && 1 + other.last_non_null().unwrap() == other.len() { - // nulls first + if + // lhs is empty, just take sorted flag from rhs + ca.is_empty() + || ( + // lhs is non-empty and all-null, so rhs must have nulls ordered first + other.is_sorted_any() && 1 + other.last_non_null().unwrap() == other.len() + ) + { other.is_sorted_flag() } else { IsSorted::Not } }, (true, false) => { - if ca.is_sorted_any() && ca.first_non_null().unwrap() == 0 { - // nulls last + if + // rhs is empty, just take sorted flag from lhs + other.is_empty() + || ( + // rhs is non-empty and all-null, so lhs must have nulls ordered last + ca.is_sorted_any() && ca.first_non_null().unwrap() == 0 + ) + { ca.is_sorted_flag() } else { IsSorted::Not diff --git a/py-polars/tests/unit/operations/test_sort.py b/py-polars/tests/unit/operations/test_sort.py index 889ddae9fe5f..75397f6c9d0c 100644 --- a/py-polars/tests/unit/operations/test_sort.py +++ b/py-polars/tests/unit/operations/test_sort.py @@ -882,3 +882,24 @@ def is_not_sorted(s: pl.Series) -> bool: ) ) ) + + # Concat with empty series + s = pl.Series([None, 1]).set_sorted() + + out = pl.concat((s.clear(), s)) + assert_series_equal(out, s) + assert out.flags["SORTED_ASC"] + + out = pl.concat((s, s.clear())) + assert_series_equal(out, s) + assert out.flags["SORTED_ASC"] + + s = pl.Series([1, None]).set_sorted() + + out = pl.concat((s.clear(), s)) + assert_series_equal(out, s) + assert out.flags["SORTED_ASC"] + + out = pl.concat((s, s.clear())) + assert_series_equal(out, s) + assert out.flags["SORTED_ASC"] From 7f6c8517259e3387ffd82c3f6c4ab9f95a836f15 Mon Sep 17 00:00:00 2001 From: nameexhaustion Date: Fri, 15 Mar 2024 20:12:18 +1100 Subject: [PATCH 7/7] lint --- py-polars/tests/unit/operations/test_sort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py-polars/tests/unit/operations/test_sort.py b/py-polars/tests/unit/operations/test_sort.py index 75397f6c9d0c..b3ef5631e7b8 100644 --- a/py-polars/tests/unit/operations/test_sort.py +++ b/py-polars/tests/unit/operations/test_sort.py @@ -885,7 +885,7 @@ def is_not_sorted(s: pl.Series) -> bool: # Concat with empty series s = pl.Series([None, 1]).set_sorted() - + out = pl.concat((s.clear(), s)) assert_series_equal(out, s) assert out.flags["SORTED_ASC"]