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

ENH: provide "inplace" argument to set_axis() #16994

Closed
wants to merge 2 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
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 @@ -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 <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 @@ -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:
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

Copy link
Contributor

Choose a reason for hiding this comment

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

add a note that the signature changed in 0.21.0

Copy link
Member Author

Choose a reason for hiding this comment

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

(done: line 483)

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 was uniformed to the rest of the API: previously,
"axis" and "labels" were respectively the first and second
positional arguments.

Returns
-------
renamed : %(klass)s or None
Copy link
Contributor

Choose a reason for hiding this comment

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

this needs updating

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure what you mean... anyway, I just made the following line more explicit

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 @@ -5763,7 +5845,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 @@ -5923,7 +6005,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 @@ -5991,7 +6073,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 @@ -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)
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
Copy link
Member

Choose a reason for hiding this comment

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

A sentence (on a newline) about why we issue FutureWarning when inplace=None would be good here.

# 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)

Copy link
Contributor

Choose a reason for hiding this comment

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

can you add a test for invalid axis (e.g. 'foo', or 2)

Copy link
Contributor

Choose a reason for hiding this comment

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

test with axis ='index' and axis='columns' (should work)

Copy link
Member Author

Choose a reason for hiding this comment

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

can you add a test for invalid axis (e.g. 'foo', or 2)

(done: line 947)

# 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_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])
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
Copy link
Member

Choose a reason for hiding this comment

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

Same comment as above

# 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_old_signature(self):
s = Series(np.arange(4), index=[1, 3, 5, 7], dtype='int64')

expected = s.copy()
expected.index = list('abcd')
Copy link
Contributor

Choose a reason for hiding this comment

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

test for invalid axis (1 or 'foo')

Copy link
Contributor

Choose a reason for hiding this comment

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

test with axis='index' (should work)

Copy link
Member Author

Choose a reason for hiding this comment

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

test for invalid axis (1 or 'foo')

(done: line 266)


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)