Skip to content

Commit 33ee865

Browse files
jbrockmendelclaude
andcommitted
TST: add sortable/orderable fixtures, use in sort-dependent tests (#54072)
Add index_sortable, index_flat_sortable, index_with_missing_sortable, and index_or_series_obj_orderable fixtures that exclude the mixed-int-string Index via params filtering. Use these in tests that require sorting/ordering so test bodies stay clean with no try/except that could swallow bugs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent dbd19d2 commit 33ee865

9 files changed

Lines changed: 117 additions & 96 deletions

File tree

pandas/conftest.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,14 @@ def index(request):
746746
return indices_dict[request.param].copy(deep=False)
747747

748748

749+
@pytest.fixture(params=[key for key in indices_dict if key != "mixed-int-string"])
750+
def index_sortable(request):
751+
"""
752+
index fixture, but excluding types that are not orderable.
753+
"""
754+
return indices_dict[request.param].copy(deep=False)
755+
756+
749757
@pytest.fixture(
750758
params=[
751759
key for key, value in indices_dict.items() if not isinstance(value, MultiIndex)
@@ -759,6 +767,20 @@ def index_flat(request):
759767
return indices_dict[key].copy(deep=False)
760768

761769

770+
@pytest.fixture(
771+
params=[
772+
key
773+
for key, value in indices_dict.items()
774+
if not isinstance(value, MultiIndex) and key != "mixed-int-string"
775+
]
776+
)
777+
def index_flat_sortable(request):
778+
"""
779+
index_flat fixture, but excluding types that are not orderable.
780+
"""
781+
return indices_dict[request.param].copy(deep=False)
782+
783+
762784
@pytest.fixture(
763785
params=[
764786
key
@@ -793,6 +815,28 @@ def index_with_missing(request):
793815
return type(ind)(vals, copy=False)
794816

795817

818+
@pytest.fixture(
819+
params=[
820+
key
821+
for key, value in indices_dict.items()
822+
if not (
823+
key.startswith(("int", "uint", "float"))
824+
or key in ["range", "empty", "repeats", "bool-dtype", "mixed-int-string"]
825+
)
826+
and not isinstance(value, MultiIndex)
827+
]
828+
)
829+
def index_with_missing_sortable(request):
830+
"""
831+
index_with_missing fixture, but excluding types that are not orderable.
832+
"""
833+
ind = indices_dict[request.param]
834+
vals = ind.values.copy()
835+
vals[0] = None
836+
vals[-1] = None
837+
return type(ind)(vals, copy=False)
838+
839+
796840
# ----------------------------------------------------------------
797841
# Series'
798842
# ----------------------------------------------------------------
@@ -871,6 +915,21 @@ def index_or_series_obj(request):
871915
return _index_or_series_objs[request.param].copy(deep=False)
872916

873917

918+
_index_or_series_objs_orderable = {
919+
key: value
920+
for key, value in _index_or_series_objs.items()
921+
if "mixed-int-string" not in key
922+
}
923+
924+
925+
@pytest.fixture(params=_index_or_series_objs_orderable.keys())
926+
def index_or_series_obj_orderable(request):
927+
"""
928+
index_or_series_obj fixture, but excluding types that are not orderable.
929+
"""
930+
return _index_or_series_objs_orderable[request.param].copy(deep=False)
931+
932+
874933
_typ_objects_series = {
875934
f"{dtype.__name__}-series": Series(dtype) for dtype in tm.PYTHON_DATA_TYPES
876935
}

pandas/tests/base/test_misc.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,10 @@ def test_memory_usage_components_narrow_series(any_real_numpy_dtype):
139139
assert total_usage == non_index_usage + index_usage
140140

141141

142-
def test_searchsorted(request, index_or_series_obj):
142+
def test_searchsorted(request, index_or_series_obj_orderable):
143143
# numpy.searchsorted calls obj.searchsorted under the hood.
144144
# See gh-12238
145-
obj = index_or_series_obj
145+
obj = index_or_series_obj_orderable
146146

147147
if isinstance(obj, pd.MultiIndex):
148148
# See gh-14833
@@ -156,11 +156,6 @@ def test_searchsorted(request, index_or_series_obj):
156156
# comparison semantics https://github.com/numpy/numpy/issues/15981
157157
mark = pytest.mark.xfail(reason="complex objects are not comparable")
158158
request.applymarker(mark)
159-
elif isinstance(obj, Index) and obj.inferred_type == "mixed-integer":
160-
# mixed int/str types are not orderable in Python 3
161-
with pytest.raises(TypeError, match="not supported between"):
162-
max(obj)
163-
return
164159

165160
max_obj = max(obj, default=0)
166161
index = np.searchsorted(obj, max_obj)

pandas/tests/base/test_value_counts.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
array,
1919
)
2020
import pandas._testing as tm
21-
from pandas.core.indexes.api import safe_sort_index
2221
from pandas.tests.base.common import allow_na_ops
2322

2423

@@ -54,8 +53,8 @@ def test_value_counts(index_or_series_obj):
5453

5554
@pytest.mark.parametrize("null_obj", [np.nan, None])
5655
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
57-
def test_value_counts_null(null_obj, index_or_series_obj):
58-
orig = index_or_series_obj
56+
def test_value_counts_null(null_obj, index_or_series_obj_orderable):
57+
orig = index_or_series_obj_orderable
5958

6059
if not allow_na_ops(orig):
6160
pytest.skip("type doesn't allow for NA operations")
@@ -98,14 +97,8 @@ def test_value_counts_null(null_obj, index_or_series_obj):
9897
expected[null_obj] = 3
9998

10099
result = obj.value_counts(dropna=False)
101-
if expected.index.inferred_type == "mixed-integer":
102-
# mixed int/str types are not orderable; use safe_sort_index
103-
sorted_idx = safe_sort_index(expected.index)
104-
expected = expected.reindex(sorted_idx)
105-
result = result.reindex(sorted_idx)
106-
else:
107-
expected = expected.sort_index()
108-
result = result.sort_index()
100+
expected = expected.sort_index()
101+
result = result.sort_index()
109102
tm.assert_series_equal(result, expected)
110103

111104

pandas/tests/indexes/multi/test_setops.py

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
is_float_dtype,
1616
is_unsigned_integer_dtype,
1717
)
18-
from pandas.core.indexes.api import safe_sort_index
1918

2019

2120
@pytest.mark.parametrize("case", [0.5, "xxx"])
@@ -626,24 +625,18 @@ def test_union_with_duplicates_keep_ea_dtype(dupe_val, any_numeric_ea_dtype):
626625

627626

628627
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
629-
def test_union_duplicates(index, request):
628+
def test_union_duplicates(index_sortable, request):
630629
# GH#38977
630+
index = index_sortable
631631
if index.empty or isinstance(index, (IntervalIndex, CategoricalIndex)):
632632
pytest.skip(f"No duplicates in an empty {type(index).__name__}")
633633

634634
values = index.unique().values.tolist()
635635
mi1 = MultiIndex.from_arrays([values, [1] * len(values)])
636636
mi2 = MultiIndex.from_arrays([[values[0], *values], [1] * (len(values) + 1)])
637-
638-
# mi2.union(mi1): mi1 (other) has no duplicates, so MultiIndex._union takes
639-
# the sort path, which emits RuntimeWarning for unorderable types
640-
warn = RuntimeWarning if index.inferred_type == "mixed-integer" else None
641-
warn_msg = "The values in the array are unorderable"
642-
643-
with tm.assert_produces_warning(warn, match=warn_msg):
644-
result = mi2.union(mi1)
645-
expected = safe_sort_index(mi2)
646-
tm.assert_index_equal(safe_sort_index(result), expected)
637+
result = mi2.union(mi1)
638+
expected = mi2.sort_values()
639+
tm.assert_index_equal(result, expected)
647640

648641
if (
649642
is_unsigned_integer_dtype(mi2.levels[0])
@@ -664,7 +657,7 @@ def test_union_duplicates(index, request):
664657
)
665658

666659
result = mi1.union(mi2)
667-
tm.assert_index_equal(safe_sort_index(result), expected)
660+
tm.assert_index_equal(result, expected)
668661

669662

670663
def test_union_keep_dtype_precision(any_real_numeric_dtype):

pandas/tests/indexes/test_common.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -436,27 +436,18 @@ def test_hasnans_isnans(self, index_flat):
436436

437437
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
438438
@pytest.mark.parametrize("na_position", [None, "middle"])
439-
def test_sort_values_invalid_na_position(index_with_missing, na_position):
440-
if index_with_missing.inferred_type == "mixed-integer":
441-
# mixed int/str types are not orderable; TypeError before ValueError
442-
with pytest.raises(TypeError, match="not supported between"):
443-
index_with_missing.sort_values(na_position=na_position)
444-
return
439+
def test_sort_values_invalid_na_position(index_with_missing_sortable, na_position):
445440
with pytest.raises(ValueError, match=f"invalid na_position: {na_position}"):
446-
index_with_missing.sort_values(na_position=na_position)
441+
index_with_missing_sortable.sort_values(na_position=na_position)
447442

448443

449444
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
450445
@pytest.mark.parametrize("na_position", ["first", "last"])
451-
def test_sort_values_with_missing(index_with_missing, na_position):
446+
def test_sort_values_with_missing(index_with_missing_sortable, na_position):
452447
# GH 35584. Test that sort_values works with missing values,
453448
# sort non-missing and place missing according to na_position
454449

455-
if index_with_missing.inferred_type == "mixed-integer":
456-
# mixed int/str types are not orderable in Python 3
457-
with pytest.raises(TypeError, match="not supported between"):
458-
index_with_missing.sort_values(na_position=na_position)
459-
return
450+
index_with_missing = index_with_missing_sortable
460451

461452
missing_count = np.sum(index_with_missing.isna())
462453
not_na_vals = index_with_missing[index_with_missing.notna()].values

pandas/tests/indexes/test_numpy_compat.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -151,22 +151,18 @@ def test_numpy_ufuncs_other(index, func):
151151

152152

153153
@pytest.mark.parametrize("func", [np.maximum, np.minimum])
154-
def test_numpy_ufuncs_reductions(index, func):
154+
def test_numpy_ufuncs_reductions(index_sortable, func):
155155
# TODO: overlap with tests.series.test_ufunc.test_reductions
156+
index = index_sortable
156157
if len(index) == 0:
157158
pytest.skip("Test doesn't make sense for empty index.")
158159

159160
if isinstance(index, CategoricalIndex) and index.dtype.ordered is False:
160161
with pytest.raises(TypeError, match="is not ordered for"):
161162
func.reduce(index)
162163
return
163-
elif index.inferred_type == "mixed-integer":
164-
# mixed int/str types are not orderable in Python 3
165-
with pytest.raises(TypeError, match="not supported between"):
166-
func.reduce(index)
167-
return
168-
else:
169-
result = func.reduce(index)
164+
165+
result = func.reduce(index)
170166

171167
if func is np.maximum:
172168
expected = index.max(skipna=False)

pandas/tests/indexes/test_old_base.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -355,27 +355,17 @@ def test_memory_usage_doesnt_trigger_engine(self, index):
355355
assert res_without_engine > 0
356356
assert res_with_engine > 0
357357

358-
def test_argsort(self, index):
358+
def test_argsort(self, index_sortable):
359+
index = index_sortable
359360
if isinstance(index, CategoricalIndex):
360361
pytest.skip(f"{type(self).__name__} separately tested")
361362

362-
if index.inferred_type == "mixed-integer":
363-
# mixed int/str types are not orderable in Python 3
364-
with pytest.raises(TypeError, match="not supported between"):
365-
index.argsort()
366-
return
367-
368363
result = index.argsort()
369364
expected = np.array(index).argsort()
370365
tm.assert_numpy_array_equal(result, expected)
371366

372-
def test_numpy_argsort(self, index):
373-
if index.inferred_type == "mixed-integer":
374-
# mixed int/str types are not orderable in Python 3
375-
with pytest.raises(TypeError, match="not supported between"):
376-
np.argsort(index)
377-
return
378-
367+
def test_numpy_argsort(self, index_sortable):
368+
index = index_sortable
379369
result = np.argsort(index)
380370
expected = index.argsort()
381371
tm.assert_numpy_array_equal(result, expected)

0 commit comments

Comments
 (0)