From 9047d60d6144aaeb4aa031de5792bb3458c76ccb Mon Sep 17 00:00:00 2001 From: arminv Date: Fri, 30 Mar 2018 04:23:08 -0400 Subject: [PATCH 01/30] Check non-hashability on series construction and renaming --- pandas/core/series.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pandas/core/series.py b/pandas/core/series.py index 30e0319346961..a0e52707ec515 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -271,7 +271,10 @@ def __init__(self, data=None, index=None, dtype=None, name=None, generic.NDFrame.__init__(self, data, fastpath=True) - self.name = name + if name is not None and not is_hashable(name): + raise TypeError('Series.name must be a hashable type') + else: + self.name = name self._set_axis(0, index, fastpath=True) def _init_dict(self, data, index=None, dtype=None): @@ -3288,7 +3291,10 @@ def rename(self, index=None, **kwargs): non_mapping = is_scalar(index) or (is_list_like(index) and not is_dict_like(index)) if non_mapping: - return self._set_name(index, inplace=kwargs.get('inplace')) + if not is_hashable(index): + raise TypeError('Series.name must be a hashable type') + else: + return self._set_name(index, inplace=kwargs.get('inplace')) return super(Series, self).rename(index=index, **kwargs) @Appender(generic._shared_docs['reindex'] % _shared_doc_kwargs) From df7650d27867dbf75e8c794b85ffa649f94cd774 Mon Sep 17 00:00:00 2001 From: arminv Date: Fri, 30 Mar 2018 09:29:16 -0400 Subject: [PATCH 02/30] Removed changes from pandas/core/series.py --- pandas/core/series.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/pandas/core/series.py b/pandas/core/series.py index a0e52707ec515..30e0319346961 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -271,10 +271,7 @@ def __init__(self, data=None, index=None, dtype=None, name=None, generic.NDFrame.__init__(self, data, fastpath=True) - if name is not None and not is_hashable(name): - raise TypeError('Series.name must be a hashable type') - else: - self.name = name + self.name = name self._set_axis(0, index, fastpath=True) def _init_dict(self, data, index=None, dtype=None): @@ -3291,10 +3288,7 @@ def rename(self, index=None, **kwargs): non_mapping = is_scalar(index) or (is_list_like(index) and not is_dict_like(index)) if non_mapping: - if not is_hashable(index): - raise TypeError('Series.name must be a hashable type') - else: - return self._set_name(index, inplace=kwargs.get('inplace')) + return self._set_name(index, inplace=kwargs.get('inplace')) return super(Series, self).rename(index=index, **kwargs) @Appender(generic._shared_docs['reindex'] % _shared_doc_kwargs) From dd642198b1b1220e19c85c1c636b150d1750054d Mon Sep 17 00:00:00 2001 From: arminv Date: Fri, 30 Mar 2018 09:52:24 -0400 Subject: [PATCH 03/30] Check non-hashability on Index construction and renaming --- pandas/core/indexes/base.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 12bb09e8f8a8a..c2010ea63136f 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -42,6 +42,7 @@ is_datetime64_any_dtype, is_datetime64tz_dtype, is_timedelta64_dtype, + is_hashable, needs_i8_conversion, is_iterator, is_list_like, is_scalar) @@ -251,6 +252,9 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, if name is None and hasattr(data, 'name'): name = data.name + if name is not None and not is_hashable(name): + raise TypeError('Index.name must be a hashable type') + if fastpath: return cls._simple_new(data, name) @@ -1392,7 +1396,10 @@ def rename(self, name, inplace=False): ------- new index (of same type and class...etc) [if inplace, returns None] """ - return self.set_names([name], inplace=inplace) + if name is not None and not is_hashable(name): + raise TypeError('Index.name must be a hashable type') + else: + return self.set_names([name], inplace=inplace) @property def _has_complex_internals(self): From 89e92ab06e0f23cd6b685bff5cdabd75410cc666 Mon Sep 17 00:00:00 2001 From: arminv Date: Fri, 30 Mar 2018 10:52:36 -0400 Subject: [PATCH 04/30] modified test_getitem_list example to disallow non-hashable names --- pandas/tests/frame/test_indexing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tests/frame/test_indexing.py b/pandas/tests/frame/test_indexing.py index a8b81b1b03552..8e10e4c4fbc65 100644 --- a/pandas/tests/frame/test_indexing.py +++ b/pandas/tests/frame/test_indexing.py @@ -125,12 +125,12 @@ def test_getitem_list(self): # tuples df = DataFrame(randn(8, 3), columns=Index([('foo', 'bar'), ('baz', 'qux'), - ('peek', 'aboo')], name=['sth', 'sth2'])) + ('peek', 'aboo')], name=('sth', 'sth2'))) result = df[[('foo', 'bar'), ('baz', 'qux')]] expected = df.iloc[:, :2] assert_frame_equal(result, expected) - assert result.columns.names == ['sth', 'sth2'] + assert result.columns.names == ('sth', 'sth2') def test_getitem_callable(self): # GH 12533 From 351691fda7e55e201d1216a4e02b58add4865d63 Mon Sep 17 00:00:00 2001 From: arminv Date: Sat, 31 Mar 2018 22:26:29 -0400 Subject: [PATCH 05/30] Changed ErrorType message for hashability requirement --- pandas/core/indexes/base.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index c2010ea63136f..3f5e6d689cf31 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -253,7 +253,8 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, name = data.name if name is not None and not is_hashable(name): - raise TypeError('Index.name must be a hashable type') + raise TypeError(__class__.__name__ + + '.name must be a hashable type') if fastpath: return cls._simple_new(data, name) @@ -1365,6 +1366,10 @@ def set_names(self, names, level=None, inplace=False): names): raise TypeError("Names must be a string") + if names is not None and not is_hashable(names) and level is None: + raise TypeError(__class__.__name__ + + '.name must be a hashable type') + if not is_list_like(names) and level is None and self.nlevels > 1: raise TypeError("Must pass list-like as `names`.") @@ -1396,10 +1401,7 @@ def rename(self, name, inplace=False): ------- new index (of same type and class...etc) [if inplace, returns None] """ - if name is not None and not is_hashable(name): - raise TypeError('Index.name must be a hashable type') - else: - return self.set_names([name], inplace=inplace) + return self.set_names([name], inplace=inplace) @property def _has_complex_internals(self): From 3a7b0b2714814ab148440d8776b6ddfc1f956fdd Mon Sep 17 00:00:00 2001 From: arminv Date: Sun, 1 Apr 2018 01:56:46 -0400 Subject: [PATCH 06/30] Fixed how rename calls set_names to allow for MultiIndex hashable type checking --- pandas/core/indexes/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 3f5e6d689cf31..5d07662c08217 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1366,7 +1366,7 @@ def set_names(self, names, level=None, inplace=False): names): raise TypeError("Names must be a string") - if names is not None and not is_hashable(names) and level is None: + if names is not None and not is_hashable(names): raise TypeError(__class__.__name__ + '.name must be a hashable type') @@ -1401,7 +1401,7 @@ def rename(self, name, inplace=False): ------- new index (of same type and class...etc) [if inplace, returns None] """ - return self.set_names([name], inplace=inplace) + return self.set_names(name, inplace=inplace) @property def _has_complex_internals(self): From 70933d59687d38e75dd38fe7dde9ddf3bf2679d7 Mon Sep 17 00:00:00 2001 From: arminv Date: Sun, 1 Apr 2018 04:40:24 -0400 Subject: [PATCH 07/30] Moved type checking from set_names back to rename --- pandas/core/indexes/base.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 5d07662c08217..799c4208d0de7 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1345,8 +1345,6 @@ def set_names(self, names, level=None, inplace=False): -------- >>> Index([1, 2, 3, 4]).set_names('foo') Int64Index([1, 2, 3, 4], dtype='int64') - >>> Index([1, 2, 3, 4]).set_names(['foo']) - Int64Index([1, 2, 3, 4], dtype='int64') >>> idx = MultiIndex.from_tuples([(1, u'one'), (1, u'two'), (2, u'one'), (2, u'two')], names=['foo', 'bar']) @@ -1366,10 +1364,6 @@ def set_names(self, names, level=None, inplace=False): names): raise TypeError("Names must be a string") - if names is not None and not is_hashable(names): - raise TypeError(__class__.__name__ + - '.name must be a hashable type') - if not is_list_like(names) and level is None and self.nlevels > 1: raise TypeError("Must pass list-like as `names`.") @@ -1401,7 +1395,11 @@ def rename(self, name, inplace=False): ------- new index (of same type and class...etc) [if inplace, returns None] """ - return self.set_names(name, inplace=inplace) + if name is not None and not is_hashable(name): + raise TypeError(__class__.__name__ + + '.name must be a hashable type') + else: + return self.set_names([name], inplace=inplace) @property def _has_complex_internals(self): From d4ed6361ff3305cf56ca51176b66ec7673724cad Mon Sep 17 00:00:00 2001 From: arminv Date: Sun, 1 Apr 2018 19:26:57 -0400 Subject: [PATCH 08/30] Moved hashable checking to set_names. Changed exception messages. --- pandas/core/indexes/base.py | 20 +++++++++++++++----- pandas/core/indexes/multi.py | 9 +++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 799c4208d0de7..f05363d5e3c06 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1357,6 +1357,20 @@ def set_names(self, names, level=None, inplace=False): labels=[[0, 0, 1, 1], [0, 1, 0, 1]], names=[u'baz', u'bar']) """ + # GH 20527 + # All items in 'names' need to be hashable: + if names is not None: + for name in names: + if is_hashable(name): + pass + else: + if self.nlevels == 1: + raise TypeError(__class__.__name__ + + '.name must be a hashable type') + else: + raise TypeError(self.__class__.__name__ + + '.name must be a hashable type') + if level is not None and self.nlevels == 1: raise ValueError('Level must be None for non-MultiIndex') @@ -1395,11 +1409,7 @@ def rename(self, name, inplace=False): ------- new index (of same type and class...etc) [if inplace, returns None] """ - if name is not None and not is_hashable(name): - raise TypeError(__class__.__name__ + - '.name must be a hashable type') - else: - return self.set_names([name], inplace=inplace) + return self.set_names([name], inplace=inplace) @property def _has_complex_internals(self): diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index d4b9545999bc7..df9f53aca49bb 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -16,6 +16,7 @@ _ensure_platform_int, is_categorical_dtype, is_object_dtype, + is_hashable, is_iterator, is_list_like, pandas_dtype, @@ -639,6 +640,14 @@ def _set_names(self, names, level=None, validate=True): Note that you generally want to set this *after* changing levels, so that it only acts on copies """ + # GH 20527 + # All items in 'names' need to be hashable: + for name in names: + if is_hashable(name): + pass + else: + raise TypeError(self.__class__.__name__ + + '.name must be a hashable type') # GH 15110 # Don't allow a single string for names in a MultiIndex From b554bb3f050fd9e1aba4cbf02322ddc9fd4d4f41 Mon Sep 17 00:00:00 2001 From: arminv Date: Sun, 1 Apr 2018 20:06:04 -0400 Subject: [PATCH 09/30] Modified test_duplicate_level_names to pass with new (hashable names) API change --- pandas/tests/indexes/test_multi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tests/indexes/test_multi.py b/pandas/tests/indexes/test_multi.py index 984f37042d600..fd0eca3037ae4 100644 --- a/pandas/tests/indexes/test_multi.py +++ b/pandas/tests/indexes/test_multi.py @@ -615,8 +615,8 @@ def test_constructor_mismatched_label_levels(self): with tm.assert_raises_regex(ValueError, label_error): self.index.copy().set_labels([[0, 0, 0, 0], [0, 0]]) - @pytest.mark.parametrize('names', [['a', 'b', 'a'], [1, 1, 2], - [1, 'a', 1]]) + @pytest.mark.parametrize('names', [['a', 'b', 'a'], ['1', '1', '2'], + ['1', 'a', '1']]) def test_duplicate_level_names(self, names): # GH18872 pytest.raises(ValueError, pd.MultiIndex.from_product, From 6efd6cc7f74d411f10ca72e58d33564f03b4c0e0 Mon Sep 17 00:00:00 2001 From: arminv Date: Sun, 1 Apr 2018 23:38:13 -0400 Subject: [PATCH 10/30] Added test_constructor_nonhashable_names for checking hashability on names --- pandas/tests/indexes/test_multi.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pandas/tests/indexes/test_multi.py b/pandas/tests/indexes/test_multi.py index fd0eca3037ae4..35434f60d7c2b 100644 --- a/pandas/tests/indexes/test_multi.py +++ b/pandas/tests/indexes/test_multi.py @@ -615,6 +615,27 @@ def test_constructor_mismatched_label_levels(self): with tm.assert_raises_regex(ValueError, label_error): self.index.copy().set_labels([[0, 0, 0, 0], [0, 0]]) + def test_constructor_nonhashable_names(self): + # GH 20527 + levels = [[1, 2], [u'one', u'two']] + labels = [[0, 0, 1, 1], [0, 1, 0, 1]] + names = ((['foo'], ['bar'])) + tm.assert_raises_regex(TypeError, "MultiIndex.name must " + "be a hashable type", + MultiIndex, levels=levels, + labels=labels, + names=names) + + # With .rename() + mi = pd.MultiIndex(levels=[[1, 2], [u'one', u'two']], + labels=[[0, 0, 1, 1], [0, 1, 0, 1]], + names=('foo', 'bar')) + assert isinstance(mi, MultiIndex) + renamed = [['foor'], ['barr']] + tm.assert_raises_regex(TypeError, "MultiIndex.name must " + "be a hashable type", + mi.rename, names=renamed) + @pytest.mark.parametrize('names', [['a', 'b', 'a'], ['1', '1', '2'], ['1', 'a', '1']]) def test_duplicate_level_names(self, names): From 4fb3a6b09825460c7ca3ff1a3db763b64f722541 Mon Sep 17 00:00:00 2001 From: arminv Date: Sun, 1 Apr 2018 23:40:48 -0400 Subject: [PATCH 11/30] Fixed a typo --- pandas/tests/indexes/test_multi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/indexes/test_multi.py b/pandas/tests/indexes/test_multi.py index 35434f60d7c2b..3abb04e67da26 100644 --- a/pandas/tests/indexes/test_multi.py +++ b/pandas/tests/indexes/test_multi.py @@ -627,7 +627,7 @@ def test_constructor_nonhashable_names(self): names=names) # With .rename() - mi = pd.MultiIndex(levels=[[1, 2], [u'one', u'two']], + mi = MultiIndex(levels=[[1, 2], [u'one', u'two']], labels=[[0, 0, 1, 1], [0, 1, 0, 1]], names=('foo', 'bar')) assert isinstance(mi, MultiIndex) From 786f43fb4ed92fdd48d6f235af1f20e290bfb6b4 Mon Sep 17 00:00:00 2001 From: arminv Date: Sun, 1 Apr 2018 23:47:21 -0400 Subject: [PATCH 12/30] Minor refactoring of test_constructor_nonhashable_names --- pandas/tests/indexes/test_multi.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pandas/tests/indexes/test_multi.py b/pandas/tests/indexes/test_multi.py index 3abb04e67da26..31993304f9824 100644 --- a/pandas/tests/indexes/test_multi.py +++ b/pandas/tests/indexes/test_multi.py @@ -620,11 +620,11 @@ def test_constructor_nonhashable_names(self): levels = [[1, 2], [u'one', u'two']] labels = [[0, 0, 1, 1], [0, 1, 0, 1]] names = ((['foo'], ['bar'])) - tm.assert_raises_regex(TypeError, "MultiIndex.name must " - "be a hashable type", + message = "MultiIndex.name must be a hashable type" + tm.assert_raises_regex(TypeError, message, MultiIndex, levels=levels, - labels=labels, - names=names) + labels=labels, + names=names) # With .rename() mi = MultiIndex(levels=[[1, 2], [u'one', u'two']], @@ -632,8 +632,7 @@ def test_constructor_nonhashable_names(self): names=('foo', 'bar')) assert isinstance(mi, MultiIndex) renamed = [['foor'], ['barr']] - tm.assert_raises_regex(TypeError, "MultiIndex.name must " - "be a hashable type", + tm.assert_raises_regex(TypeError, message, mi.rename, names=renamed) @pytest.mark.parametrize('names', [['a', 'b', 'a'], ['1', '1', '2'], From 01b712e7d5c78e27b4bc7882b85d15db2602883b Mon Sep 17 00:00:00 2001 From: arminv Date: Mon, 2 Apr 2018 00:38:29 -0400 Subject: [PATCH 13/30] Added test_constructor_nonhashable_name for checking hashability on name --- pandas/tests/indexes/test_base.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index ff9c86fbfe384..72330ffe508a2 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -436,6 +436,20 @@ def test_constructor_empty(self): assert isinstance(empty, MultiIndex) assert not len(empty) + def test_constructor_nonhashable_name(self): + # GH 20527 + data = [1, 2, 3] + name = ['0'] + message = "Index.name must be a hashable type" + tm.assert_raises_regex(TypeError, message, data=data, name=name) + + # With .rename() + idx = Index([1, 2, 3], name='0') + assert isinstance(idx, Index) + renamed = ['1'] + tm.assert_raises_regex(TypeError, message, + idx.rename, name=renamed) + def test_view_with_args(self): restricted = ['unicodeIndex', 'strIndex', 'catIndex', 'boolIndex', From 6f13cd094a1922a2cb2db87206cea7e2b0672202 Mon Sep 17 00:00:00 2001 From: arminv Date: Mon, 2 Apr 2018 01:48:09 -0400 Subject: [PATCH 14/30] Added note in Other API Changes on hashability of names --- doc/source/whatsnew/v0.23.0.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v0.23.0.txt b/doc/source/whatsnew/v0.23.0.txt index 09bd09b06d9b9..18a848c09da5b 100644 --- a/doc/source/whatsnew/v0.23.0.txt +++ b/doc/source/whatsnew/v0.23.0.txt @@ -778,6 +778,7 @@ Other API Changes - A :class:`Series` of ``dtype=category`` constructed from an empty ``dict`` will now have categories of ``dtype=object`` rather than ``dtype=float64``, consistently with the case in which an empty list is passed (:issue:`18515`) - All-NaN levels in a ``MultiIndex`` are now assigned ``float`` rather than ``object`` dtype, promoting consistency with ``Index`` (:issue:`17929`). - Levels names of a ``MultiIndex`` (when not None) are now required to be unique: trying to create a ``MultiIndex`` with repeated names will raise a ``ValueError`` (:issue:`18872`) +- Constructing or renaming with non-hashable ``names`` in a ``MultiIndex`` (or ``name`` in an ``Index``) now raise ``TypeError`` exception (:issue:`20527`) - :func:`Index.map` can now accept ``Series`` and dictionary input objects (:issue:`12756`, :issue:`18482`, :issue:`18509`). - :func:`DataFrame.unstack` will now default to filling with ``np.nan`` for ``object`` columns. (:issue:`12815`) - :class:`IntervalIndex` constructor will raise if the ``closed`` parameter conflicts with how the input data is inferred to be closed (:issue:`18421`) From 26433c35fd850cfede3ad764b166ff184ebbed72 Mon Sep 17 00:00:00 2001 From: arminv Date: Mon, 2 Apr 2018 02:10:18 -0400 Subject: [PATCH 15/30] Improved wording of the note --- doc/source/whatsnew/v0.23.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.23.0.txt b/doc/source/whatsnew/v0.23.0.txt index 18a848c09da5b..b45ab971826b2 100644 --- a/doc/source/whatsnew/v0.23.0.txt +++ b/doc/source/whatsnew/v0.23.0.txt @@ -778,7 +778,7 @@ Other API Changes - A :class:`Series` of ``dtype=category`` constructed from an empty ``dict`` will now have categories of ``dtype=object`` rather than ``dtype=float64``, consistently with the case in which an empty list is passed (:issue:`18515`) - All-NaN levels in a ``MultiIndex`` are now assigned ``float`` rather than ``object`` dtype, promoting consistency with ``Index`` (:issue:`17929`). - Levels names of a ``MultiIndex`` (when not None) are now required to be unique: trying to create a ``MultiIndex`` with repeated names will raise a ``ValueError`` (:issue:`18872`) -- Constructing or renaming with non-hashable ``names`` in a ``MultiIndex`` (or ``name`` in an ``Index``) now raise ``TypeError`` exception (:issue:`20527`) +- Both construction and renaming of ``Index``/``MultiIndex`` with non-hashable ``name``/``names`` will now raise ``TypeError`` (:issue:`20527`) - :func:`Index.map` can now accept ``Series`` and dictionary input objects (:issue:`12756`, :issue:`18482`, :issue:`18509`). - :func:`DataFrame.unstack` will now default to filling with ``np.nan`` for ``object`` columns. (:issue:`12815`) - :class:`IntervalIndex` constructor will raise if the ``closed`` parameter conflicts with how the input data is inferred to be closed (:issue:`18421`) From 91ef466c26253b06d4a2b0ab55104a36806f0c17 Mon Sep 17 00:00:00 2001 From: arminv Date: Mon, 2 Apr 2018 03:05:05 -0400 Subject: [PATCH 16/30] Addressed PEP 8 issues --- pandas/core/indexes/base.py | 6 +++--- pandas/core/indexes/multi.py | 2 +- pandas/tests/indexes/test_base.py | 2 +- pandas/tests/indexes/test_multi.py | 12 +++++------- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index f05363d5e3c06..d63d63b9ad69b 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -254,7 +254,7 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, if name is not None and not is_hashable(name): raise TypeError(__class__.__name__ + - '.name must be a hashable type') + '.name must be a hashable type') if fastpath: return cls._simple_new(data, name) @@ -1366,10 +1366,10 @@ def set_names(self, names, level=None, inplace=False): else: if self.nlevels == 1: raise TypeError(__class__.__name__ + - '.name must be a hashable type') + '.name must be a hashable type') else: raise TypeError(self.__class__.__name__ + - '.name must be a hashable type') + '.name must be a hashable type') if level is not None and self.nlevels == 1: raise ValueError('Level must be None for non-MultiIndex') diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index df9f53aca49bb..aac0854ad98fc 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -647,7 +647,7 @@ def _set_names(self, names, level=None, validate=True): pass else: raise TypeError(self.__class__.__name__ + - '.name must be a hashable type') + '.name must be a hashable type') # GH 15110 # Don't allow a single string for names in a MultiIndex diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 72330ffe508a2..256077e630712 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -448,7 +448,7 @@ def test_constructor_nonhashable_name(self): assert isinstance(idx, Index) renamed = ['1'] tm.assert_raises_regex(TypeError, message, - idx.rename, name=renamed) + idx.rename, name=renamed) def test_view_with_args(self): diff --git a/pandas/tests/indexes/test_multi.py b/pandas/tests/indexes/test_multi.py index 31993304f9824..bb0338864e1ed 100644 --- a/pandas/tests/indexes/test_multi.py +++ b/pandas/tests/indexes/test_multi.py @@ -622,18 +622,16 @@ def test_constructor_nonhashable_names(self): names = ((['foo'], ['bar'])) message = "MultiIndex.name must be a hashable type" tm.assert_raises_regex(TypeError, message, - MultiIndex, levels=levels, - labels=labels, - names=names) + MultiIndex, levels=levels, + labels=labels, names=names) # With .rename() mi = MultiIndex(levels=[[1, 2], [u'one', u'two']], - labels=[[0, 0, 1, 1], [0, 1, 0, 1]], - names=('foo', 'bar')) + labels=[[0, 0, 1, 1], [0, 1, 0, 1]], + names=('foo', 'bar')) assert isinstance(mi, MultiIndex) renamed = [['foor'], ['barr']] - tm.assert_raises_regex(TypeError, message, - mi.rename, names=renamed) + tm.assert_raises_regex(TypeError, message, mi.rename, names=renamed) @pytest.mark.parametrize('names', [['a', 'b', 'a'], ['1', '1', '2'], ['1', 'a', '1']]) From 85e35ea8e0c5d13209f5d7d0385836360e39e161 Mon Sep 17 00:00:00 2001 From: arminv Date: Mon, 2 Apr 2018 03:44:38 -0400 Subject: [PATCH 17/30] Modified exception message of Index --- pandas/core/indexes/base.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index d63d63b9ad69b..5e115677e36c6 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1365,8 +1365,7 @@ def set_names(self, names, level=None, inplace=False): pass else: if self.nlevels == 1: - raise TypeError(__class__.__name__ + - '.name must be a hashable type') + raise TypeError('Index.name must be a hashable type') else: raise TypeError(self.__class__.__name__ + '.name must be a hashable type') From 5c2e240841c00401296689692cd21d357ebd2eae Mon Sep 17 00:00:00 2001 From: arminv Date: Mon, 2 Apr 2018 03:56:05 -0400 Subject: [PATCH 18/30] Changed exception message format --- pandas/core/indexes/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 5e115677e36c6..5d8634d809ddf 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -253,7 +253,7 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, name = data.name if name is not None and not is_hashable(name): - raise TypeError(__class__.__name__ + + raise TypeError(cls.__name__ + '.name must be a hashable type') if fastpath: From d98014fd9703a468ffe6ee3f2fa14d2c438e1e13 Mon Sep 17 00:00:00 2001 From: arminv Date: Sat, 7 Apr 2018 23:23:05 -0400 Subject: [PATCH 19/30] Refactoring --- pandas/core/indexes/base.py | 13 +++++-------- pandas/core/indexes/multi.py | 6 ++---- pandas/tests/indexes/test_base.py | 9 +++++---- pandas/tests/indexes/test_multi.py | 1 + 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 5d8634d809ddf..1b3eb364fa8e3 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1345,6 +1345,8 @@ def set_names(self, names, level=None, inplace=False): -------- >>> Index([1, 2, 3, 4]).set_names('foo') Int64Index([1, 2, 3, 4], dtype='int64') + >>> Index([1, 2, 3, 4]).set_names(['foo']) + Int64Index([1, 2, 3, 4], dtype='int64') >>> idx = MultiIndex.from_tuples([(1, u'one'), (1, u'two'), (2, u'one'), (2, u'two')], names=['foo', 'bar']) @@ -1361,14 +1363,9 @@ def set_names(self, names, level=None, inplace=False): # All items in 'names' need to be hashable: if names is not None: for name in names: - if is_hashable(name): - pass - else: - if self.nlevels == 1: - raise TypeError('Index.name must be a hashable type') - else: - raise TypeError(self.__class__.__name__ + - '.name must be a hashable type') + if not is_hashable(name): + raise TypeError(self.__class__.__name__ + + '.name must be a hashable type') if level is not None and self.nlevels == 1: raise ValueError('Level must be None for non-MultiIndex') diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index aac0854ad98fc..3449c95ef9b08 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -642,10 +642,8 @@ def _set_names(self, names, level=None, validate=True): """ # GH 20527 # All items in 'names' need to be hashable: - for name in names: - if is_hashable(name): - pass - else: + for levels, name in enumerate(names): + if not is_hashable(name): raise TypeError(self.__class__.__name__ + '.name must be a hashable type') diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 256077e630712..cb82ff4b94cf8 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -438,17 +438,18 @@ def test_constructor_empty(self): def test_constructor_nonhashable_name(self): # GH 20527 - data = [1, 2, 3] + idx = self.create_index() name = ['0'] message = "Index.name must be a hashable type" - tm.assert_raises_regex(TypeError, message, data=data, name=name) + tm.assert_raises_regex(TypeError, message, name=name) # With .rename() - idx = Index([1, 2, 3], name='0') assert isinstance(idx, Index) - renamed = ['1'] + renamed = [['1']] tm.assert_raises_regex(TypeError, message, idx.rename, name=renamed) + tm.assert_raises_regex(TypeError, message, + idx.set_names, names=renamed) def test_view_with_args(self): diff --git a/pandas/tests/indexes/test_multi.py b/pandas/tests/indexes/test_multi.py index bb0338864e1ed..f96e3fa19db44 100644 --- a/pandas/tests/indexes/test_multi.py +++ b/pandas/tests/indexes/test_multi.py @@ -632,6 +632,7 @@ def test_constructor_nonhashable_names(self): assert isinstance(mi, MultiIndex) renamed = [['foor'], ['barr']] tm.assert_raises_regex(TypeError, message, mi.rename, names=renamed) + tm.assert_raises_regex(TypeError, message, mi.set_names, names=renamed) @pytest.mark.parametrize('names', [['a', 'b', 'a'], ['1', '1', '2'], ['1', 'a', '1']]) From edfbd1d6b252a93de44f8b0ae00671af99cb3396 Mon Sep 17 00:00:00 2001 From: arminv Date: Mon, 9 Apr 2018 17:18:57 -0400 Subject: [PATCH 20/30] Added internal comment --- pandas/tests/indexes/test_base.py | 1 + pandas/tests/indexes/test_multi.py | 1 + 2 files changed, 2 insertions(+) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index cb82ff4b94cf8..e0f90993d34b7 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -448,6 +448,7 @@ def test_constructor_nonhashable_name(self): renamed = [['1']] tm.assert_raises_regex(TypeError, message, idx.rename, name=renamed) + # With .set_names() tm.assert_raises_regex(TypeError, message, idx.set_names, names=renamed) diff --git a/pandas/tests/indexes/test_multi.py b/pandas/tests/indexes/test_multi.py index f96e3fa19db44..49af50836f8a1 100644 --- a/pandas/tests/indexes/test_multi.py +++ b/pandas/tests/indexes/test_multi.py @@ -632,6 +632,7 @@ def test_constructor_nonhashable_names(self): assert isinstance(mi, MultiIndex) renamed = [['foor'], ['barr']] tm.assert_raises_regex(TypeError, message, mi.rename, names=renamed) + # With .set_names() tm.assert_raises_regex(TypeError, message, mi.set_names, names=renamed) @pytest.mark.parametrize('names', [['a', 'b', 'a'], ['1', '1', '2'], From fa526558de4392cf3408624a1bef559925fe0bee Mon Sep 17 00:00:00 2001 From: arminv Date: Tue, 10 Apr 2018 09:44:31 -0400 Subject: [PATCH 21/30] Refactoring --- pandas/core/indexes/base.py | 8 ++++---- pandas/core/indexes/multi.py | 4 ++-- pandas/tests/indexes/test_base.py | 1 - pandas/tests/indexes/test_multi.py | 1 - 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 1b3eb364fa8e3..7463a28472c30 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -253,8 +253,8 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, name = data.name if name is not None and not is_hashable(name): - raise TypeError(cls.__name__ + - '.name must be a hashable type') + raise TypeError('{}.name must be a hashable type' + .format(cls.__name__)) if fastpath: return cls._simple_new(data, name) @@ -1364,8 +1364,8 @@ def set_names(self, names, level=None, inplace=False): if names is not None: for name in names: if not is_hashable(name): - raise TypeError(self.__class__.__name__ + - '.name must be a hashable type') + raise TypeError('{}.name must be a hashable type' + .format(self.__class__.__name__)) if level is not None and self.nlevels == 1: raise ValueError('Level must be None for non-MultiIndex') diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 3449c95ef9b08..ec3c1c1bf5b6d 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -644,8 +644,8 @@ def _set_names(self, names, level=None, validate=True): # All items in 'names' need to be hashable: for levels, name in enumerate(names): if not is_hashable(name): - raise TypeError(self.__class__.__name__ + - '.name must be a hashable type') + raise TypeError('{}.name must be a hashable type' + .format(self.__class__.__name__)) # GH 15110 # Don't allow a single string for names in a MultiIndex diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index e0f90993d34b7..4265c4b56434e 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -444,7 +444,6 @@ def test_constructor_nonhashable_name(self): tm.assert_raises_regex(TypeError, message, name=name) # With .rename() - assert isinstance(idx, Index) renamed = [['1']] tm.assert_raises_regex(TypeError, message, idx.rename, name=renamed) diff --git a/pandas/tests/indexes/test_multi.py b/pandas/tests/indexes/test_multi.py index 49af50836f8a1..88dc4cbaf7bb3 100644 --- a/pandas/tests/indexes/test_multi.py +++ b/pandas/tests/indexes/test_multi.py @@ -629,7 +629,6 @@ def test_constructor_nonhashable_names(self): mi = MultiIndex(levels=[[1, 2], [u'one', u'two']], labels=[[0, 0, 1, 1], [0, 1, 0, 1]], names=('foo', 'bar')) - assert isinstance(mi, MultiIndex) renamed = [['foor'], ['barr']] tm.assert_raises_regex(TypeError, message, mi.rename, names=renamed) # With .set_names() From c0f69365993c0cafa90b936576c35cf9af95e82d Mon Sep 17 00:00:00 2001 From: arminv Date: Tue, 10 Apr 2018 10:48:49 -0400 Subject: [PATCH 22/30] Moved check from set_names to _set_names --- pandas/core/indexes/base.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 7463a28472c30..f974986e4c9ed 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1316,6 +1316,13 @@ def _get_names(self): return FrozenList((self.name, )) def _set_names(self, values, level=None): + # GH 20527 + # All items in 'name' need to be hashable: + if values is not None: + for name in values: + if not is_hashable(name): + raise TypeError('{}.name must be a hashable type' + .format(self.__class__.__name__)) if len(values) != 1: raise ValueError('Length of new names must be 1, got %d' % len(values)) @@ -1359,13 +1366,6 @@ def set_names(self, names, level=None, inplace=False): labels=[[0, 0, 1, 1], [0, 1, 0, 1]], names=[u'baz', u'bar']) """ - # GH 20527 - # All items in 'names' need to be hashable: - if names is not None: - for name in names: - if not is_hashable(name): - raise TypeError('{}.name must be a hashable type' - .format(self.__class__.__name__)) if level is not None and self.nlevels == 1: raise ValueError('Level must be None for non-MultiIndex') From a9c14e6eb58f320111107b2c19bf9bb58a615caa Mon Sep 17 00:00:00 2001 From: Jeff Reback Date: Tue, 10 Apr 2018 21:58:56 -0400 Subject: [PATCH 23/30] test with fixture --- pandas/tests/indexes/test_base.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 4265c4b56434e..ad7f0d7aa1946 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -436,9 +436,12 @@ def test_constructor_empty(self): assert isinstance(empty, MultiIndex) assert not len(empty) - def test_constructor_nonhashable_name(self): + def test_constructor_nonhashable_name(self, indices): # GH 20527 - idx = self.create_index() + + if isinstance(indices, MultiIndex): + pytest.skip("multiindex handled in test_multiindex.py") + name = ['0'] message = "Index.name must be a hashable type" tm.assert_raises_regex(TypeError, message, name=name) @@ -446,10 +449,10 @@ def test_constructor_nonhashable_name(self): # With .rename() renamed = [['1']] tm.assert_raises_regex(TypeError, message, - idx.rename, name=renamed) + indices.rename, name=renamed) # With .set_names() tm.assert_raises_regex(TypeError, message, - idx.set_names, names=renamed) + indices.set_names, names=renamed) def test_view_with_args(self): From b1cb7fd1c57036b7e5a3fce2b312175e7f23e0fa Mon Sep 17 00:00:00 2001 From: arminv Date: Tue, 17 Apr 2018 06:21:46 -0400 Subject: [PATCH 24/30] Refactoring. Internal docstring. Minor typos --- pandas/core/indexes/base.py | 49 ++++++++++++++++++++++++++----- pandas/tests/indexes/test_base.py | 2 +- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index f974986e4c9ed..ccc21cf66b6ae 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -252,10 +252,6 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, if name is None and hasattr(data, 'name'): name = data.name - if name is not None and not is_hashable(name): - raise TypeError('{}.name must be a hashable type' - .format(cls.__name__)) - if fastpath: return cls._simple_new(data, name) @@ -478,7 +474,7 @@ def _simple_new(cls, values, name=None, dtype=None, **kwargs): result = object.__new__(cls) result._data = values - result.name = name + result._set_names([name]) for k, v in compat.iteritems(kwargs): setattr(result, k, v) return result._reset_identity() @@ -1316,6 +1312,45 @@ def _get_names(self): return FrozenList((self.name, )) def _set_names(self, values, level=None): + """ + Set new names on index. + + Parameters + ---------- + values : str or sequence + name(s) to set + level : int, level name, or sequence of int/level names (default None) + If the index is a MultiIndex (hierarchical), level(s) to set (None + for all levels). Otherwise level must be None + + Returns + ------- + index : Index + + See Also + -------- + Index.set_names : Set new names on index. Defaults to returning + new index. + Index.rename : Set new names on index. Defaults to returning new index. + + Notes + ----- + Both `set_names` and `rename` call this function. + + Examples + -------- + on an index with no names: + >>> I1 = Index([1, 2, 3, 4]) + >>> I1._set_names([0]) + >>> I1 + Int64Index([1, 2, 3, 4], dtype='int64', name=0) + + set multiple names: + >>> I2 = Index([1, 2, 3, 4], name="foo") + >>> I2._set_names([(0, 1)]) + >>> I2 + Int64Index([1, 2, 3, 4], dtype='int64', name=(0, 1)) + """ # GH 20527 # All items in 'name' need to be hashable: if values is not None: @@ -1351,9 +1386,9 @@ def set_names(self, names, level=None, inplace=False): Examples -------- >>> Index([1, 2, 3, 4]).set_names('foo') - Int64Index([1, 2, 3, 4], dtype='int64') + Int64Index([1, 2, 3, 4], dtype='int64', name='foo') >>> Index([1, 2, 3, 4]).set_names(['foo']) - Int64Index([1, 2, 3, 4], dtype='int64') + Int64Index([1, 2, 3, 4], dtype='int64', name='foo') >>> idx = MultiIndex.from_tuples([(1, u'one'), (1, u'two'), (2, u'one'), (2, u'two')], names=['foo', 'bar']) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 0beb98a496309..682517f5a6fb1 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -439,7 +439,7 @@ def test_constructor_nonhashable_name(self, indices): # GH 20527 if isinstance(indices, MultiIndex): - pytest.skip("multiindex handled in test_multiindex.py") + pytest.skip("multiindex handled in test_multi.py") name = ['0'] message = "Index.name must be a hashable type" From 863f7d3d3cf5c72f47f3a4bc53ac82fe13eb1d9c Mon Sep 17 00:00:00 2001 From: arminv Date: Tue, 17 Apr 2018 06:23:00 -0400 Subject: [PATCH 25/30] PEP 8 --- pandas/core/indexes/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index ccc21cf66b6ae..db87e9e7b3399 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1344,7 +1344,7 @@ def _set_names(self, values, level=None): >>> I1._set_names([0]) >>> I1 Int64Index([1, 2, 3, 4], dtype='int64', name=0) - + set multiple names: >>> I2 = Index([1, 2, 3, 4], name="foo") >>> I2._set_names([(0, 1)]) From 7092d49afe70d3d7008f10e25031fcd9604f762c Mon Sep 17 00:00:00 2001 From: arminv Date: Tue, 17 Apr 2018 06:46:54 -0400 Subject: [PATCH 26/30] Improved docstring wording --- pandas/core/indexes/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index db87e9e7b3399..4c29e47681719 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1335,7 +1335,7 @@ def _set_names(self, values, level=None): Notes ----- - Both `set_names` and `rename` call this function. + Both `set_names` and `rename` call this function to set name. Examples -------- From 4a500ba2b388bed7aed2c9cabb859efb30c2ee88 Mon Sep 17 00:00:00 2001 From: arminv Date: Sat, 21 Apr 2018 02:25:27 -0400 Subject: [PATCH 27/30] Shorten docstring --- pandas/core/indexes/base.py | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 4c29e47681719..b4bf48045ab69 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -474,7 +474,7 @@ def _simple_new(cls, values, name=None, dtype=None, **kwargs): result = object.__new__(cls) result._data = values - result._set_names([name]) + result.name = name for k, v in compat.iteritems(kwargs): setattr(result, k, v) return result._reset_identity() @@ -1326,30 +1326,6 @@ def _set_names(self, values, level=None): Returns ------- index : Index - - See Also - -------- - Index.set_names : Set new names on index. Defaults to returning - new index. - Index.rename : Set new names on index. Defaults to returning new index. - - Notes - ----- - Both `set_names` and `rename` call this function to set name. - - Examples - -------- - on an index with no names: - >>> I1 = Index([1, 2, 3, 4]) - >>> I1._set_names([0]) - >>> I1 - Int64Index([1, 2, 3, 4], dtype='int64', name=0) - - set multiple names: - >>> I2 = Index([1, 2, 3, 4], name="foo") - >>> I2._set_names([(0, 1)]) - >>> I2 - Int64Index([1, 2, 3, 4], dtype='int64', name=(0, 1)) """ # GH 20527 # All items in 'name' need to be hashable: From 04f2eed99d85c3476f417b74869113bceddd2291 Mon Sep 17 00:00:00 2001 From: arminv Date: Sat, 21 Apr 2018 20:24:38 -0400 Subject: [PATCH 28/30] Added examples --- pandas/core/indexes/base.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index b4bf48045ab69..9734ae2affc28 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1313,7 +1313,7 @@ def _get_names(self): def _set_names(self, values, level=None): """ - Set new names on index. + Set new names on index. Each name has to be a hashable type. Parameters ---------- @@ -1323,9 +1323,18 @@ def _set_names(self, values, level=None): If the index is a MultiIndex (hierarchical), level(s) to set (None for all levels). Otherwise level must be None - Returns - ------- - index : Index + Raises + ------ + TypeError if each name is not hashable. + + Examples + -------- + >>> Index([1, 2, 3, 4])._set_names(['foo']) + Int64Index([1, 2, 3, 4], dtype='int64', name='foo') + >>> Index([1, 2, 3, 4])._set_names([(0, 1)]) + Int64Index([1, 2, 3, 4], dtype='int64', name=(0, 1)) + >>> Index([1, 2, 3, 4])._set_names(([0], [1])) + TypeError: Int64Index.name must be a hashable type """ # GH 20527 # All items in 'name' need to be hashable: From 1a68188bdde882373d595bff4aea983112dcdfd6 Mon Sep 17 00:00:00 2001 From: Jeff Reback Date: Sun, 22 Apr 2018 10:35:15 -0400 Subject: [PATCH 29/30] remove examples from _set_names --- pandas/core/indexes/base.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 9734ae2affc28..923520576624a 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1326,15 +1326,6 @@ def _set_names(self, values, level=None): Raises ------ TypeError if each name is not hashable. - - Examples - -------- - >>> Index([1, 2, 3, 4])._set_names(['foo']) - Int64Index([1, 2, 3, 4], dtype='int64', name='foo') - >>> Index([1, 2, 3, 4])._set_names([(0, 1)]) - Int64Index([1, 2, 3, 4], dtype='int64', name=(0, 1)) - >>> Index([1, 2, 3, 4])._set_names(([0], [1])) - TypeError: Int64Index.name must be a hashable type """ # GH 20527 # All items in 'name' need to be hashable: From 97a2b06c305f70ffd0c1e4fcbce4169d1f54ccf9 Mon Sep 17 00:00:00 2001 From: Jeff Reback Date: Sun, 22 Apr 2018 10:47:30 -0400 Subject: [PATCH 30/30] consolidate logic a bit --- pandas/core/indexes/base.py | 16 ++++++++------ pandas/core/indexes/multi.py | 43 +++++++++++++++++++++++++++--------- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 923520576624a..f392a716d9e5b 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1327,16 +1327,18 @@ def _set_names(self, values, level=None): ------ TypeError if each name is not hashable. """ - # GH 20527 - # All items in 'name' need to be hashable: - if values is not None: - for name in values: - if not is_hashable(name): - raise TypeError('{}.name must be a hashable type' - .format(self.__class__.__name__)) + if not is_list_like(values): + raise ValueError('Names must be a list-like') if len(values) != 1: raise ValueError('Length of new names must be 1, got %d' % len(values)) + + # GH 20527 + # All items in 'name' need to be hashable: + for name in values: + if not is_hashable(name): + raise TypeError('{}.name must be a hashable type' + .format(self.__class__.__name__)) self.name = values[0] names = property(fset=_set_names, fget=_get_names) diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index b82517c960f40..fbcf06a28c1e5 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -635,18 +635,29 @@ def _get_names(self): def _set_names(self, names, level=None, validate=True): """ + Set new names on index. Each name has to be a hashable type. + + Parameters + ---------- + values : str or sequence + name(s) to set + level : int, level name, or sequence of int/level names (default None) + If the index is a MultiIndex (hierarchical), level(s) to set (None + for all levels). Otherwise level must be None + validate : boolean, default True + validate that the names match level lengths + + Raises + ------ + TypeError if each name is not hashable. + + Notes + ----- sets names on levels. WARNING: mutates! Note that you generally want to set this *after* changing levels, so that it only acts on copies """ - # GH 20527 - # All items in 'names' need to be hashable: - for levels, name in enumerate(names): - if not is_hashable(name): - raise TypeError('{}.name must be a hashable type' - .format(self.__class__.__name__)) - # GH 15110 # Don't allow a single string for names in a MultiIndex if names is not None and not is_list_like(names): @@ -669,10 +680,20 @@ def _set_names(self, names, level=None, validate=True): # set the name for l, name in zip(level, names): - if name is not None and name in used: - raise ValueError('Duplicated level name: "{}", assigned to ' - 'level {}, is already used for level ' - '{}.'.format(name, l, used[name])) + if name is not None: + + # GH 20527 + # All items in 'names' need to be hashable: + if not is_hashable(name): + raise TypeError('{}.name must be a hashable type' + .format(self.__class__.__name__)) + + if name in used: + raise ValueError( + 'Duplicated level name: "{}", assigned to ' + 'level {}, is already used for level ' + '{}.'.format(name, l, used[name])) + self.levels[l].rename(name, inplace=True) used[name] = l