-
-
Notifications
You must be signed in to change notification settings - Fork 17.9k
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
ENH: Allow rename_axis to specify index and columns arguments #20046
Changes from 15 commits
bb12644
99011df
4b45493
5ff65a6
36e5412
a14e6a1
c8d6604
5806d9a
f28246f
2f53685
5fd1ddf
3e8aaf3
5ec7748
5c9c3ce
0542cf0
39938a1
65e3733
e965c5b
1263a47
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -454,3 +454,21 @@ def _pipe(obj, func, *args, **kwargs): | |
return func(*args, **kwargs) | ||
else: | ||
return func(obj, *args, **kwargs) | ||
|
||
|
||
def get_rename_function(mapper): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you make this private (e.g. _get_rename_function) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
""" | ||
Returns a function that will map names/labels, dependent if mapper | ||
is a dict, Series or just a function. | ||
""" | ||
if isinstance(mapper, (dict, ABCSeries)): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
def f(x): | ||
if x in mapper: | ||
return mapper[x] | ||
TomAugspurger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
else: | ||
return x | ||
else: | ||
f = mapper | ||
|
||
return f |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1080,20 +1080,6 @@ def rename(self, *args, **kwargs): | |
if com.count_not_none(*axes.values()) == 0: | ||
raise TypeError('must pass an index to rename') | ||
|
||
# renamer function if passed a dict | ||
def _get_rename_function(mapper): | ||
if isinstance(mapper, (dict, ABCSeries)): | ||
|
||
def f(x): | ||
if x in mapper: | ||
return mapper[x] | ||
else: | ||
return x | ||
else: | ||
f = mapper | ||
|
||
return f | ||
|
||
self._consolidate_inplace() | ||
result = self if inplace else self.copy(deep=copy) | ||
|
||
|
@@ -1102,7 +1088,7 @@ def f(x): | |
v = axes.get(self._AXIS_NAMES[axis]) | ||
if v is None: | ||
continue | ||
f = _get_rename_function(v) | ||
f = com.get_rename_function(v) | ||
|
||
baxis = self._get_block_manager_axis(axis) | ||
if level is not None: | ||
|
@@ -1116,16 +1102,25 @@ def f(x): | |
else: | ||
return result.__finalize__(self) | ||
|
||
def rename_axis(self, mapper, axis=0, copy=True, inplace=False): | ||
def rename_axis(self, mapper=None, **kwargs): | ||
TomAugspurger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
Alter the name of the index or columns. | ||
Alter the name of the index or name of index backing the columns. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand this change, is it a typo? Oh, I think I saying "alter the name of the Index object that is the columns"? If so, I think that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will use "alter the name of the Index object that is the columns." |
||
|
||
Parameters | ||
---------- | ||
mapper : scalar, list-like, optional | ||
Value to set as the axis name attribute. | ||
axis : {0 or 'index', 1 or 'columns'}, default 0 | ||
The index or the name of the axis. | ||
Value to set the axis name attribute. | ||
index, columns : scalar, list-like, 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/or ``columns``. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add a versionchanged There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
.. versionchanged:: 0.24.0 | ||
|
||
axis : int or string, default 0 | ||
copy : boolean, default True | ||
Also copy underlying data. | ||
inplace : boolean, default False | ||
|
@@ -1144,6 +1139,23 @@ def rename_axis(self, mapper, axis=0, copy=True, inplace=False): | |
deprecated and will be removed in a future version. Use ``rename`` | ||
instead. | ||
|
||
``DataFrame.rename_axis`` supports two calling conventions | ||
|
||
* ``(index=index_mapper, columns=columns_mapper, ...)`` | ||
* ``(mapper, axis={'index', 'columns'}, ...)`` | ||
|
||
The first calling convention will only modify the names of | ||
the index and/or the names of the index backing the columns. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same: index -> Index, or just remove. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. changed to "names of the Index object that is the columns." |
||
In this case, the parameter ``copy`` is ignored. | ||
|
||
The second calling convention will modify the names of the | ||
the corresponding index if mapper is a list or a scalar. | ||
However, if mapper is dict-like or a function, it will use the | ||
deprecated behavior of modifying the axis *labels*. | ||
|
||
We *highly* recommend using keyword arguments to clarify your | ||
intent. | ||
|
||
See Also | ||
-------- | ||
pandas.Series.rename : Alter Series index labels or name | ||
|
@@ -1177,20 +1189,94 @@ def rename_axis(self, mapper, axis=0, copy=True, inplace=False): | |
0 1 4 | ||
1 2 5 | ||
2 3 6 | ||
""" | ||
|
||
>>> mi = pd.MultiIndex.from_product([['a', 'b', 'c'], [1, 2]], | ||
... names=['let','num']) | ||
>>> df = pd.DataFrame({'x': [i for i in range(len(mi))], | ||
... 'y' : [i*10 for i in range(len(mi))]}, | ||
... index=mi) | ||
>>> df.rename_axis(index={'num' : 'n'}) | ||
x y | ||
let n | ||
a 1 0 0 | ||
2 1 10 | ||
b 1 2 20 | ||
2 3 30 | ||
c 1 4 40 | ||
2 5 50 | ||
|
||
>>> cdf = df.rename_axis(columns='col') | ||
>>> cdf | ||
col x y | ||
let num | ||
a 1 0 0 | ||
2 1 10 | ||
b 1 2 20 | ||
2 3 30 | ||
c 1 4 40 | ||
2 5 50 | ||
|
||
>>> cdf.rename_axis(columns=str.upper) | ||
COL x y | ||
let num | ||
a 1 0 0 | ||
2 1 10 | ||
b 1 2 20 | ||
2 3 30 | ||
c 1 4 40 | ||
2 5 50 | ||
|
||
""" | ||
axes, kwargs = self._construct_axes_from_arguments((), kwargs) | ||
copy = kwargs.pop('copy', True) | ||
inplace = kwargs.pop('inplace', False) | ||
axis = kwargs.pop('axis', 0) | ||
if axis is not None: | ||
axis = self._get_axis_number(axis) | ||
|
||
if kwargs: | ||
raise TypeError('rename_axis() got an unexpected keyword ' | ||
'argument "{0}"'.format(list(kwargs.keys())[0])) | ||
|
||
inplace = validate_bool_kwarg(inplace, 'inplace') | ||
non_mapper = is_scalar(mapper) or (is_list_like(mapper) and not | ||
is_dict_like(mapper)) | ||
if non_mapper: | ||
return self._set_axis_name(mapper, axis=axis, inplace=inplace) | ||
|
||
if (mapper is not None): | ||
# Use v0.23 behavior if a scalar or list | ||
non_mapper = is_scalar(mapper) or (is_list_like(mapper) and not | ||
is_dict_like(mapper)) | ||
if non_mapper: | ||
return self._set_axis_name(mapper, axis=axis, inplace=inplace) | ||
else: | ||
# Deprecated (v0.21) behavior is if mapper is specified, | ||
# and not a list or scalar, then call rename | ||
msg = ("Using 'rename_axis' to alter labels is deprecated. " | ||
"Use '.rename' instead") | ||
warnings.warn(msg, FutureWarning, stacklevel=2) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think if you increase stack level by 1, you can revert your changes to the tests below. The stack level increased by 1 since you're using the decorator to fix the function signature. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the tip. It worked. |
||
axis = self._get_axis_name(axis) | ||
d = {'copy': copy, 'inplace': inplace} | ||
d[axis] = mapper | ||
return self.rename(**d) | ||
else: | ||
msg = ("Using 'rename_axis' to alter labels is deprecated. " | ||
"Use '.rename' instead") | ||
warnings.warn(msg, FutureWarning, stacklevel=2) | ||
axis = self._get_axis_name(axis) | ||
d = {'copy': copy, 'inplace': inplace} | ||
d[axis] = mapper | ||
return self.rename(**d) | ||
# Use new behavior. Means that index and/or columns | ||
# is specified | ||
result = self if inplace else self.copy(deep=copy) | ||
|
||
for axis in lrange(self._AXIS_LEN): | ||
v = axes.get(self._AXIS_NAMES[axis]) | ||
if v is None: | ||
continue | ||
non_mapper = is_scalar(v) or (is_list_like(v) and not | ||
is_dict_like(v)) | ||
if non_mapper: | ||
newnames = v | ||
else: | ||
f = com.get_rename_function(v) | ||
curnames = self._get_axis(axis).names | ||
newnames = [f(name) for name in curnames] | ||
result._set_axis_name(newnames, axis=axis, | ||
inplace=True) | ||
if not inplace: | ||
return result | ||
|
||
def _set_axis_name(self, name, axis=0, inplace=False): | ||
""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -538,6 +538,44 @@ def test_rename_axis_warns(self): | |
df['A'].rename_axis(id) | ||
assert 'rename' in str(w[0].message) | ||
|
||
def test_rename_axis_mapper(self): | ||
# GH 19978 | ||
mi = MultiIndex.from_product([['a', 'b', 'c'], [1, 2]], | ||
names=['ll', 'nn']) | ||
df = DataFrame({'x': [i for i in range(len(mi))], | ||
'y': [i * 10 for i in range(len(mi))]}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you sprinkle some comments to delineate the cases you are testing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
index=mi) | ||
result = df.rename_axis('cols', axis=1) | ||
tm.assert_index_equal(result.columns, | ||
Index(['x', 'y'], name='cols')) | ||
|
||
result = result.rename_axis(columns={'cols': 'new'}, axis=1) | ||
tm.assert_index_equal(result.columns, | ||
Index(['x', 'y'], name='new')) | ||
|
||
result = df.rename_axis(index={'ll': 'foo'}) | ||
assert result.index.names == ['foo', 'nn'] | ||
|
||
result = df.rename_axis(index=str.upper, axis=0) | ||
assert result.index.names == ['LL', 'NN'] | ||
|
||
result = df.rename_axis(index=['foo', 'goo']) | ||
assert result.index.names == ['foo', 'goo'] | ||
|
||
sdf = df.reset_index().set_index('nn').drop(columns=['ll', 'y']) | ||
result = sdf.rename_axis(index='foo', columns='meh') | ||
assert result.index.name == 'foo' | ||
assert result.columns.name == 'meh' | ||
|
||
with tm.assert_raises_regex(TypeError, 'Must pass'): | ||
df.rename_axis(index='wrong') | ||
|
||
with tm.assert_raises_regex(ValueError, 'Length of names'): | ||
df.rename_axis(index=['wrong']) | ||
|
||
with tm.assert_raises_regex(TypeError, 'bogus'): | ||
df.rename_axis(bogus=None) | ||
|
||
def test_rename_multiindex(self): | ||
|
||
tuples_index = [('foo1', 'bar1'), ('foo2', 'bar2')] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you make this pep-y, iow spaces after commas (you can also line up the x and y columns on separate lines)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done