API: disallow duplicate level names
State: closed

- [x] closes #18872
- [x] tests added / passed
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry


Labels:
name: API Design
name: MultiIndex

Comments:
+1 on the idea. Some cases to think about: pd.NaT, np.nan, anything else where x != x
> anything else where x != x

I see only three options:
1. we don't do anything: the user just knows that passing ``np.nan`` as level name is stupid because ``np.nan != np.nan``
2. we build (and keep updated) an ``Index`` out of level names, and use it to access levels with the same rules with which you access items in a ``Series``
3. just like ``None``, ``np.nan`` and friends _can_ be used as level names and _can_ be duplicated but _cannot_ be used to refer to the levels (e.g. ``get_level_values(np.nan)`` will immediately raise). We describe this behavior in the docstring of ``get_level_values()`` and in all other methods which accept a level name, we state "see ``get_level_values()``"

The current PR does 1., I would be OK with 3., while I'm skeptical that 2. is worth the effort.

(If we go for 3, I would leave it to a separate PR)
# [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/18882?src=pr&el=h1) Report
> Merging [#18882](https://codecov.io/gh/pandas-dev/pandas/pull/18882?src=pr&el=desc) into [master](https://codecov.io/gh/pandas-dev/pandas/commit/4883a432a21e65d998a57708e20196627f7c60d7?src=pr&el=desc) will **increase** coverage by `<.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/pandas-dev/pandas/pull/18882/graphs/tree.svg?src=pr&token=eZ4WkYLtcO&height=150&width=650)](https://codecov.io/gh/pandas-dev/pandas/pull/18882?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master   #18882      +/-   ##
==========================================
+ Coverage   91.58%   91.58%   +<.01%     
==========================================
  Files         150      150              
  Lines       48967    48962       -5     
==========================================
- Hits        44846    44842       -4     
+ Misses       4121     4120       -1
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `89.94% <100%> (ø)` | :arrow_up: |
| #single | `41.73% <60%> (ø)` | :arrow_up: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/18882?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/core/reshape/reshape.py](https://codecov.io/gh/pandas-dev/pandas/pull/18882/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvcmVzaGFwZS9yZXNoYXBlLnB5) | `100% <ø> (ø)` | :arrow_up: |
| [pandas/core/indexes/multi.py](https://codecov.io/gh/pandas-dev/pandas/pull/18882/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9tdWx0aS5weQ==) | `96.22% <100%> (-0.08%)` | :arrow_down: |
| [pandas/util/testing.py](https://codecov.io/gh/pandas-dev/pandas/pull/18882/diff?src=pr&el=tree#diff-cGFuZGFzL3V0aWwvdGVzdGluZy5weQ==) | `84.9% <0%> (+0.21%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/18882?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/18882?src=pr&el=footer). Last update [4883a43...53cb26c](https://codecov.io/gh/pandas-dev/pandas/pull/18882?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

rebased, ping
Hello @toobaz! Thanks for updating the PR.





















Cheers ! There are no PEP8 issues in this Pull Request. :beers: 

##### Comment last updated on December 29, 2017 at 09:15 Hours UTC
@jreback ping
thanks!
FYI, this is causing failures on dask due to operations like:

```python
In [1]: import dask.dataframe as dd

In [2]: import pandas as pd

In [3]:     pdf = pd.DataFrame({'a': [1, 2, 3, 4, 5, 6, 7, 8, 9],
   ...:                         'b': [4, 5, 6, 3, 2, 1, 0, 0, 0]},
   ...:                        index=[0, 1, 3, 5, 6, 8, 9, 9, 9]).set_index("a")
   ...:
   ...:

In [4]: pdf.groupby(pdf.index).apply(lambda x: x.b)
Out[4]:
a  a
1  1    4
2  2    5
3  3    6
4  4    3
5  5    2
6  6    1
7  7    0
8  8    0
9  9    0
Name: b, dtype: int64
```

essentially, grouping by an index and doing a `.apply` that returns a `Series` whose index is the same as the original (at least the same name). This seems like a fairly common use-case.

I think we should make this a warning for now and and raise later.