Repository: pandas-dev/pandas

Issue:
Make DataFrame.pivot index argument truly optional
Creating a pivot table from a DataFrame is easy.

``` python
In [17]: df
Out[17]: 
                 date variable     value
0 2000-01-03 00:00:00        A  0.528219
1 2000-01-04 00:00:00        A -0.135071
2 2000-01-05 00:00:00        A -0.343018
3 2000-01-03 00:00:00        B -0.097701
4 2000-01-04 00:00:00        B -1.383248

In [18]: df.pivot('date', 'variable', 'value')
Out[18]: 
variable           A         B
date                          
2000-01-03  0.528219 -0.097701
2000-01-04 -0.135071 -1.383248
2000-01-05 -0.343018       NaN
```

However if the index has been set, and there is no column to be set as index, pivot fails. From api point of view, the `index` argument is optional, but in fact it is not.

``` python
In [19]: df.set_index('date', inplace=True)

In [20]: df
Out[20]: 
           variable     value
date                         
2000-01-03        A  0.528219
2000-01-04        A -0.135071
2000-01-05        A -0.343018
2000-01-03        B -0.097701
2000-01-04        B -1.383248

In [21]: df.pivot(columns='variable', values='value')
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
...
KeyError: u'no item named None'
```

Of course one can reset the index before using pivot.

``` python
In [22]: df.reset_index().pivot('date', 'variable', 'value')
Out[22]: 
variable           A         B
date                          
2000-01-03  0.528219 -0.097701
2000-01-04 -0.135071 -1.383248
2000-01-05 -0.343018       NaN
```



Discussion:

Resolving patch:
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -3460,8 +3460,9 @@ def pivot(self, index=None, columns=None, values=None):
 
         Parameters
         ----------
-        index : string or object
-            Column name to use to make new frame's index
+        index : string or object, optional
+            Column name to use to make new frame's index. If None, uses
+            existing index.
         columns : string or object
             Column name to use to make new frame's columns
         values : string or object, optional
diff --git a/pandas/core/reshape.py b/pandas/core/reshape.py
--- a/pandas/core/reshape.py
+++ b/pandas/core/reshape.py
@@ -319,11 +319,17 @@ def pivot(self, index=None, columns=None, values=None):
     See DataFrame.pivot
     """
     if values is None:
-        indexed = self.set_index([index, columns])
+        cols = [columns] if index is None else [index, columns]
+        append = index is None
+        indexed = self.set_index(cols, append=append)
         return indexed.unstack(columns)
     else:
+        if index is None:
+            index = self.index
+        else:
+            index = self[index]
         indexed = Series(self[values].values,
-                         index=MultiIndex.from_arrays([self[index],
+                         index=MultiIndex.from_arrays([index,
                                                        self[columns]]))
         return indexed.unstack(columns)
 


Test patch:
diff --git a/pandas/tests/test_frame.py b/pandas/tests/test_frame.py
--- a/pandas/tests/test_frame.py
+++ b/pandas/tests/test_frame.py
@@ -9478,6 +9478,47 @@ def test_pivot_integer_bug(self):
         repr(result)
         self.assert_numpy_array_equal(result.columns, ['A', 'B'])
 
+    def test_pivot_index_none(self):
+        # gh-3962
+        data = {
+            'index': ['A', 'B', 'C', 'C', 'B', 'A'],
+            'columns': ['One', 'One', 'One', 'Two', 'Two', 'Two'],
+            'values': [1., 2., 3., 3., 2., 1.]
+        }
+
+        frame = DataFrame(data).set_index('index')
+        result = frame.pivot(columns='columns', values='values')
+        expected = DataFrame({
+            'One': {'A': 1., 'B': 2., 'C': 3.},
+            'Two': {'A': 1., 'B': 2., 'C': 3.}
+        })
+
+        expected.index.name, expected.columns.name = 'index', 'columns'
+        assert_frame_equal(result, expected)
+
+        # omit values
+        result = frame.pivot(columns='columns')
+
+        expected.columns = pd.MultiIndex.from_tuples([('values', 'One'),
+                                                     ('values', 'Two')],
+                                                     names=[None, 'columns'])
+        expected.index.name = 'index'
+        assert_frame_equal(result, expected, check_names=False)
+        self.assertEqual(result.index.name, 'index',)
+        self.assertEqual(result.columns.names, (None, 'columns'))
+        expected.columns = expected.columns.droplevel(0)
+
+        data = {
+            'index': range(7),
+            'columns': ['One', 'One', 'One', 'Two', 'Two', 'Two'],
+            'values': [1., 2., 3., 3., 2., 1.]
+        }
+
+        result = frame.pivot(columns='columns', values='values')
+
+        expected.columns.name = 'columns'
+        assert_frame_equal(result, expected)
+
     def test_reindex(self):
         newFrame = self.frame.reindex(self.ts1.index)