Skip to content

Commit

Permalink
API: Added axis argument to rename, reindex (pandas-dev#17800)
Browse files Browse the repository at this point in the history
* API: Added axis argument to rename

xref: pandas-dev#12392

* API: Accept 'axis' keyword argument for reindex
  • Loading branch information
TomAugspurger authored and alanbato committed Nov 10, 2017
1 parent 9d4bcf5 commit ae3a18a
Show file tree
Hide file tree
Showing 9 changed files with 529 additions and 18 deletions.
24 changes: 22 additions & 2 deletions doc/source/basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1217,6 +1217,15 @@ following can be done:
This means that the reindexed Series's index is the same Python object as the
DataFrame's index.

.. versionadded:: 0.21.0

:meth:`DataFrame.reindex` also supports an "axis-style" calling convention,
where you specify a single ``labels`` argument and the ``axis`` it applies to.

.. ipython:: python
df.reindex(['c', 'f', 'b'], axis='index')
df.reindex(['three', 'two', 'one'], axis='columns')
.. seealso::

Expand Down Expand Up @@ -1413,12 +1422,23 @@ Series can also be used:

.. ipython:: python
df.rename(columns={'one' : 'foo', 'two' : 'bar'},
index={'a' : 'apple', 'b' : 'banana', 'd' : 'durian'})
df.rename(columns={'one': 'foo', 'two': 'bar'},
index={'a': 'apple', 'b': 'banana', 'd': 'durian'})
If the mapping doesn't include a column/index label, it isn't renamed. Also
extra labels in the mapping don't throw an error.

.. versionadded:: 0.21.0

:meth:`DataFrame.rename` also supports an "axis-style" calling convention, where
you specify a single ``mapper`` and the ``axis`` to apply that mapping to.

.. ipython:: python
df.rename({'one': 'foo', 'two': 'bar'}, axis='columns'})
df.rename({'a': 'apple', 'b': 'banana', 'd': 'durian'}, axis='columns'})
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.
Expand Down
34 changes: 34 additions & 0 deletions doc/source/whatsnew/v0.21.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,40 @@ For example:
# the following is now equivalent
df.drop(columns=['B', 'C'])

.. _whatsnew_0210.enhancements.rename_reindex_axis:

``rename``, ``reindex`` now also accept axis keyword
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The :meth:`DataFrame.rename` and :meth:`DataFrame.reindex` methods have gained
the ``axis`` keyword to specify the axis to target with the operation
(:issue:`12392`).

Here's ``rename``:

.. ipython:: python

df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
df.rename(str.lower, axis='columns')
df.rename(id, axis='index')

And ``reindex``:

.. ipython:: python

df.reindex(['A', 'B', 'C'], axis='columns')
df.reindex([0, 1, 3], axis='index')

The "index, columns" style continues to work as before.

.. ipython:: python

df.rename(index=id, columns=str.lower)
df.reindex(index=[0, 1, 3], columns=['A', 'B', 'C'])

We *highly* encourage using named arguments to avoid confusion when using either
style.

.. _whatsnew_0210.enhancements.categorical_dtype:

``CategoricalDtype`` for specifying categoricals
Expand Down
136 changes: 132 additions & 4 deletions pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
_values_from_object,
_maybe_box_datetimelike,
_dict_compat,
_all_not_none,
standardize_mapping)
from pandas.core.generic import NDFrame, _shared_docs
from pandas.core.index import (Index, MultiIndex, _ensure_index,
Expand Down Expand Up @@ -111,7 +112,13 @@
optional_by="""
by : str or list of str
Name or list of names which refer to the axis items.""",
versionadded_to_excel='')
versionadded_to_excel='',
optional_labels="""labels : array-like, optional
New labels / index to conform the axis specified by 'axis' to.""",
optional_axis="""axis : int or str, optional
Axis to target. Can be either the axis name ('index', 'columns')
or number (0, 1).""",
)

_numeric_only_doc = """numeric_only : boolean, default None
Include only float, int, boolean data. If None, will attempt to use
Expand Down Expand Up @@ -2776,6 +2783,47 @@ def reindexer(value):

return np.atleast_2d(np.asarray(value))

def _validate_axis_style_args(self, arg, arg_name, index, columns,
axis, method_name):
if axis is not None:
# Using "axis" style, along with a positional arg
# Both index and columns should be None then
axis = self._get_axis_name(axis)
if index is not None or columns is not None:
msg = (
"Can't specify both 'axis' and 'index' or 'columns'. "
"Specify either\n"
"\t.{method_name}.rename({arg_name}, axis=axis), or\n"
"\t.{method_name}.rename(index=index, columns=columns)"
).format(arg_name=arg_name, method_name=method_name)
raise TypeError(msg)
if axis == 'index':
index = arg
elif axis == 'columns':
columns = arg

elif _all_not_none(arg, index, columns):
msg = (
"Cannot specify all of '{arg_name}', 'index', and 'columns'. "
"Specify either {arg_name} and 'axis', or 'index' and "
"'columns'."
).format(arg_name=arg_name)
raise TypeError(msg)

elif _all_not_none(arg, index):
# This is the "ambiguous" case, so emit a warning
msg = (
"Interpreting call to '.{method_name}(a, b)' as "
"'.{method_name}(index=a, columns=b)'. "
"Use keyword arguments to remove any ambiguity."
).format(method_name=method_name)
warnings.warn(msg, stacklevel=3)
index, columns = arg, index
elif index is None:
# This is for the default axis, like reindex([0, 1])
index = arg
return index, columns

@property
def _series(self):
result = {}
Expand Down Expand Up @@ -2902,7 +2950,11 @@ def align(self, other, join='outer', axis=None, level=None, copy=True,
broadcast_axis=broadcast_axis)

@Appender(_shared_docs['reindex'] % _shared_doc_kwargs)
def reindex(self, index=None, columns=None, **kwargs):
def reindex(self, labels=None, index=None, columns=None, axis=None,
**kwargs):
index, columns = self._validate_axis_style_args(labels, 'labels',
index, columns,
axis, 'reindex')
return super(DataFrame, self).reindex(index=index, columns=columns,
**kwargs)

Expand All @@ -2914,8 +2966,84 @@ def reindex_axis(self, labels, axis=0, method=None, level=None, copy=True,
method=method, level=level, copy=copy,
limit=limit, fill_value=fill_value)

@Appender(_shared_docs['rename'] % _shared_doc_kwargs)
def rename(self, index=None, columns=None, **kwargs):
def rename(self, mapper=None, index=None, columns=None, axis=None,
**kwargs):
"""Alter axes labels.
Function / dict values must be unique (1-to-1). Labels not contained in
a dict / Series will be left as-is. Extra labels listed don't throw an
error.
See the :ref:`user guide <basics.rename>` for more.
Parameters
----------
mapper, index, columns : dict-like or function, optional
dict-like or functions transformations to apply to
that axis' values. Use either ``mapper`` and ``axis`` to
specify the axis to target with ``mapper``, or ``index`` and
``columns``.
axis : int or str, optional
Axis to target with ``mapper``. Can be either the axis name
('index', 'columns') or number (0, 1). The default is 'index'.
copy : boolean, default True
Also copy underlying data
inplace : boolean, default False
Whether to return a new %(klass)s. If True then value of copy is
ignored.
level : int or level name, default None
In case of a MultiIndex, only rename labels in the specified
level.
Returns
-------
renamed : DataFrame
See Also
--------
pandas.DataFrame.rename_axis
Examples
--------
``DataFrame.rename`` supports two calling conventions
* ``(index=index_mapper, columns=columns_mapper, ...)
* ``(mapper, axis={'index', 'columns'}, ...)
We *highly* recommend using keyword arguments to clarify your
intent.
>>> df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
>>> df.rename(index=str, columns={"A": "a", "B": "c"})
a c
0 1 4
1 2 5
2 3 6
>>> df.rename(index=str, columns={"A": "a", "C": "c"})
a B
0 1 4
1 2 5
2 3 6
Using axis-style parameters
>>> df.rename(str.lower, axis='columns')
a b
0 1 4
1 2 5
2 3 6
>>> df.rename({1: 2, 2: 4}, axis='index')
A B
0 1 4
2 2 5
4 3 6
"""
index, columns = self._validate_axis_style_args(mapper, 'mapper',
index, columns,
axis, 'rename')
return super(DataFrame, self).rename(index=index, columns=columns,
**kwargs)

Expand Down
Loading

0 comments on commit ae3a18a

Please sign in to comment.