Skip to content

Commit f7c867e

Browse files
author
tp
committed
Categorical.rename_categories can take a callable
1 parent 07d8c2d commit f7c867e

File tree

5 files changed

+38
-3
lines changed

5 files changed

+38
-3
lines changed

doc/source/whatsnew/v0.22.0.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ Other Enhancements
139139
- :func:`read_excel()` has gained the ``nrows`` parameter (:issue:`16645`)
140140
- :func:``DataFrame.to_json`` and ``Series.to_json`` now accept an ``index`` argument which allows the user to exclude the index from the JSON output (:issue:`17394`)
141141
- ``IntervalIndex.to_tuples()`` has gained the ``na_tuple`` parameter to control whether NA is returned as a tuple of NA, or NA itself (:issue:`18756`)
142+
- ``Categorical.rename_categories``, ``CategoricalIndex.rename_categories`` and :attr:`Series.cat.rename_categories`
143+
can how take a callable as their argument (:issue:`18862`)
142144

143145
.. _whatsnew_0220.api_breaking:
144146

pandas/core/categorical.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -844,15 +844,22 @@ def rename_categories(self, new_categories, inplace=False):
844844
845845
Parameters
846846
----------
847-
new_categories : list-like or dict-like
847+
new_categories : list-like, dict-like or callable
848848
849849
* list-like: all items must be unique and the number of items in
850850
the new categories must match the existing number of categories.
851851
852852
* dict-like: specifies a mapping from
853853
old categories to new. Categories not contained in the mapping
854854
are passed through and extra categories in the mapping are
855-
ignored. *New in version 0.21.0*.
855+
ignored.
856+
857+
.. versionadded:: 0.21.0
858+
859+
* callable : a callable that is called on all items in the old
860+
categories and whose return values comprise the new categories.
861+
862+
.. versionadded:: 0.22.0
856863
857864
.. warning::
858865
@@ -890,6 +897,12 @@ def rename_categories(self, new_categories, inplace=False):
890897
>>> c.rename_categories({'a': 'A', 'c': 'C'})
891898
[A, A, b]
892899
Categories (2, object): [A, b]
900+
901+
You may also provide a callable to create the new categories
902+
903+
>>> c.rename_categories(lambda x: x.upper())
904+
[A, A, B]
905+
Categories (2, object): [A, B]
893906
"""
894907
inplace = validate_bool_kwarg(inplace, 'inplace')
895908
cat = self if inplace else self.copy()
@@ -906,6 +919,8 @@ def rename_categories(self, new_categories, inplace=False):
906919
if is_dict_like(new_categories):
907920
cat.categories = [new_categories.get(item, item)
908921
for item in cat.categories]
922+
elif callable(new_categories):
923+
cat.categories = [new_categories(item) for item in cat.categories]
909924
else:
910925
cat.categories = new_categories
911926
if not inplace:

pandas/tests/categorical/test_api.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,14 @@ def test_rename_categories(self):
7171

7272
exp_cat = Index(["a", "b", "c"])
7373
tm.assert_index_equal(cat.categories, exp_cat)
74-
res = cat.rename_categories([1, 2, 3], inplace=True)
74+
75+
# GH18862 (let rename_categories take callables)
76+
result = cat.rename_categories(lambda x: x.upper())
77+
expected = Categorical(["A", "B", "C", "A"])
78+
tm.assert_categorical_equal(result, expected)
7579

7680
# and now inplace
81+
res = cat.rename_categories([1, 2, 3], inplace=True)
7782
assert res is None
7883
tm.assert_numpy_array_equal(cat.__array__(), np.array([1, 2, 3, 1],
7984
dtype=np.int64))

pandas/tests/indexes/test_category.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,11 @@ def test_method_delegation(self):
185185
tm.assert_index_equal(result, CategoricalIndex(
186186
list('ffggef'), categories=list('efg')))
187187

188+
# GH18862 (let rename_categories take callables)
189+
result = ci.rename_categories(lambda x: x.upper())
190+
tm.assert_index_equal(result, CategoricalIndex(
191+
list('AABBCA'), categories=list('CAB')))
192+
188193
ci = CategoricalIndex(list('aabbca'), categories=list('cab'))
189194
result = ci.add_categories(['d'])
190195
tm.assert_index_equal(result, CategoricalIndex(

pandas/tests/series/test_api.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,14 @@ def f():
588588
pytest.raises(Exception, f)
589589
# right: s.cat.set_categories([4,3,2,1])
590590

591+
# GH18862 (let Series.cat.rename_categories take callables)
592+
s = Series(Categorical(["a", "b", "c", "a"], ordered=True))
593+
result = s.cat.rename_categories(lambda x: x.upper())
594+
expected = Series(Categorical(["A", "B", "C", "A"],
595+
categories=["A", "B", "C"],
596+
ordered=True))
597+
tm.assert_series_equal(result, expected)
598+
591599
def test_str_accessor_api_for_categorical(self):
592600
# https://github.com/pandas-dev/pandas/issues/10661
593601
from pandas.core.strings import StringMethods

0 commit comments

Comments
 (0)