diff --git a/doc/source/whatsnew/v0.18.1.txt b/doc/source/whatsnew/v0.18.1.txt index e664020946baf..7701bc66b8b92 100644 --- a/doc/source/whatsnew/v0.18.1.txt +++ b/doc/source/whatsnew/v0.18.1.txt @@ -165,3 +165,4 @@ Bug Fixes - Bug in ``pivot_table`` when ``margins=True`` and ``dropna=True`` where nulls still contributed to margin count (:issue:`12577`) +- Bug in ``Series.name`` when ``name`` attribute can be a hashable type (:issue:`12610`) \ No newline at end of file diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 24b883b90cf5d..cbcd949dd5c36 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -81,7 +81,7 @@ class NDFrame(PandasObject): copy : boolean, default False """ _internal_names = ['_data', '_cacher', '_item_cache', '_cache', 'is_copy', - '_subtyp', '_index', '_default_kind', + '_subtyp', '_name', '_index', '_default_kind', '_default_fill_value', '_metadata', '__array_struct__', '__array_interface__'] _internal_names_set = set(_internal_names) diff --git a/pandas/core/series.py b/pandas/core/series.py index b25cd63acaff6..cc58b32de999a 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -232,7 +232,7 @@ def __init__(self, data=None, index=None, dtype=None, name=None, generic.NDFrame.__init__(self, data, fastpath=True) - object.__setattr__(self, 'name', name) + self.name = name self._set_axis(0, index, fastpath=True) @classmethod @@ -301,6 +301,16 @@ def _update_inplace(self, result, **kwargs): # we want to call the generic version and not the IndexOpsMixin return generic.NDFrame._update_inplace(self, result, **kwargs) + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + if value is not None and not com.is_hashable(value): + raise TypeError('Series.name must be a hashable type') + object.__setattr__(self, '_name', value) + # ndarray compatibility @property def dtype(self): diff --git a/pandas/tests/series/test_alter_axes.py b/pandas/tests/series/test_alter_axes.py index 4f9a55908fe96..ee054437e60a8 100644 --- a/pandas/tests/series/test_alter_axes.py +++ b/pandas/tests/series/test_alter_axes.py @@ -1,6 +1,8 @@ # coding=utf-8 # pylint: disable-msg=E1101,W0612 +from datetime import datetime + import numpy as np import pandas as pd @@ -64,7 +66,7 @@ def test_rename_by_series(self): def test_rename_set_name(self): s = Series(range(4), index=list('abcd')) - for name in ['foo', ['foo'], ('foo',)]: + for name in ['foo', 123, 123., datetime(2001, 11, 11), ('foo',)]: result = s.rename(name) self.assertEqual(result.name, name) self.assert_numpy_array_equal(result.index.values, s.index.values) @@ -72,12 +74,18 @@ def test_rename_set_name(self): def test_rename_set_name_inplace(self): s = Series(range(3), index=list('abc')) - for name in ['foo', ['foo'], ('foo',)]: + for name in ['foo', 123, 123., datetime(2001, 11, 11), ('foo',)]: s.rename(name, inplace=True) self.assertEqual(s.name, name) self.assert_numpy_array_equal(s.index.values, np.array(['a', 'b', 'c'])) + def test_set_name_attribute(self): + s = Series([1, 2, 3]) + for name in [7, 7., 'name', datetime(2001, 1, 1), (1,), u"\u05D0"]: + s.name = name + self.assertEqual(s.name, name) + def test_set_name(self): s = Series([1, 2, 3]) s2 = s._set_name('foo') diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 356267630b274..89793018b5193 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -725,3 +725,14 @@ def f(): self.assertEqual(s.dtype, 'timedelta64[ns]') s = Series([pd.NaT, np.nan, '1 Day']) self.assertEqual(s.dtype, 'timedelta64[ns]') + + def test_constructor_name_hashable(self): + for n in [777, 777., 'name', datetime(2001, 11, 11), (1, ), u"\u05D0"]: + for data in [[1, 2, 3], np.ones(3), {'a': 0, 'b': 1}]: + s = Series(data, name=n) + self.assertEqual(s.name, n) + + def test_constructor_name_unhashable(self): + for n in [['name_list'], np.ones(2), {1: 2}]: + for data in [['name_list'], np.ones(2), {1: 2}]: + self.assertRaises(TypeError, Series, data, name=n) diff --git a/pandas/tests/series/test_io.py b/pandas/tests/series/test_io.py index beb023215e6ce..2cdd6b22afc74 100644 --- a/pandas/tests/series/test_io.py +++ b/pandas/tests/series/test_io.py @@ -146,8 +146,9 @@ def test_timeseries_periodindex(self): self.assertEqual(new_ts.index.freq, 'M') def test_pickle_preserve_name(self): - unpickled = self._pickle_roundtrip_name(self.ts) - self.assertEqual(unpickled.name, self.ts.name) + for n in [777, 777., 'name', datetime(2001, 11, 11), (1, 2)]: + unpickled = self._pickle_roundtrip_name(tm.makeTimeSeries(name=n)) + self.assertEqual(unpickled.name, n) def _pickle_roundtrip_name(self, obj): diff --git a/pandas/tests/series/test_repr.py b/pandas/tests/series/test_repr.py index ec2efdcf40705..68ad29f1418c3 100644 --- a/pandas/tests/series/test_repr.py +++ b/pandas/tests/series/test_repr.py @@ -97,7 +97,7 @@ def test_repr(self): rep_str = repr(ser) self.assertIn("Name: 0", rep_str) - ser = Series(["a\n\r\tb"], name=["a\n\r\td"], index=["a\n\r\tf"]) + ser = Series(["a\n\r\tb"], name="a\n\r\td", index=["a\n\r\tf"]) self.assertFalse("\t" in repr(ser)) self.assertFalse("\r" in repr(ser)) self.assertFalse("a\n" in repr(ser))