Skip to content

Commit f9787e1

Browse files
TomAugspurgercldy
authored andcommitted
ENH/API: Scalar values for Series.rename and NDFrame.rename_axis
closes pandas-dev#9494 closes pandas-dev#11965 Okay refactored to overload `NDFrame.rename` and `NDFrame.rename_axis` New rules: - Series.rename(scalar) or Series.rename(list-like) will alter `Series.name` - NDFrame.rename(mapping) and NDFrame.rename(function) operate as before (alter labels) - NDFrame.rename_axis(scaler) or NDFrame.rename_axis(list-like) will change the Index.name or MutliIndex.names - NDFrame.rename_axis(mapping) and NDFrame.rename_axis(function) operate as before. I don't like overloading the method like this, but `rename` and `rename_axis` are the right method names. It's *mostly* backwards compatible, aside from people doing things like `try: series.rename(x): except TypeError: ...` and I don't know what our guarantees are around things like that. Author: Tom Augspurger <[email protected]> Closes pandas-dev#11980 from TomAugspurger/chainable-rename and squashes the following commits: b36df76 [Tom Augspurger] ENH/API: Series.rename and NDFrame.rename_axis
1 parent 406d55c commit f9787e1

File tree

9 files changed

+287
-10
lines changed

9 files changed

+287
-10
lines changed

doc/source/basics.rst

+9
Original file line numberDiff line numberDiff line change
@@ -1170,6 +1170,15 @@ The :meth:`~DataFrame.rename` method also provides an ``inplace`` named
11701170
parameter that is by default ``False`` and copies the underlying data. Pass
11711171
``inplace=True`` to rename the data in place.
11721172

1173+
.. versionadded:: 0.18.0
1174+
1175+
Finally, :meth:`~Series.rename` also accepts a scalar or list-like
1176+
for altering the ``Series.name`` attribute.
1177+
1178+
.. ipython:: python
1179+
1180+
s.rename("scalar-name")
1181+
11731182
.. _basics.rename_axis:
11741183

11751184
The Panel class has a related :meth:`~Panel.rename_axis` class which can rename

doc/source/dsintro.rst

+11
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,17 @@ Series can also have a ``name`` attribute:
214214
The Series ``name`` will be assigned automatically in many cases, in particular
215215
when taking 1D slices of DataFrame as you will see below.
216216

217+
.. versionadded:: 0.18.0
218+
219+
You can rename a Series with the :meth:`pandas.Series.rename` method.
220+
221+
.. ipython:: python
222+
223+
s2 = s.rename("different")
224+
s2.name
225+
226+
Note that ``s`` and ``s2`` refer to different objects.
227+
217228
.. _basics.dataframe:
218229

219230
DataFrame

doc/source/whatsnew/v0.18.0.txt

+21
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,27 @@ And multiple aggregations
104104
r.agg({'A' : ['mean','std'],
105105
'B' : ['mean','std']})
106106

107+
.. _whatsnew_0180.enhancements.rename:
108+
109+
``Series.rename`` and ``NDFrame.rename_axis`` can now take a scalar or list-like
110+
argument for altering the Series or axis *name*, in addition to their old behaviors of altering labels. (:issue:`9494`, :issue:`11965`)
111+
112+
.. ipython: python
113+
114+
s = pd.Series(np.random.randn(10))
115+
s.rename('newname')
116+
117+
.. ipython: python
118+
119+
df = pd.DataFrame(np.random.randn(10, 2))
120+
(df.rename_axis("indexname")
121+
.rename_axis("columns_name", axis="columns"))
122+
123+
The new functionality works well in method chains.
124+
Previously these methods only accepted functions or dicts mapping a *label* to a new label.
125+
This continues to work as before for function or dict-like values.
126+
127+
107128
.. _whatsnew_0180.enhancements.rangeindex:
108129

109130
Range Index

pandas/core/common.py

+4
Original file line numberDiff line numberDiff line change
@@ -2461,6 +2461,10 @@ def is_list_like(arg):
24612461
not isinstance(arg, compat.string_and_binary_types))
24622462

24632463

2464+
def is_dict_like(arg):
2465+
return hasattr(arg, '__getitem__') and hasattr(arg, 'keys')
2466+
2467+
24642468
def is_named_tuple(arg):
24652469
return isinstance(arg, tuple) and hasattr(arg, '_fields')
24662470

pandas/core/generic.py

+129-9
Original file line numberDiff line numberDiff line change
@@ -546,13 +546,16 @@ def swaplevel(self, i, j, axis=0):
546546
_shared_docs['rename'] = """
547547
Alter axes input function or functions. Function / dict values must be
548548
unique (1-to-1). Labels not contained in a dict / Series will be left
549-
as-is.
549+
as-is. Alternatively, change ``Series.name`` with a scalar
550+
value (Series only).
550551
551552
Parameters
552553
----------
553-
%(axes)s : dict-like or function, optional
554-
Transformation to apply to that axis values
555-
554+
%(axes)s : scalar, list-like, dict-like or function, optional
555+
Scalar or list-like will alter the ``Series.name`` attribute,
556+
and raise on DataFrame or Panel.
557+
dict-like or functions are transformations to apply to
558+
that axis' values
556559
copy : boolean, default True
557560
Also copy underlying data
558561
inplace : boolean, default False
@@ -562,6 +565,43 @@ def swaplevel(self, i, j, axis=0):
562565
Returns
563566
-------
564567
renamed : %(klass)s (new object)
568+
569+
See Also
570+
--------
571+
pandas.NDFrame.rename_axis
572+
573+
Examples
574+
--------
575+
>>> s = pd.Series([1, 2, 3])
576+
>>> s
577+
0 1
578+
1 2
579+
2 3
580+
dtype: int64
581+
>>> s.rename("my_name") # scalar, changes Series.name
582+
0 1
583+
1 2
584+
2 3
585+
Name: my_name, dtype: int64
586+
>>> s.rename(lambda x: x ** 2) # function, changes labels
587+
0 1
588+
1 2
589+
4 3
590+
dtype: int64
591+
>>> s.rename({1: 3, 2: 5}) # mapping, changes labels
592+
0 1
593+
3 2
594+
5 3
595+
dtype: int64
596+
>>> df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
597+
>>> df.rename(2)
598+
...
599+
TypeError: 'int' object is not callable
600+
>>> df.rename(index=str, columns={"A": "a", "B": "c"})
601+
a c
602+
0 1 4
603+
1 2 5
604+
2 3 6
565605
"""
566606

567607
@Appender(_shared_docs['rename'] % dict(axes='axes keywords for this'
@@ -617,12 +657,15 @@ def f(x):
617657
def rename_axis(self, mapper, axis=0, copy=True, inplace=False):
618658
"""
619659
Alter index and / or columns using input function or functions.
660+
A scaler or list-like for ``mapper`` will alter the ``Index.name``
661+
or ``MultiIndex.names`` attribute.
662+
A function or dict for ``mapper`` will alter the labels.
620663
Function / dict values must be unique (1-to-1). Labels not contained in
621664
a dict / Series will be left as-is.
622665
623666
Parameters
624667
----------
625-
mapper : dict-like or function, optional
668+
mapper : scalar, list-like, dict-like or function, optional
626669
axis : int or string, default 0
627670
copy : boolean, default True
628671
Also copy underlying data
@@ -631,11 +674,88 @@ def rename_axis(self, mapper, axis=0, copy=True, inplace=False):
631674
Returns
632675
-------
633676
renamed : type of caller
677+
678+
See Also
679+
--------
680+
pandas.NDFrame.rename
681+
pandas.Index.rename
682+
683+
Examples
684+
--------
685+
>>> df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
686+
>>> df.rename_axis("foo") # scalar, alters df.index.name
687+
A B
688+
foo
689+
0 1 4
690+
1 2 5
691+
2 3 6
692+
>>> df.rename_axis(lambda x: 2 * x) # function: alters labels
693+
A B
694+
0 1 4
695+
2 2 5
696+
4 3 6
697+
>>> df.rename_axis({"A": "ehh", "C": "see"}, axis="columns") # mapping
698+
ehh B
699+
0 1 4
700+
1 2 5
701+
2 3 6
702+
"""
703+
is_scalar_or_list = (
704+
(not com.is_sequence(mapper) and not callable(mapper)) or
705+
(com.is_list_like(mapper) and not com.is_dict_like(mapper))
706+
)
707+
708+
if is_scalar_or_list:
709+
return self._set_axis_name(mapper, axis=axis)
710+
else:
711+
axis = self._get_axis_name(axis)
712+
d = {'copy': copy, 'inplace': inplace}
713+
d[axis] = mapper
714+
return self.rename(**d)
715+
716+
def _set_axis_name(self, name, axis=0):
634717
"""
635-
axis = self._get_axis_name(axis)
636-
d = {'copy': copy, 'inplace': inplace}
637-
d[axis] = mapper
638-
return self.rename(**d)
718+
Alter the name or names of the axis, returning self.
719+
720+
Parameters
721+
----------
722+
name : str or list of str
723+
Name for the Index, or list of names for the MultiIndex
724+
axis : int or str
725+
0 or 'index' for the index; 1 or 'columns' for the columns
726+
727+
Returns
728+
-------
729+
renamed : type of caller
730+
731+
See Also
732+
--------
733+
pandas.DataFrame.rename
734+
pandas.Series.rename
735+
pandas.Index.rename
736+
737+
Examples
738+
--------
739+
>>> df._set_axis_name("foo")
740+
A
741+
foo
742+
0 1
743+
1 2
744+
2 3
745+
>>> df.index = pd.MultiIndex.from_product([['A'], ['a', 'b', 'c']])
746+
>>> df._set_axis_name(["bar", "baz"])
747+
A
748+
bar baz
749+
A a 1
750+
b 2
751+
c 3
752+
"""
753+
axis = self._get_axis_number(axis)
754+
idx = self._get_axis(axis).set_names(name)
755+
756+
renamed = self.copy(deep=True)
757+
renamed.set_axis(axis, idx)
758+
return renamed
639759

640760
# ----------------------------------------------------------------------
641761
# Comparisons

pandas/core/series.py

+21
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import types
1010
import warnings
11+
from collections import MutableMapping
1112

1213
from numpy import nan, ndarray
1314
import numpy as np
@@ -1114,6 +1115,20 @@ def to_sparse(self, kind='block', fill_value=None):
11141115
return SparseSeries(self, kind=kind,
11151116
fill_value=fill_value).__finalize__(self)
11161117

1118+
def _set_name(self, name, inplace=False):
1119+
'''
1120+
Set the Series name.
1121+
1122+
Parameters
1123+
----------
1124+
name : str
1125+
inplace : bool
1126+
whether to modify `self` directly or return a copy
1127+
'''
1128+
ser = self if inplace else self.copy()
1129+
ser.name = name
1130+
return ser
1131+
11171132
# ----------------------------------------------------------------------
11181133
# Statistics, overridden ndarray methods
11191134

@@ -2323,6 +2338,12 @@ def align(self, other, join='outer', axis=None, level=None, copy=True,
23232338

23242339
@Appender(generic._shared_docs['rename'] % _shared_doc_kwargs)
23252340
def rename(self, index=None, **kwargs):
2341+
is_scalar_or_list = (
2342+
(not com.is_sequence(index) and not callable(index)) or
2343+
(com.is_list_like(index) and not isinstance(index, MutableMapping))
2344+
)
2345+
if is_scalar_or_list:
2346+
return self._set_name(index, inplace=kwargs.get('inplace'))
23262347
return super(Series, self).rename(index=index, **kwargs)
23272348

23282349
@Appender(generic._shared_docs['reindex'] % _shared_doc_kwargs)

pandas/tests/series/test_alter_axes.py

+23
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,29 @@ def test_rename(self):
5555
renamed = renamer.rename({})
5656
self.assertEqual(renamed.index.name, renamer.index.name)
5757

58+
def test_rename_set_name(self):
59+
s = Series(range(4), index=list('abcd'))
60+
for name in ['foo', ['foo'], ('foo',)]:
61+
result = s.rename(name)
62+
self.assertEqual(result.name, name)
63+
self.assert_numpy_array_equal(result.index.values, s.index.values)
64+
self.assertTrue(s.name is None)
65+
66+
def test_rename_set_name_inplace(self):
67+
s = Series(range(3), index=list('abc'))
68+
for name in ['foo', ['foo'], ('foo',)]:
69+
s.rename(name, inplace=True)
70+
self.assertEqual(s.name, name)
71+
self.assert_numpy_array_equal(s.index.values,
72+
np.array(['a', 'b', 'c']))
73+
74+
def test_set_name(self):
75+
s = Series([1, 2, 3])
76+
s2 = s._set_name('foo')
77+
self.assertEqual(s2.name, 'foo')
78+
self.assertTrue(s.name is None)
79+
self.assertTrue(s is not s2)
80+
5881
def test_rename_inplace(self):
5982
renamer = lambda x: x.strftime('%Y%m%d')
6083
expected = renamer(self.ts.index[0])

pandas/tests/test_common.py

+11
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,17 @@ def test_is_list_like():
631631
assert not com.is_list_like(f)
632632

633633

634+
def test_is_dict_like():
635+
passes = [{}, {'A': 1}, pd.Series([1])]
636+
fails = ['1', 1, [1, 2], (1, 2), range(2), pd.Index([1])]
637+
638+
for p in passes:
639+
assert com.is_dict_like(p)
640+
641+
for f in fails:
642+
assert not com.is_dict_like(f)
643+
644+
634645
def test_is_named_tuple():
635646
passes = (collections.namedtuple('Test', list('abc'))(1, 2, 3), )
636647
fails = ((1, 2, 3), 'a', Series({'pi': 3.14}))

0 commit comments

Comments
 (0)