Skip to content
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

API/ENH: relabel method #15104

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
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
3 changes: 3 additions & 0 deletions doc/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ Reindexing / Selection / Label manipulation
Series.reindex
Series.reindex_like
Series.rename
Series.relabel
Series.rename_axis
Series.reset_index
Series.sample
Expand Down Expand Up @@ -902,6 +903,7 @@ Reindexing / Selection / Label manipulation
DataFrame.reindex_axis
DataFrame.reindex_like
DataFrame.rename
DataFrame.relabel
DataFrame.rename_axis
DataFrame.reset_index
DataFrame.sample
Expand Down Expand Up @@ -1185,6 +1187,7 @@ Reindexing / Selection / Label manipulation
Panel.reindex_axis
Panel.reindex_like
Panel.rename
Panel.relabel
Panel.sample
Panel.select
Panel.take
Expand Down
13 changes: 13 additions & 0 deletions doc/source/basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,19 @@ for altering the ``Series.name`` attribute.

s.rename("scalar-name")

.. versionadded:: 0.20.0

The :meth:`~DataFrame.relabel` method allows you to relabel an axis by
assigning a new set labels, which must match the length of the original
axis.

.. ipython:: python

df = pd.DataFrame({'a': [1, 2], 'b': [3, 4]})
df
df.relabel(index=[5, 6], columns=['J', 'K'])


.. _basics.rename_axis:

The Panel class has a related :meth:`~Panel.rename_axis` class which can rename
Expand Down
17 changes: 17 additions & 0 deletions doc/source/indexing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1586,6 +1586,23 @@ If you create an index yourself, you can just assign it to the ``index`` field:

data.index = index

.. versionadded:: 0.20.0

Alternatively, the :meth:`~DataFrame.relabel` can be used to assign new
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these underlined? or is that the diff?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the diff - no clue why it looks like that though

labels to an existing index on an object.

.. ipython:: python
:suppress:

data = data.reset_index()

.. ipython:: python

data
data.relabel(index=['a', 'b', 'c', 'd'])
data.relabel(columns=['q', 'w', 'r', 't'])


.. _indexing.view_versus_copy:

Returning a view versus a copy
Expand Down
16 changes: 16 additions & 0 deletions doc/source/whatsnew/v0.20.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,22 @@ support for bz2 compression in the python 2 c-engine improved (:issue:`14874`).
df = pd.read_table(url, compression='bz2') # explicitly specify compression
df.head(2)

Changing axes with ``relabel``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

``Series`` and ``DataFrame`` have gained the ``relabel`` method for assigning
new labels to the index or columns (:issue:`14829`)

.. ipython:: python

s = pd.Series([1, 2], index=['a', 'b'])
s
s.relabel(index=[7, 8])

df = pd.DataFrame({'a': [1, 2], 'b': [3, 4]})
df
df.relabel(index=[5, 6], columns=['J', 'K'])

.. _whatsnew_0200.enhancements.other:

Other enhancements
Expand Down
5 changes: 5 additions & 0 deletions pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -2802,6 +2802,11 @@ def rename(self, index=None, columns=None, **kwargs):
return super(DataFrame, self).rename(index=index, columns=columns,
**kwargs)

@Appender(_shared_docs['relabel'] % _shared_doc_kwargs)
def relabel(self, index=None, columns=None, copy=True, inplace=False):
return super(DataFrame, self).relabel(index=index, columns=columns,
copy=copy, inplace=inplace)

@Appender(_shared_docs['fillna'] % _shared_doc_kwargs)
def fillna(self, value=None, method=None, axis=None, inplace=False,
limit=None, downcast=None, **kwargs):
Expand Down
88 changes: 88 additions & 0 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,94 @@ def f(x):

rename.__doc__ = _shared_docs['rename']

_shared_docs['relabel'] = """
Assign new axes based on list-like labels

.. versionadded:: 0.20.0

Parameters
----------
%(axes)s : list-like, optional
Labels to construct new axis from - number of labels
must match the length of the existing axis
copy : boolean, default True
Also copy underlying data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we have this flag (copy) on .rename as well. I think these are confusing, though not sure what a better API is for these. (and copy=True, inplace=True is pretty much meaningless.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, not sure - I agree it's confusing. I'd be tempted to deprecate copy altogether, though if you know what you're doing I suppose it's useful.

inplace : boolean, default False
Whether to return a new %(klass)s. If True then value of copy is
ignored.

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

See Also
--------
pandas.Series.rename
pandas.DataFrame.rename
pandas.Panel.rename

Examples
--------
>>> s = pd.Series([1, 2, 3])
>>> s
0 1
1 2
2 3
dtype: int64
>>> s.relabel(index=[4, 5, 6])
4 1
5 2
6 3
dtype: int64

>>> df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
>>> df
A B
0 1 4
1 2 5
2 3 6
>>> df.relabel(columns=['J', 'K'])
J K
0 1 4
1 2 5
2 3 6
>>> df.relabel(columns=['J', 'K'], index=[2, 3, 4])
J K
2 1 4
3 2 5
4 3 6
"""

@Appender(_shared_docs['relabel'] % dict(axes='axes keywords for this'
' object', klass='NDFrame'))
def relabel(self, *args, **kwargs):
axes, kwargs = self._construct_axes_from_arguments(args, kwargs)
copy = kwargs.pop('copy', True)
inplace = kwargs.pop('inplace', False)

if kwargs:
raise TypeError('relabel() got an unexpected keyword '
'argument "{0}"'.format(list(kwargs.keys())[0]))

if com._count_not_none(*axes.values()) == 0:
raise TypeError('must pass an index to relabel')

self._consolidate_inplace()
result = self if inplace else self.copy(deep=copy)

# start in the axis order to eliminate too many copies
for axis in lrange(self._AXIS_LEN):
v = axes.get(self._AXIS_NAMES[axis])
if v is None:
continue
baxis = self._get_block_manager_axis(axis)
result._set_axis(axis=baxis, labels=v)

if inplace:
self._update_inplace(result._data)
else:
return result.__finalize__(self)

def rename_axis(self, mapper, axis=0, copy=True, inplace=False):
"""
Alter index and / or columns using input function or functions.
Expand Down
9 changes: 9 additions & 0 deletions pandas/core/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,15 @@ def rename(self, items=None, major_axis=None, minor_axis=None, **kwargs):
return super(Panel, self).rename(items=items, major_axis=major_axis,
minor_axis=minor_axis, **kwargs)

@Appender(_shared_docs['relabel'] % _shared_doc_kwargs)
def relabel(self, items=None, major_axis=None, minor_axis=None, **kwargs):
major_axis = (major_axis if major_axis is not None else
kwargs.pop('major', None))
minor_axis = (minor_axis if minor_axis is not None else
kwargs.pop('minor', None))
return super(Panel, self).relabel(items=items, major_axis=major_axis,
minor_axis=minor_axis, **kwargs)

@Appender(_shared_docs['reindex_axis'] % _shared_doc_kwargs)
def reindex_axis(self, labels, axis=0, method=None, level=None, copy=True,
limit=None, fill_value=np.nan):
Expand Down
5 changes: 5 additions & 0 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -2364,6 +2364,11 @@ def rename(self, index=None, **kwargs):
return self._set_name(index, inplace=kwargs.get('inplace'))
return super(Series, self).rename(index=index, **kwargs)

@Appender(generic._shared_docs['relabel'] % _shared_doc_kwargs)
def relabel(self, index=None, copy=True, inplace=False):
return super(Series, self).relabel(index=index, copy=copy,
inplace=inplace)

@Appender(generic._shared_docs['reindex'] % _shared_doc_kwargs)
def reindex(self, index=None, **kwargs):
return super(Series, self).reindex(index=index, **kwargs)
Expand Down
47 changes: 46 additions & 1 deletion pandas/tests/test_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,51 @@ def test_rename(self):

# multiple axes at once

def test_relabel(self):
# GH 14829
idx = list('ABCD')
labels = list('abcd')

for axis in self._axes():
kwargs = {axis: idx}

obj = self._construct(4, **kwargs)
result = obj.relabel(**{axis: labels})
expected = obj.copy()
setattr(expected, axis, labels)
# relabel a single axis
self._compare(result, expected)

# length must match
with tm.assertRaises(ValueError):
obj.relabel(**{axis: list('abcde')})

with tm.assertRaises(TypeError):
obj.relabel(**{axis: 5})

# multiple axes
obj = self._construct(4)
expected = obj.copy()
for axis in self._axes():
setattr(expected, axis, labels)

result = obj.relabel(**{axis: labels for axis in
self._axes()})
self._compare(result, expected)

# inplace
result = obj.copy()
kwargs = {axis: labels for axis in self._axes()}
kwargs['inplace'] = True
result.relabel(**kwargs)

self._compare(result, expected)

for box in [np.array, Series, Index]:
result = obj.relabel(**{axis: box(labels) for axis in
self._axes()})
self._compare(result, expected)

def test_rename_axis(self):
idx = list('ABCD')
# relabeling values passed into self.rename
Expand Down Expand Up @@ -1598,7 +1643,7 @@ def test_to_xarray(self):
'test_stat_unexpected_keyword', 'test_api_compat',
'test_stat_non_defaults_args',
'test_clip', 'test_truncate_out_of_bounds', 'test_numpy_clip',
'test_metadata_propagation']:
'test_metadata_propagation', 'test_relabel']:

def f():
def tester(self):
Expand Down