Repository: pandas-dev/pandas

Issue:
BUG: drop on axis with duplicate values doesn't raise when label is absent
#### Code Sample, a copy-pastable example if possible

```
>>> items
             item   name
store1      dress   V
store2       food   A
store2      water   C
```

CASE 1 : axis set to column and given a value which is not available in the structure. Gives ValueError.


```
>>> items.drop('abcd',axis=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.6/site-packages/pandas/core/generic.py", line 2530, in drop
    obj = obj._drop_axis(labels, axis, level=level, errors=errors)
  File "/usr/local/lib/python3.6/site-packages/pandas/core/generic.py", line 2562, in _drop_axis
    new_axis = axis.drop(labels, errors=errors)
  File "/usr/local/lib/python3.6/site-packages/pandas/core/indexes/base.py", line 3744, in drop
    labels[mask])
ValueError: labels ['abcd'] not contained in axis
```

CASE 2 : axis set to row, and given a value which is not available in structure should behave in the same way as the above case does.
Instead it just prints the structure table. Shouldn’t it be throwing ValueError ? 

```
>>> items.drop('abcd',axis=0)
             item   name
store1      dress   V
store2       food   A
store2      water   C
>>> 
```

#### Problem description

When provided invalid column name(axis=1) it throws "ValueError". Same happens with row as well(axis=0). But when there are duplicate indices in the DataFrame it doesn't.

#### Expected Output


```
>>> items.drop('d',axis=0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.6/site-packages/pandas/core/generic.py", line 2530, in drop
    obj = obj._drop_axis(labels, axis, level=level, errors=errors)
  File "/usr/local/lib/python3.6/site-packages/pandas/core/generic.py", line 2562, in _drop_axis
    new_axis = axis.drop(labels, errors=errors)
  File "/usr/local/lib/python3.6/site-packages/pandas/core/indexes/base.py", line 3744, in drop
    labels[mask])
ValueError: labels ['d'] not contained in axis
```

#### Output of ``pd.show_versions()``

<details>
INSTALLED VERSIONS
------------------
commit: None
python: 3.6.4.final.0
python-bits: 64
OS: Darwin
OS-release: 17.3.0
machine: x86_64
processor: i386
byteorder: little
LC_ALL: None
LANG: en_AU.UTF-8
LOCALE: en_AU.UTF-8

pandas: 0.22.0
pytest: None
pip: 9.0.1
setuptools: 36.5.0
Cython: 0.27.3
numpy: 1.13.3
scipy: None
pyarrow: None
xarray: None
IPython: None
sphinx: None
patsy: None
dateutil: 2.6.1
pytz: 2017.3
blosc: None
bottleneck: None
tables: None
numexpr: None
feather: None
matplotlib: None
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml: None
bs4: None
html5lib: None
sqlalchemy: None
pymysql: None
psycopg2: None
jinja2: None
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None
</details>


Discussion:
@vinu2003 Thanks for the report! As you mentioned on the mailing list, this is triggered by the fact there is a duplicate index and is indeed a bug. 
Related to https://github.com/pandas-dev/pandas/issues/18561 (I also mentioned this bug there), but since it is not exactly the same keeping this open as a separate issue.

I reformatted your post a bit, please make sure in the future to use correct markdown syntax (so it 'looks' good, github provides some help if you edit/post a comment)
Reproducible example:

```
In [18]: df = pd.DataFrame({"A": [1, 1, 1], "B": [1, 1, 1]}, index=['a', 'b', 'c'])

In [19]: df.drop("d", axis=0)
...
ValueError: labels ['d'] not contained in axis

In [20]: df = pd.DataFrame({"A": [1, 1, 1], "B": [1, 1, 1]}, index=['a', 'b', 'b'])

In [21]: df.drop("d", axis=0)
Out[21]: 
   A  B
a  1  1
b  1  1
b  1  1
```
I'm going to take a look since I don't see any other PR's for this issue.

Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -2806,6 +2806,11 @@ def drop(self, labels=None, axis=0, index=None, columns=None, level=None,
         -------
         dropped : type of caller
 
+        Raises
+        ------
+        KeyError
+            If none of the labels are found in the selected axis
+
         Examples
         --------
         >>> df = pd.DataFrame(np.arange(12).reshape(3,4),
@@ -2909,6 +2914,9 @@ def _drop_axis(self, labels, axis, level=None, errors='raise'):
             else:
                 indexer = ~axis.isin(labels)
 
+            if errors == 'raise' and indexer.all():
+                raise KeyError('{} not found in axis'.format(labels))
+
             slicer = [slice(None)] * self.ndim
             slicer[self._get_axis_number(axis_name)] = indexer
 
diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py
--- a/pandas/core/indexes/base.py
+++ b/pandas/core/indexes/base.py
@@ -3760,6 +3760,11 @@ def drop(self, labels, errors='raise'):
         Returns
         -------
         dropped : Index
+
+        Raises
+        ------
+        KeyError
+            If none of the labels are found in the selected axis
         """
         arr_dtype = 'object' if self.dtype == 'object' else None
         labels = _index_labels_to_array(labels, dtype=arr_dtype)
@@ -3767,8 +3772,8 @@ def drop(self, labels, errors='raise'):
         mask = indexer == -1
         if mask.any():
             if errors != 'ignore':
-                raise ValueError('labels %s not contained in axis' %
-                                 labels[mask])
+                raise KeyError(
+                    'labels %s not contained in axis' % labels[mask])
             indexer = indexer[~mask]
         return self.delete(indexer)
 
diff --git a/pandas/core/reshape/pivot.py b/pandas/core/reshape/pivot.py
--- a/pandas/core/reshape/pivot.py
+++ b/pandas/core/reshape/pivot.py
@@ -75,7 +75,7 @@ def pivot_table(data, values=None, index=None, columns=None, aggfunc='mean',
         for key in keys:
             try:
                 values = values.drop(key)
-            except (TypeError, ValueError):
+            except (TypeError, ValueError, KeyError):
                 pass
         values = list(values)
 


Test patch:
diff --git a/pandas/tests/frame/test_axis_select_reindex.py b/pandas/tests/frame/test_axis_select_reindex.py
--- a/pandas/tests/frame/test_axis_select_reindex.py
+++ b/pandas/tests/frame/test_axis_select_reindex.py
@@ -41,8 +41,8 @@ def test_drop_names(self):
             assert obj.columns.name == 'second'
         assert list(df.columns) == ['d', 'e', 'f']
 
-        pytest.raises(ValueError, df.drop, ['g'])
-        pytest.raises(ValueError, df.drop, ['g'], 1)
+        pytest.raises(KeyError, df.drop, ['g'])
+        pytest.raises(KeyError, df.drop, ['g'], 1)
 
         # errors = 'ignore'
         dropped = df.drop(['g'], errors='ignore')
@@ -87,10 +87,10 @@ def test_drop(self):
         assert_frame_equal(simple.drop(
             [0, 3], axis='index'), simple.loc[[1, 2], :])
 
-        pytest.raises(ValueError, simple.drop, 5)
-        pytest.raises(ValueError, simple.drop, 'C', 1)
-        pytest.raises(ValueError, simple.drop, [1, 5])
-        pytest.raises(ValueError, simple.drop, ['A', 'C'], 1)
+        pytest.raises(KeyError, simple.drop, 5)
+        pytest.raises(KeyError, simple.drop, 'C', 1)
+        pytest.raises(KeyError, simple.drop, [1, 5])
+        pytest.raises(KeyError, simple.drop, ['A', 'C'], 1)
 
         # errors = 'ignore'
         assert_frame_equal(simple.drop(5, errors='ignore'), simple)
@@ -1128,3 +1128,26 @@ def test_reindex_multi(self):
         expected = df.reindex([0, 1]).reindex(columns=['a', 'b'])
 
         assert_frame_equal(result, expected)
+
+    data = [[1, 2, 3], [1, 2, 3]]
+
+    @pytest.mark.parametrize('actual', [
+        DataFrame(data=data, index=['a', 'a']),
+        DataFrame(data=data, index=['a', 'b']),
+        DataFrame(data=data, index=['a', 'b']).set_index([0, 1]),
+        DataFrame(data=data, index=['a', 'a']).set_index([0, 1])
+    ])
+    def test_raise_on_drop_duplicate_index(self, actual):
+
+        # issue 19186
+        level = 0 if isinstance(actual.index, MultiIndex) else None
+        with pytest.raises(KeyError):
+            actual.drop('c', level=level, axis=0)
+        with pytest.raises(KeyError):
+            actual.T.drop('c', level=level, axis=1)
+        expected_no_err = actual.drop('c', axis=0, level=level,
+                                      errors='ignore')
+        assert_frame_equal(expected_no_err, actual)
+        expected_no_err = actual.T.drop('c', axis=1, level=level,
+                                        errors='ignore')
+        assert_frame_equal(expected_no_err.T, actual)
diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py
--- a/pandas/tests/indexes/test_base.py
+++ b/pandas/tests/indexes/test_base.py
@@ -1396,8 +1396,8 @@ def test_drop(self):
         expected = self.strIndex[lrange(5) + lrange(10, n)]
         tm.assert_index_equal(dropped, expected)
 
-        pytest.raises(ValueError, self.strIndex.drop, ['foo', 'bar'])
-        pytest.raises(ValueError, self.strIndex.drop, ['1', 'bar'])
+        pytest.raises(KeyError, self.strIndex.drop, ['foo', 'bar'])
+        pytest.raises(KeyError, self.strIndex.drop, ['1', 'bar'])
 
         # errors='ignore'
         mixed = drop.tolist() + ['foo']
@@ -1419,7 +1419,7 @@ def test_drop(self):
         tm.assert_index_equal(dropped, expected)
 
         # errors='ignore'
-        pytest.raises(ValueError, ser.drop, [3, 4])
+        pytest.raises(KeyError, ser.drop, [3, 4])
 
         dropped = ser.drop(4, errors='ignore')
         expected = Index([1, 2, 3])
@@ -1448,7 +1448,7 @@ def test_drop_tuple(self, values, to_drop):
 
         removed = index.drop(to_drop[1])
         for drop_me in to_drop[1], [to_drop[1]]:
-            pytest.raises(ValueError, removed.drop, drop_me)
+            pytest.raises(KeyError, removed.drop, drop_me)
 
     def test_tuple_union_bug(self):
         import pandas
diff --git a/pandas/tests/series/test_indexing.py b/pandas/tests/series/test_indexing.py
--- a/pandas/tests/series/test_indexing.py
+++ b/pandas/tests/series/test_indexing.py
@@ -1838,8 +1838,8 @@ def test_drop(self):
 
         # single string/tuple-like
         s = Series(range(3), index=list('abc'))
-        pytest.raises(ValueError, s.drop, 'bc')
-        pytest.raises(ValueError, s.drop, ('a', ))
+        pytest.raises(KeyError, s.drop, 'bc')
+        pytest.raises(KeyError, s.drop, ('a', ))
 
         # errors='ignore'
         s = Series(range(3), index=list('abc'))
@@ -1861,7 +1861,7 @@ def test_drop(self):
 
         # GH 16877
         s = Series([2, 3], index=[0, 1])
-        with tm.assert_raises_regex(ValueError, 'not contained in axis'):
+        with tm.assert_raises_regex(KeyError, 'not contained in axis'):
             s.drop([False, True])
 
     def test_align(self):
diff --git a/pandas/tests/test_panel.py b/pandas/tests/test_panel.py
--- a/pandas/tests/test_panel.py
+++ b/pandas/tests/test_panel.py
@@ -2302,7 +2302,7 @@ def check_drop(drop_val, axis_number, aliases, expected):
             expected = Panel({"One": df})
             check_drop('Two', 0, ['items'], expected)
 
-            pytest.raises(ValueError, panel.drop, 'Three')
+            pytest.raises(KeyError, panel.drop, 'Three')
 
             # errors = 'ignore'
             dropped = panel.drop('Three', errors='ignore')