Repository: pandas-dev/pandas

Issue:
loc allows different length boolean indexer 
Is this a bug, or deliberate?

```python
In [45]: a.loc[[True, False]]  # too few?
Out[45]:
a    1
dtype: int64

In [46]: a.loc[[True, False, True, False, False]]  # too many?
Out[46]:
a    1
c    3
dtype: int64
```

The fact that we raise here, when we have too many true values makes me think this is a bug

```python
In [53]: a.loc[[True, False, True, False, True]]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
~/Envs/dask-dev/lib/python3.7/site-packages/pandas/core/indexing.py in _getbool_axis(self, key, axis)
   1517         try:
-> 1518             return self.obj._take(inds, axis=axis)
   1519         except Exception as detail:

~/Envs/dask-dev/lib/python3.7/site-packages/pandas/core/series.py in _take(self, indices, axis, is_copy)
   3925         indices = ensure_platform_int(indices)
-> 3926         new_index = self.index.take(indices)
   3927

~/Envs/dask-dev/lib/python3.7/site-packages/pandas/core/indexes/base.py in take(self, indices, axis, allow_fill, fill_value, **kwargs)
    793                                                fill_value=fill_value,
--> 794                                                na_value=self._na_value)
    795         else:

~/Envs/dask-dev/lib/python3.7/site-packages/pandas/core/indexes/base.py in _assert_take_fillable(self, values, indices, allow_fill, fill_value, na_value)
    819         else:
--> 820             taken = values.take(indices)
    821         return taken

IndexError: index 4 is out of bounds for axis 0 with size 3

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
<ipython-input-53-9fd96bbd72ce> in <module>
----> 1 a.loc[[True, False, True, False, True]]

~/Envs/dask-dev/lib/python3.7/site-packages/pandas/core/indexing.py in __getitem__(self, key)
   1498
   1499             maybe_callable = com.apply_if_callable(key, self.obj)
-> 1500             return self._getitem_axis(maybe_callable, axis=axis)
   1501
   1502     def _is_scalar_access(self, key):

~/Envs/dask-dev/lib/python3.7/site-packages/pandas/core/indexing.py in _getitem_axis(self, key, axis)
   1867             return self._get_slice_axis(key, axis=axis)
   1868         elif com.is_bool_indexer(key):
-> 1869             return self._getbool_axis(key, axis=axis)
   1870         elif is_list_like_indexer(key):
   1871

~/Envs/dask-dev/lib/python3.7/site-packages/pandas/core/indexing.py in _getbool_axis(self, key, axis)
   1518             return self.obj._take(inds, axis=axis)
   1519         except Exception as detail:
-> 1520             raise self._exception(detail)
   1521
   1522     def _get_slice_axis(self, slice_obj, axis=None):

KeyError: IndexError('index 4 is out of bounds for axis 0 with size 3')
```

In the docs at http://pandas-docs.github.io/pandas-docs-travis/user_guide/indexing.html#boolean-indexing we say

> You may select rows from a DataFrame using a boolean vector the same length as the DataFrame’s index

Note that this only affects `.loc`. `Series.__getitem__` and `DataFrame.__getitem__` are unaffected.


Discussion:

Resolving patch:
diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py
--- a/pandas/core/indexing.py
+++ b/pandas/core/indexing.py
@@ -2407,29 +2407,54 @@ def convert_to_index_sliceable(obj, key):
     return None
 
 
-def check_bool_indexer(ax, key):
-    # boolean indexing, need to check that the data are aligned, otherwise
-    # disallowed
+def check_bool_indexer(index: Index, key) -> np.ndarray:
+    """
+    Check if key is a valid boolean indexer for an object with such index and
+    perform reindexing or conversion if needed.
+
+    This function assumes that is_bool_indexer(key) == True.
+
+    Parameters
+    ----------
+    index : Index
+        Index of the object on which the indexing is done
+    key : list-like
+        Boolean indexer to check
 
-    # this function assumes that is_bool_indexer(key) == True
+    Returns
+    -------
+    result: np.array
+        Resulting key
 
+    Raises
+    ------
+    IndexError
+        If the key does not have the same length as index
+
+    IndexingError
+        If the index of the key is unalignable to index
+
+    """
     result = key
-    if isinstance(key, ABCSeries) and not key.index.equals(ax):
-        result = result.reindex(ax)
+    if isinstance(key, ABCSeries) and not key.index.equals(index):
+        result = result.reindex(index)
         mask = isna(result._values)
         if mask.any():
             raise IndexingError('Unalignable boolean Series provided as '
                                 'indexer (index of the boolean Series and of '
-                                'the indexed object do not match')
+                                'the indexed object do not match).')
         result = result.astype(bool)._values
-    elif is_sparse(result):
-        result = result.to_dense()
-        result = np.asarray(result, dtype=bool)
     else:
-        # is_bool_indexer has already checked for nulls in the case of an
-        # object array key, so no check needed here
+        if is_sparse(result):
+            result = result.to_dense()
         result = np.asarray(result, dtype=bool)
 
+        # GH26658
+        if len(result) != len(index):
+            raise IndexError(
+                'Item wrong length {} instead of {}.'.format(len(result),
+                                                             len(index)))
+
     return result
 
 


Test patch:
diff --git a/pandas/tests/indexing/test_iloc.py b/pandas/tests/indexing/test_iloc.py
--- a/pandas/tests/indexing/test_iloc.py
+++ b/pandas/tests/indexing/test_iloc.py
@@ -265,6 +265,16 @@ def test_iloc_getitem_bool(self):
                           typs=['labels', 'mixed', 'ts', 'floats', 'empty'],
                           fails=IndexError)
 
+    @pytest.mark.parametrize('index', [[True, False],
+                                       [True, False, True, False]])
+    def test_iloc_getitem_bool_diff_len(self, index):
+        # GH26658
+        s = Series([1, 2, 3])
+        with pytest.raises(IndexError,
+                           match=('Item wrong length {} instead of {}.'.format(
+                               len(index), len(s)))):
+            _ = s.iloc[index]
+
     def test_iloc_getitem_slice(self):
 
         # slices
@@ -614,10 +624,10 @@ def test_iloc_mask(self):
                                  'cannot use an indexable as a mask'),
             ('locs', ''): 'Unalignable boolean Series provided as indexer '
                           '(index of the boolean Series and of the indexed '
-                          'object do not match',
+                          'object do not match).',
             ('locs', '.loc'): 'Unalignable boolean Series provided as indexer '
                               '(index of the boolean Series and of the '
-                              'indexed object do not match',
+                              'indexed object do not match).',
             ('locs', '.iloc'): ('iLocation based boolean indexing on an '
                                 'integer type is not available'),
         }
diff --git a/pandas/tests/indexing/test_loc.py b/pandas/tests/indexing/test_loc.py
--- a/pandas/tests/indexing/test_loc.py
+++ b/pandas/tests/indexing/test_loc.py
@@ -194,7 +194,17 @@ def test_loc_getitem_bool(self):
                           typs=['ints', 'uints', 'labels',
                                 'mixed', 'ts', 'floats'])
         self.check_result('bool', 'loc', b, 'ix', b, typs=['empty'],
-                          fails=KeyError)
+                          fails=IndexError)
+
+    @pytest.mark.parametrize('index', [[True, False],
+                                       [True, False, True, False]])
+    def test_loc_getitem_bool_diff_len(self, index):
+        # GH26658
+        s = Series([1, 2, 3])
+        with pytest.raises(IndexError,
+                           match=('Item wrong length {} instead of {}.'.format(
+                               len(index), len(s)))):
+            _ = s.loc[index]
 
     def test_loc_getitem_int_slice(self):