Skip to content

Commit

Permalink
ENH/API: Series.rename and NDFrame.rename_axis
Browse files Browse the repository at this point in the history
Overloading the APIs of Series.rename and
NDFramed.rename_axis for method chaining.
  • Loading branch information
TomAugspurger committed Feb 10, 2016
1 parent 0320e3b commit b36df76
Show file tree
Hide file tree
Showing 9 changed files with 287 additions and 10 deletions.
9 changes: 9 additions & 0 deletions doc/source/basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,15 @@ The :meth:`~DataFrame.rename` method also provides an ``inplace`` named
parameter that is by default ``False`` and copies the underlying data. Pass
``inplace=True`` to rename the data in place.

.. versionadded:: 0.18.0

Finally, :meth:`~Series.rename` also accepts a scalar or list-like
for altering the ``Series.name`` attribute.

.. ipython:: python
s.rename("scalar-name")
.. _basics.rename_axis:

The Panel class has a related :meth:`~Panel.rename_axis` class which can rename
Expand Down
11 changes: 11 additions & 0 deletions doc/source/dsintro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,17 @@ Series can also have a ``name`` attribute:
The Series ``name`` will be assigned automatically in many cases, in particular
when taking 1D slices of DataFrame as you will see below.

.. versionadded:: 0.18.0

You can rename a Series with the :meth:`pandas.Series.rename` method.

.. ipython:: python
s2 = s.rename("different")
s2.name
Note that ``s`` and ``s2`` refer to different objects.

.. _basics.dataframe:

DataFrame
Expand Down
21 changes: 21 additions & 0 deletions doc/source/whatsnew/v0.18.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,27 @@ And multiple aggregations
r.agg({'A' : ['mean','std'],
'B' : ['mean','std']})

.. _whatsnew_0180.enhancements.rename:

``Series.rename`` and ``NDFrame.rename_axis`` can now take a scalar or list-like
argument for altering the Series or axis *name*, in addition to their old behaviors of altering labels. (:issue:`9494`, :issue:`11965`)

.. ipython: python

s = pd.Series(np.random.randn(10))
s.rename('newname')

.. ipython: python

df = pd.DataFrame(np.random.randn(10, 2))
(df.rename_axis("indexname")
.rename_axis("columns_name", axis="columns"))

The new functionality works well in method chains.
Previously these methods only accepted functions or dicts mapping a *label* to a new label.
This continues to work as before for function or dict-like values.


.. _whatsnew_0180.enhancements.rangeindex:

Range Index
Expand Down
4 changes: 4 additions & 0 deletions pandas/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2456,6 +2456,10 @@ def is_list_like(arg):
not isinstance(arg, compat.string_and_binary_types))


def is_dict_like(arg):
return hasattr(arg, '__getitem__') and hasattr(arg, 'keys')


def is_named_tuple(arg):
return isinstance(arg, tuple) and hasattr(arg, '_fields')

Expand Down
138 changes: 129 additions & 9 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,13 +546,16 @@ def swaplevel(self, i, j, axis=0):
_shared_docs['rename'] = """
Alter axes input function or functions. Function / dict values must be
unique (1-to-1). Labels not contained in a dict / Series will be left
as-is.
as-is. Alternatively, change ``Series.name`` with a scalar
value (Series only).
Parameters
----------
%(axes)s : dict-like or function, optional
Transformation to apply to that axis values
%(axes)s : scalar, list-like, dict-like or function, optional
Scalar or list-like will alter the ``Series.name`` attribute,
and raise on DataFrame or Panel.
dict-like or functions are transformations to apply to
that axis' values
copy : boolean, default True
Also copy underlying data
inplace : boolean, default False
Expand All @@ -562,6 +565,43 @@ def swaplevel(self, i, j, axis=0):
Returns
-------
renamed : %(klass)s (new object)
See Also
--------
pandas.NDFrame.rename_axis
Examples
--------
>>> s = pd.Series([1, 2, 3])
>>> s
0 1
1 2
2 3
dtype: int64
>>> s.rename("my_name") # scalar, changes Series.name
0 1
1 2
2 3
Name: my_name, dtype: int64
>>> s.rename(lambda x: x ** 2) # function, changes labels
0 1
1 2
4 3
dtype: int64
>>> s.rename({1: 3, 2: 5}) # mapping, changes labels
0 1
3 2
5 3
dtype: int64
>>> df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
>>> df.rename(2)
...
TypeError: 'int' object is not callable
>>> df.rename(index=str, columns={"A": "a", "B": "c"})
a c
0 1 4
1 2 5
2 3 6
"""

@Appender(_shared_docs['rename'] % dict(axes='axes keywords for this'
Expand Down Expand Up @@ -617,12 +657,15 @@ def f(x):
def rename_axis(self, mapper, axis=0, copy=True, inplace=False):
"""
Alter index and / or columns using input function or functions.
A scaler or list-like for ``mapper`` will alter the ``Index.name``
or ``MultiIndex.names`` attribute.
A function or dict for ``mapper`` will alter the labels.
Function / dict values must be unique (1-to-1). Labels not contained in
a dict / Series will be left as-is.
Parameters
----------
mapper : dict-like or function, optional
mapper : scalar, list-like, dict-like or function, optional
axis : int or string, default 0
copy : boolean, default True
Also copy underlying data
Expand All @@ -631,11 +674,88 @@ def rename_axis(self, mapper, axis=0, copy=True, inplace=False):
Returns
-------
renamed : type of caller
See Also
--------
pandas.NDFrame.rename
pandas.Index.rename
Examples
--------
>>> df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
>>> df.rename_axis("foo") # scalar, alters df.index.name
A B
foo
0 1 4
1 2 5
2 3 6
>>> df.rename_axis(lambda x: 2 * x) # function: alters labels
A B
0 1 4
2 2 5
4 3 6
>>> df.rename_axis({"A": "ehh", "C": "see"}, axis="columns") # mapping
ehh B
0 1 4
1 2 5
2 3 6
"""
is_scalar_or_list = (
(not com.is_sequence(mapper) and not callable(mapper)) or
(com.is_list_like(mapper) and not com.is_dict_like(mapper))
)

if is_scalar_or_list:
return self._set_axis_name(mapper, axis=axis)
else:
axis = self._get_axis_name(axis)
d = {'copy': copy, 'inplace': inplace}
d[axis] = mapper
return self.rename(**d)

def _set_axis_name(self, name, axis=0):
"""
axis = self._get_axis_name(axis)
d = {'copy': copy, 'inplace': inplace}
d[axis] = mapper
return self.rename(**d)
Alter the name or names of the axis, returning self.
Parameters
----------
name : str or list of str
Name for the Index, or list of names for the MultiIndex
axis : int or str
0 or 'index' for the index; 1 or 'columns' for the columns
Returns
-------
renamed : type of caller
See Also
--------
pandas.DataFrame.rename
pandas.Series.rename
pandas.Index.rename
Examples
--------
>>> df._set_axis_name("foo")
A
foo
0 1
1 2
2 3
>>> df.index = pd.MultiIndex.from_product([['A'], ['a', 'b', 'c']])
>>> df._set_axis_name(["bar", "baz"])
A
bar baz
A a 1
b 2
c 3
"""
axis = self._get_axis_number(axis)
idx = self._get_axis(axis).set_names(name)

renamed = self.copy(deep=True)
renamed.set_axis(axis, idx)
return renamed

# ----------------------------------------------------------------------
# Comparisons
Expand Down
21 changes: 21 additions & 0 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import types
import warnings
from collections import MutableMapping

from numpy import nan, ndarray
import numpy as np
Expand Down Expand Up @@ -1109,6 +1110,20 @@ def to_sparse(self, kind='block', fill_value=None):
return SparseSeries(self, kind=kind,
fill_value=fill_value).__finalize__(self)

def _set_name(self, name, inplace=False):
'''
Set the Series name.
Parameters
----------
name : str
inplace : bool
whether to modify `self` directly or return a copy
'''
ser = self if inplace else self.copy()
ser.name = name
return ser

# ----------------------------------------------------------------------
# Statistics, overridden ndarray methods

Expand Down Expand Up @@ -2313,6 +2328,12 @@ def align(self, other, join='outer', axis=None, level=None, copy=True,

@Appender(generic._shared_docs['rename'] % _shared_doc_kwargs)
def rename(self, index=None, **kwargs):
is_scalar_or_list = (
(not com.is_sequence(index) and not callable(index)) or
(com.is_list_like(index) and not isinstance(index, MutableMapping))
)
if is_scalar_or_list:
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)
Expand Down
23 changes: 23 additions & 0 deletions pandas/tests/series/test_alter_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,29 @@ def test_rename(self):
renamed = renamer.rename({})
self.assertEqual(renamed.index.name, renamer.index.name)

def test_rename_set_name(self):
s = Series(range(4), index=list('abcd'))
for name in ['foo', ['foo'], ('foo',)]:
result = s.rename(name)
self.assertEqual(result.name, name)
self.assert_numpy_array_equal(result.index.values, s.index.values)
self.assertTrue(s.name is None)

def test_rename_set_name_inplace(self):
s = Series(range(3), index=list('abc'))
for name in ['foo', ['foo'], ('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(self):
s = Series([1, 2, 3])
s2 = s._set_name('foo')
self.assertEqual(s2.name, 'foo')
self.assertTrue(s.name is None)
self.assertTrue(s is not s2)

def test_rename_inplace(self):
renamer = lambda x: x.strftime('%Y%m%d')
expected = renamer(self.ts.index[0])
Expand Down
11 changes: 11 additions & 0 deletions pandas/tests/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,17 @@ def test_is_list_like():
assert not com.is_list_like(f)


def test_is_dict_like():
passes = [{}, {'A': 1}, pd.Series([1])]
fails = ['1', 1, [1, 2], (1, 2), range(2), pd.Index([1])]

for p in passes:
assert com.is_dict_like(p)

for f in fails:
assert not com.is_dict_like(f)


def test_is_named_tuple():
passes = (collections.namedtuple('Test', list('abc'))(1, 2, 3), )
fails = ((1, 2, 3), 'a', Series({'pi': 3.14}))
Expand Down
Loading

0 comments on commit b36df76

Please sign in to comment.