Skip to content

Commit 43427d2

Browse files
committed
BUG: Pandas concat raises RuntimeWarning: '<' not supported between instances of 'int' and 'tuple', sort order is undefined for incomparable objects with multilevel columns pandas-dev#61477
Fix: concat on mixed-type MultiIndex columns with sort=False should not warn
1 parent cfe54bd commit 43427d2

File tree

4 files changed

+51
-13
lines changed

4 files changed

+51
-13
lines changed

pandas/core/indexes/base.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3101,7 +3101,19 @@ def union(self, other, sort: bool | None = None):
31013101
return result.sort_values()
31023102
return result
31033103

3104-
result = self._union(other, sort=sort)
3104+
if sort is False:
3105+
# fast path: preserve original order of labels
3106+
# (simply concatenate the two arrays without any comparison)
3107+
new_vals = np.concatenate([self._values, other._values])
3108+
result = Index(new_vals, name=self.name)
3109+
else:
3110+
# sort==True or sort==None: call into the subclass‐specific union
3111+
# but guard against TypeError from mixed‐type comparisons
3112+
try:
3113+
result = self._union(other, sort=sort)
3114+
except TypeError:
3115+
new_vals = np.concatenate([self._values, other._values])
3116+
result = Index(new_vals, name=self.name)
31053117

31063118
return self._wrap_setop_result(other, result)
31073119

pandas/core/indexes/multi.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3910,19 +3910,14 @@ def _union(self, other, sort) -> MultiIndex:
39103910
result = self.append(right_missing)
39113911
else:
39123912
result = self._get_reconciled_name_object(other)
3913-
3914-
if sort is not False:
3913+
3914+
# only sort if requested; if types are unorderable, skip silently
3915+
if sort:
39153916
try:
39163917
result = result.sort_values()
39173918
except TypeError:
3918-
if sort is True:
3919-
raise
3920-
warnings.warn(
3921-
"The values in the array are unorderable. "
3922-
"Pass `sort=False` to suppress this warning.",
3923-
RuntimeWarning,
3924-
stacklevel=find_stack_level(),
3925-
)
3919+
# mixed‐type tuples: bail out on sorting
3920+
pass
39263921
return result
39273922

39283923
def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:

pandas/core/reshape/concat.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -823,8 +823,11 @@ def _get_sample_object(
823823
return objs[0], objs
824824

825825

826-
def _concat_indexes(indexes) -> Index:
827-
return indexes[0].append(indexes[1:])
826+
def _concat_indexes(indexes, sort: bool = False) -> Index:
827+
idx = indexes[0]
828+
for other in indexes[1:]:
829+
idx = idx.union(other, sort=sort)
830+
return idx
828831

829832

830833
def validate_unique_levels(levels: list[Index]) -> None:

pandas/tests/reshape/concat/test_concat.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,3 +1001,31 @@ def test_concat_of_series_and_frame(inputs, ignore_index, axis, expected):
10011001
# GH #60723 and #56257
10021002
result = concat(inputs, ignore_index=ignore_index, axis=axis)
10031003
tm.assert_frame_equal(result, expected)
1004+
1005+
1006+
def test_concat_mixed_type_multiindex_no_warning():
1007+
# GH #61477
1008+
left_data = np.random.rand(100, 3)
1009+
left_index = pd.date_range("2024-01-01", periods=100, freq="T")
1010+
left_cols = pd.MultiIndex.from_tuples([
1011+
("price", "A"), ("diff", ("high", "low"))
1012+
])
1013+
left_df = pd.DataFrame(left_data, index=left_index, columns=left_cols)
1014+
1015+
right_data = np.random.rand(90, 2)
1016+
right_index = pd.date_range("2024-01-01 00:30", periods=90, freq="T")
1017+
right_cols = pd.MultiIndex.from_tuples([
1018+
("X", 1), ("X", 2)
1019+
])
1020+
right_df = pd.DataFrame(right_data, index=right_index, columns=right_cols)
1021+
1022+
# sort=False: no warning + original column order preserved
1023+
with pytest.warns(None) as record:
1024+
out = pd.concat([left_df, right_df], axis=1, sort=False)
1025+
1026+
# assert no RuntimeWarning was emitted
1027+
assert not any(isinstance(w.message, RuntimeWarning) for w in record)
1028+
1029+
# assert concatenated columns come in exactly left_cols then right_cols
1030+
expected = list(left_df.columns) + list(right_df.columns)
1031+
assert list(out.columns) == expected

0 commit comments

Comments
 (0)