BUG: Fix for passing multiple ints as levels in DataFrame.stack() (#7660)
State: closed

closes #7660
The specific case that came up in #7660 (and originally in #7653) seems easy enough to fix, so I've covered that in the PR. I feel like this raises a few other potential problems though, e.g.:
- If the list of level numbers is not in order, is there any sensible way to deal with them? I've sorted the level list in my fix, as that seems like the most straightforward way of making sure that when you do each stack, it's only the level numbers higher than the current that are affected. This might produce undesired results though, so maybe we should just raise a `ValueError` if the level numbers aren't sorted?
- I'm not sure how to extend this to deal with negative level numbers.


Labels:
name: Bug
name: Reshaping
description: Concat, Merge/Join, Stack/Unstack, Explode

Comments:
their is a method

index._get_level_number() which handles translation and validation (not ordering though)

also shouldn't this be core/reshape/Stack ?

The initial bug report was using columns with no level names- if you have names I can see how `index._get_level_number()` helps you keep track, but without names I'm not sure how you can use it? The whole issue comes up because as you do the stacks one by one, the level numbers for the remaining levels change.

The existing behaviour handles the multiple levels case within `DataFrame.stack()` and just sends one level at a time to `core.reshape.stack`, I guess I could try to move the whole thing, including existing behaviour, over to `core.reshape.stack` if that's the most logical place for it?

get_level_number validates - that's the main use (eg an out of range level number) also handles ambiguity - eg if a level is named 1

u can make a function in core/reshape to handle this kind of thing - maybe multi_stack (and move pretty much the entire stack routine their)

@jreback: I went back over this and I think what I've come up with now is better overall. Moved the bulk of the logic to the reshape module. At the moment I think I've got a pretty good handle on the cases where
- All the passed levels match up to the column levels, whether they're ints or strings
- All the passed levels are ints, but don't match up to column levels: in this case it seems
  pretty safe to assume they represent level numbers, rather than names

Not sure what to to do with more ambiguous cases though, since I guess someone could
pass a mixture of ints and strings. Maybe just throw an exception and say that the list
of levels must either be all names or all level numbers?

looks reasonable. just add some out-of-bounds checking on the levels (and tests for those). pls add a note in v0.15.0 as well (API section is ok)

@jreback  OK, this ended up requiring a few more changes. `index._get_level_number()` was already doing most of the validation of the levels, so by adding a check for out-of-range negative level numbers, it became possible to use that to do _all_ the conversion and out-of-bounds checks in the stack functions. I've added test cases, but let me know if this is too many changes for a single PR. Can also add more to the release notes. 

Otherwise, if things are looking good, I can squash back to a single commit.

minor comments, then pls squish

@jreback Fixed those two things then squashed, should be good now.

@jreback: Added some spacing around sections and some examples to the docs

@jreback Sorry, went through a few different options while trying to come up with succinct example data, didn't realize that import was still in there.

@onesandzeroes thanks for this!

pls check the docs after built and confirm that they look good