diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 1619ba1a45739..cf586a6a7a043 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -608,6 +608,7 @@ MultiIndex ^^^^^^^^^^ - Bug in which incorrect exception raised by :class:`Timedelta` when testing the membership of :class:`MultiIndex` (:issue:`24570`) +- :meth:`MultiIndex.isin` now raises ``ValueError`` when items in values are not the same length as the number of levels in the MultiIndex (:issue:`26622`) - I/O diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 9217b388ce86b..64855d2ccdeb6 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -3175,6 +3175,14 @@ def _wrap_joined_index(self, joined, other): @Appender(Index.isin.__doc__) def isin(self, values, level=None): if level is None: + # validate value length + nlvl = len(self.levels) + for val in values: + if len(val) != nlvl: + raise ValueError('Length of each element in values must ' + 'match number of levels in MultiIndex. ' + 'len({}) != {}'.format(val, nlvl)) + values = MultiIndex.from_tuples(values, names=self.names).values return algos.isin(self.values, values) diff --git a/pandas/tests/indexes/multi/test_contains.py b/pandas/tests/indexes/multi/test_contains.py index 4b6934d445fd0..53ae716e334c5 100644 --- a/pandas/tests/indexes/multi/test_contains.py +++ b/pandas/tests/indexes/multi/test_contains.py @@ -1,3 +1,5 @@ +import re + import numpy as np import pytest @@ -58,6 +60,25 @@ def test_isin(): assert result.dtype == np.bool_ +@pytest.mark.parametrize('values, expected_msg', + [([('foo',), ('bar', 3), ('quux',)], + "len(('foo',)) != 2"), + ([('foo', 2), ('bar', 3, 2), ('quux', 4)], + "len(('bar', 3, 2)) != 2")]) +def test_isin_bad_values_raises(values, expected_msg): + idx = MultiIndex.from_arrays([ + ['qux', 'baz', 'foo', 'bar'], + np.arange(4) + ]) + + msg = re.escape('Length of each element in values must ' + 'match number of levels in MultiIndex. ' + + expected_msg) + + with pytest.raises(ValueError, match=msg): + idx.isin(values) + + @pytest.mark.skipif(PYPY, reason="tuples cmp recursively on PyPy") def test_isin_nan_not_pypy(): idx = MultiIndex.from_arrays([['foo', 'bar'], [1.0, np.nan]])