Skip to content

Commit 8778760

Browse files
endremborzajreback
authored andcommitted
Multiindex recurse error fix (#29260)
1 parent 0be573e commit 8778760

File tree

4 files changed

+43
-2
lines changed

4 files changed

+43
-2
lines changed

doc/source/whatsnew/v1.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,7 @@ Reshaping
946946
- :func:`qcut` and :func:`cut` now handle boolean input (:issue:`20303`)
947947
- Fix to ensure all int dtypes can be used in :func:`merge_asof` when using a tolerance value. Previously every non-int64 type would raise an erroneous ``MergeError`` (:issue:`28870`).
948948
- Better error message in :func:`get_dummies` when `columns` isn't a list-like value (:issue:`28383`)
949+
- Bug in :meth:`Index.join` that caused infinite recursion error for mismatched ``MultiIndex`` name orders. (:issue:`25760`, :issue:`28956`)
949950
- Bug :meth:`Series.pct_change` where supplying an anchored frequency would throw a ValueError (:issue:`28664`)
950951
- Bug where :meth:`DataFrame.equals` returned True incorrectly in some cases when two DataFrames had the same columns in different orders (:issue:`28839`)
951952
- Bug in :meth:`DataFrame.replace` that caused non-numeric replacer's dtype not respected (:issue:`26632`)

pandas/core/indexes/base.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -3368,8 +3368,13 @@ def _join_multi(self, other, how, return_indexers=True):
33683368
ldrop_names = list(self_names - overlap)
33693369
rdrop_names = list(other_names - overlap)
33703370

3371-
self_jnlevels = self.droplevel(ldrop_names)
3372-
other_jnlevels = other.droplevel(rdrop_names)
3371+
# if only the order differs
3372+
if not len(ldrop_names + rdrop_names):
3373+
self_jnlevels = self
3374+
other_jnlevels = other.reorder_levels(self.names)
3375+
else:
3376+
self_jnlevels = self.droplevel(ldrop_names)
3377+
other_jnlevels = other.droplevel(rdrop_names)
33733378

33743379
# Join left and right
33753380
# Join on same leveled multi-index frames is supported

pandas/tests/indexes/multi/test_join.py

+16
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,19 @@ def test_join_self_unique(idx, join_type):
8787
if idx.is_unique:
8888
joined = idx.join(idx, how=join_type)
8989
assert (idx == joined).all()
90+
91+
92+
def test_join_multi_wrong_order():
93+
# GH 25760
94+
# GH 28956
95+
96+
midx1 = pd.MultiIndex.from_product([[1, 2], [3, 4]], names=["a", "b"])
97+
midx2 = pd.MultiIndex.from_product([[1, 2], [3, 4]], names=["b", "a"])
98+
99+
join_idx, lidx, ridx = midx1.join(midx2, return_indexers=False)
100+
101+
exp_ridx = np.array([-1, -1, -1, -1], dtype=np.intp)
102+
103+
tm.assert_index_equal(midx1, join_idx)
104+
assert lidx is None
105+
tm.assert_numpy_array_equal(ridx, exp_ridx)

pandas/tests/reshape/merge/test_multi.py

+19
Original file line numberDiff line numberDiff line change
@@ -828,3 +828,22 @@ def test_single_common_level(self):
828828
).set_index(["key", "X", "Y"])
829829

830830
tm.assert_frame_equal(result, expected)
831+
832+
def test_join_multi_wrong_order(self):
833+
# GH 25760
834+
# GH 28956
835+
836+
midx1 = pd.MultiIndex.from_product([[1, 2], [3, 4]], names=["a", "b"])
837+
midx3 = pd.MultiIndex.from_tuples([(4, 1), (3, 2), (3, 1)], names=["b", "a"])
838+
839+
left = pd.DataFrame(index=midx1, data={"x": [10, 20, 30, 40]})
840+
right = pd.DataFrame(index=midx3, data={"y": ["foo", "bar", "fing"]})
841+
842+
result = left.join(right)
843+
844+
expected = pd.DataFrame(
845+
index=midx1,
846+
data={"x": [10, 20, 30, 40], "y": ["fing", "foo", "bar", np.nan]},
847+
)
848+
849+
tm.assert_frame_equal(result, expected)

0 commit comments

Comments
 (0)