diff --git a/doc/source/whatsnew/v0.21.0.txt b/doc/source/whatsnew/v0.21.0.txt index cba3691b25ab1c..27d3be63e89a77 100644 --- a/doc/source/whatsnew/v0.21.0.txt +++ b/doc/source/whatsnew/v0.21.0.txt @@ -66,6 +66,7 @@ Other Enhancements - :func:`Series.to_dict` and :func:`DataFrame.to_dict` now support an ``into`` keyword which allows you to specify the ``collections.Mapping`` subclass that you would like returned. The default is ``dict``, which is backwards compatible. (:issue:`16122`) - :func:`RangeIndex.append` now returns a ``RangeIndex`` object when possible (:issue:`16212`) - :func:`Series.rename_axis` and :func:`DataFrame.rename_axis` with ``inplace=True`` now return ``None`` while renaming the axis inplace. (:issue:`15704`) +- :func:`Series.set_axis` and :func:`DataFrame.set_axis` now support the ``inplace`` parameter. (:issue:`14636`) - :func:`Series.to_pickle` and :func:`DataFrame.to_pickle` have gained a ``protocol`` parameter (:issue:`16252`). By default, this parameter is set to `HIGHEST_PROTOCOL `__ - :func:`api.types.infer_dtype` now infers decimals. (:issue:`15690`) - :func:`read_feather` has gained the ``nthreads`` parameter for multi-threaded operations (:issue:`16359`) @@ -140,6 +141,7 @@ Other API Changes - ``Index.get_indexer_non_unique()`` now returns a ndarray indexer rather than an ``Index``; this is consistent with ``Index.get_indexer()`` (:issue:`16819`) - Removed the ``@slow`` decorator from ``pandas.util.testing``, which caused issues for some downstream packages' test suites. Use ``@pytest.mark.slow`` instead, which achieves the same thing (:issue:`16850`) - Moved definition of ``MergeError`` to the ``pandas.errors`` module. +- The signature of :func:`Series.set_axis` and :func:`DataFrame.set_axis` has been changed from ``set_axis(axis, labels)`` to ``set_axis(labels, axis=0)``, for consistency with the rest of the API. The old signature is still supported and causes a ``FutureWarning`` to be emitted (:issue:`14636`) .. _whatsnew_0210.deprecations: diff --git a/pandas/core/generic.py b/pandas/core/generic.py index c95129bdaa0059..60f5efc021df82 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -466,9 +466,86 @@ def _expand_axes(self, key): return new_axes - def set_axis(self, axis, labels): - """ public verson of axis assignment """ - setattr(self, self._get_axis_name(axis), labels) + _shared_docs['set_axis'] = """Assign desired index to given axis + + Parameters + ---------- + labels: list-like or Index + The values for the new index + axis : int or string, default 0 + inplace : boolean, default None + Whether to return a new %(klass)s instance. + + WARNING: inplace=None currently falls back to to True, but + in a future version, will default to False. Use inplace=True + explicitly rather than relying on the default. + + Returns + ------- + renamed : %(klass)s or None + New object if inplace=False, None otherwise. + + See Also + -------- + pandas.NDFrame.rename + + Examples + -------- + >>> s = pd.Series([1, 2, 3]) + >>> s + 0 1 + 1 2 + 2 3 + dtype: int64 + >>> s.set_axis(['a', 'b', 'c'], axis=0, inplace=False) + a 1 + b 2 + c 3 + dtype: int64 + >>> df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]}) + >>> df.set_axis(['a', 'b', 'c'], axis=0, inplace=False) + A B + a 1 4 + b 2 5 + c 3 6 + >>> df.set_axis(['I', 'II'], axis=1, inplace=False) + I II + 0 1 4 + 1 2 5 + 2 3 6 + >>> df.set_axis(['i', 'ii'], axis=1, inplace=True) + >>> df + i ii + 0 1 4 + 1 2 5 + 2 3 6 + + """ + + @Appender(_shared_docs['set_axis'] % dict(klass='NDFrame')) + def set_axis(self, labels, axis=0, inplace=None): + if isinstance(labels, (int, str)): + warnings.warn( + "set_axis now takes \"labels\" as first argument, and " + "\"axis\" as named parameter. The old form, with \"axis\" as " + "first parameter and \"labels\" as second, is still supported " + "but will be deprecated in a future version of pandas.", + FutureWarning, stacklevel=2) + labels, axis = axis, labels + + if inplace is None: + warnings.warn( + "set_axis currently defaults to operating inplace.\nThis " + "will change in a future version of pandas, use " + "inplace=True to avoid this warning.", + FutureWarning, stacklevel=2) + inplace = True + if inplace: + setattr(self, self._get_axis_name(axis), labels) + else: + obj = self.copy() + obj.set_axis(labels, axis=axis, inplace=True) + return obj def _set_axis(self, axis, labels): self._data.set_axis(axis, labels) @@ -875,7 +952,7 @@ def _set_axis_name(self, name, axis=0, inplace=False): inplace = validate_bool_kwarg(inplace, 'inplace') renamed = self if inplace else self.copy() - renamed.set_axis(axis, idx) + renamed.set_axis(idx, axis=axis, inplace=True) if not inplace: return renamed @@ -5763,7 +5840,7 @@ def slice_shift(self, periods=1, axis=0): new_obj = self._slice(vslicer, axis=axis) shifted_axis = self._get_axis(axis)[islicer] - new_obj.set_axis(axis, shifted_axis) + new_obj.set_axis(shifted_axis, axis=axis, inplace=True) return new_obj.__finalize__(self) @@ -5923,7 +6000,7 @@ def _tz_convert(ax, tz): ax = _tz_convert(ax, tz) result = self._constructor(self._data, copy=copy) - result.set_axis(axis, ax) + result.set_axis(ax, axis=axis, inplace=True) return result.__finalize__(self) @deprecate_kwarg(old_arg_name='infer_dst', new_arg_name='ambiguous', @@ -5991,7 +6068,7 @@ def _tz_localize(ax, tz, ambiguous): ax = _tz_localize(ax, tz, ambiguous) result = self._constructor(self._data, copy=copy) - result.set_axis(axis, ax) + result.set_axis(ax, axis=axis, inplace=True) return result.__finalize__(self) # ---------------------------------------------------------------------- diff --git a/pandas/core/groupby.py b/pandas/core/groupby.py index daf3381ae4e890..c8a7ee752d243f 100644 --- a/pandas/core/groupby.py +++ b/pandas/core/groupby.py @@ -530,10 +530,11 @@ def _set_result_index_ordered(self, result): if not self.grouper.is_monotonic: index = Index(np.concatenate( self._get_indices(self.grouper.result_index))) - result.set_axis(self.axis, index) + result.set_axis(index, axis=self.axis, inplace=True) result = result.sort_index(axis=self.axis) - result.set_axis(self.axis, self.obj._get_axis(self.axis)) + result.set_axis(self.obj._get_axis(self.axis), axis=self.axis, + inplace=True) return result def _dir_additions(self): diff --git a/pandas/core/reshape/pivot.py b/pandas/core/reshape/pivot.py index 0581ec7484c491..0e351c9df62854 100644 --- a/pandas/core/reshape/pivot.py +++ b/pandas/core/reshape/pivot.py @@ -311,8 +311,9 @@ def _all_key(key): except TypeError: # we cannot reshape, so coerce the axis - piece.set_axis(cat_axis, piece._get_axis( - cat_axis)._to_safe_for_reshape()) + piece.set_axis(piece._get_axis( + cat_axis)._to_safe_for_reshape(), + axis=cat_axis, inplace=True) piece[all_key] = margin[key] table_pieces.append(piece) diff --git a/pandas/tests/frame/test_alter_axes.py b/pandas/tests/frame/test_alter_axes.py index 434c02b8eba2f3..5c6f0da95fd77d 100644 --- a/pandas/tests/frame/test_alter_axes.py +++ b/pandas/tests/frame/test_alter_axes.py @@ -908,3 +908,57 @@ def test_set_reset_index(self): df = df.set_index('B') df = df.reset_index() + + def test_set_axis_inplace(self): + # GH14636 + df = DataFrame({'A': [1.1, 2.2, 3.3], + 'B': [5.0, 6.1, 7.2], + 'C': [4.4, 5.5, 6.6]}, + index=[2010, 2011, 2012]) + + expected = {0: df.copy(), + 1: df.copy()} + expected[0].index = list('abc') + expected[1].columns = list('abc') + expected['index'] = expected[0] + expected['columns'] = expected[1] + + for axis in expected: + # inplace=True + # The FutureWarning comes from the fact that we would like to have + # inplace default to False some day + for inplace, warn in (None, FutureWarning), (True, None): + kwargs = {'inplace': inplace} + + result = df.copy() + with tm.assert_produces_warning(warn): + result.set_axis(list('abc'), axis=axis, **kwargs) + tm.assert_frame_equal(result, expected[axis]) + + # inplace=False + result = df.set_axis(list('abc'), axis=axis, inplace=False) + tm.assert_frame_equal(expected[axis], result) + + # omitting the "axis" parameter + with tm.assert_produces_warning(None): + result = df.set_axis(list('abc'), inplace=False) + tm.assert_frame_equal(result, expected[0]) + + def test_set_axis_old_signature(self): + df = DataFrame({'A': [1.1, 2.2, 3.3], + 'B': [5.0, 6.1, 7.2], + 'C': [4.4, 5.5, 6.6]}, + index=[2010, 2011, 2012]) + + expected = {0: df.copy(), + 1: df.copy()} + expected[0].index = list('abc') + expected[1].columns = list('abc') + expected['index'] = expected[0] + expected['columns'] = expected[1] + + # old signature + for axis in expected: + with tm.assert_produces_warning(FutureWarning): + result = df.set_axis(axis, list('abc'), inplace=False) + tm.assert_frame_equal(result, expected[axis]) diff --git a/pandas/tests/series/test_alter_axes.py b/pandas/tests/series/test_alter_axes.py index d93f0326fd3b11..d5c5f36d77a495 100644 --- a/pandas/tests/series/test_alter_axes.py +++ b/pandas/tests/series/test_alter_axes.py @@ -234,3 +234,42 @@ def test_rename_axis_inplace(self): assert no_return is None assert_series_equal(result, expected) + + def test_set_axis_inplace(self): + # GH14636 + + s = Series(np.arange(4), index=[1, 3, 5, 7], dtype='int64') + + expected = s.copy() + expected.index = list('abcd') + + for axis in 0, 'index': + # inplace=True + # The FutureWarning comes from the fact that we would like to have + # inplace default to False some day + for inplace, warn in (None, FutureWarning), (True, None): + result = s.copy() + kwargs = {'inplace': inplace} + with tm.assert_produces_warning(warn): + result.set_axis(list('abcd'), axis=axis, **kwargs) + tm.assert_series_equal(result, expected) + + # inplace=False + result = s.set_axis(list('abcd'), axis=0, inplace=False) + tm.assert_series_equal(expected, result) + + # omitting the "axis" parameter + with tm.assert_produces_warning(None): + result = s.set_axis(list('abcd'), inplace=False) + tm.assert_series_equal(result, expected) + + def test_set_axis_old_signature(self): + s = Series(np.arange(4), index=[1, 3, 5, 7], dtype='int64') + + expected = s.copy() + expected.index = list('abcd') + + for axis in 0, 'index': + with tm.assert_produces_warning(FutureWarning): + result = s.set_axis(0, list('abcd'), inplace=False) + tm.assert_series_equal(result, expected)