Repository: pandas-dev/pandas
Pull request: BUG: Cleaner exception when `.iloc` called with non-integer list
- [x] closes #25753
- [x] tests added / passed
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry

Issue #25753 was raised requesting more verbose error messages when calling `loc` and `iloc` with mixed dtype arguments. In this pull request, I've added a new error message when calling `iloc` with a non-numeric dtype list-like object. 

This fixes numpy ultimately raising an ambiguous exception when, for example, calling `.iloc` with a list of string column names:
```
>>> print(data.iloc[['Colorado', 'Utah'], [3, 0, 1]])
Old:
TypeError: cannot perform reduce with flexible type
New:
IndexError: .iloc requires integer indexers, got ['Colorado' 'Utah']
```

However, the current exception when calling `.loc` with integer column names when the column names are actually strings I feel is already accurate:
```
>>> print(data.loc[['Colorado', 'Utah'], [3, 0, 1]])
KeyError: "None of [Int64Index([3, 0, 1], dtype='int64')] are in the [columns]"
```

Problem statement:
ERR: Raise a better message when mixing positional and label indexing in loc/iloc
#### Code Sample, a copy-pastable example if possible

```python
import numpy as np

data = DataFrame(np.arange(16).reshape((4, 4)),
    index=['Ohio', 'Colorado', 'Utah', 'New York'],
    columns=['one', 'two', 'three', 'four'])

data[data < 5] = 0


print(data.iloc[['Colorado', 'Utah'], [3, 0, 1]])
# .ix is deprecated. Please use
# .loc for label based indexing or
# .iloc for positional indexing


print(data.iloc[['Colorado', 'Utah'], [3, 0, 1]])
#TypeError: cannot perform reduce with flexible type

print(data.loc[['Colorado', 'Utah'], [3, 0, 1]])
#KeyError: "None of [Int64Index([3, 0, 1], dtype='int64')] are in the [columns]"
```
#### Problem description
I followed the suggesion to move the ix to iloc and this is what I have got
I have used python 3.7.2 on an elemntary os 18.04

Hints from issue discussion:
Mixing positional and label based indexing is not allowed in `loc` or `iloc`.

So either `data.iloc[[1, 2], [3, 0, 1]]` or `data.loc[['Colorado', 'Utah'], ['four', 'one', 'two]]`
@mroeschke : the error message `TypeError: cannot perform reduce with flexible type` does not appear helpful. perhaps this should be raised as a seperate issue.
I am happy with the answer provided.
Just unhappy for this decision becouse It would potentially break old code as soon as ix will be fully deprecated. 

The example cames from data analysis with python book and can be easily reproduced. 
Fair. `iloc` and `loc` could have a better error messages with mixed positional/label indexing.

The decision to deprecate `ix` was made was because its indexing operation was too flexible and not explicit compared to the other multitude of indexers.

Commit message: added tests for new exception

Patch to review:
diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst
index 1d2466adf9265..3944cc725f0ed 100644
--- a/doc/source/whatsnew/v0.25.0.rst
+++ b/doc/source/whatsnew/v0.25.0.rst
@@ -204,7 +204,7 @@ Interval
 Indexing
 ^^^^^^^^
 
--
+- Added an exception message when calling :meth:`DataFrame.iloc` with a list of non-numeric objects.
 -
 -
 
diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py
index 623a48acdd48b..d6f6913d30c4b 100755
--- a/pandas/core/indexing.py
+++ b/pandas/core/indexing.py
@@ -2079,6 +2079,10 @@ def _validate_key(self, key, axis):
             arr = np.array(key)
             len_axis = len(self.obj._get_axis(axis))
 
+            if not np.issubdtype(arr.dtype, np.number):
+                raise IndexError(".iloc requires integer indexers, got "
+                                 "{arr}".format(arr=arr))
+
             if len(arr) and (arr.max() >= len_axis or arr.min() < -len_axis):
                 raise IndexError("positional indexers are out-of-bounds")
         else:
diff --git a/pandas/tests/indexing/test_iloc.py b/pandas/tests/indexing/test_iloc.py
index 7147757953b01..2505eacee1ca2 100644
--- a/pandas/tests/indexing/test_iloc.py
+++ b/pandas/tests/indexing/test_iloc.py
@@ -118,6 +118,40 @@ def check(result, expected):
         with pytest.raises(IndexError, match=msg):
             dfl.iloc[:, 4]
 
+    def test_iloc_non_integer(self):
+
+        # iloc should throw IndexError if non-numeric list is passed
+        df = DataFrame(np.random.random_sample((20, 5)), columns=list('ABCDE'))
+
+        msg = '.iloc requires integer indexers, got'
+        with pytest.raises(IndexError, match=msg):
+            df.iloc[:, ['A', 'D']]
+        with pytest.raises(IndexError, match=msg):
+            df.iloc[['1', '2'], :]
+        with pytest.raises(IndexError, match=msg):
+            df.iloc[[pd.datetime(2019, 1, 1)], :]
+
+        # Numeric or booleans should not raise errors
+        result = df.iloc[:, [0, 1]]
+        expected = df.loc[:, ['A', 'B']]
+        tm.assert_frame_equal(result, expected)
+
+        result = df.iloc[[0, 1], [0, 1, 3]]
+        expected = df.loc[[0, 1], ['A', 'B', 'D']]
+        tm.assert_frame_equal(result, expected)
+
+        result = df.iloc[:, [True, False, False, True]]
+        expected = df.loc[:, ['A', 'D']]
+        tm.assert_frame_equal(result, expected)
+
+        result = df.iloc[:, [0, 0.5]]
+        expected = df.loc[:, ['A', 'A']]
+        tm.assert_frame_equal(result, expected)
+
+        result = df.iloc[[True, False, True], :]
+        expected = df.loc[[0, 2], :]
+        tm.assert_frame_equal(result, expected)
+
     def test_iloc_getitem_int(self):
 
         # integer


Code review comments:
diff_hunk: @@ -118,6 +118,40 @@ def check(result, expected):
         with pytest.raises(IndexError, match=msg):
             dfl.iloc[:, 4]
 
+    def test_iloc_non_integer(self):
+
+        # iloc should throw IndexError if non-numeric list is passed
+        df = DataFrame(np.random.random_sample((20, 5)), columns=list('ABCDE'))
+
+        msg = '.iloc requires integer indexers, got'
+        with pytest.raises(IndexError, match=msg):
path: pandas/tests/indexing/test_iloc.py
text: @user1:
Can you move these into a separate method with `_raises` appended to the name and leverage parametrization?

@author:
I refactored the tests into `test_iloc_non_integer_raises`. Let me know if that is what you were looking for.