Skip to content

Commit

Permalink
ENH: provide "inplace" argument to set_axis()
Browse files Browse the repository at this point in the history
closes #14636

Author: Pietro Battiston <me@pietrobattiston.it>

Closes #16994 from toobaz/set_axis_inplace and squashes the following commits:

8fb9d0f [Pietro Battiston] REF: adapt NDFrame.set_axis() calls to new signature
409f502 [Pietro Battiston] ENH: provide "inplace" argument to set_axis(), change signature
  • Loading branch information
toobaz authored and jreback committed Jul 24, 2017
1 parent 1d0e6a1 commit 9e6bb42
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 11 deletions.
1 change: 1 addition & 0 deletions doc/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ Reindexing / Selection / Label manipulation
Series.reset_index
Series.sample
Series.select
Series.set_axis
Series.take
Series.tail
Series.truncate
Expand Down
2 changes: 2 additions & 0 deletions doc/source/whatsnew/v0.21.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,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 <https://docs.python.org/3/library/pickle.html#data-stream-format>`__
- :func:`api.types.infer_dtype` now infers decimals. (:issue:`15690`)
- :func:`read_feather` has gained the ``nthreads`` parameter for multi-threaded operations (:issue:`16359`)
Expand Down Expand Up @@ -202,6 +203,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:
Expand Down
96 changes: 89 additions & 7 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,9 +466,91 @@ 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.
.. versionadded:: 0.21.0
The signature is make consistent to the rest of the API.
Previously, the "axis" and "labels" arguments were respectively
the first and second positional arguments.
Returns
-------
renamed : %(klass)s or None
An object of same type as caller 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 is_scalar(labels):
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)
Expand Down Expand Up @@ -875,7 +957,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

Expand Down Expand Up @@ -5721,7 +5803,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)

Expand Down Expand Up @@ -5881,7 +5963,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',
Expand Down Expand Up @@ -5949,7 +6031,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)

# ----------------------------------------------------------------------
Expand Down
5 changes: 3 additions & 2 deletions pandas/core/groupby.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
5 changes: 3 additions & 2 deletions pandas/core/reshape/pivot.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,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)
Expand Down
59 changes: 59 additions & 0 deletions pandas/tests/frame/test_alter_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -908,3 +908,62 @@ 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])

# wrong values for the "axis" parameter
for axis in 3, 'foo':
with tm.assert_raises_regex(ValueError, 'No axis named'):
df.set_axis(list('abc'), axis=axis, inplace=False)

def test_set_axis_prior_to_deprecation_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])
44 changes: 44 additions & 0 deletions pandas/tests/series/test_alter_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,47 @@ 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)

# wrong values for the "axis" parameter
for axis in 2, 'foo':
with tm.assert_raises_regex(ValueError, 'No axis named'):
s.set_axis(list('abcd'), axis=axis, inplace=False)

def test_set_axis_prior_to_deprecation_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)

0 comments on commit 9e6bb42

Please sign in to comment.