Skip to content

Commit 3479c27

Browse files
committed
Merge pull request #4718 from jtratner/multiindex-rename
ENH: Better rename/set_names handling for Index
2 parents 285b917 + c95fc72 commit 3479c27

File tree

5 files changed

+93
-5
lines changed

5 files changed

+93
-5
lines changed

doc/source/release.rst

+2
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,8 @@ See :ref:`Internal Refactoring<whatsnew_0130.refactoring>`
306306
- Fix boolean comparison with a DataFrame on the lhs, and a list/tuple on the rhs (:issue:`4576`)
307307
- Fix error/dtype conversion with setitem of ``None`` on ``Series/DataFrame`` (:issue:`4667`)
308308
- Fix decoding based on a passed in non-default encoding in ``pd.read_stata`` (:issue:`4626`)
309+
- Fix some inconsistencies with ``Index.rename`` and ``MultiIndex.rename``,
310+
etc. (:issue:`4718`, :issue:`4628`)
309311

310312
pandas 0.12
311313
===========

pandas/core/base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def __hash__(self):
114114
def _disabled(self, *args, **kwargs):
115115
"""This method will not function because object is immutable."""
116116
raise TypeError("'%s' does not support mutable operations." %
117-
self.__class__)
117+
self.__class__.__name__)
118118

119119
def __unicode__(self):
120120
from pandas.core.common import pprint_thing

pandas/core/common.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1783,7 +1783,7 @@ def is_re_compilable(obj):
17831783

17841784

17851785
def is_list_like(arg):
1786-
return hasattr(arg, '__iter__') and not isinstance(arg, compat.string_types)
1786+
return hasattr(arg, '__iter__') and not isinstance(arg, compat.string_and_binary_types)
17871787

17881788

17891789
def _is_sequence(x):

pandas/core/index.py

+20-2
Original file line numberDiff line numberDiff line change
@@ -273,16 +273,33 @@ def set_names(self, names, inplace=False):
273273
274274
Returns
275275
-------
276-
new index (of same type and class...etc)
276+
new index (of same type and class...etc) [if inplace, returns None]
277277
"""
278+
if not com.is_list_like(names):
279+
raise TypeError("Must pass list-like as `names`.")
278280
if inplace:
279281
idx = self
280282
else:
281283
idx = self._shallow_copy()
282284
idx._set_names(names)
283-
return idx
285+
if not inplace:
286+
return idx
284287

285288
def rename(self, name, inplace=False):
289+
"""
290+
Set new names on index. Defaults to returning new index.
291+
292+
Parameters
293+
----------
294+
name : str or list
295+
name to set
296+
inplace : bool
297+
if True, mutates in place
298+
299+
Returns
300+
-------
301+
new index (of same type and class...etc) [if inplace, returns None]
302+
"""
286303
return self.set_names([name], inplace=inplace)
287304

288305
@property
@@ -1556,6 +1573,7 @@ class MultiIndex(Index):
15561573
_levels = FrozenList()
15571574
_labels = FrozenList()
15581575
_comparables = ['names']
1576+
rename = Index.set_names
15591577

15601578
def __new__(cls, levels=None, labels=None, sortorder=None, names=None,
15611579
copy=False):

pandas/tests/test_index.py

+69-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pandas.compat import range, lrange, lzip, u, zip
55
import operator
66
import pickle
7+
import re
78
import unittest
89
import nose
910
import os
@@ -44,10 +45,35 @@ def test_wrong_number_names(self):
4445
def testit(ind):
4546
ind.names = ["apple", "banana", "carrot"]
4647

47-
indices = (self.dateIndex, self.unicodeIndex, self.strIndex, self.intIndex, self.floatIndex, self.empty, self.tuples)
48+
indices = (self.dateIndex, self.unicodeIndex, self.strIndex,
49+
self.intIndex, self.floatIndex, self.empty, self.tuples)
4850
for ind in indices:
4951
assertRaisesRegexp(ValueError, "^Length", testit, ind)
5052

53+
def test_set_name_methods(self):
54+
new_name = "This is the new name for this index"
55+
indices = (self.dateIndex, self.intIndex, self.unicodeIndex,
56+
self.empty)
57+
for ind in indices:
58+
original_name = ind.name
59+
new_ind = ind.set_names([new_name])
60+
self.assertEqual(new_ind.name, new_name)
61+
self.assertEqual(ind.name, original_name)
62+
res = ind.rename(new_name, inplace=True)
63+
# should return None
64+
self.assert_(res is None)
65+
self.assertEqual(ind.name, new_name)
66+
self.assertEqual(ind.names, [new_name])
67+
with assertRaisesRegexp(TypeError, "list-like"):
68+
# should still fail even if it would be the right length
69+
ind.set_names("a")
70+
# rename in place just leaves tuples and other containers alone
71+
name = ('A', 'B')
72+
ind = self.intIndex
73+
ind.rename(name, inplace=True)
74+
self.assertEqual(ind.name, name)
75+
self.assertEqual(ind.names, [name])
76+
5177
def test_hash_error(self):
5278
self.assertRaises(TypeError, hash, self.strIndex)
5379

@@ -1018,6 +1044,48 @@ def setUp(self):
10181044
labels=[major_labels, minor_labels],
10191045
names=self.index_names)
10201046

1047+
def test_set_names_and_rename(self):
1048+
# so long as these are synonyms, we don't need to test set_names
1049+
self.assert_(self.index.rename == self.index.set_names)
1050+
new_names = [name + "SUFFIX" for name in self.index_names]
1051+
ind = self.index.set_names(new_names)
1052+
self.assertEqual(self.index.names, self.index_names)
1053+
self.assertEqual(ind.names, new_names)
1054+
with assertRaisesRegexp(ValueError, "^Length"):
1055+
ind.set_names(new_names + new_names)
1056+
new_names2 = [name + "SUFFIX2" for name in new_names]
1057+
res = ind.set_names(new_names2, inplace=True)
1058+
self.assert_(res is None)
1059+
self.assertEqual(ind.names, new_names2)
1060+
1061+
def test_set_levels_and_set_labels(self):
1062+
# side note - you probably wouldn't want to use levels and labels
1063+
# directly like this - but it is possible.
1064+
levels, labels = self.index.levels, self.index.labels
1065+
new_levels = [[lev + 'a' for lev in level] for level in levels]
1066+
major_labels, minor_labels = labels
1067+
major_labels = [(x + 1) % 3 for x in major_labels]
1068+
minor_labels = [(x + 1) % 1 for x in minor_labels]
1069+
new_labels = [major_labels, minor_labels]
1070+
1071+
def test_metadata_immutable(self):
1072+
levels, labels = self.index.levels, self.index.labels
1073+
# shouldn't be able to set at either the top level or base level
1074+
mutable_regex = re.compile('does not support mutable operations')
1075+
with assertRaisesRegexp(TypeError, mutable_regex):
1076+
levels[0] = levels[0]
1077+
with assertRaisesRegexp(TypeError, mutable_regex):
1078+
levels[0][0] = levels[0][0]
1079+
# ditto for labels
1080+
with assertRaisesRegexp(TypeError, mutable_regex):
1081+
labels[0] = labels[0]
1082+
with assertRaisesRegexp(TypeError, mutable_regex):
1083+
labels[0][0] = labels[0][0]
1084+
# and for names
1085+
names = self.index.names
1086+
with assertRaisesRegexp(TypeError, mutable_regex):
1087+
names[0] = names[0]
1088+
10211089
def test_copy_in_constructor(self):
10221090
levels = np.array(["a", "b", "c"])
10231091
labels = np.array([1, 1, 2, 0, 0, 1, 1])

0 commit comments

Comments
 (0)