From 134d2f199f2b01296cbe5fd08e63d8f7f69ab76f Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 25 Aug 2021 16:20:30 -0700 Subject: [PATCH 1/3] BUG: MultiIndex.putmask --- pandas/core/indexes/base.py | 2 ++ pandas/core/indexes/multi.py | 7 ++++++- pandas/tests/indexes/multi/test_putmask.py | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 pandas/tests/indexes/multi/test_putmask.py diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 2615bfffb3cd9..1b89b587c9142 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -4891,6 +4891,8 @@ def putmask(self, mask, value) -> Index: ) np.putmask(values, mask, converted) + if self._is_multi: + return type(self).from_tuples(values, name=self.name) return type(self)._simple_new(values, name=self.name) def equals(self, other: Any) -> bool: diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 4d80480468adb..c3e5b3f6a6e0b 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -3678,7 +3678,12 @@ def astype(self, dtype, copy: bool = True): return self def _validate_fill_value(self, item): - if not isinstance(item, tuple): + if isinstance(item, MultiIndex): + # GH#43212 + if item.nlevels != self.nlevels: + raise ValueError("Item must have length equal to number of levels.") + return item._values + elif not isinstance(item, tuple): # Pad the key with empty strings if lower levels of the key # aren't specified: item = (item,) + ("",) * (self.nlevels - 1) diff --git a/pandas/tests/indexes/multi/test_putmask.py b/pandas/tests/indexes/multi/test_putmask.py new file mode 100644 index 0000000000000..2a24be9003302 --- /dev/null +++ b/pandas/tests/indexes/multi/test_putmask.py @@ -0,0 +1,17 @@ +import numpy as np + +from pandas import MultiIndex +import pandas._testing as tm + + +def test_putmask_multiindex_other(): + # GH#43212 `value` is also a MultiIndex + + left = MultiIndex.from_tuples([(np.nan, 6), (np.nan, 6), ("a", 4)]) + right = MultiIndex.from_tuples([("a", 1), ("a", 1), ("d", 1)]) + mask = np.array([True, True, False]) + + result = left.putmask(mask, right) + + expected = MultiIndex.from_tuples([right[0], right[1], left[2]]) + tm.assert_index_equal(result, expected) From a8e0df501a1ff1f839e4dbc18c7d935379d71645 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 25 Aug 2021 16:21:27 -0700 Subject: [PATCH 2/3] whatsnew --- doc/source/whatsnew/v1.4.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index 205a49e7786a7..c255eeb31f547 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -311,6 +311,7 @@ MultiIndex - Bug in :meth:`MultiIndex.get_loc` where the first level is a :class:`DatetimeIndex` and a string key is passed (:issue:`42465`) - Bug in :meth:`MultiIndex.reindex` when passing a ``level`` that corresponds to an ``ExtensionDtype`` level (:issue:`42043`) - Bug in :meth:`MultiIndex.get_loc` raising ``TypeError`` instead of ``KeyError`` on nested tuple (:issue:`42440`) +- Bug in :meth:`MultiIndex.putmask` where the other value was also a :class:`MultiIndex` (:issue:`43212`) - I/O From 7d80e9877a763fbcdad055a696d39bbe782e4368 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 25 Aug 2021 19:06:04 -0700 Subject: [PATCH 3/3] mypy fixup --- pandas/core/indexes/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 1b89b587c9142..6195671b4cb05 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -4892,7 +4892,10 @@ def putmask(self, mask, value) -> Index: np.putmask(values, mask, converted) if self._is_multi: - return type(self).from_tuples(values, name=self.name) + # error: "Type[Index]" has no attribute "from_tuples" + return type(self).from_tuples( # type: ignore[attr-defined] + values, name=self.name + ) return type(self)._simple_new(values, name=self.name) def equals(self, other: Any) -> bool: