From 88c3337898322d2bf25cd976377cc43ec795452e Mon Sep 17 00:00:00 2001 From: phofl Date: Sat, 17 Oct 2020 23:50:16 +0200 Subject: [PATCH 1/6] BUG: join did not work correctly when one MultiIndex had only one level --- doc/source/whatsnew/v1.2.0.rst | 1 + pandas/core/indexes/base.py | 2 +- pandas/tests/indexes/multi/test_drop.py | 8 ++++++++ pandas/tests/reshape/merge/test_join.py | 17 +++++++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index c9a1dbd0ae90d..2f230b49b1511 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -474,6 +474,7 @@ Reshaping - Bug in func :meth:`crosstab` when using multiple columns with ``margins=True`` and ``normalize=True`` (:issue:`35144`) - Bug in :meth:`DataFrame.agg` with ``func={'name':}`` incorrectly raising ``TypeError`` when ``DataFrame.columns==['Name']`` (:issue:`36212`) - Bug in :meth:`Series.transform` would give incorrect results or raise when the argument ``func`` was dictionary (:issue:`35811`) +- Bug in :func:`join` over :class:`MultiIndex` returned wrong result, when one of both indexes had only one level (:issue:`36909`) - Sparse diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 87dd15d5b142b..633156f36ab2a 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1552,7 +1552,7 @@ def droplevel(self, level=0): levnums = sorted(self._get_level_number(lev) for lev in level)[::-1] - if len(level) == 0: + if len(level) == 0 and (len(self) > 1 or not isinstance(self, ABCMultiIndex)): return self if len(level) >= self.nlevels: raise ValueError( diff --git a/pandas/tests/indexes/multi/test_drop.py b/pandas/tests/indexes/multi/test_drop.py index 6ba565f0406ab..d9407e6324533 100644 --- a/pandas/tests/indexes/multi/test_drop.py +++ b/pandas/tests/indexes/multi/test_drop.py @@ -139,3 +139,11 @@ def test_drop_not_lexsorted(): tm.assert_index_equal(lexsorted_mi, not_lexsorted_mi) with tm.assert_produces_warning(PerformanceWarning): tm.assert_index_equal(lexsorted_mi.drop("a"), not_lexsorted_mi.drop("a")) + + +def test_droplevel_multiindex_one_level(): + # GH: 37207 + index = pd.MultiIndex.from_tuples([(2,)], names=("b",)) + result = index.droplevel([]) + expected = pd.Int64Index([2], name="b") + tm.assert_index_equal(result, expected) diff --git a/pandas/tests/reshape/merge/test_join.py b/pandas/tests/reshape/merge/test_join.py index d4d4c4190417e..d6e3143f46400 100644 --- a/pandas/tests/reshape/merge/test_join.py +++ b/pandas/tests/reshape/merge/test_join.py @@ -879,3 +879,20 @@ def _join_by_hand(a, b, how="left"): for col, s in b_re.items(): a_re[col] = s return a_re.reindex(columns=result_columns) + + +@pytest.mark.parametrize("how", ["left", "right", "inner", "outer"]) +def test_join_multiindex_one_level(how): + # GH: 36909 + left = pd.DataFrame( + data={"c": 3}, index=pd.MultiIndex.from_tuples([(1, 2)], names=("a", "b")) + ) + right = pd.DataFrame( + data={"d": 4}, index=pd.MultiIndex.from_tuples([(2,)], names=("b",)) + ) + result = left.join(right, how=how) + expected = pd.DataFrame( + {"c": [3], "d": [4]}, + index=pd.MultiIndex.from_tuples([(2, 1)], names=["b", "a"]), + ) + tm.assert_frame_equal(result, expected) From fd06392d2402b06488f20613a9e3b9c8b2547724 Mon Sep 17 00:00:00 2001 From: phofl Date: Sat, 17 Oct 2020 23:50:39 +0200 Subject: [PATCH 2/6] Change pr number --- pandas/tests/indexes/multi/test_drop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/indexes/multi/test_drop.py b/pandas/tests/indexes/multi/test_drop.py index d9407e6324533..551f07c71e4b5 100644 --- a/pandas/tests/indexes/multi/test_drop.py +++ b/pandas/tests/indexes/multi/test_drop.py @@ -142,7 +142,7 @@ def test_drop_not_lexsorted(): def test_droplevel_multiindex_one_level(): - # GH: 37207 + # GH: 37208 index = pd.MultiIndex.from_tuples([(2,)], names=("b",)) result = index.droplevel([]) expected = pd.Int64Index([2], name="b") From d57f186d2884f703ae04540b8e3ac0b4bdea31c4 Mon Sep 17 00:00:00 2001 From: phofl Date: Sat, 2 Jan 2021 00:42:57 +0100 Subject: [PATCH 3/6] Move whatsnew --- doc/source/whatsnew/v1.3.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 9d1b3eaebdf8b..ddbc976a045a4 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -294,6 +294,7 @@ Groupby/resample/rolling Reshaping ^^^^^^^^^ - Bug in :meth:`DataFrame.unstack` with missing levels led to incorrect index names (:issue:`37510`) +- Bug in :func:`join` over :class:`MultiIndex` returned wrong result, when one of both indexes had only one level (:issue:`36909`) - Bug in :func:`concat` incorrectly casting to ``object`` dtype in some cases when one or more of the operands is empty (:issue:`38843`) - From 42135d215ba8aa707d0265c6e34ae8d196a6ef62 Mon Sep 17 00:00:00 2001 From: phofl Date: Sat, 2 Jan 2021 00:52:09 +0100 Subject: [PATCH 4/6] Remove pd --- pandas/tests/reshape/merge/test_join.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/tests/reshape/merge/test_join.py b/pandas/tests/reshape/merge/test_join.py index 8bf21a122989b..4650029bb3e98 100644 --- a/pandas/tests/reshape/merge/test_join.py +++ b/pandas/tests/reshape/merge/test_join.py @@ -820,14 +820,14 @@ def test_join_cross(input_col, output_cols): @pytest.mark.parametrize("how", ["left", "right", "inner", "outer"]) def test_join_multiindex_one_level(how): # GH#36909 - left = pd.DataFrame( + left = DataFrame( data={"c": 3}, index=pd.MultiIndex.from_tuples([(1, 2)], names=("a", "b")) ) - right = pd.DataFrame( + right = DataFrame( data={"d": 4}, index=pd.MultiIndex.from_tuples([(2,)], names=("b",)) ) result = left.join(right, how=how) - expected = pd.DataFrame( + expected = DataFrame( {"c": [3], "d": [4]}, index=pd.MultiIndex.from_tuples([(2, 1)], names=["b", "a"]), ) From da316f0463fa16322161cfd07d81cdb9fd2142a6 Mon Sep 17 00:00:00 2001 From: phofl Date: Sun, 3 Jan 2021 19:10:05 +0100 Subject: [PATCH 5/6] Usef fixture --- pandas/tests/reshape/merge/test_join.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pandas/tests/reshape/merge/test_join.py b/pandas/tests/reshape/merge/test_join.py index 4650029bb3e98..f64283147764f 100644 --- a/pandas/tests/reshape/merge/test_join.py +++ b/pandas/tests/reshape/merge/test_join.py @@ -817,8 +817,7 @@ def test_join_cross(input_col, output_cols): tm.assert_frame_equal(result, expected) -@pytest.mark.parametrize("how", ["left", "right", "inner", "outer"]) -def test_join_multiindex_one_level(how): +def test_join_multiindex_one_level(join_type): # GH#36909 left = DataFrame( data={"c": 3}, index=pd.MultiIndex.from_tuples([(1, 2)], names=("a", "b")) @@ -826,7 +825,7 @@ def test_join_multiindex_one_level(how): right = DataFrame( data={"d": 4}, index=pd.MultiIndex.from_tuples([(2,)], names=("b",)) ) - result = left.join(right, how=how) + result = left.join(right, how=join_type) expected = DataFrame( {"c": [3], "d": [4]}, index=pd.MultiIndex.from_tuples([(2, 1)], names=["b", "a"]), From bd063c863084ffeb92e039fa578dc32267ca6e35 Mon Sep 17 00:00:00 2001 From: phofl Date: Sun, 3 Jan 2021 19:17:00 +0100 Subject: [PATCH 6/6] Remove len self --- pandas/core/indexes/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 6d342e47831f9..25af311808f96 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1672,7 +1672,7 @@ def _drop_level_numbers(self, levnums: List[int]): Drop MultiIndex levels by level _number_, not name. """ - if not levnums and (len(self) > 1 or not isinstance(self, ABCMultiIndex)): + if not levnums and not isinstance(self, ABCMultiIndex): return self if len(levnums) >= self.nlevels: raise ValueError(