Repository: pandas-dev/pandas

Issue:
BUG: pd.crosstab cannot handle series with the same name
the following works fine

> > > c = pd.Series(rand(3))
> > > pd.crosstab(c,c)

however, when giving the series a name, it fails:

> > > c.name='test'
> > > pd.crosstab(c,c)

Traceback (most recent call last):
  File "C:\Python35\Lib\site-packages\pandas\indexes\multi.py", line 515, in _get_level_number
    'level number' % level)
ValueError: The name test occurs multiple times, use a level number

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<pyshell#56>", line 1, in <module>
    pd.crosstab(c,c)
  File "C:\Python35\Lib\site-packages\pandas\tools\pivot.py", line 462, in crosstab
    aggfunc=len, margins=margins, dropna=dropna)
  File "C:\Python35\Lib\site-packages\pandas\tools\pivot.py", line 127, in pivot_table
    table = agged.unstack(to_unstack)
  File "C:\Python35\Lib\site-packages\pandas\core\frame.py", line 3946, in unstack
    return unstack(self, level, fill_value)
  File "C:\Python35\Lib\site-packages\pandas\core\reshape.py", line 398, in unstack
    return _unstack_multiple(obj, level)
  File "C:\Python35\Lib\site-packages\pandas\core\reshape.py", line 252, in _unstack_multiple
    clocs = [index._get_level_number(i) for i in clocs]
  File "C:\Python35\Lib\site-packages\pandas\core\reshape.py", line 252, in <listcomp>
    clocs = [index._get_level_number(i) for i in clocs]
  File "C:\Python35\Lib\site-packages\pandas\indexes\multi.py", line 519, in _get_level_number
    raise KeyError('Level %s not found' % str(level))
KeyError: 'Level test not found'

The use case for having series with the same name is e.g. where you want to crosstab data selected from a dataframe with a multi index. For example: c1 = mydf.xs(val1).mycolumn and c2 = mydf.xs(val2).mycolumn and then you want to do pd.crosstab(c1,c2). This will produce the error as in the simple example above.



Discussion:
```
In [1]: c = pd.Series(np.random.rand(3))

In [2]: pd.crosstab(c,c)
```

you didn't specify the version, but this is a dupe of #10291, fixed by #12287 in 0.18.0

```
Out[2]: 
col_0     0.144258  0.581666  0.722605
row_0                                 
0.144258         1         0         0
0.581666         0         1         0
0.722605         0         0         1

In [3]: pd.__version__
Out[3]: u'0.18.1'
```

version is 0.18.1 - apologies for omitting.
to generate the error, you need
c.name='test'
before pd.crosstab
See original post for the generated error message.

Hmm, that's not very friendly.

```
In [1]: s = Series(np.random.randn(3), name='foo')

In [2]: pd.crosstab(s, s)
KeyError: 'Level foo not found'
```

Should be a straightforward fix if you'd like to give it a go.

I can try, but it's going to be a few weeks until I'll have a free weekend.
Btw, the error I get if I run your code above in a regular python shell is different from yours (instead it is like the one I pasted in the original post).

@stefandw you might have an older version; this is on 0.18.1

definitely 0.18.1. I think in your case it just shows the last generated error, whereas in mine it shows more of the trace (the KeyError happens during the handling of an original ValueError).

@jreback What's the desired error message in such a case? (The expected output/message)

So see the following examples which all work correctly. If these are named the same, then BOTH the index and col should then be named foo. So the error itself it the bug, it shouldn't error.

```
In [11]: s = pd.Series(range(3))

In [12]: pd.crosstab(s, s)
Out[12]: 
col_0  0  1  2
row_0         
0      1  0  0
1      0  1  0
2      0  0  1

In [13]: pd.crosstab(s, s.rename('foo'))
Out[13]: 
foo    0  1  2
row_0         
0      1  0  0
1      0  1  0
2      0  0  1

In [14]: pd.crosstab(s.rename('bar'), s.rename('foo'))
Out[14]: 
foo  0  1  2
bar         
0    1  0  0
1    0  1  0
2    0  0  1
```

actually now that I am looking, I think the assignment of `row_0`, `col_0` to index/col labels when None are provided is wrong. They should be `None`

@jorisvandenbossche ?

Looks like `row_0` and `col_0` is the expected output tested here: https://github.com/pandas-dev/pandas/blob/master/pandas/tools/tests/test_pivot.py#L966

And assigned here by `_get_names`: https://github.com/pandas-dev/pandas/blob/master/pandas/tools/pivot.py#L585

Resolving patch:
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
@@ -148,8 +148,17 @@ def pivot_table(data, values=None, index=None, columns=None, aggfunc='mean',
 
     table = agged
     if table.index.nlevels > 1:
-        to_unstack = [agged.index.names[i] or i
-                      for i in range(len(index), len(keys))]
+        # Related GH #17123
+        # If index_names are integers, determine whether the integers refer
+        # to the level position or name.
+        index_names = agged.index.names[:len(index)]
+        to_unstack = []
+        for i in range(len(index), len(keys)):
+            name = agged.index.names[i]
+            if name is None or name in index_names:
+                to_unstack.append(i)
+            else:
+                to_unstack.append(name)
         table = agged.unstack(to_unstack)
 
     if not dropna:


Test patch:
diff --git a/pandas/tests/reshape/test_pivot.py b/pandas/tests/reshape/test_pivot.py
--- a/pandas/tests/reshape/test_pivot.py
+++ b/pandas/tests/reshape/test_pivot.py
@@ -1513,6 +1513,16 @@ def test_crosstab_with_numpy_size(self):
                                 columns=expected_column)
         tm.assert_frame_equal(result, expected)
 
+    def test_crosstab_dup_index_names(self):
+        # GH 13279
+        s = pd.Series(range(3), name='foo')
+        result = pd.crosstab(s, s)
+        expected_index = pd.Index(range(3), name='foo')
+        expected = pd.DataFrame(np.eye(3, dtype=np.int64),
+                                index=expected_index,
+                                columns=expected_index)
+        tm.assert_frame_equal(result, expected)
+
 
 class TestPivotAnnual(object):
     """