Repository: pandas-dev/pandas

Issue:
Indexing single level with list of missing labels raises if (and only if) it has length 1
#### Code Sample, a copy-pastable example if possible

```python
In [2]: mi = pd.MultiIndex.from_tuples([[1, 2, -1], [3,4, -2], [5, 6, -3]])                                                                                                                                                                   

In [3]: s = pd.Series(1, mi)                                                                                                                                                                                                                  

In [4]: s.loc[[100, 101]]                                                                                                                                                                                                                     
Out[4]: Series([], dtype: int64)

In [5]: s.loc[[100]]                                                                                                                                                                                                                          
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-5-7a5bc11c892d> in <module>
----> 1 s.loc[[100]]

~/nobackup/repo/pandas/pandas/core/indexing.py in __getitem__(self, key)
   1427 
   1428             maybe_callable = com.apply_if_callable(key, self.obj)
-> 1429             return self._getitem_axis(maybe_callable, axis=axis)
   1430 
   1431     def _is_scalar_access(self, key):

~/nobackup/repo/pandas/pandas/core/indexing.py in _getitem_axis(self, key, axis)
   1829                     raise ValueError('Cannot index with multidimensional key')
   1830 
-> 1831                 return self._getitem_iterable(key, axis=axis)
   1832 
   1833             # nested tuple slicing

~/nobackup/repo/pandas/pandas/core/indexing.py in _getitem_iterable(self, key, axis)
   1131             # A collection of keys
   1132             keyarr, indexer = self._get_listlike_indexer(key, axis,
-> 1133                                                          raise_missing=False)
   1134             return self.obj._reindex_with_indexers({axis: [keyarr, indexer]},
   1135                                                    copy=True, allow_dups=True)

~/nobackup/repo/pandas/pandas/core/indexing.py in _get_listlike_indexer(self, key, axis, raise_missing)
   1068         # if it cannot handle:
   1069         indexer, keyarr = ax._convert_listlike_indexer(key,
-> 1070                                                        kind=self.name)
   1071         # We only act on all found values:
   1072         if indexer is not None and (indexer != -1).all():

~/nobackup/repo/pandas/pandas/core/indexes/multi.py in _convert_listlike_indexer(self, keyarr, kind)
   2255             mask = check == -1
   2256             if mask.any():
-> 2257                 raise KeyError('%s not in index' % keyarr[mask])
   2258 
   2259         return indexer, keyarr

KeyError: '[100] not in index'
```

#### Problem description

Because of a [trivial typo](https://github.com/pandas-dev/pandas/blob/af7f2ef73e449f01acc6de47463c9b1440c6b0fb/pandas/core/indexing.py#L1821), behavior depends on whether the list has length at least 2. The "one item" behavior is arguably seldomly used, and future incompatible (xref: #20916 ).

#### Expected Output

Like ``Out[4]:``.

#### Output of ``pd.show_versions()``

<details>

INSTALLED VERSIONS
------------------
commit           : b4913295ab549652d1714082a60ec730436ed612
python           : 3.7.3.candidate.1
python-bits      : 64
OS               : Linux
OS-release       : 4.9.0-9-amd64
machine          : x86_64
processor        : 
byteorder        : little
LC_ALL           : None
LANG             : it_IT.UTF-8
LOCALE           : it_IT.UTF-8

pandas           : 0.25.0.dev0+849.gb4913295a
numpy            : 1.16.4
pytz             : 2016.7
dateutil         : 2.8.0
pip              : 9.0.1
setuptools       : 41.0.1
Cython           : 0.29.2
pytest           : 4.6.3
hypothesis       : 3.71.11
sphinx           : 1.4.9
blosc            : None
feather          : None
xlsxwriter       : 0.9.6
lxml.etree       : 4.3.2
html5lib         : 0.999999999
pymysql          : None
psycopg2         : None
jinja2           : 2.10.1
IPython          : 7.5.0
pandas_datareader: 0.2.1
bs4              : 4.5.3
bottleneck       : 1.2.1
fastparquet      : None
gcsfs            : None
lxml.etree       : 4.3.2
matplotlib       : 3.0.2
numexpr          : 2.6.9
openpyxl         : 2.3.0
pandas_gbq       : None
pyarrow          : None
pytables         : None
s3fs             : None
scipy            : 1.1.0
sqlalchemy       : 1.0.15
tables           : 3.4.4
xarray           : None
xlrd             : 1.0.0
xlwt             : 1.3.0
xlsxwriter       : 0.9.6


</details>



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
@@ -1856,7 +1856,7 @@ def _getitem_axis(self, key, axis=None):
 
                 if (
                     not isinstance(key, tuple)
-                    and len(key) > 1
+                    and len(key)
                     and not isinstance(key[0], tuple)
                 ):
                     key = tuple([key])


Test patch:
diff --git a/pandas/tests/indexing/multiindex/test_loc.py b/pandas/tests/indexing/multiindex/test_loc.py
--- a/pandas/tests/indexing/multiindex/test_loc.py
+++ b/pandas/tests/indexing/multiindex/test_loc.py
@@ -130,6 +130,19 @@ def test_loc_multiindex_missing_label_raises(self):
         with pytest.raises(KeyError, match=r"^2$"):
             df.loc[2]
 
+    @pytest.mark.parametrize("key, pos", [([2, 4], [0, 1]), ([2], []), ([2, 3], [])])
+    def test_loc_multiindex_list_missing_label(self, key, pos):
+        # GH 27148 - lists with missing labels do not raise:
+        df = DataFrame(
+            np.random.randn(3, 3),
+            columns=[[2, 2, 4], [6, 8, 10]],
+            index=[[4, 4, 8], [8, 10, 12]],
+        )
+
+        expected = df.iloc[pos]
+        result = df.loc[key]
+        tm.assert_frame_equal(result, expected)
+
     def test_loc_multiindex_too_many_dims_raises(self):
         # GH 14885
         s = Series(
@@ -280,47 +293,27 @@ def convert_nested_indexer(indexer_type, keys):
 
 
 @pytest.mark.parametrize(
-    "indexer, is_level1, expected_error",
+    "indexer, pos",
     [
-        ([], False, None),  # empty ok
-        (["A"], False, None),
-        (["A", "D"], False, None),
-        (["D"], False, r"\['D'\] not in index"),  # not any values found
-        (pd.IndexSlice[:, ["foo"]], True, None),
-        (pd.IndexSlice[:, ["foo", "bah"]], True, None),
+        ([], []),  # empty ok
+        (["A"], slice(3)),
+        (["A", "D"], slice(3)),
+        (["D", "E"], []),  # no values found - fine
+        (["D"], []),  # same, with single item list: GH 27148
+        (pd.IndexSlice[:, ["foo"]], slice(2, None, 3)),
+        (pd.IndexSlice[:, ["foo", "bah"]], slice(2, None, 3)),
     ],
 )
-def test_loc_getitem_duplicates_multiindex_missing_indexers(
-    indexer, is_level1, expected_error
-):
+def test_loc_getitem_duplicates_multiindex_missing_indexers(indexer, pos):
     # GH 7866
     # multi-index slicing with missing indexers
     idx = MultiIndex.from_product(
         [["A", "B", "C"], ["foo", "bar", "baz"]], names=["one", "two"]
     )
     s = Series(np.arange(9, dtype="int64"), index=idx).sort_index()
-
-    if indexer == []:
-        expected = s.iloc[[]]
-    elif is_level1:
-        expected = Series(
-            [0, 3, 6],
-            index=MultiIndex.from_product(
-                [["A", "B", "C"], ["foo"]], names=["one", "two"]
-            ),
-        ).sort_index()
-    else:
-        exp_idx = MultiIndex.from_product(
-            [["A"], ["foo", "bar", "baz"]], names=["one", "two"]
-        )
-        expected = Series(np.arange(3, dtype="int64"), index=exp_idx).sort_index()
-
-    if expected_error is not None:
-        with pytest.raises(KeyError, match=expected_error):
-            s.loc[indexer]
-    else:
-        result = s.loc[indexer]
-        tm.assert_series_equal(result, expected)
+    expected = s.iloc[pos]
+    result = s.loc[indexer]
+    tm.assert_series_equal(result, expected)
 
 
 def test_series_loc_getitem_fancy(multiindex_year_month_day_dataframe_random_data):
diff --git a/pandas/tests/indexing/multiindex/test_slice.py b/pandas/tests/indexing/multiindex/test_slice.py
--- a/pandas/tests/indexing/multiindex/test_slice.py
+++ b/pandas/tests/indexing/multiindex/test_slice.py
@@ -117,11 +117,11 @@ def test_per_axis_per_level_getitem(self):
         with pytest.raises(ValueError):
             df.loc[(slice(None), np.array([True, False])), :]
 
-        # ambiguous cases
-        # these can be multiply interpreted (e.g. in this case
-        # as df.loc[slice(None),[1]] as well
-        with pytest.raises(KeyError, match=r"'\[1\] not in index'"):
-            df.loc[slice(None), [1]]
+        # ambiguous notation
+        # this is interpreted as slicing on both axes (GH #16396)
+        result = df.loc[slice(None), [1]]
+        expected = df.iloc[:, []]
+        tm.assert_frame_equal(result, expected)
 
         result = df.loc[(slice(None), [1]), :]
         expected = df.iloc[[0, 3]]