Skip to content

Commit 615fbb3

Browse files
MaxVanDeursenTomAugspurger
authored andcommitted
ENH: Add errors parameter to DataFrame.rename (#25535)
* ENH: GH13473 Add errors parameter to DataFrame.rename
1 parent 221be3b commit 615fbb3

File tree

4 files changed

+74
-11
lines changed

4 files changed

+74
-11
lines changed

doc/source/whatsnew/v0.25.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Other Enhancements
2525
- ``Series.str`` has gained :meth:`Series.str.casefold` method to removes all case distinctions present in a string (:issue:`25405`)
2626
- :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`)
2727
- :meth:`DatetimeIndex.union` now supports the ``sort`` argument. The behaviour of the sort parameter matches that of :meth:`Index.union` (:issue:`24994`)
28-
-
28+
- :meth:`DataFrame.rename` now supports the ``errors`` argument to raise errors when attempting to rename nonexistent keys (:issue:`13473`)
2929

3030
.. _whatsnew_0250.api_breaking:
3131

pandas/core/frame.py

+32-8
Original file line numberDiff line numberDiff line change
@@ -3911,7 +3911,8 @@ def drop(self, labels=None, axis=0, index=None, columns=None,
39113911

39123912
@rewrite_axis_style_signature('mapper', [('copy', True),
39133913
('inplace', False),
3914-
('level', None)])
3914+
('level', None),
3915+
('errors', 'ignore')])
39153916
def rename(self, *args, **kwargs):
39163917
"""
39173918
Alter axes labels.
@@ -3924,30 +3925,49 @@ def rename(self, *args, **kwargs):
39243925
39253926
Parameters
39263927
----------
3927-
mapper, index, columns : dict-like or function, optional
3928-
dict-like or functions transformations to apply to
3928+
mapper : dict-like or function
3929+
Dict-like or functions transformations to apply to
39293930
that axis' values. Use either ``mapper`` and ``axis`` to
39303931
specify the axis to target with ``mapper``, or ``index`` and
39313932
``columns``.
3932-
axis : int or str, optional
3933+
index : dict-like or function
3934+
Alternative to specifying axis (``mapper, axis=0``
3935+
is equivalent to ``index=mapper``).
3936+
columns : dict-like or function
3937+
Alternative to specifying axis (``mapper, axis=1``
3938+
is equivalent to ``columns=mapper``).
3939+
axis : int or str
39333940
Axis to target with ``mapper``. Can be either the axis name
39343941
('index', 'columns') or number (0, 1). The default is 'index'.
3935-
copy : boolean, default True
3936-
Also copy underlying data
3937-
inplace : boolean, default False
3942+
copy : bool, default True
3943+
Also copy underlying data.
3944+
inplace : bool, default False
39383945
Whether to return a new DataFrame. If True then value of copy is
39393946
ignored.
39403947
level : int or level name, default None
39413948
In case of a MultiIndex, only rename labels in the specified
39423949
level.
3950+
errors : {'ignore', 'raise'}, default 'ignore'
3951+
If 'raise', raise a `KeyError` when a dict-like `mapper`, `index`,
3952+
or `columns` contains labels that are not present in the Index
3953+
being transformed.
3954+
If 'ignore', existing keys will be renamed and extra keys will be
3955+
ignored.
39433956
39443957
Returns
39453958
-------
39463959
DataFrame
3960+
DataFrame with the renamed axis labels.
3961+
3962+
Raises
3963+
------
3964+
KeyError
3965+
If any of the labels is not found in the selected axis and
3966+
"errors='raise'".
39473967
39483968
See Also
39493969
--------
3950-
DataFrame.rename_axis
3970+
DataFrame.rename_axis : Set the name of the axis.
39513971
39523972
Examples
39533973
--------
@@ -3973,6 +3993,10 @@ def rename(self, *args, **kwargs):
39733993
1 2 5
39743994
2 3 6
39753995
3996+
>>> df.rename(index=str, columns={"A": "a", "C": "c"}, errors="raise")
3997+
Traceback (most recent call last):
3998+
KeyError: ['C'] not found in axis
3999+
39764000
Using axis-style parameters
39774001
39784002
>>> df.rename(str.lower, axis='columns')

pandas/core/generic.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -981,11 +981,23 @@ def rename(self, *args, **kwargs):
981981
level : int or level name, default None
982982
In case of a MultiIndex, only rename labels in the specified
983983
level.
984+
errors : {'ignore', 'raise'}, default 'ignore'
985+
If 'raise', raise a `KeyError` when a dict-like `mapper`, `index`,
986+
or `columns` contains labels that are not present in the Index
987+
being transformed.
988+
If 'ignore', existing keys will be renamed and extra keys will be
989+
ignored.
984990
985991
Returns
986992
-------
987993
renamed : %(klass)s (new object)
988994
995+
Raises
996+
------
997+
KeyError
998+
If any of the labels is not found in the selected axis and
999+
"errors='raise'".
1000+
9891001
See Also
9901002
--------
9911003
NDFrame.rename_axis
@@ -1065,6 +1077,7 @@ def rename(self, *args, **kwargs):
10651077
inplace = kwargs.pop('inplace', False)
10661078
level = kwargs.pop('level', None)
10671079
axis = kwargs.pop('axis', None)
1080+
errors = kwargs.pop('errors', 'ignore')
10681081
if axis is not None:
10691082
# Validate the axis
10701083
self._get_axis_number(axis)
@@ -1085,10 +1098,19 @@ def rename(self, *args, **kwargs):
10851098
if v is None:
10861099
continue
10871100
f = com._get_rename_function(v)
1088-
10891101
baxis = self._get_block_manager_axis(axis)
10901102
if level is not None:
10911103
level = self.axes[axis]._get_level_number(level)
1104+
1105+
# GH 13473
1106+
if not callable(v):
1107+
indexer = self.axes[axis].get_indexer_for(v)
1108+
if errors == 'raise' and len(indexer[indexer == -1]):
1109+
missing_labels = [label for index, label in enumerate(v)
1110+
if indexer[index] == -1]
1111+
raise KeyError('{} not found in axis'
1112+
.format(missing_labels))
1113+
10921114
result._data = result._data.rename_axis(f, axis=baxis, copy=copy,
10931115
level=level)
10941116
result._clear_item_cache()

pandas/tests/frame/test_alter_axes.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,23 @@ def test_rename_bug2(self):
872872
columns=["a"])
873873
tm.assert_frame_equal(df, expected)
874874

875+
def test_rename_errors_raises(self):
876+
df = DataFrame(columns=['A', 'B', 'C', 'D'])
877+
with pytest.raises(KeyError, match='\'E\'] not found in axis'):
878+
df.rename(columns={'A': 'a', 'E': 'e'}, errors='raise')
879+
880+
@pytest.mark.parametrize('mapper, errors, expected_columns', [
881+
({'A': 'a', 'E': 'e'}, 'ignore', ['a', 'B', 'C', 'D']),
882+
({'A': 'a'}, 'raise', ['a', 'B', 'C', 'D']),
883+
(str.lower, 'raise', ['a', 'b', 'c', 'd'])])
884+
def test_rename_errors(self, mapper, errors, expected_columns):
885+
# GH 13473
886+
# rename now works with errors parameter
887+
df = DataFrame(columns=['A', 'B', 'C', 'D'])
888+
result = df.rename(columns=mapper, errors=errors)
889+
expected = DataFrame(columns=expected_columns)
890+
tm.assert_frame_equal(result, expected)
891+
875892
def test_reorder_levels(self):
876893
index = MultiIndex(levels=[['bar'], ['one', 'two', 'three'], [0, 1]],
877894
codes=[[0, 0, 0, 0, 0, 0],
@@ -1329,7 +1346,7 @@ def test_rename_signature(self):
13291346
sig = inspect.signature(DataFrame.rename)
13301347
parameters = set(sig.parameters)
13311348
assert parameters == {"self", "mapper", "index", "columns", "axis",
1332-
"inplace", "copy", "level"}
1349+
"inplace", "copy", "level", "errors"}
13331350

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

0 commit comments

Comments
 (0)