Repository: pandas-dev/pandas
Pull request: BUG: __getitem__ raise blank KeyError for IntervalIndex and missing keys
- [x] closes #27365
- [x] tests added / passed
- [x] passes `black pandas`
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry

Without ``tolist`` error would look like ``KeyError: array([6])``

Problem statement:
ERR: missing key not displayed in exception message for IntervalIndex
#### Code Sample, a copy-pastable example if possible

adapted from `test_non_matching` in `pandas\tests\indexing\interval\test_interval.py`

```python
>>> import numpy as np
>>> import pandas as pd
>>> pd.__version__
'0.25.0rc0+59.g0437f6899'
>>>
>>> s=pd.Series(np.arange(5), pd.IntervalIndex.from_breaks(np.arange(6)))
>>> s
(0, 1]    0
(1, 2]    1
(2, 3]    2
(3, 4]    3
(4, 5]    4
dtype: int32
>>>
>>> s.loc[[4,5]]
(3, 4]    3
(4, 5]    4
dtype: int32
>>>
>>> s.loc[[4,5,6]]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexing.py", line 1409, in __getitem__
    return self._getitem_axis(maybe_callable, axis=axis)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexing.py", line 1816, in _getitem_axis
    return self._getitem_iterable(key, axis=axis)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexing.py", line 1118, in _getitem_iterable
    keyarr, indexer = self._get_listlike_indexer(key, axis, raise_missing=False)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexing.py", line 1060, in _get_listlike_indexer
    indexer, keyarr = ax._convert_listlike_indexer(key, kind=self.name)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexes\base.py", line 3239, in _convert_listlike_indexer
    indexer = self._convert_list_indexer(keyarr, kind=kind)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexes\interval.py", line 626, in _convert_list_indexer
    raise KeyError
KeyError
>>>
>>> s.to_frame().loc[[4,5,6]]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexing.py", line 1409, in __getitem__
    return self._getitem_axis(maybe_callable, axis=axis)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexing.py", line 1816, in _getitem_axis
    return self._getitem_iterable(key, axis=axis)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexing.py", line 1118, in _getitem_iterable
    keyarr, indexer = self._get_listlike_indexer(key, axis, raise_missing=False)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexing.py", line 1060, in _get_listlike_indexer
    indexer, keyarr = ax._convert_listlike_indexer(key, kind=self.name)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexes\base.py", line 3239, in _convert_listlike_indexer
    indexer = self._convert_list_indexer(keyarr, kind=kind)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexes\interval.py", line 626, in _convert_list_indexer
    raise KeyError
KeyError
>>>


```
#### Problem description

With a list indexer, the missing key should be in the exception message
 
#### Expected Output
```python
KeyError: 6
```

another example - from `test_loc_with_overlap` in `pandas\tests\indexing\interval\test_interval_new.py`

```python
>>> idx = pd.IntervalIndex.from_tuples([(1, 5), (3, 7)])
>>> s = pd.Series(range(len(idx)), index=idx)
>>> s
(1, 5]    0
(3, 7]    1
dtype: int64
>>>
>>> s.loc[pd.Interval(3, 5)]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexing.py", line 1409, in __getitem__
    return self._getitem_axis(maybe_callable, axis=axis)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexing.py", line 1827, in _getitem_axis
    return self._get_label(key, axis=axis)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexing.py", line 152, in _get_label
    return self.obj._xs(label, axis=axis)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\generic.py", line 3738, in xs
    loc = self.index.get_loc(key)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexes\interval.py", line 875, in get_loc
    raise KeyError(key)
KeyError: Interval(3, 5, closed='right')
>>>
>>> s.loc[[pd.Interval(3, 5)]]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexing.py", line 1409, in __getitem__
    return self._getitem_axis(maybe_callable, axis=axis)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexing.py", line 1816, in _getitem_axis
    return self._getitem_iterable(key, axis=axis)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexing.py", line 1118, in _getitem_iterable
    keyarr, indexer = self._get_listlike_indexer(key, axis, raise_missing=False)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexing.py", line 1060, in _get_listlike_indexer
    indexer, keyarr = ax._convert_listlike_indexer(key, kind=self.name)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexes\base.py", line 3239, in _convert_listlike_indexer
    indexer = self._convert_list_indexer(keyarr, kind=kind)
  File "C:\Users\simon\OneDrive\code\pandas-simonjayhawkins\pandas\core\indexes\interval.py", line 626, in _convert_list_indexer
    raise KeyError
KeyError
>>>

```
#### Output of ``pd.show_versions()``

<details>

[paste the output of ``pd.show_versions()`` here below this line]

</details>

Hints from issue discussion:
Hi, I will take a look at this and add error messages.
I am aware of the changes going around the Interval contains function. In the `pandas\tests\indexing\interval\test_interval_new.py`, the test is skipped. 
`pytestmark = pytest.mark.skip(reason="new indexing tests for issue 16316")`

I have added error msg in the codebase. Should I enable the test_interval_new.py ? Thanks~
@simonjayhawkins Can you provide some feedback on if we want to skip the test in test_interval_new.py?

Commit message: Escape sequence

Patch to review:
diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst
index 28f7df98cb86b..5352c42cc73e0 100644
--- a/doc/source/whatsnew/v1.2.0.rst
+++ b/doc/source/whatsnew/v1.2.0.rst
@@ -584,6 +584,7 @@ Indexing
 - Bug in :meth:`DataFrame.loc` returned requested key plus missing values when ``loc`` was applied to single level from :class:`MultiIndex` (:issue:`27104`)
 - Bug in indexing on a :class:`Series` or :class:`DataFrame` with a :class:`CategoricalIndex` using a listlike indexer containing NA values (:issue:`37722`)
 - Bug in :meth:`DataFrame.xs` ignored ``droplevel=False`` for columns (:issue:`19056`)
+- Bug in :meth:`Series.__getitem__` and :meth:`DataFrame.__getitem__` returns blank ``KeyError`` without missing keys for :class:`IntervalIndex` (:issue:`27365`)
 
 Missing
 ^^^^^^^
diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py
index 2aec86c9cdfae..6da2dd2cf8572 100644
--- a/pandas/core/indexes/interval.py
+++ b/pandas/core/indexes/interval.py
@@ -845,7 +845,7 @@ def _convert_list_indexer(self, keyarr):
 
         # we have missing values
         if (locs == -1).any():
-            raise KeyError
+            raise KeyError(keyarr[locs == -1].tolist())
 
         return locs
 
diff --git a/pandas/tests/indexing/interval/test_interval.py b/pandas/tests/indexing/interval/test_interval.py
index df59d09edd3ef..65a3632a0b0a9 100644
--- a/pandas/tests/indexing/interval/test_interval.py
+++ b/pandas/tests/indexing/interval/test_interval.py
@@ -65,10 +65,10 @@ def test_non_matching(self):
 
         # this is a departure from our current
         # indexing scheme, but simpler
-        with pytest.raises(KeyError, match="^$"):
+        with pytest.raises(KeyError, match=r"^\[-1\]$"):
             s.loc[[-1, 3, 4, 5]]
 
-        with pytest.raises(KeyError, match="^$"):
+        with pytest.raises(KeyError, match=r"^\[-1\]$"):
             s.loc[[-1, 3]]
 
     @pytest.mark.arm_slow
@@ -107,11 +107,11 @@ def test_loc_getitem_frame(self):
         expected = df.take([4, 5, 4, 5])
         tm.assert_frame_equal(result, expected)
 
-        with pytest.raises(KeyError, match="^$"):
+        with pytest.raises(KeyError, match=r"^\[10\]$"):
             df.loc[[10]]
 
         # partial missing
-        with pytest.raises(KeyError, match="^$"):
+        with pytest.raises(KeyError, match=r"^\[10\]$"):
             df.loc[[10, 4]]
 
 
diff --git a/pandas/tests/indexing/interval/test_interval_new.py b/pandas/tests/indexing/interval/test_interval_new.py
index 03c3034772bc6..a9512bc97d9de 100644
--- a/pandas/tests/indexing/interval/test_interval_new.py
+++ b/pandas/tests/indexing/interval/test_interval_new.py
@@ -204,13 +204,13 @@ def test_loc_with_overlap(self):
         with pytest.raises(KeyError, match=re.escape("Interval(3, 5, closed='right')")):
             s.loc[Interval(3, 5)]
 
-        with pytest.raises(KeyError, match="^$"):
+        with pytest.raises(KeyError, match=r"^\[Interval\(3, 5, closed='right'\)\]$"):
             s.loc[[Interval(3, 5)]]
 
         with pytest.raises(KeyError, match=re.escape("Interval(3, 5, closed='right')")):
             s[Interval(3, 5)]
 
-        with pytest.raises(KeyError, match="^$"):
+        with pytest.raises(KeyError, match=r"^\[Interval\(3, 5, closed='right'\)\]$"):
             s[[Interval(3, 5)]]
 
         # slices with interval (only exact matches)
@@ -266,3 +266,11 @@ def test_non_unique_moar(self):
         expected = s.iloc[[0, 1]]
         result = s[[Interval(1, 3)]]
         tm.assert_series_equal(expected, result)
+
+    def test_missing_key_error_message(self, frame_or_series):
+        # GH#27365
+        obj = frame_or_series(
+            np.arange(5), index=IntervalIndex.from_breaks(np.arange(6))
+        )
+        with pytest.raises(KeyError, match=r"\[6\]"):
+            obj.loc[[4, 5, 6]]


Code review comments:
diff_hunk: @@ -845,7 +845,7 @@ def _convert_list_indexer(self, keyarr):
 
         # we have missing values
         if (locs == -1).any():
-            raise KeyError
+            raise KeyError(keyarr[locs == -1].tolist())
path: pandas/core/indexes/interval.py
text: @user1:
is this user faing?

@author:
Yep, non existing Keys for IntervalIndex

@user2:
@user1 i think this is about ready, only question is if we want to further flesh out the exception message here

diff_hunk: @@ -584,6 +584,7 @@ Indexing
 - Bug in :meth:`DataFrame.loc` returned requested key plus missing values when ``loc`` was applied to single level from :class:`MultiIndex` (:issue:`27104`)
 - Bug in indexing on a :class:`Series` or :class:`DataFrame` with a :class:`CategoricalIndex` using a listlike indexer containing NA values (:issue:`37722`)
 - Bug in :meth:`DataFrame.xs` ignored ``droplevel=False`` for columns (:issue:`19056`)
+- Bug in :meth:`Series.__getitem__` and :meth:`DataFrame.__getitem__` returns blank ``KeyError`` without missing keys for :class:`IntervalIndex` (:issue:`27365`)
path: doc/source/whatsnew/v1.2.0.rst
text: @user1:
returns -> raising

@author:
Sorry, changed