Repository: pandas-dev/pandas

Issue:
Melting with not present column does not produce error
#### Code Sample

```python
import pandas as pd
import numpy as np
# Generate data
people = ['Susie', 'Alejandro']
day = ['Monday', 'Tuesday', 'Wednesday']
data = [[person, d, *np.random.randint(0, 5, 2)]  for person in people for d in day]
df = pd.DataFrame(data, columns=['Name', 'day', 'burgers', 'fries'])
df.head()
```
Name | day | burgers | fries
-- | -- | -- | --
Susie | Monday | 4 | 0
Susie | Tuesday | 0 | 1
Susie | Wednesday | 0 | 1
Alejandro | Monday | 4 | 2
Alejandro | Tuesday | 2 | 0


```python
# Melt on column that's not present in `df`
df.melt(['Name', 'day'], ['Burgers', 'fries'])
```
Outputs warning:
```
/home/ubuntu/anaconda3/lib/python3.6/site-packages/pandas/core/indexing.py:1472: FutureWarning: 
Passing list-likes to .loc or [] with any missing label will raise
KeyError in the future, you can use .reindex() as an alternative.

See the documentation here:
https://pandas.pydata.org/pandas-docs/stable/indexing.html#deprecate-loc-reindex-listlike
  return self._getitem_tuple(key)
```
#### Problem description
This behavior should produce an **error**. Warnings aren't always taken seriously and if this melting operation were apart of a larger chain of operations, it would be unclear what the cause of the warning was since the warning does not directly address the actual problem. There is no scenario in which support for melting on non-present columns would be beneficial and there are clear reasons why it would be beneficial to alert users if they've made this mistake.

#### Expected Output
`df.melt(['Name', 'day'], ['Burgers', 'fries'])` should be treated like `df.melt(['Name', 'Day'])` (where `Day` is not present in the df) and produce a Traceback.


Discussion:
That warning is coming indirectly via a `.loc`. I'm not sure when it's slated to be enforced.

Regardless, we could add a check to `melt` that all the names are valid. Interested in submitting a PR?
I think the check needs to happen roughly before https://github.com/pandas-dev/pandas/blob/adc54fe6a48483defab173d3af986fe47bb5e3eb/pandas/core/reshape/melt.py#L48 

Resolving patch:
diff --git a/pandas/core/reshape/melt.py b/pandas/core/reshape/melt.py
--- a/pandas/core/reshape/melt.py
+++ b/pandas/core/reshape/melt.py
@@ -13,6 +13,7 @@
 from pandas import compat
 from pandas.core.arrays import Categorical
 from pandas.core.frame import _shared_docs
+from pandas.core.indexes.base import Index
 from pandas.core.reshape.concat import concat
 from pandas.core.tools.numeric import to_numeric
 
@@ -24,6 +25,12 @@
 def melt(frame, id_vars=None, value_vars=None, var_name=None,
          value_name='value', col_level=None):
     # TODO: what about the existing index?
+    # If multiindex, gather names of columns on all level for checking presence
+    # of `id_vars` and `value_vars`
+    if isinstance(frame.columns, ABCMultiIndex):
+        cols = [x for c in frame.columns for x in c]
+    else:
+        cols = list(frame.columns)
     if id_vars is not None:
         if not is_list_like(id_vars):
             id_vars = [id_vars]
@@ -32,7 +39,13 @@ def melt(frame, id_vars=None, value_vars=None, var_name=None,
             raise ValueError('id_vars must be a list of tuples when columns'
                              ' are a MultiIndex')
         else:
+            # Check that `id_vars` are in frame
             id_vars = list(id_vars)
+            missing = Index(np.ravel(id_vars)).difference(cols)
+            if not missing.empty:
+                raise KeyError("The following 'id_vars' are not present"
+                               " in the DataFrame: {missing}"
+                               "".format(missing=list(missing)))
     else:
         id_vars = []
 
@@ -45,6 +58,12 @@ def melt(frame, id_vars=None, value_vars=None, var_name=None,
                              ' columns are a MultiIndex')
         else:
             value_vars = list(value_vars)
+            # Check that `value_vars` are in frame
+            missing = Index(np.ravel(value_vars)).difference(cols)
+            if not missing.empty:
+                raise KeyError("The following 'value_vars' are not present in"
+                               " the DataFrame: {missing}"
+                               "".format(missing=list(missing)))
         frame = frame.loc[:, id_vars + value_vars]
     else:
         frame = frame.copy()


Test patch:
diff --git a/pandas/tests/reshape/test_melt.py b/pandas/tests/reshape/test_melt.py
--- a/pandas/tests/reshape/test_melt.py
+++ b/pandas/tests/reshape/test_melt.py
@@ -101,6 +101,14 @@ def test_vars_work_with_multiindex(self):
         result = self.df1.melt(id_vars=[('A', 'a')], value_vars=[('B', 'b')])
         tm.assert_frame_equal(result, expected)
 
+    def test_single_vars_work_with_multiindex(self):
+        expected = DataFrame({
+            'A': {0: 1.067683, 1: -1.321405, 2: -0.807333},
+            'CAP': {0: 'B', 1: 'B', 2: 'B'},
+            'value': {0: -1.110463, 1: 0.368915, 2: 0.08298}})
+        result = self.df1.melt(['A'], ['B'], col_level=0)
+        tm.assert_frame_equal(result, expected)
+
     def test_tuple_vars_fail_with_multiindex(self):
         # melt should fail with an informative error message if
         # the columns have a MultiIndex and a tuple is passed
@@ -233,6 +241,49 @@ def test_pandas_dtypes(self, col):
         expected.columns = ['klass', 'col', 'attribute', 'value']
         tm.assert_frame_equal(result, expected)
 
+    def test_melt_missing_columns_raises(self):
+        # GH-23575
+        # This test is to ensure that pandas raises an error if melting is
+        # attempted with column names absent from the dataframe
+
+        # Generate data
+        df = pd.DataFrame(np.random.randn(5, 4), columns=list('abcd'))
+
+        # Try to melt with missing `value_vars` column name
+        msg = "The following '{Var}' are not present in the DataFrame: {Col}"
+        with pytest.raises(
+                KeyError,
+                match=msg.format(Var='value_vars', Col="\\['C'\\]")):
+            df.melt(['a', 'b'], ['C', 'd'])
+
+        # Try to melt with missing `id_vars` column name
+        with pytest.raises(
+                KeyError,
+                match=msg.format(Var='id_vars', Col="\\['A'\\]")):
+            df.melt(['A', 'b'], ['c', 'd'])
+
+        # Multiple missing
+        with pytest.raises(
+                KeyError,
+                match=msg.format(Var='id_vars',
+                                 Col="\\['not_here', 'or_there'\\]")):
+            df.melt(['a', 'b', 'not_here', 'or_there'], ['c', 'd'])
+
+        # Multiindex melt fails if column is missing from multilevel melt
+        multi = df.copy()
+        multi.columns = [list('ABCD'), list('abcd')]
+        with pytest.raises(
+            KeyError,
+            match=msg.format(Var='id_vars',
+                             Col="\\['E'\\]")):
+            multi.melt([('E', 'a')], [('B', 'b')])
+        # Multiindex fails if column is missing from single level melt
+        with pytest.raises(
+            KeyError,
+            match=msg.format(Var='value_vars',
+                             Col="\\['F'\\]")):
+            multi.melt(['A'], ['F'], col_level=0)
+
 
 class TestLreshape(object):