Skip to content

ENH: Add errors parameter to DataFrame.rename #25535

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 5, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v0.25.0.rst
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ Other Enhancements
- ``Series.str`` has gained :meth:`Series.str.casefold` method to removes all case distinctions present in a string (:issue:`25405`)
- :meth:`DataFrame.set_index` now works for instances of ``abc.Iterator``, provided their output is of the same length as the calling frame (:issue:`22484`, :issue:`24984`)
- :meth:`DatetimeIndex.union` now supports the ``sort`` argument. The behaviour of the sort parameter matches that of :meth:`Index.union` (:issue:`24994`)
-
- :meth:`DataFrame.rename` now supports the ``errors`` argument to raise errors when attempting to rename nonexistent keys (:issue:`13473`)

.. _whatsnew_0250.api_breaking:

40 changes: 32 additions & 8 deletions pandas/core/frame.py
Original file line number Diff line number Diff line change
@@ -3911,7 +3911,8 @@ def drop(self, labels=None, axis=0, index=None, columns=None,

@rewrite_axis_style_signature('mapper', [('copy', True),
('inplace', False),
('level', None)])
('level', None),
('errors', 'ignore')])
def rename(self, *args, **kwargs):
"""
Alter axes labels.
@@ -3924,30 +3925,49 @@ def rename(self, *args, **kwargs):

Parameters
----------
mapper, index, columns : dict-like or function, optional
dict-like or functions transformations to apply to
mapper : dict-like or function
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
index : dict-like or function
Alternative to specifying axis (``mapper, axis=0``
is equivalent to ``index=mapper``).
columns : dict-like or function
Alternative to specifying axis (``mapper, axis=1``
is equivalent to ``columns=mapper``).
axis : int or str
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
copy : bool, default True
Also copy underlying data.
inplace : bool, default False
Whether to return a new DataFrame. 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.
errors : {'ignore', 'raise'}, default 'ignore'
If 'raise', raise a `KeyError` when a dict-like `mapper`, `index`,
or `columns` contains labels that are not present in the Index
being transformed.
If 'ignore', existing keys will be renamed and extra keys will be
ignored.

Returns
-------
DataFrame
DataFrame with the renamed axis labels.

Raises
------
KeyError
If any of the labels is not found in the selected axis and
"errors='raise'".

See Also
--------
DataFrame.rename_axis
DataFrame.rename_axis : Set the name of the axis.

Examples
--------
@@ -3973,6 +3993,10 @@ def rename(self, *args, **kwargs):
1 2 5
2 3 6

>>> df.rename(index=str, columns={"A": "a", "C": "c"}, errors="raise")
Traceback (most recent call last):
KeyError: ['C'] not found in axis

Using axis-style parameters

>>> df.rename(str.lower, axis='columns')
24 changes: 23 additions & 1 deletion pandas/core/generic.py
Original file line number Diff line number Diff line change
@@ -981,11 +981,23 @@ def rename(self, *args, **kwargs):
level : int or level name, default None
In case of a MultiIndex, only rename labels in the specified
level.
errors : {'ignore', 'raise'}, default 'ignore'
If 'raise', raise a `KeyError` when a dict-like `mapper`, `index`,
or `columns` contains labels that are not present in the Index
being transformed.
If 'ignore', existing keys will be renamed and extra keys will be
ignored.

Returns
-------
renamed : %(klass)s (new object)

Raises
------
KeyError
If any of the labels is not found in the selected axis and
"errors='raise'".

See Also
--------
NDFrame.rename_axis
@@ -1065,6 +1077,7 @@ def rename(self, *args, **kwargs):
inplace = kwargs.pop('inplace', False)
level = kwargs.pop('level', None)
axis = kwargs.pop('axis', None)
errors = kwargs.pop('errors', 'ignore')
if axis is not None:
# Validate the axis
self._get_axis_number(axis)
@@ -1085,10 +1098,19 @@ def rename(self, *args, **kwargs):
if v is None:
continue
f = com._get_rename_function(v)

baxis = self._get_block_manager_axis(axis)
if level is not None:
level = self.axes[axis]._get_level_number(level)

# GH 13473
if not callable(v):
indexer = self.axes[axis].get_indexer_for(v)
if errors == 'raise' and len(indexer[indexer == -1]):
missing_labels = [label for index, label in enumerate(v)
if indexer[index] == -1]
raise KeyError('{} not found in axis'
.format(missing_labels))

result._data = result._data.rename_axis(f, axis=baxis, copy=copy,
level=level)
result._clear_item_cache()
19 changes: 18 additions & 1 deletion pandas/tests/frame/test_alter_axes.py
Original file line number Diff line number Diff line change
@@ -871,6 +871,23 @@ def test_rename_bug2(self):
columns=["a"])
tm.assert_frame_equal(df, expected)

def test_rename_errors_raises(self):
df = DataFrame(columns=['A', 'B', 'C', 'D'])
with pytest.raises(KeyError, match='\'E\'] not found in axis'):
df.rename(columns={'A': 'a', 'E': 'e'}, errors='raise')

@pytest.mark.parametrize('mapper, errors, expected_columns', [
({'A': 'a', 'E': 'e'}, 'ignore', ['a', 'B', 'C', 'D']),
({'A': 'a'}, 'raise', ['a', 'B', 'C', 'D']),
(str.lower, 'raise', ['a', 'b', 'c', 'd'])])
def test_rename_errors(self, mapper, errors, expected_columns):
# GH 13473
# rename now works with errors parameter
df = DataFrame(columns=['A', 'B', 'C', 'D'])
result = df.rename(columns=mapper, errors=errors)
expected = DataFrame(columns=expected_columns)
tm.assert_frame_equal(result, expected)

def test_reorder_levels(self):
index = MultiIndex(levels=[['bar'], ['one', 'two', 'three'], [0, 1]],
codes=[[0, 0, 0, 0, 0, 0],
@@ -1328,7 +1345,7 @@ def test_rename_signature(self):
sig = inspect.signature(DataFrame.rename)
parameters = set(sig.parameters)
assert parameters == {"self", "mapper", "index", "columns", "axis",
"inplace", "copy", "level"}
"inplace", "copy", "level", "errors"}

@pytest.mark.skipif(PY2, reason="inspect.signature")
def test_reindex_signature(self):