pydata/pandas — pull request #8809 by onesandzeroes (2014-11-14 00:18:43+00:00)
Title: BUG: Passing multiple levels to stack when having mixed integer/string level names (#8584)

Stacking should now work as expected for the cases described in the bug report (#8584). So we can pass a list of both ints and strs to `DataFrame.stack`, provided the ints are level names. Mixed ints and strs when the ints aren't level names raises `ValueError`, but that's unchanged.

I've added a new hidden method `_swaplevel_assume_numbers` since the main cause of the bug was
ambiguity between ints as names and ints as level numbers, and this allows us to make sure the
swap is done correctly once we reach that point.

Another alternative would have been to add an `as_numbers=True` flag to the existing `swaplevel` method. We could keep existing behaviour intact by having the default be `as_numbers=False`. If that
seems like the better option now that you've seen the PR, let me know and it should be pretty easy
to switch.

```python
import pandas as pd
from pandas import DataFrame, MultiIndex
from numpy.random import randn

columns = MultiIndex.from_tuples([('A', 'cat', 'long'), ('B', 'cat', 'long'), ('A', 'dog', 'short'), ('B', 'dog', 'short')],
names=['exp', 'animal', 'hair_length'])
df = DataFrame(randn(4, 4), columns=columns)

df.columns.names = ['exp', 'animal', 1]
print(df.stack(level=['animal', 1]))

# exp A B
# animal 1
# 0 cat long 0.758850 0.327957
# dog short 0.429752 0.236901
# 1 cat long 0.572633 1.057861
# dog short 0.802539 1.321191
# 2 cat long 0.283352 0.088000
# dog short -0.814458 -1.688529
# 3 cat long 2.009912 -1.738292
# dog short 1.272569 -0.133434

df.columns.names = ['exp', 'animal', 0]
print(df.stack(level=['animal', 0]))

# exp A B
# animal 0
# 0 cat long 0.758850 0.327957
# dog short 0.429752 0.236901
# 1 cat long 0.572633 1.057861
# dog short 0.802539 1.321191
# 2 cat long 0.283352 0.088000
# dog short -0.814458 -1.688529
# 3 cat long 2.009912 -1.738292
# dog short 1.272569 -0.133434
```