ERR: disallow non-hashables in Index/MultiIndex construction & rename
State: closed

Index & MultiIndex names need to be hashable. Both constructing and renaming without a hashable name raise TypeError exceptions now.

**Examples:**

- Index:

```
In [2]: pd.Index([1, 2, 3], name=['foo'])
>>> Int64Index([1, 2, 3], dtype='int64', name=['foo'])
```

```
In [3]: pd.Index([1, 2, 3], name='foo').rename(['bar'])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-f3327eccf0fc> in <module>()
----> 1 pd.Index([1, 2, 3], name='foo').rename(['bar'])

~/Documents/GitHub/pandas/pandas/core/indexes/base.py in rename(self, name, inplace)
   1406         new index (of same type and class...etc) [if inplace, returns None]
   1407         """
-> 1408         return self.set_names([name], inplace=inplace)
   1409 
   1410     @property

~/Documents/GitHub/pandas/pandas/core/indexes/base.py in set_names(self, names, level, inplace)
   1387         else:
   1388             idx = self._shallow_copy()
-> 1389         idx._set_names(names, level=level)
   1390         if not inplace:
   1391             return idx

~/Documents/GitHub/pandas/pandas/core/indexes/base.py in _set_names(self, values, level)
   1323                 if not is_hashable(name):
   1324                     raise TypeError('{}.name must be a hashable type'
-> 1325                                     .format(self.__class__.__name__))
   1326         if len(values) != 1:
   1327             raise ValueError('Length of new names must be 1, got %d' %

TypeError: Int64Index.name must be a hashable type
```

- MultiIndex:

```
In [4]: pd.MultiIndex(levels=[[1, 2], [u'one', u'two']],
                        labels=[[0, 0, 1, 1], [0, 1, 0, 1]],
                        names=((['foo'], ['bar'])))
                        
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-d211526eaa3d> in <module>()
      1 pd.MultiIndex(levels=[[1, 2], [u'one', u'two']],
      2                     labels=[[0, 0, 1, 1], [0, 1, 0, 1]],
----> 3                     names=((['foo'], ['bar'])))
      4 

~/Documents/GitHub/pandas/pandas/core/indexes/multi.py in __new__(cls, levels, labels, sortorder, names, dtype, copy, name, verify_integrity, _set_identity)
    230         if names is not None:
    231             # handles name validation
--> 232             result._set_names(names)
    233 
    234         if sortorder is not None:

~/Documents/GitHub/pandas/pandas/core/indexes/multi.py in _set_names(self, names, level, validate)
    646             if not is_hashable(name):
    647                 raise TypeError('{}.name must be a hashable type'
--> 648                                 .format(self.__class__.__name__))
    649 
    650         # GH 15110

TypeError: MultiIndex.name must be a hashable type
```
```
In [10]: pd.MultiIndex(levels=[[1, 2], [u'one', u'two']],
                       labels=[[0, 0, 1, 1], [0, 1, 0, 1]],
                       names=('foo', 'bar')).rename(([1], [2]))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-ff74dfc48455> in <module>()
      1 pd.MultiIndex(levels=[[1, 2], [u'one', u'two']],
      2                      labels=[[0, 0, 1, 1], [0, 1, 0, 1]],
----> 3                      names=('foo', 'bar')).rename(([1], [2]))
      4 

~/Documents/GitHub/pandas/pandas/core/indexes/base.py in set_names(self, names, level, inplace)
   1387         else:
   1388             idx = self._shallow_copy()
-> 1389         idx._set_names(names, level=level)
   1390         if not inplace:
   1391             return idx

~/Documents/GitHub/pandas/pandas/core/indexes/multi.py in _set_names(self, names, level, validate)
    646             if not is_hashable(name):
    647                 raise TypeError('{}.name must be a hashable type'
--> 648                                 .format(self.__class__.__name__))
    649 
    650         # GH 15110

TypeError: MultiIndex.name must be a hashable type
```

Checklist for other PRs (remove this part if you are doing a PR for the pandas documentation sprint):

- [x] closes [#20527](https://github.com/pandas-dev/pandas/issues/20527)
- [x] tests added / passed
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry

Labels:
name: Indexing
description: Related to indexing on series/frames, not to indexes themselves
name: Error Reporting
description: Incorrect or improved errors from pandas

Comments:
this already works on Serie

the issue is about Index
Sorry I got confused. I will update it for Index.
# [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/20548?src=pr&el=h1) Report
> Merging [#20548](https://codecov.io/gh/pandas-dev/pandas/pull/20548?src=pr&el=desc) into [master](https://codecov.io/gh/pandas-dev/pandas/commit/8def64931af8a01f4af50d79a8d628fe3e63f00c?src=pr&el=desc) will **decrease** coverage by `<.01%`.
> The diff coverage is `90%`.

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

```diff
@@            Coverage Diff             @@
##           master   #20548      +/-   ##
==========================================
- Coverage   91.84%   91.84%   -0.01%     
==========================================
  Files         153      153              
  Lines       49305    49313       +8     
==========================================
+ Hits        45286    45293       +7     
- Misses       4019     4020       +1
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `90.23% <90%> (-0.01%)` | :arrow_down: |
| #single | `41.89% <60%> (ø)` | :arrow_up: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/20548?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/core/indexes/multi.py](https://codecov.io/gh/pandas-dev/pandas/pull/20548/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9tdWx0aS5weQ==) | `95.07% <100%> (+0.01%)` | :arrow_up: |
| [pandas/core/indexes/base.py](https://codecov.io/gh/pandas-dev/pandas/pull/20548/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9iYXNlLnB5) | `96.63% <80%> (-0.05%)` | :arrow_down: |
| [pandas/core/generic.py](https://codecov.io/gh/pandas-dev/pandas/pull/20548/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZ2VuZXJpYy5weQ==) | `95.94% <0%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/20548?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/20548?src=pr&el=footer). Last update [8def649...97a2b06](https://codecov.io/gh/pandas-dev/pandas/pull/20548?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

Hello @arminv! Thanks for updating the PR.











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

##### Comment last updated on April 22, 2018 at 14:48 Hours UTC
I'd prefer to make this a FutureWarning for a release, and then change it to raise. I don't think there's anything preventing that, correct?
@TomAugspurger It works fine for an Index:

```
In [2]: pd.Index([1, 2, 3], name=['foo'])
~/Documents/GitHub/pandas/pandas/core/indexes/base.py:260: FutureWarning: Index.name must be a hashable type. In future this will raise a TypeError.
  warnings.warn(cls.__name__ + msg, FutureWarning)
Out[2]: Int64Index([1, 2, 3], dtype='int64', name=['foo'])
``` 

But MultiIndex already raises `TypeError` somewhere else, so the user gets an exception (after the warning):
```
In [6]: pd.MultiIndex(levels=[[1, 2], [u'one', u'two']],
   ...:    ...:                     labels=[[0, 0, 1, 1], [0, 1, 0, 1]],
   ...:    ...:                     names=((['foo'], ['bar'])))
   ...:                     
~/Documents/GitHub/pandas/pandas/core/indexes/multi.py:652: FutureWarning: MultiIndex.name must be a hashable type. In future this will raise a TypeError.
  msg, FutureWarning)
--------------------------------------------------------------------------
TypeError                                Traceback (most recent call last)
<ipython-input-6-d211526eaa3d> in <module>()
      1 pd.MultiIndex(levels=[[1, 2], [u'one', u'two']],
      2                     labels=[[0, 0, 1, 1], [0, 1, 0, 1]],
----> 3                     names=((['foo'], ['bar'])))
      4 

~/Documents/GitHub/pandas/pandas/core/indexes/multi.py in __new__(cls, levels, labels, sortorder, names, dtype, copy, name, verify_integrity, _set_identity)
    230         if names is not None:
    231             # handles name validation
--> 232             result._set_names(names)
    233 
    234         if sortorder is not None:

~/Documents/GitHub/pandas/pandas/core/indexes/multi.py in _set_names(self, names, level, validate)
    676         # set the name
    677         for l, name in zip(level, names):
--> 678             if name is not None and name in used:
    679                 raise ValueError('Duplicated level name: "{}", assigned to '
    680                                  'level {}, is already used for level '

TypeError: unhashable type: 'list'
```
Is it a good idea to show FutureWarning (for both Index & MultiIndex) and change the exception for MultiIndex?
i don’t think this is a big deal to simply change this and raise an error 
Hmm, I didn't realize that MI already raised, though that's pretty clearly
a result of the implementation.

I would still prefer a deprecation cycle, but I'm not -1 on just breaking.

On Mon, Apr 2, 2018 at 11:58 PM, Jeff Reback <notifications@github.com>
wrote:

> i don’t think this is a big deal to simply change this and raise an error
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/pandas-dev/pandas/pull/20548#issuecomment-378128529>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/ABQHIq3y7ht3T4pygAN4Wt8HuRsnmkyxks5tkwF-gaJpZM4TBptC>
> .
>

@jreback I'm not sure if here is the right place to ask this, but I came across [this PR (closed)](https://github.com/pandas-dev/pandas/pull/19168) where the last comment is:

> OK, so in light of #19295 (comment) I guess here we want pd.Index(names=) to 1) raise an error if resulting in a non-multi index, 2) raise a DeprecationWarning if resulting in a MultiIndex.

> We also want (in other PRs) 1) all non-multi subclasses to raise an error if passed names, 2) pd.MultiIndex to raise a DeprecationWarning if passed names.

>let's do this, so closing this PR...

I understand that [this PR](https://github.com/pandas-dev/pandas/pull/20017) solved the problem for Index subclasses (partially) by disallowing kwargs and hence `names`. However, it seems like we are not raising / giving warning for `Index`/`MultiIndex` (as you agreed on in that comment). Am I correct? Do we still want to implement these?
see https://github.com/pandas-dev/pandas/issues/19295

does need to be fixed / it’s orthogonal (somewhat) from this one though 
@jreback 

`MultiIndex`: checks are already done in `_set_names`, as `set_names` is not defined there.

`Index`: `rename` calls `set_names` which in turn calls `_set_names`. So I agree that we should do checks in `_set_names` instead. This would also make it consistent with how we do `MultiIndex` (above).

So we'll have something like this after:

```
In [3]: pd.Index([1, 2, 3], name='0').rename(['1'])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-c2fc352ddf8e> in <module>()
----> 1 pd.Index([1, 2, 3], name='0').rename(['1'])

~/Documents/GitHub/pandas/pandas/core/indexes/base.py in rename(self, name, inplace)
   1411         new index (of same type and class...etc) [if inplace, returns None]
   1412         """
-> 1413         return self.set_names([name], inplace=inplace)
   1414 
   1415     @property

~/Documents/GitHub/pandas/pandas/core/indexes/base.py in set_names(self, names, level, inplace)
   1392         else:
   1393             idx = self._shallow_copy()
-> 1394         idx._set_names(names, level=level)
   1395         if not inplace:
   1396             return idx

~/Documents/GitHub/pandas/pandas/core/indexes/base.py in _set_names(self, values, level)
   1321                 if not is_hashable(name):
   1322                     raise TypeError('{}.name must be a hashable type'
-> 1323                                     .format(self.__class__.__name__))
   1324         if len(values) != 1:
   1325             raise ValueError('Length of new names must be 1, got %d' %

TypeError: Int64Index.name must be a hashable type```
can you update. ping on green.
moved the logic slightly. will merge on green.
Thanks @arminv !