From a308d875335275cc6c467646d8871aa1c5cade5a Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 10 Jan 2017 19:19:18 -0600 Subject: [PATCH 1/3] API/ENH: relabel method --- doc/source/api.rst | 3 ++ doc/source/basics.rst | 13 +++++ doc/source/indexing.rst | 17 +++++++ doc/source/whatsnew/v0.20.0.txt | 16 ++++++ pandas/core/frame.py | 5 ++ pandas/core/generic.py | 86 +++++++++++++++++++++++++++++++++ pandas/core/panel.py | 9 ++++ pandas/core/series.py | 5 ++ pandas/tests/test_generic.py | 43 +++++++++++++++++ 9 files changed, 197 insertions(+) diff --git a/doc/source/api.rst b/doc/source/api.rst index 272dfe72eafe7..2acc23f588885 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -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 @@ -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 @@ -1185,6 +1187,7 @@ Reindexing / Selection / Label manipulation Panel.reindex_axis Panel.reindex_like Panel.rename + Panel.relabel Panel.sample Panel.select Panel.take diff --git a/doc/source/basics.rst b/doc/source/basics.rst index 2e8abe0a5c329..943336d9aada7 100644 --- a/doc/source/basics.rst +++ b/doc/source/basics.rst @@ -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 diff --git a/doc/source/indexing.rst b/doc/source/indexing.rst index 1ea6662a4edb0..602d9cf15f96e 100644 --- a/doc/source/indexing.rst +++ b/doc/source/indexing.rst @@ -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 +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 diff --git a/doc/source/whatsnew/v0.20.0.txt b/doc/source/whatsnew/v0.20.0.txt index c9ea7b427b3f2..14ed8059b1247 100644 --- a/doc/source/whatsnew/v0.20.0.txt +++ b/doc/source/whatsnew/v0.20.0.txt @@ -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 diff --git a/pandas/core/frame.py b/pandas/core/frame.py index b9290c0ce3457..beae70d40bc1c 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -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): diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 0b5767da74cad..8d2802756e2dc 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -688,6 +688,92 @@ 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 + 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.NDFrame.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. diff --git a/pandas/core/panel.py b/pandas/core/panel.py index f708774dd84ff..9189936286474 100644 --- a/pandas/core/panel.py +++ b/pandas/core/panel.py @@ -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): diff --git a/pandas/core/series.py b/pandas/core/series.py index 0b29e8c93a12d..ed5bc586c947b 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -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) diff --git a/pandas/tests/test_generic.py b/pandas/tests/test_generic.py index f32990ff32cbe..d89f14d3f4c64 100644 --- a/pandas/tests/test_generic.py +++ b/pandas/tests/test_generic.py @@ -101,6 +101,49 @@ 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() + result.relabel(**{axis: labels for axis in + self._axes()}, inplace=True) + 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 From 6bbf39f1c0331a96ecc4292c059ca19cffb9f9be Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 11 Jan 2017 04:43:16 -0600 Subject: [PATCH 2/3] fix test for py2 --- pandas/tests/test_generic.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pandas/tests/test_generic.py b/pandas/tests/test_generic.py index d89f14d3f4c64..e8dcd8c0a6953 100644 --- a/pandas/tests/test_generic.py +++ b/pandas/tests/test_generic.py @@ -135,8 +135,9 @@ def test_relabel(self): # inplace result = obj.copy() - result.relabel(**{axis: labels for axis in - self._axes()}, inplace=True) + 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]: From db1779b9f343fa40b6030d22306936e135e9e875 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 11 Jan 2017 18:17:59 -0600 Subject: [PATCH 3/3] catch panel4d warnings --- pandas/core/generic.py | 4 +++- pandas/tests/test_generic.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 8d2802756e2dc..b9784f9bf7339 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -710,7 +710,9 @@ def f(x): See Also -------- - pandas.NDFrame.rename + pandas.Series.rename + pandas.DataFrame.rename + pandas.Panel.rename Examples -------- diff --git a/pandas/tests/test_generic.py b/pandas/tests/test_generic.py index e8dcd8c0a6953..e43f5fb2e73e7 100644 --- a/pandas/tests/test_generic.py +++ b/pandas/tests/test_generic.py @@ -138,6 +138,7 @@ def test_relabel(self): 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]: @@ -1642,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):