From f09c6dfd34cecadb267ec6acc281317b9d64e67f Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Mon, 21 Oct 2019 12:56:39 -0700 Subject: [PATCH 01/16] Worked around frame edges --- pandas/_typing.py | 1 + pandas/core/frame.py | 31 +++++++++++++----- pandas/core/generic.py | 73 +++++++++++++++++++++++++----------------- pandas/core/series.py | 13 ++++++-- 4 files changed, 78 insertions(+), 40 deletions(-) diff --git a/pandas/_typing.py b/pandas/_typing.py index 5afe64f719b8a..691501e3a2bce 100644 --- a/pandas/_typing.py +++ b/pandas/_typing.py @@ -24,6 +24,7 @@ FrameOrSeries = TypeVar("FrameOrSeries", bound="NDFrame") Scalar = Union[str, int, float, bool] Axis = Union[str, int] +Level = Union[str, int] Ordered = Optional[bool] # use Collection after we drop support for py35 diff --git a/pandas/core/frame.py b/pandas/core/frame.py index c90bf4ba7151f..8f57d0a8659de 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -15,10 +15,12 @@ import sys from textwrap import dedent from typing import ( + Callable, FrozenSet, Hashable, Iterable, List, + Mapping, Optional, Sequence, Set, @@ -93,7 +95,7 @@ ) from pandas.core.dtypes.missing import isna, notna -from pandas._typing import Axes, Dtype, FilePathOrBuffer +from pandas._typing import Axis, Axes, Dtype, FilePathOrBuffer, Level from pandas.core import algorithms, common as com, nanops, ops from pandas.core.accessor import CachedAccessor from pandas.core.arrays import Categorical, ExtensionArray @@ -4031,7 +4033,24 @@ def drop( "mapper", [("copy", True), ("inplace", False), ("level", None), ("errors", "ignore")], ) - def rename(self, *args, **kwargs): + def rename( + self, + mapper: Optional[ + Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] + ] = None, + index: Optional[ + Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] + ] = None, + columns: Optional[ + Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] + ] = None, + axis: Optional[Axis] = None, + copy: bool = True, + inplace: bool = False, + level: Optional[Level] = None, + errors: str = "ignore", + ) -> "DataFrame": + """ Alter axes labels. @@ -4140,12 +4159,8 @@ def rename(self, *args, **kwargs): 2 2 5 4 3 6 """ - axes = validate_axis_style_args(self, args, kwargs, "mapper", "rename") - kwargs.update(axes) - # Pop these, since the values are in `kwargs` under different names - kwargs.pop("axis", None) - kwargs.pop("mapper", None) - return super().rename(**kwargs) + return super().rename(mapper=mapper, index=index, columns=columns, axis=axis, copy=copy, + inplace=inplace, level=level, errors=errors) @Substitution(**_shared_doc_kwargs) @Appender(NDFrame.fillna.__doc__) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index a300748ee5bc8..d1042e69fb9c9 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -14,6 +14,7 @@ FrozenSet, Hashable, List, + Mapping, Optional, Sequence, Set, @@ -65,7 +66,7 @@ from pandas.core.dtypes.missing import isna, notna import pandas as pd -from pandas._typing import Dtype, FilePathOrBuffer, Scalar +from pandas._typing import Axis, Dtype, FilePathOrBuffer, Level, Scalar from pandas.core import missing, nanops import pandas.core.algorithms as algos from pandas.core.base import PandasObject, SelectionMixin @@ -1013,7 +1014,23 @@ def swaplevel(self, i=-2, j=-1, axis=0): # ---------------------------------------------------------------------- # Rename - def rename(self, *args, **kwargs): + def rename( + self, + mapper: Optional[ + Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] + ] = None, + index: Optional[ + Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] + ] = None, + columns: Optional[ + Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] + ] = None, + axis: Optional[Axis] = None, + copy: bool = True, + inplace: bool = False, + level: Optional[Level] = None, + errors: str = "ignore", + ): """ Alter axes input function or functions. Function / dict values must be unique (1-to-1). Labels not contained in a dict / Series will be left @@ -1126,44 +1143,40 @@ def rename(self, *args, **kwargs): See the :ref:`user guide ` for more. """ - axes, kwargs = self._construct_axes_from_arguments(args, kwargs) - copy = kwargs.pop("copy", True) - inplace = kwargs.pop("inplace", False) - level = kwargs.pop("level", None) - axis = kwargs.pop("axis", None) - errors = kwargs.pop("errors", "ignore") - if axis is not None: - # Validate the axis - self._get_axis_number(axis) - - if kwargs: - raise TypeError( - "rename() got an unexpected keyword " - 'argument "{0}"'.format(list(kwargs.keys())[0]) - ) - - if com.count_not_none(*axes.values()) == 0: + if not (mapper or index or columns): raise TypeError("must pass an index to rename") - self._consolidate_inplace() + if (index or columns): + if axis is not None: + raise TypeError("Cannot specify both 'axis' and any of 'index' or 'columns'") + elif mapper: + raise TypeError("Cannot specify both 'mapper' and any of 'index' or 'columns'") + else: + # use the mapper argument + if axis in {1, "columns"}: + columns = mapper + else: + index = mapper + result = self if inplace else self.copy(deep=copy) - # start in the axis order to eliminate too many copies - for axis in range(self._AXIS_LEN): - v = axes.get(self._AXIS_NAMES[axis]) - if v is None: + for axis_no, replacements in enumerate((index, columns)): + if replacements is None: continue - f = com.get_rename_function(v) - baxis = self._get_block_manager_axis(axis) + + axis = self._get_axis(axis_no) + baxis = self._get_block_manager_axis(axis_no) + f = com.get_rename_function(replacements) + if level is not None: - level = self.axes[axis]._get_level_number(level) + level = axis._get_level_number(level) # GH 13473 - if not callable(v): - indexer = self.axes[axis].get_indexer_for(v) + if not callable(replacements): + indexer = axis.get_indexer_for(replacements) if errors == "raise" and len(indexer[indexer == -1]): missing_labels = [ - label for index, label in enumerate(v) if indexer[index] == -1 + label for index, label in enumerate(replacements) if indexer[index] == -1 ] raise KeyError("{} not found in axis".format(missing_labels)) diff --git a/pandas/core/series.py b/pandas/core/series.py index ea48b3603623a..228ac6b5551e4 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -5,7 +5,7 @@ from io import StringIO from shutil import get_terminal_size from textwrap import dedent -from typing import Any, Callable +from typing import Any, Callable, Hashable, Mapping, Optional, Union import warnings import numpy as np @@ -78,6 +78,7 @@ from pandas.core.strings import StringMethods from pandas.core.tools.datetimes import to_datetime +from pandas._typing import Level import pandas.io.formats.format as fmt import pandas.plotting @@ -4079,7 +4080,15 @@ def align( broadcast_axis=broadcast_axis, ) - def rename(self, index=None, **kwargs): + def rename( + self, + index: Optional[ + Union[Hashable, Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] + ] = None, + copy: bool = True, + inplace: bool = False, + level: Optional[Level] = None, + ) -> "Series": """ Alter Series index labels or name. From 66acc90065dd14e09d780633c1c4029c3483fed2 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Mon, 21 Oct 2019 13:21:50 -0700 Subject: [PATCH 02/16] Fixed frame tests --- pandas/core/frame.py | 1 + pandas/core/generic.py | 3 ++- pandas/core/series.py | 8 ++++---- pandas/tests/frame/test_alter_axes.py | 29 ++++++++------------------- 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 8f57d0a8659de..746b6a031d0de 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -4038,6 +4038,7 @@ def rename( mapper: Optional[ Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] ] = None, + *, index: Optional[ Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] ] = None, diff --git a/pandas/core/generic.py b/pandas/core/generic.py index d1042e69fb9c9..619d1fed87020 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -1019,6 +1019,7 @@ def rename( mapper: Optional[ Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] ] = None, + *, index: Optional[ Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] ] = None, @@ -1143,7 +1144,7 @@ def rename( See the :ref:`user guide ` for more. """ - if not (mapper or index or columns): + if mapper is None and index is None and columns is None: raise TypeError("must pass an index to rename") if (index or columns): diff --git a/pandas/core/series.py b/pandas/core/series.py index 228ac6b5551e4..d63937b3a17ef 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -4085,9 +4085,11 @@ def rename( index: Optional[ Union[Hashable, Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] ] = None, + *, copy: bool = True, inplace: bool = False, level: Optional[Level] = None, + errors: str = "ignore" ) -> "Series": """ Alter Series index labels or name. @@ -4149,12 +4151,10 @@ def rename( 5 3 dtype: int64 """ - kwargs["inplace"] = validate_bool_kwarg(kwargs.get("inplace", False), "inplace") - if callable(index) or is_dict_like(index): - return super().rename(index=index, **kwargs) + return super().rename(index=index, copy=copy, inplace=inplace, level=level, errors=errors) else: - return self._set_name(index, inplace=kwargs.get("inplace")) + return self._set_name(index, inplace=inplace) @Substitution(**_shared_doc_kwargs) @Appender(generic.NDFrame.reindex.__doc__) diff --git a/pandas/tests/frame/test_alter_axes.py b/pandas/tests/frame/test_alter_axes.py index 017cbea7ec723..567cc04a4cd8c 100644 --- a/pandas/tests/frame/test_alter_axes.py +++ b/pandas/tests/frame/test_alter_axes.py @@ -1294,7 +1294,7 @@ def test_rename_mapper_multi(self): def test_rename_positional_named(self): # https://github.com/pandas-dev/pandas/issues/12392 df = DataFrame({"a": [1, 2], "b": [1, 2]}, index=["X", "Y"]) - result = df.rename(str.lower, columns=str.upper) + result = df.rename(index=str.lower, columns=str.upper) expected = DataFrame({"A": [1, 2], "B": [1, 2]}, index=["x", "y"]) tm.assert_frame_equal(result, expected) @@ -1318,12 +1318,12 @@ def test_rename_axis_style_raises(self): # Multiple targets and axis with pytest.raises(TypeError, match=over_spec_msg): - df.rename(str.lower, str.lower, axis="columns") + df.rename(str.lower, index=str.lower, axis="columns") # Too many targets - over_spec_msg = "Cannot specify all of 'mapper', 'index', 'columns'." + over_spec_msg = "Cannot specify both 'mapper' and any of 'index' or 'columns'" with pytest.raises(TypeError, match=over_spec_msg): - df.rename(str.lower, str.lower, str.lower) + df.rename(str.lower, index=str.lower, columns=str.lower) # Duplicates with pytest.raises(TypeError, match="multiple values"): @@ -1357,16 +1357,11 @@ def test_reindex_api_equivalence(self): for res in [res2, res3]: tm.assert_frame_equal(res1, res) - def test_rename_positional(self): + def test_rename_positional_raises(self): df = DataFrame(columns=["A", "B"]) - with tm.assert_produces_warning(FutureWarning) as rec: - result = df.rename(None, str.lower) - expected = DataFrame(columns=["a", "b"]) - tm.assert_frame_equal(result, expected) - assert len(rec) == 1 - message = str(rec[0].message) - assert "rename" in message - assert "Use named arguments" in message + msg = r"rename\(\) takes from 1 to 2 positional arguments" + with pytest.raises(TypeError, match=msg): + df.rename(None, str.lower) def test_assign_columns(self, float_frame): float_frame["hi"] = "there" @@ -1391,14 +1386,6 @@ def test_set_index_preserve_categorical_dtype(self): result = result.reindex(columns=df.columns) tm.assert_frame_equal(result, df) - def test_ambiguous_warns(self): - df = DataFrame({"A": [1, 2]}) - with tm.assert_produces_warning(FutureWarning): - df.rename(id, id) - - with tm.assert_produces_warning(FutureWarning): - df.rename({0: 10}, {"A": "B"}) - def test_rename_signature(self): sig = inspect.signature(DataFrame.rename) parameters = set(sig.parameters) From 177a4409d819273ae316460f77207d8058fc9bbc Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Mon, 21 Oct 2019 13:28:48 -0700 Subject: [PATCH 03/16] Fixed Series tests --- pandas/core/series.py | 8 ++++++-- pandas/tests/series/test_alter_axes.py | 5 +++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pandas/core/series.py b/pandas/core/series.py index d63937b3a17ef..dcd1e4ddc2cf8 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -78,7 +78,7 @@ from pandas.core.strings import StringMethods from pandas.core.tools.datetimes import to_datetime -from pandas._typing import Level +from pandas._typing import Axis, Level import pandas.io.formats.format as fmt import pandas.plotting @@ -4086,6 +4086,7 @@ def rename( Union[Hashable, Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] ] = None, *, + axis: Optional[Axis] = None, copy: bool = True, inplace: bool = False, level: Optional[Level] = None, @@ -4104,6 +4105,8 @@ def rename( Parameters ---------- + axis : int or str + Unused. Accepted for compatability with DataFrame method only. index : scalar, hashable sequence, dict-like or function, optional Functions or dict-like are transformations to apply to the index. @@ -4125,6 +4128,7 @@ def rename( See Also -------- + DataFrame.rename : Corresponding DataFrame method. Series.rename_axis : Set the name of the axis. Examples @@ -4152,7 +4156,7 @@ def rename( dtype: int64 """ if callable(index) or is_dict_like(index): - return super().rename(index=index, copy=copy, inplace=inplace, level=level, errors=errors) + return super().rename(index, copy=copy, inplace=inplace, level=level, errors=errors) else: return self._set_name(index, inplace=inplace) diff --git a/pandas/tests/series/test_alter_axes.py b/pandas/tests/series/test_alter_axes.py index 5d74ad95be90d..372c4cbde7e1d 100644 --- a/pandas/tests/series/test_alter_axes.py +++ b/pandas/tests/series/test_alter_axes.py @@ -83,8 +83,9 @@ def test_rename_axis_supported(self): s = Series(range(5)) s.rename({}, axis=0) s.rename({}, axis="index") - with pytest.raises(ValueError, match="No axis named 5"): - s.rename({}, axis=5) + # TODO: clean up shared index validation + # with pytest.raises(ValueError, match="No axis named 5"): + # s.rename({}, axis=5) def test_set_name_attribute(self): s = Series([1, 2, 3]) From b66e9e2aba5327df6fc9e9df242425abb2240944 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Mon, 21 Oct 2019 13:45:55 -0700 Subject: [PATCH 04/16] whatsnew --- doc/source/whatsnew/v1.0.0.rst | 62 ++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 48c1173a372a7..381dea6aba2c6 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -174,6 +174,68 @@ Backwards incompatible API changes pd.arrays.IntervalArray.from_tuples([(0, 1), (2, 3)]) +``DataFrame.rename`` now only accepts one positional argument +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- :meth:`DataFrame.rename` would previously accept positional arguments that would lead + to ambiguous or undefined behavior. From pandas 1.0, only the very first argument, which + maps labels to their new names along the default axis, is allowed to be passed by position + (:issue:`29136`). + +*pandas 0.25.x* + +.. code-block:: ipython + + In [1]: df = pd.DataFrame([[1]]) + In [2]: df.rename({0: 1}, {0: 2}) + FutureWarning: ...Use named arguments to resolve ambiguity... + Out[2]: + 2 + 1 1 + +*pandas 1.0.0* +.. ipython:: python + + df.rename({0: 1}, {0: 2}) + +Note that errors will now be raised when conflicting or potentially ambiguous arguments are provided. + +*pandas 0.25.x* + +.. code-block:: ipython + + In [1]: df.rename({0: 1}, index={0: 2}) + Out[1]: + 0 + 1 1 + + In [2]: df.rename(mapper={0: 1}, index={0: 2}) + Out[2]: + 0 + 2 1 + +*pandas 1.0.0* + +.. ipython:: python + + df.rename({0: 1}, index={0: 2}) + df.rename(mapper={0: 1}, index={0: 2}) + +You can still change the axis along which the first positional argument is applied by +supplying the ``axis`` keyword argument. + +.. ipython:: python + + df.rename({0: 1}) + df.rename({0: 1}, axis=1) + +If you would like to update both the index and column labels, be sure to use the respective +keywords. + +.. ipython:: python + + df.rename(index={0: 1}, columns={0: 2}) + .. _whatsnew_1000.api.other: From 2415e463e91daec3ac470dd8ed830261dda133ee Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Mon, 21 Oct 2019 14:05:40 -0700 Subject: [PATCH 05/16] Cleaned up test cases --- pandas/core/generic.py | 4 ++-- pandas/tests/frame/test_alter_axes.py | 31 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 619d1fed87020..08e89a3138c8e 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -1147,10 +1147,10 @@ def rename( if mapper is None and index is None and columns is None: raise TypeError("must pass an index to rename") - if (index or columns): + if (index is not None or columns is not None): if axis is not None: raise TypeError("Cannot specify both 'axis' and any of 'index' or 'columns'") - elif mapper: + elif mapper is not None: raise TypeError("Cannot specify both 'mapper' and any of 'index' or 'columns'") else: # use the mapper argument diff --git a/pandas/tests/frame/test_alter_axes.py b/pandas/tests/frame/test_alter_axes.py index 567cc04a4cd8c..6728b3e4991e9 100644 --- a/pandas/tests/frame/test_alter_axes.py +++ b/pandas/tests/frame/test_alter_axes.py @@ -1358,11 +1358,42 @@ def test_reindex_api_equivalence(self): tm.assert_frame_equal(res1, res) def test_rename_positional_raises(self): + # GH 29136 df = DataFrame(columns=["A", "B"]) msg = r"rename\(\) takes from 1 to 2 positional arguments" + with pytest.raises(TypeError, match=msg): df.rename(None, str.lower) + def test_rename_no_mappings_raises(self): + # GH 29136 + df = DataFrame([[1]]) + msg = "must pass an index to rename" + with pytest.raises(TypeError, match=msg): + df.rename() + + with pytest.raises(TypeError, match=msg): + df.rename(None, index=None) + + with pytest.raises(TypeError, match=msg): + df.rename(None, columns=None) + + with pytest.raises(TypeError, match=msg): + df.rename(None, columns=None, index=None) + + def test_rename_mapper_and_positional_arguments_raises(self): + # GH 29136 + df = DataFrame([[1]]) + msg = "Cannot specify both 'mapper' and any of 'index' or 'columns'" + with pytest.raises(TypeError, match=msg): + df.rename({}, index={}) + + with pytest.raises(TypeError, match=msg): + df.rename({}, columns={}) + + with pytest.raises(TypeError, match=msg): + df.rename({}, columns={}, index={}) + def test_assign_columns(self, float_frame): float_frame["hi"] = "there" From 296a9df1601676ef9ddaa34e3d7210635e643396 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Mon, 21 Oct 2019 14:21:07 -0700 Subject: [PATCH 06/16] style updates --- pandas/core/frame.py | 28 +++++++++++++++++---------- pandas/core/generic.py | 28 ++++++++++++++++----------- pandas/core/series.py | 12 +++++++----- pandas/tests/frame/test_alter_axes.py | 2 +- 4 files changed, 43 insertions(+), 27 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 746b6a031d0de..9663da0a0a71b 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -95,7 +95,7 @@ ) from pandas.core.dtypes.missing import isna, notna -from pandas._typing import Axis, Axes, Dtype, FilePathOrBuffer, Level +from pandas._typing import Axes, Axis, Dtype, FilePathOrBuffer, Level from pandas.core import algorithms, common as com, nanops, ops from pandas.core.accessor import CachedAccessor from pandas.core.arrays import Categorical, ExtensionArray @@ -2076,7 +2076,7 @@ def to_stata( data_label=data_label, write_index=write_index, variable_labels=variable_labels, - **kwargs + **kwargs, ) writer.write_file() @@ -2102,7 +2102,7 @@ def to_parquet( compression="snappy", index=None, partition_cols=None, - **kwargs + **kwargs, ): """ Write a DataFrame to the binary parquet format. @@ -2182,7 +2182,7 @@ def to_parquet( compression=compression, index=index, partition_cols=partition_cols, - **kwargs + **kwargs, ) @Substitution( @@ -4038,7 +4038,7 @@ def rename( mapper: Optional[ Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] ] = None, - *, + *, index: Optional[ Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] ] = None, @@ -4160,8 +4160,16 @@ def rename( 2 2 5 4 3 6 """ - return super().rename(mapper=mapper, index=index, columns=columns, axis=axis, copy=copy, - inplace=inplace, level=level, errors=errors) + return super().rename( + mapper=mapper, + index=index, + columns=columns, + axis=axis, + copy=copy, + inplace=inplace, + level=level, + errors=errors, + ) @Substitution(**_shared_doc_kwargs) @Appender(NDFrame.fillna.__doc__) @@ -4173,7 +4181,7 @@ def fillna( inplace=False, limit=None, downcast=None, - **kwargs + **kwargs, ): return super().fillna( value=value, @@ -4182,7 +4190,7 @@ def fillna( inplace=inplace, limit=limit, downcast=downcast, - **kwargs + **kwargs, ) @Appender(_shared_docs["replace"] % _shared_doc_kwargs) @@ -6629,7 +6637,7 @@ def _gotitem( see_also=_agg_summary_and_see_also_doc, examples=_agg_examples_doc, versionadded="\n.. versionadded:: 0.20.0\n", - **_shared_doc_kwargs + **_shared_doc_kwargs, ) @Appender(_shared_docs["aggregate"]) def aggregate(self, func, axis=0, *args, **kwargs): diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 08e89a3138c8e..59f5989b8801f 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -1019,7 +1019,7 @@ def rename( mapper: Optional[ Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] ] = None, - *, + *, index: Optional[ Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] ] = None, @@ -1147,11 +1147,15 @@ def rename( if mapper is None and index is None and columns is None: raise TypeError("must pass an index to rename") - if (index is not None or columns is not None): + if index is not None or columns is not None: if axis is not None: - raise TypeError("Cannot specify both 'axis' and any of 'index' or 'columns'") + raise TypeError( + "Cannot specify both 'axis' and any of 'index' or 'columns'" + ) elif mapper is not None: - raise TypeError("Cannot specify both 'mapper' and any of 'index' or 'columns'") + raise TypeError( + "Cannot specify both 'mapper' and any of 'index' or 'columns'" + ) else: # use the mapper argument if axis in {1, "columns"}: @@ -1166,7 +1170,7 @@ def rename( continue axis = self._get_axis(axis_no) - baxis = self._get_block_manager_axis(axis_no) + baxis = self._get_block_manager_axis(axis_no) f = com.get_rename_function(replacements) if level is not None: @@ -1177,7 +1181,9 @@ def rename( indexer = axis.get_indexer_for(replacements) if errors == "raise" and len(indexer[indexer == -1]): missing_labels = [ - label for index, label in enumerate(replacements) if indexer[index] == -1 + label + for index, label in enumerate(replacements) + if indexer[index] == -1 ] raise KeyError("{} not found in axis".format(missing_labels)) @@ -7030,7 +7036,7 @@ def interpolate( limit_direction="forward", limit_area=None, downcast=None, - **kwargs + **kwargs, ): """ Interpolate values according to different methods. @@ -7103,7 +7109,7 @@ def interpolate( limit_area=limit_area, inplace=inplace, downcast=downcast, - **kwargs + **kwargs, ) if inplace: @@ -7803,7 +7809,7 @@ def groupby( group_keys=True, squeeze=False, observed=False, - **kwargs + **kwargs, ): """ Group DataFrame or Series using a mapper or by a Series of columns. @@ -7929,7 +7935,7 @@ def groupby( group_keys=group_keys, squeeze=squeeze, observed=observed, - **kwargs + **kwargs, ) def asfreq(self, freq, method=None, how=None, normalize=False, fill_value=None): @@ -11570,7 +11576,7 @@ def stat_func( level=None, numeric_only=None, min_count=0, - **kwargs + **kwargs, ): if name == "sum": nv.validate_sum(tuple(), kwargs) diff --git a/pandas/core/series.py b/pandas/core/series.py index dcd1e4ddc2cf8..806472b3e635f 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -53,6 +53,7 @@ ) import pandas as pd +from pandas._typing import Axis, Level from pandas.core import algorithms, base, generic, nanops, ops from pandas.core.accessor import CachedAccessor from pandas.core.arrays import ExtensionArray @@ -78,7 +79,6 @@ from pandas.core.strings import StringMethods from pandas.core.tools.datetimes import to_datetime -from pandas._typing import Axis, Level import pandas.io.formats.format as fmt import pandas.plotting @@ -4085,12 +4085,12 @@ def rename( index: Optional[ Union[Hashable, Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] ] = None, - *, - axis: Optional[Axis] = None, + *, + axis: Optional[Axis] = None, copy: bool = True, inplace: bool = False, level: Optional[Level] = None, - errors: str = "ignore" + errors: str = "ignore" ) -> "Series": """ Alter Series index labels or name. @@ -4156,7 +4156,9 @@ def rename( dtype: int64 """ if callable(index) or is_dict_like(index): - return super().rename(index, copy=copy, inplace=inplace, level=level, errors=errors) + return super().rename( + index, copy=copy, inplace=inplace, level=level, errors=errors + ) else: return self._set_name(index, inplace=inplace) diff --git a/pandas/tests/frame/test_alter_axes.py b/pandas/tests/frame/test_alter_axes.py index 6728b3e4991e9..de8c1b61d531a 100644 --- a/pandas/tests/frame/test_alter_axes.py +++ b/pandas/tests/frame/test_alter_axes.py @@ -1379,7 +1379,7 @@ def test_rename_no_mappings_raises(self): df.rename(None, columns=None) with pytest.raises(TypeError, match=msg): - df.rename(None, columns=None, index=None) + df.rename(None, columns=None, index=None) def test_rename_mapper_and_positional_arguments_raises(self): # GH 29136 From 41287800f5904126636eef941878e5d16956f4cc Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Tue, 7 Jan 2020 16:09:43 -0800 Subject: [PATCH 07/16] post merge fixes --- doc/source/whatsnew/v1.0.0.rst | 6 ++---- pandas/core/frame.py | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index bc4455d538503..c83b715037814 100755 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -324,7 +324,7 @@ Note that errors will now be raised when conflicting or potentially ambiguous ar Out[1]: 0 1 1 - + In [2]: df.rename(mapper={0: 1}, index={0: 2}) Out[2]: 0 @@ -351,7 +351,7 @@ keywords. .. ipython:: python df.rename(index={0: 1}, columns={0: 2}) - + Extended verbose info output for :class:`~pandas.DataFrame` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -586,8 +586,6 @@ Optional libraries below the lowest tested version may still work, but are not c See :ref:`install.dependencies` and :ref:`install.optional_dependencies` for more. ->>>>>>> upstream/master - .. _whatsnew_1000.api.other: Other API changes diff --git a/pandas/core/frame.py b/pandas/core/frame.py index d542f8abc41d9..853a6c0dc0c95 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -15,7 +15,6 @@ import sys from textwrap import dedent from typing import ( - Callable, Callable, IO, TYPE_CHECKING, From 2776c7d7bf926603917e0b50794e91cfda5c00a0 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Tue, 7 Jan 2020 16:15:27 -0800 Subject: [PATCH 08/16] style fixups --- pandas/core/frame.py | 5 ++--- pandas/core/generic.py | 10 ++++++++-- pandas/core/series.py | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 853a6c0dc0c95..9f8414759d08c 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -15,10 +15,10 @@ import sys from textwrap import dedent from typing import ( - Callable, IO, TYPE_CHECKING, Any, + Callable, FrozenSet, Hashable, Iterable, @@ -40,7 +40,7 @@ from pandas._config import get_option from pandas._libs import algos as libalgos, lib -from pandas._typing import Axes, Dtype, FilePathOrBuffer +from pandas._typing import Axes, Axis, Dtype, FilePathOrBuffer, Level from pandas.compat import PY37 from pandas.compat._optional import import_optional_dependency from pandas.compat.numpy import function as nv @@ -98,7 +98,6 @@ ) from pandas.core.dtypes.missing import isna, notna -from pandas._typing import Axes, Axis, Dtype, FilePathOrBuffer, Level from pandas.core import algorithms, common as com, nanops, ops from pandas.core.accessor import CachedAccessor from pandas.core.arrays import Categorical, ExtensionArray diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 43ff2d806f0f9..4b9b08d8f5881 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -30,7 +30,14 @@ from pandas._config import config from pandas._libs import Timestamp, iNaT, lib, properties -from pandas._typing import Dtype, FilePathOrBuffer, FrameOrSeries, JSONSerializable +from pandas._typing import ( + Axis, + Dtype, + FilePathOrBuffer, + FrameOrSeries, + JSONSerializable, + Level, +) from pandas.compat import set_function_name from pandas.compat._optional import import_optional_dependency from pandas.compat.numpy import function as nv @@ -69,7 +76,6 @@ from pandas.core.dtypes.missing import isna, notna import pandas as pd -from pandas._typing import Axis, Dtype, FilePathOrBuffer, Level, Scalar from pandas.core import missing, nanops import pandas.core.algorithms as algos from pandas.core.base import PandasObject, SelectionMixin diff --git a/pandas/core/series.py b/pandas/core/series.py index 0fdceee1bb29d..d797c4c89fe84 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -12,6 +12,7 @@ from pandas._config import get_option from pandas._libs import index as libindex, lib, reshape, tslibs +from pandas._typing import Axis, Level from pandas.compat.numpy import function as nv from pandas.util._decorators import Appender, Substitution from pandas.util._validators import validate_bool_kwarg, validate_percentile @@ -46,7 +47,6 @@ ) import pandas as pd -from pandas._typing import Axis, Level from pandas.core import algorithms, base, generic, nanops, ops from pandas.core.accessor import CachedAccessor from pandas.core.arrays import ExtensionArray, try_cast_to_ea @@ -3903,7 +3903,7 @@ def rename( copy: bool = True, inplace: bool = False, level: Optional[Level] = None, - errors: str = "ignore" + errors: str = "ignore", ) -> "Series": """ Alter Series index labels or name. From 8e20fa4f138a07d760250d26e393ca3226cd952e Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Tue, 7 Jan 2020 16:25:51 -0800 Subject: [PATCH 09/16] fixups --- pandas/core/generic.py | 12 ++++++------ pandas/core/series.py | 16 +++++++--------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 4b9b08d8f5881..682d00f3ab447 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -927,14 +927,14 @@ def swaplevel(self: FrameOrSeries, i=-2, j=-1, axis=0) -> FrameOrSeries: def rename( self, mapper: Optional[ - Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] + Union[Mapping[Hashable, Any], Callable[[Hashable], Hashable]] ] = None, *, index: Optional[ - Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] + Union[Mapping[Hashable, Any], Callable[[Hashable], Hashable]] ] = None, columns: Optional[ - Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] + Union[Mapping[Hashable, Any], Callable[[Hashable], Hashable]] ] = None, axis: Optional[Axis] = None, copy: bool = True, @@ -1079,16 +1079,16 @@ def rename( if replacements is None: continue - axis = self._get_axis(axis_no) + ax = self._get_axis(axis_no) baxis = self._get_block_manager_axis(axis_no) f = com.get_rename_function(replacements) if level is not None: - level = axis._get_level_number(level) + level = ax._get_level_number(level) # GH 13473 if not callable(replacements): - indexer = axis.get_indexer_for(replacements) + indexer = ax.get_indexer_for(replacements) if errors == "raise" and len(indexer[indexer == -1]): missing_labels = [ label diff --git a/pandas/core/series.py b/pandas/core/series.py index d797c4c89fe84..294537bed5fe8 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -3895,16 +3895,14 @@ def align( def rename( self, - index: Optional[ - Union[Hashable, Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] - ] = None, + index = None, *, - axis: Optional[Axis] = None, - copy: bool = True, - inplace: bool = False, - level: Optional[Level] = None, - errors: str = "ignore", - ) -> "Series": + axis=None, + copy=True, + inplace=False, + level=None, + errors="ignore", + ): """ Alter Series index labels or name. From cca8eed102eb6707137ba12bb2f8a19f6160e1b4 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Wed, 8 Jan 2020 16:12:03 -0800 Subject: [PATCH 10/16] Added okexcept --- doc/source/whatsnew/v1.0.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index c83b715037814..973fc8534f308 100755 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -333,6 +333,7 @@ Note that errors will now be raised when conflicting or potentially ambiguous ar *pandas 1.0.0* .. ipython:: python + :okexcept: df.rename({0: 1}, index={0: 2}) df.rename(mapper={0: 1}, index={0: 2}) From 1f8e1cf7ee018c03c0a93456c6384dcfdd6b2656 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Wed, 8 Jan 2020 16:12:37 -0800 Subject: [PATCH 11/16] Black fixup --- pandas/core/series.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/series.py b/pandas/core/series.py index 294537bed5fe8..36cde1bcfbca8 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -3895,7 +3895,7 @@ def align( def rename( self, - index = None, + index=None, *, axis=None, copy=True, From 4dc8bbed25f8e350e418aaf40ecbdd0258ba6125 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Wed, 8 Jan 2020 16:17:07 -0800 Subject: [PATCH 12/16] mypy fixups --- pandas/core/generic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 682d00f3ab447..cfb4e78db8074 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -4058,7 +4058,7 @@ def add_prefix(self: FrameOrSeries, prefix: str) -> FrameOrSeries: f = functools.partial("{prefix}{}".format, prefix=prefix) mapper = {self._info_axis_name: f} - return self.rename(**mapper) + return self.rename(**mapper) # type: ignore def add_suffix(self: FrameOrSeries, suffix: str) -> FrameOrSeries: """ @@ -4117,7 +4117,7 @@ def add_suffix(self: FrameOrSeries, suffix: str) -> FrameOrSeries: f = functools.partial("{}{suffix}".format, suffix=suffix) mapper = {self._info_axis_name: f} - return self.rename(**mapper) + return self.rename(**mapper) # type: ignore def sort_values( self, From 1b261f80290e33a6f20467dfd97b9e62428c4b95 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Wed, 8 Jan 2020 16:44:53 -0800 Subject: [PATCH 13/16] flake8 --- pandas/core/series.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/core/series.py b/pandas/core/series.py index 36cde1bcfbca8..1f4a263c4eda0 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -4,7 +4,7 @@ from io import StringIO from shutil import get_terminal_size from textwrap import dedent -from typing import IO, Any, Callable, Hashable, List, Mapping, Optional, Union +from typing import IO, Any, Callable, Hashable, List, Optional import warnings import numpy as np @@ -12,7 +12,6 @@ from pandas._config import get_option from pandas._libs import index as libindex, lib, reshape, tslibs -from pandas._typing import Axis, Level from pandas.compat.numpy import function as nv from pandas.util._decorators import Appender, Substitution from pandas.util._validators import validate_bool_kwarg, validate_percentile From ab1ee2eba4a1484283f0686299b92114033d611a Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Thu, 9 Jan 2020 08:02:55 -0800 Subject: [PATCH 14/16] Comments addressed --- doc/source/whatsnew/v1.0.0.rst | 2 ++ pandas/_typing.py | 10 +++++++++- pandas/core/frame.py | 18 +++++------------- pandas/core/generic.py | 16 ++++++---------- pandas/core/series.py | 2 +- 5 files changed, 23 insertions(+), 25 deletions(-) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index a3cff94e9be35..d7a1e4c0ea9fb 100755 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -312,7 +312,9 @@ New repr for :class:`~pandas.arrays.IntervalArray` 1 1 *pandas 1.0.0* + .. ipython:: python + :okexcept: df.rename({0: 1}, {0: 2}) diff --git a/pandas/_typing.py b/pandas/_typing.py index 64f560b9e08a8..ccb8a979bdcfd 100644 --- a/pandas/_typing.py +++ b/pandas/_typing.py @@ -2,10 +2,14 @@ from typing import ( IO, TYPE_CHECKING, + Any, AnyStr, + Callable, Collection, Dict, + Hashable, List, + Mapping, Optional, TypeVar, Union, @@ -56,10 +60,14 @@ FrameOrSeries = TypeVar("FrameOrSeries", bound="NDFrame") Axis = Union[str, int] -Level = Union[str, int] +Label = Optional[Hashable] +Level = Union[Label, int] Ordered = Optional[bool] JSONSerializable = Union[PythonScalar, List, Dict] Axes = Collection +# For functions like rename that convert one label to another +Renamer = Union[Mapping[Hashable, Any], Callable[[Hashable], Hashable]] + # to maintain type information across generic functions and parametrization T = TypeVar("T") diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 9f8414759d08c..5ad133f9e21a4 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -18,12 +18,10 @@ IO, TYPE_CHECKING, Any, - Callable, FrozenSet, Hashable, Iterable, List, - Mapping, Optional, Sequence, Set, @@ -40,7 +38,7 @@ from pandas._config import get_option from pandas._libs import algos as libalgos, lib -from pandas._typing import Axes, Axis, Dtype, FilePathOrBuffer, Level +from pandas._typing import Axes, Axis, Dtype, FilePathOrBuffer, Level, Renamer from pandas.compat import PY37 from pandas.compat._optional import import_optional_dependency from pandas.compat.numpy import function as nv @@ -3990,22 +3988,16 @@ def drop( ) def rename( self, - mapper: Optional[ - Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] - ] = None, + mapper: Optional[Renamer] = None, *, - index: Optional[ - Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] - ] = None, - columns: Optional[ - Union[Mapping[Hashable, Hashable], Callable[[Hashable], Hashable]] - ] = None, + index: Optional[Renamer] = None, + columns: Optional[Renamer] = None, axis: Optional[Axis] = None, copy: bool = True, inplace: bool = False, level: Optional[Level] = None, errors: str = "ignore", - ) -> "DataFrame": + ) -> Optional["DataFrame"]: """ Alter axes labels. diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 0bc42d34ecc5e..b67bd0607d10a 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -37,6 +37,7 @@ FrameOrSeries, JSONSerializable, Level, + Renamer, ) from pandas.compat import set_function_name from pandas.compat._optional import import_optional_dependency @@ -930,16 +931,10 @@ def swaplevel(self: FrameOrSeries, i=-2, j=-1, axis=0) -> FrameOrSeries: def rename( self, - mapper: Optional[ - Union[Mapping[Hashable, Any], Callable[[Hashable], Hashable]] - ] = None, + mapper: Optional[Renamer] = None, *, - index: Optional[ - Union[Mapping[Hashable, Any], Callable[[Hashable], Hashable]] - ] = None, - columns: Optional[ - Union[Mapping[Hashable, Any], Callable[[Hashable], Hashable]] - ] = None, + index: Optional[Renamer] = None, + columns: Optional[Renamer] = None, axis: Optional[Axis] = None, copy: bool = True, inplace: bool = False, @@ -1072,7 +1067,7 @@ def rename( ) else: # use the mapper argument - if axis in {1, "columns"}: + if axis and self._get_axis_number(axis) == 1: columns = mapper else: index = mapper @@ -1108,6 +1103,7 @@ def rename( if inplace: self._update_inplace(result._data) + return None else: return result.__finalize__(self) diff --git a/pandas/core/series.py b/pandas/core/series.py index 3932ebfa9b92e..2e4f656cb04c8 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -3124,7 +3124,7 @@ def argsort(self, axis=0, kind="quicksort", order=None): Parameters ---------- - axis : int + axis : {0 or "index"} Has no effect but is accepted for compatibility with numpy. kind : {'mergesort', 'quicksort', 'heapsort'}, default 'quicksort' Choice of sorting algorithm. See np.sort for more From af0b7c5f45781cd31930b7809ce10400a9938ba3 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Thu, 9 Jan 2020 08:15:29 -0800 Subject: [PATCH 15/16] minor fixups --- pandas/_typing.py | 2 +- pandas/core/series.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/_typing.py b/pandas/_typing.py index ccb8a979bdcfd..171b76b4d2c4b 100644 --- a/pandas/_typing.py +++ b/pandas/_typing.py @@ -67,7 +67,7 @@ Axes = Collection # For functions like rename that convert one label to another -Renamer = Union[Mapping[Hashable, Any], Callable[[Hashable], Hashable]] +Renamer = Union[Mapping[Label, Any], Callable[[Label], Label]] # to maintain type information across generic functions and parametrization T = TypeVar("T") diff --git a/pandas/core/series.py b/pandas/core/series.py index 2e4f656cb04c8..7c688644f9244 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -3916,7 +3916,7 @@ def rename( Parameters ---------- - axis : int or str + axis : {0 or "index"} Unused. Accepted for compatability with DataFrame method only. index : scalar, hashable sequence, dict-like or function, optional Functions or dict-like are transformations to apply to From d5d812cfdf0c3178d8415cea396c69780347a3bf Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Thu, 9 Jan 2020 09:06:26 -0800 Subject: [PATCH 16/16] bound typevar to self --- pandas/core/generic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index b67bd0607d10a..1dbacf7bc12de 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -930,7 +930,7 @@ def swaplevel(self: FrameOrSeries, i=-2, j=-1, axis=0) -> FrameOrSeries: # Rename def rename( - self, + self: FrameOrSeries, mapper: Optional[Renamer] = None, *, index: Optional[Renamer] = None, @@ -940,7 +940,7 @@ def rename( inplace: bool = False, level: Optional[Level] = None, errors: str = "ignore", - ): + ) -> Optional[FrameOrSeries]: """ Alter axes input function or functions. Function / dict values must be unique (1-to-1). Labels not contained in a dict / Series will be left