Skip to content

Commit df50e88

Browse files
Christopher C. Aycockjreback
Christopher C. Aycock
authored andcommitted
BUG: Index.copy() honors 'name' parameter (#14302)
closes #14302 Author: Christopher C. Aycock <[email protected]> Closes #14303 from chrisaycock/master and squashes the following commits: bbfc79f [Christopher C. Aycock] BUG: Index.copy() honors 'name' parameter (#14302)
1 parent 71df09c commit df50e88

File tree

5 files changed

+83
-17
lines changed

5 files changed

+83
-17
lines changed

doc/source/whatsnew/v0.19.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1583,3 +1583,4 @@ Bug Fixes
15831583
- Bugs in ``stack``, ``get_dummies``, ``make_axis_dummies`` which don't preserve categorical dtypes in (multi)indexes (:issue:`13854`)
15841584
- ``PeridIndex`` can now accept ``list`` and ``array`` which contains ``pd.NaT`` (:issue:`13430`)
15851585
- Bug in ``df.groupby`` where ``.median()`` returns arbitrary values if grouped dataframe contains empty bins (:issue:`13629`)
1586+
- Bug in ``Index.copy()`` where ``name`` parameter was ignored (:issue:`14302`)

pandas/indexes/base.py

+24-10
Original file line numberDiff line numberDiff line change
@@ -621,26 +621,40 @@ def _coerce_scalar_to_index(self, item):
621621

622622
@Appender(_index_shared_docs['copy'])
623623
def copy(self, name=None, deep=False, dtype=None, **kwargs):
624-
names = kwargs.get('names')
625-
if names is not None and name is not None:
626-
raise TypeError("Can only provide one of `names` and `name`")
627624
if deep:
628-
from copy import deepcopy
629625
new_index = self._shallow_copy(self._data.copy())
630-
name = name or deepcopy(self.name)
631626
else:
632627
new_index = self._shallow_copy()
633-
name = self.name
634-
if name is not None:
635-
names = [name]
636-
if names:
637-
new_index = new_index.set_names(names)
628+
629+
names = kwargs.get('names')
630+
names = self._validate_names(name=name, names=names, deep=deep)
631+
new_index = new_index.set_names(names)
632+
638633
if dtype:
639634
new_index = new_index.astype(dtype)
640635
return new_index
641636

642637
__copy__ = copy
643638

639+
def _validate_names(self, name=None, names=None, deep=False):
640+
"""
641+
Handles the quirks of having a singular 'name' parameter for general
642+
Index and plural 'names' parameter for MultiIndex.
643+
"""
644+
from copy import deepcopy
645+
if names is not None and name is not None:
646+
raise TypeError("Can only provide one of `names` and `name`")
647+
elif names is None and name is None:
648+
return deepcopy(self.names) if deep else self.names
649+
elif names is not None:
650+
if not is_list_like(names):
651+
raise TypeError("Must pass list-like as `names`.")
652+
return names
653+
else:
654+
if not is_list_like(name):
655+
return [name]
656+
return name
657+
644658
def __unicode__(self):
645659
"""
646660
Return a string representation for this object.

pandas/indexes/multi.py

+12-7
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ def set_labels(self, labels, level=None, inplace=False,
346346
labels = property(fget=_get_labels, fset=__set_labels)
347347

348348
def copy(self, names=None, dtype=None, levels=None, labels=None,
349-
deep=False, _set_identity=False):
349+
deep=False, _set_identity=False, **kwargs):
350350
"""
351351
Make a copy of this object. Names, dtype, levels and labels can be
352352
passed and will be set on new copy.
@@ -368,15 +368,20 @@ def copy(self, names=None, dtype=None, levels=None, labels=None,
368368
``deep``, but if ``deep`` is passed it will attempt to deepcopy.
369369
This could be potentially expensive on large MultiIndex objects.
370370
"""
371+
name = kwargs.get('name')
372+
names = self._validate_names(name=name, names=names, deep=deep)
373+
371374
if deep:
372375
from copy import deepcopy
373-
levels = levels if levels is not None else deepcopy(self.levels)
374-
labels = labels if labels is not None else deepcopy(self.labels)
375-
names = names if names is not None else deepcopy(self.names)
376+
if levels is None:
377+
levels = deepcopy(self.levels)
378+
if labels is None:
379+
labels = deepcopy(self.labels)
376380
else:
377-
levels = self.levels
378-
labels = self.labels
379-
names = self.names
381+
if levels is None:
382+
levels = self.levels
383+
if labels is None:
384+
labels = self.labels
380385
return MultiIndex(levels=levels, labels=labels, names=names,
381386
sortorder=self.sortorder, verify_integrity=False,
382387
_set_identity=_set_identity)

pandas/tests/indexes/test_base.py

+24
Original file line numberDiff line numberDiff line change
@@ -1817,6 +1817,30 @@ def test_copy_name(self):
18171817
s3 = s1 * s2
18181818
self.assertEqual(s3.index.name, 'mario')
18191819

1820+
def test_copy_name2(self):
1821+
# Check that adding a "name" parameter to the copy is honored
1822+
# GH14302
1823+
idx = pd.Index([1, 2], name='MyName')
1824+
idx1 = idx.copy()
1825+
1826+
self.assertTrue(idx.equals(idx1))
1827+
self.assertEqual(idx.name, 'MyName')
1828+
self.assertEqual(idx1.name, 'MyName')
1829+
1830+
idx2 = idx.copy(name='NewName')
1831+
1832+
self.assertTrue(idx.equals(idx2))
1833+
self.assertEqual(idx.name, 'MyName')
1834+
self.assertEqual(idx2.name, 'NewName')
1835+
1836+
idx3 = idx.copy(names=['NewName'])
1837+
1838+
self.assertTrue(idx.equals(idx3))
1839+
self.assertEqual(idx.name, 'MyName')
1840+
self.assertEqual(idx.names, ['MyName'])
1841+
self.assertEqual(idx3.name, 'NewName')
1842+
self.assertEqual(idx3.names, ['NewName'])
1843+
18201844
def test_union_base(self):
18211845
idx = self.create_index()
18221846
first = idx[3:]

pandas/tests/indexes/test_multi.py

+22
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,28 @@ def test_set_value_keeps_names(self):
415415
self.assertIsNone(df.is_copy)
416416
self.assertEqual(df.index.names, ('Name', 'Number'))
417417

418+
def test_copy_names(self):
419+
# Check that adding a "names" parameter to the copy is honored
420+
# GH14302
421+
multi_idx = pd.Index([(1, 2), (3, 4)], names=['MyName1', 'MyName2'])
422+
multi_idx1 = multi_idx.copy()
423+
424+
self.assertTrue(multi_idx.equals(multi_idx1))
425+
self.assertEqual(multi_idx.names, ['MyName1', 'MyName2'])
426+
self.assertEqual(multi_idx1.names, ['MyName1', 'MyName2'])
427+
428+
multi_idx2 = multi_idx.copy(names=['NewName1', 'NewName2'])
429+
430+
self.assertTrue(multi_idx.equals(multi_idx2))
431+
self.assertEqual(multi_idx.names, ['MyName1', 'MyName2'])
432+
self.assertEqual(multi_idx2.names, ['NewName1', 'NewName2'])
433+
434+
multi_idx3 = multi_idx.copy(name=['NewName1', 'NewName2'])
435+
436+
self.assertTrue(multi_idx.equals(multi_idx3))
437+
self.assertEqual(multi_idx.names, ['MyName1', 'MyName2'])
438+
self.assertEqual(multi_idx3.names, ['NewName1', 'NewName2'])
439+
418440
def test_names(self):
419441

420442
# names are assigned in __init__

0 commit comments

Comments
 (0)