Repository: deppen8/pandas-vet

Issue:
Check for .stack
Check for `.stack` method. [See flashcard](https://deppen8.github.io/pandas-bw/reshape-split-apply-combine/melt-vs-stack.png). Give error message:

> Prefer '.melt' to '.stack'. '.melt' allows direct column renaming and avoids a MultiIndex

Comments:

Patch:
diff --git a/pandas_vet/__init__.py b/pandas_vet/__init__.py
index 1b54d23..6237dd5 100644
--- a/pandas_vet/__init__.py
+++ b/pandas_vet/__init__.py
@@ -37,6 +37,7 @@ class Visitor(ast.NodeVisitor):
         self.errors.extend(check_for_notnull(node))
         self.errors.extend(check_for_pivot(node))
         self.errors.extend(check_for_unstack(node))
+        self.errors.extend(check_for_stack(node))
         self.errors.extend(check_for_arithmetic_methods(node))
         self.errors.extend(check_for_comparison_methods(node))
         self.errors.extend(check_for_read_table(node))
@@ -193,6 +194,17 @@ def check_for_unstack(node: ast.Call) -> List:
     return []
 
 
+def check_for_stack(node: ast.Call) -> List:
+    """
+    Check AST for occurence of the `.stack()` method on the pandas data frame.
+
+    Error/warning message to recommend use of `.melt()` method instead.
+    """
+    if isinstance(node.func, ast.Attribute) and node.func.attr == "stack":
+        return [PD013(node.lineno, node.col_offset)]
+    return []
+
+
 def check_for_values(node: ast.Attribute) -> List:
     """
     Check AST for occurence of the `.values` attribute on the pandas data frame.
@@ -255,3 +267,6 @@ PD011 = VetError(
 PD012 = VetError(
     message="PDO12 '.read_csv' is preferred to '.read_table'; provides same functionality"
 )
+PD013 = VetError(
+    message="PD013 '.melt' is preferred to '.stack'; provides same functionality"
+)


Test patch:
diff --git a/tests/test_PD005.py b/tests/test_PD005.py
index 9c68e0e..1fcc596 100644
--- a/tests/test_PD005.py
+++ b/tests/test_PD005.py
@@ -11,7 +11,8 @@ from pandas_vet import PD005
 
 def test_PD005_pass_arithmetic_operator():
     """
-    Test that using binary arithmetic operator explicitly does not result in an error.
+    Test that explicit use of binary arithmetic operator does not
+    result in an error.
     """
     arithmetic_operators = [
         '+',
diff --git a/tests/test_PD006.py b/tests/test_PD006.py
index cb30365..7dae856 100644
--- a/tests/test_PD006.py
+++ b/tests/test_PD006.py
@@ -11,7 +11,8 @@ from pandas_vet import PD006
 
 def test_PD006_pass_comparison_operator():
     """
-    Test that using binary comparison operator explicitly does not result in an error.
+    Test that explicit use of binary comparison operator does not
+    result in an error.
     """
     comparison_operators = ['>',  '<',  '>=', '<=', '==', '!=']
     for op in comparison_operators:
diff --git a/tests/test_PD010.py b/tests/test_PD010.py
index d3ec020..1e897bd 100644
--- a/tests/test_PD010.py
+++ b/tests/test_PD010.py
@@ -12,7 +12,15 @@ def test_PD010_pass():
     """
     Test that using .pivot_table() explicitly does not result in an error.
     """
-    statement = "table = df.pivot_table(df, values='D', index=['A', 'B'], columns=['C'], aggfunc=np.sum, fill_value=0)"
+    statement = """table = df.pivot_table(
+        df,
+        values='D',
+        index=['A', 'B'],
+        columns=['C'],
+        aggfunc=np.sum,
+        fill_value=0
+        )
+    """
     tree = ast.parse(statement)
     actual = list(VetPlugin(tree).run())
     expected = []
@@ -21,9 +29,16 @@ def test_PD010_pass():
 
 def test_PD010_fail_pivot():
     """
-    Test that using either pd.pivot(df) or df.pivot() methods results in an error.
+    Test that using either pd.pivot(df) or df.pivot() methods
+    results in an error.
+    """
+    statement = """table = pd.pivot(
+        df,
+        index='foo',
+        columns='bar',
+        values='baz'
+        )
     """
-    statement = "table = pd.pivot(df, index='foo', columns='bar', values='baz')"
     tree = ast.parse(statement)
     actual = list(VetPlugin(tree).run())
     expected = [PD010(1, 8)]
diff --git a/tests/test_PD011.py b/tests/test_PD011.py
index b8f9cf1..6a671e2 100644
--- a/tests/test_PD011.py
+++ b/tests/test_PD011.py
@@ -1,6 +1,6 @@
 """
-Test to check for use of the pandas dataframe `.array` attribute 
-or `.to_array()` method in preference to `.values` attribute.  
+Test to check for use of the pandas dataframe `.array` attribute
+or `.to_array()` method in preference to `.values` attribute.
 """
 import ast
 
@@ -40,6 +40,7 @@ def test_PD011_fail_values():
     expected = [PD011(1, 9)]
     assert actual == expected
 
+
 def test_PD011_pass_node_Name():
     """
     Test that where 'values' is a Name does NOT raise an error
diff --git a/tests/test_PD012.py b/tests/test_PD012.py
index ee45e2e..176b2d4 100644
--- a/tests/test_PD012.py
+++ b/tests/test_PD012.py
@@ -1,6 +1,6 @@
 """
 Test to check for use of the pandas soon-to-be-deprecated `.read_table()`
-method. 
+method.
 """
 import ast
 
diff --git a/tests/test_PD013.py b/tests/test_PD013.py
new file mode 100644
index 0000000..dededc0
--- /dev/null
+++ b/tests/test_PD013.py
@@ -0,0 +1,35 @@
+"""
+Test to check functionality for use of the `.melt()` data frame
+method in preference to `.stack()` method.
+"""
+import ast
+
+from pandas_vet import VetPlugin
+from pandas_vet import PD013
+
+
+def test_PD013_pass():
+    """
+    Test that using .melt() explicitly does not result in an error.
+    """
+    statement = """table = df.melt(
+        id_vars='airline',
+        value_vars=['ATL', 'DEN', 'DFW'],
+        value_name='airline delay'
+        )
+    """
+    tree = ast.parse(statement)
+    actual = list(VetPlugin(tree).run())
+    expected = []
+    assert actual == expected
+
+
+def test_PD013_fail_stack():
+    """
+    Test that using .stack() results in an error.
+    """
+    statement = "table = df.stack(level=-1, dropna=True)"
+    tree = ast.parse(statement)
+    actual = list(VetPlugin(tree).run())
+    expected = [PD013(1, 8)]
+    assert actual == expected