diff --git a/doc/source/whatsnew/v0.24.1.rst b/doc/source/whatsnew/v0.24.1.rst index 222963a7ff71a..c8b5417a5b77f 100644 --- a/doc/source/whatsnew/v0.24.1.rst +++ b/doc/source/whatsnew/v0.24.1.rst @@ -25,6 +25,7 @@ Fixed Regressions - Fixed regression in :func:`read_sql` when passing certain queries with MySQL/pymysql (:issue:`24988`). - Fixed regression in :class:`Index.intersection` incorrectly sorting the values by default (:issue:`24959`). - Fixed regression in :func:`merge` when merging an empty ``DataFrame`` with multiple timezone-aware columns on one of the timezone-aware columns (:issue:`25014`). +- Fixed regression in :meth:`Series.rename_axis` and :meth:`DataFrame.rename_axis` where passing ``None`` failed to remove the axis name (:issue:`25034`) .. _whatsnew_0241.enhancements: diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 2b97661fe9ec3..1d8077873e1ea 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -61,6 +61,10 @@ by : str or list of str Name or list of names to sort by""") +# sentinel value to use as kwarg in place of None when None has special meaning +# and needs to be distinguished from a user explicitly passing None. +sentinel = object() + def _single_replace(self, to_replace, method, inplace, limit): """ @@ -290,11 +294,16 @@ def _construct_axes_dict_for_slice(self, axes=None, **kwargs): d.update(kwargs) return d - def _construct_axes_from_arguments(self, args, kwargs, require_all=False): + def _construct_axes_from_arguments( + self, args, kwargs, require_all=False, sentinel=None): """Construct and returns axes if supplied in args/kwargs. If require_all, raise if all axis arguments are not supplied return a tuple of (axes, kwargs). + + sentinel specifies the default parameter when an axis is not + supplied; useful to distinguish when a user explicitly passes None + in scenarios where None has special meaning. """ # construct the args @@ -322,7 +331,7 @@ def _construct_axes_from_arguments(self, args, kwargs, require_all=False): raise TypeError("not enough/duplicate arguments " "specified!") - axes = {a: kwargs.pop(a, None) for a in self._AXIS_ORDERS} + axes = {a: kwargs.pop(a, sentinel) for a in self._AXIS_ORDERS} return axes, kwargs @classmethod @@ -1089,7 +1098,7 @@ def rename(self, *args, **kwargs): @rewrite_axis_style_signature('mapper', [('copy', True), ('inplace', False)]) - def rename_axis(self, mapper=None, **kwargs): + def rename_axis(self, mapper=sentinel, **kwargs): """ Set the name of the axis for the index or columns. @@ -1218,7 +1227,8 @@ class name cat 4 0 monkey 2 2 """ - axes, kwargs = self._construct_axes_from_arguments((), kwargs) + axes, kwargs = self._construct_axes_from_arguments( + (), kwargs, sentinel=sentinel) copy = kwargs.pop('copy', True) inplace = kwargs.pop('inplace', False) axis = kwargs.pop('axis', 0) @@ -1231,7 +1241,7 @@ class name inplace = validate_bool_kwarg(inplace, 'inplace') - if (mapper is not None): + if (mapper is not sentinel): # Use v0.23 behavior if a scalar or list non_mapper = is_scalar(mapper) or (is_list_like(mapper) and not is_dict_like(mapper)) @@ -1254,7 +1264,7 @@ class name for axis in lrange(self._AXIS_LEN): v = axes.get(self._AXIS_NAMES[axis]) - if v is None: + if v is sentinel: continue non_mapper = is_scalar(v) or (is_list_like(v) and not is_dict_like(v)) diff --git a/pandas/tests/frame/test_alter_axes.py b/pandas/tests/frame/test_alter_axes.py index c2355742199dc..2d1afa2281d44 100644 --- a/pandas/tests/frame/test_alter_axes.py +++ b/pandas/tests/frame/test_alter_axes.py @@ -600,6 +600,26 @@ def test_rename_axis_mapper(self): with pytest.raises(TypeError, match='bogus'): df.rename_axis(bogus=None) + @pytest.mark.parametrize('kwargs, rename_index, rename_columns', [ + ({'mapper': None, 'axis': 0}, True, False), + ({'mapper': None, 'axis': 1}, False, True), + ({'index': None}, True, False), + ({'columns': None}, False, True), + ({'index': None, 'columns': None}, True, True), + ({}, False, False)]) + def test_rename_axis_none(self, kwargs, rename_index, rename_columns): + # GH 25034 + index = Index(list('abc'), name='foo') + columns = Index(['col1', 'col2'], name='bar') + data = np.arange(6).reshape(3, 2) + df = DataFrame(data, index, columns) + + result = df.rename_axis(**kwargs) + expected_index = index.rename(None) if rename_index else index + expected_columns = columns.rename(None) if rename_columns else columns + expected = DataFrame(data, expected_index, expected_columns) + tm.assert_frame_equal(result, expected) + def test_rename_multiindex(self): tuples_index = [('foo1', 'bar1'), ('foo2', 'bar2')] diff --git a/pandas/tests/series/test_alter_axes.py b/pandas/tests/series/test_alter_axes.py index 04c54bcf8c22c..73adc7d4bf82f 100644 --- a/pandas/tests/series/test_alter_axes.py +++ b/pandas/tests/series/test_alter_axes.py @@ -258,6 +258,17 @@ def test_rename_axis_inplace(self, datetime_series): assert no_return is None tm.assert_series_equal(result, expected) + @pytest.mark.parametrize('kwargs', [{'mapper': None}, {'index': None}, {}]) + def test_rename_axis_none(self, kwargs): + # GH 25034 + index = Index(list('abc'), name='foo') + df = Series([1, 2, 3], index=index) + + result = df.rename_axis(**kwargs) + expected_index = index.rename(None) if kwargs else index + expected = Series([1, 2, 3], index=expected_index) + tm.assert_series_equal(result, expected) + def test_set_axis_inplace_axes(self, axis_series): # GH14636 ser = Series(np.arange(4), index=[1, 3, 5, 7], dtype='int64')