From f0b9eb81bacf0fe8bd630cf15fb6a74085c86b43 Mon Sep 17 00:00:00 2001 From: tp Date: Sat, 18 Apr 2020 15:04:32 +0100 Subject: [PATCH 1/5] REF: _get_axis, _get_axis_name, _get_axis_number --- pandas/core/computation/align.py | 2 +- pandas/core/frame.py | 19 +++++++- pandas/core/generic.py | 73 +++++++++++++++------------- pandas/core/series.py | 2 - pandas/io/json/_json.py | 9 ++-- pandas/tests/generic/test_generic.py | 20 ++++++-- pandas/util/_validators.py | 6 +-- 7 files changed, 80 insertions(+), 51 deletions(-) diff --git a/pandas/core/computation/align.py b/pandas/core/computation/align.py index e45d3ca66b6ec..82867cf9dcd29 100644 --- a/pandas/core/computation/align.py +++ b/pandas/core/computation/align.py @@ -38,7 +38,7 @@ def _align_core_single_unary_op( def _zip_axes_from_type( typ: Type[FrameOrSeries], new_axes: Sequence[int] ) -> Dict[str, int]: - axes = {name: new_axes[i] for i, name in typ._AXIS_NAMES.items()} + axes = {name: new_axes[i] for i, name in enumerate(typ._AXIS_ORDERS)} return axes diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 85bb47485a2e7..dc0ec4294942b 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -8787,8 +8787,11 @@ def isin(self, values) -> "DataFrame": # ---------------------------------------------------------------------- # Add index and columns _AXIS_ORDERS = ["index", "columns"] - _AXIS_NUMBERS = {"index": 0, "columns": 1} - _AXIS_NAMES = {0: "index", 1: "columns"} + _AXIS_TO_AXIS_NUMBER: Dict[Axis, int] = { + **NDFrame._AXIS_TO_AXIS_NUMBER, + 1: 1, + "columns": 1, + } _AXIS_REVERSED = True _AXIS_LEN = len(_AXIS_ORDERS) _info_axis_number = 1 @@ -8801,6 +8804,18 @@ def isin(self, values) -> "DataFrame": axis=0, doc="The column labels of the DataFrame." ) + @property + def _AXIS_NUMBERS(self) -> Dict[str, int]: + """.. deprecated:: 1.1.0""" + super()._AXIS_NUMBERS + return {"index": 0, "columns": 1} + + @property + def _AXIS_NAMES(self) -> Dict[int, str]: + """.. deprecated:: 1.1.0""" + super()._AXIS_NAMES + return {0: "index", 1: "columns"} + # ---------------------------------------------------------------------- # Add plotting methods to DataFrame plot = CachedAccessor("plot", pandas.plotting.PlotAccessor) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 673d661e9eff4..43881904d6bc7 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -67,7 +67,6 @@ is_dict_like, is_extension_array_dtype, is_float, - is_integer, is_list_like, is_number, is_numeric_dtype, @@ -302,19 +301,36 @@ def _data(self): # ---------------------------------------------------------------------- # Axis - _AXIS_ALIASES = {"rows": 0} - _AXIS_IALIASES = {0: "rows"} _stat_axis_number = 0 _stat_axis_name = "index" _ix = None _AXIS_ORDERS: List[str] - _AXIS_NUMBERS: Dict[str, int] - _AXIS_NAMES: Dict[int, str] + _AXIS_TO_AXIS_NUMBER: Dict[Axis, int] = {0: 0, "index": 0, "rows": 0} _AXIS_REVERSED: bool _info_axis_number: int _info_axis_name: str _AXIS_LEN: int + @property + def _AXIS_NUMBERS(self) -> Dict[str, int]: + """.. deprecated:: 1.1.0""" + warnings.warn( + "_AXIS_NUMBERS has been deprecated. Call ._get_axis_number instead", + FutureWarning, + stacklevel=3, + ) + return {"index": 0} + + @property + def _AXIS_NAMES(self) -> Dict[int, str]: + """.. deprecated:: 1.1.0""" + warnings.warn( + "_AXIS_NAMES has been deprecated. Call ._get_axis_name instead", + FutureWarning, + stacklevel=3, + ) + return {0: "index"} + def _construct_axes_dict(self, axes=None, **kwargs): """Return an axes dictionary for myself.""" d = {a: self._get_axis(a) for a in (axes or self._AXIS_ORDERS)} @@ -353,37 +369,24 @@ def _construct_axes_from_arguments( return axes, kwargs @classmethod - def _get_axis_number(cls, axis) -> int: - axis = cls._AXIS_ALIASES.get(axis, axis) - if is_integer(axis): - if axis in cls._AXIS_NAMES: - return axis - else: - try: - return cls._AXIS_NUMBERS[axis] - except KeyError: - pass - raise ValueError(f"No axis named {axis} for object type {cls.__name__}") + def _get_axis_number(cls, axis: Axis) -> int: + try: + return cls._AXIS_TO_AXIS_NUMBER[axis] + except KeyError: + raise ValueError(f"No axis named {axis} for object type {cls.__name__}") @classmethod - def _get_axis_name(cls, axis) -> str: - axis = cls._AXIS_ALIASES.get(axis, axis) - if isinstance(axis, str): - if axis in cls._AXIS_NUMBERS: - return axis - else: - try: - return cls._AXIS_NAMES[axis] - except KeyError: - pass - raise ValueError(f"No axis named {axis} for object type {cls.__name__}") + def _get_axis_name(cls, axis: Axis) -> str: + axis_number = cls._get_axis_number(axis) + return cls._AXIS_ORDERS[axis_number] - def _get_axis(self, axis) -> Index: - name = self._get_axis_name(axis) - return getattr(self, name) + def _get_axis(self, axis: Axis) -> Index: + axis_number = self._get_axis_number(axis) + assert axis_number in {0, 1} + return self.index if axis_number == 0 else self.columns @classmethod - def _get_block_manager_axis(cls, axis) -> int: + def _get_block_manager_axis(cls, axis: Axis) -> int: """Map the axis to the block_manager axis.""" axis = cls._get_axis_number(axis) if cls._AXIS_REVERSED: @@ -448,11 +451,11 @@ def _get_cleaned_column_resolvers(self) -> Dict[str, ABCSeries]: } @property - def _info_axis(self): + def _info_axis(self) -> Index: return getattr(self, self._info_axis_name) @property - def _stat_axis(self): + def _stat_axis(self) -> Index: return getattr(self, self._stat_axis_name) @property @@ -813,7 +816,7 @@ def squeeze(self, axis=None): >>> df_0a.squeeze() 1 """ - axis = self._AXIS_NAMES if axis is None else (self._get_axis_number(axis),) + axis = range(self._AXIS_LEN) if axis is None else (self._get_axis_number(axis),) return self.iloc[ tuple( 0 if i in axis and len(a) == 1 else slice(None) @@ -1156,7 +1159,7 @@ class name result = self if inplace else self.copy(deep=copy) for axis in range(self._AXIS_LEN): - v = axes.get(self._AXIS_NAMES[axis]) + v = axes.get(self._get_axis_name(axis)) if v is lib.no_default: continue non_mapper = is_scalar(v) or (is_list_like(v) and not is_dict_like(v)) diff --git a/pandas/core/series.py b/pandas/core/series.py index 5a1d7f3b90bd9..9ef865a964123 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -4599,8 +4599,6 @@ def to_period(self, freq=None, copy=True) -> "Series": # ---------------------------------------------------------------------- # Add index _AXIS_ORDERS = ["index"] - _AXIS_NUMBERS = {"index": 0} - _AXIS_NAMES = {0: "index"} _AXIS_REVERSED = False _AXIS_LEN = len(_AXIS_ORDERS) _info_axis_number = 0 diff --git a/pandas/io/json/_json.py b/pandas/io/json/_json.py index 20724a498b397..eb7f15c78b671 100644 --- a/pandas/io/json/_json.py +++ b/pandas/io/json/_json.py @@ -867,12 +867,15 @@ def _convert_axes(self): """ Try to convert axes. """ - for axis in self.obj._AXIS_NUMBERS.keys(): + for axis_name in self.obj._AXIS_ORDERS: new_axis, result = self._try_convert_data( - axis, self.obj._get_axis(axis), use_dtypes=False, convert_dates=True + name=axis_name, + data=self.obj._get_axis(axis_name), + use_dtypes=False, + convert_dates=True, ) if result: - setattr(self.obj, axis, new_axis) + setattr(self.obj, axis_name, new_axis) def _try_convert_types(self): raise AbstractMethodError(self) diff --git a/pandas/tests/generic/test_generic.py b/pandas/tests/generic/test_generic.py index 2c8261a6dcc5a..7f255aa83495f 100644 --- a/pandas/tests/generic/test_generic.py +++ b/pandas/tests/generic/test_generic.py @@ -901,12 +901,22 @@ def test_pipe_tuple_error(self): @pytest.mark.parametrize("box", [pd.Series, pd.DataFrame]) def test_axis_classmethods(self, box): obj = box(dtype=object) - values = ( - list(box._AXIS_NAMES.keys()) - + list(box._AXIS_NUMBERS.keys()) - + list(box._AXIS_ALIASES.keys()) - ) + values = box._AXIS_TO_AXIS_NUMBER.keys() for v in values: assert obj._get_axis_number(v) == box._get_axis_number(v) assert obj._get_axis_name(v) == box._get_axis_name(v) assert obj._get_block_manager_axis(v) == box._get_block_manager_axis(v) + + @pytest.mark.parametrize("box", [pd.Series, pd.DataFrame]) + def test_axis_names_deprecated(self, box): + # GH33637 + obj = box(dtype=object) + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + obj._AXIS_NAMES + + @pytest.mark.parametrize("box", [pd.Series, pd.DataFrame]) + def test_axis_numbers_deprecated(self, box): + # GH33637 + obj = box(dtype=object) + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + obj._AXIS_NUMBERS diff --git a/pandas/util/_validators.py b/pandas/util/_validators.py index 682575cc9ed48..53a25eb321b73 100644 --- a/pandas/util/_validators.py +++ b/pandas/util/_validators.py @@ -257,7 +257,7 @@ def validate_axis_style_args(data, args, kwargs, arg_name, method_name): # like out = {'index': foo, 'columns': bar} # Start by validating for consistency - if "axis" in kwargs and any(x in kwargs for x in data._AXIS_NUMBERS): + if "axis" in kwargs and any(x in kwargs for x in data._AXIS_TO_AXIS_NUMBER): msg = "Cannot specify both 'axis' and any of 'index' or 'columns'." raise TypeError(msg) @@ -302,8 +302,8 @@ def validate_axis_style_args(data, args, kwargs, arg_name, method_name): "a 'TypeError'." ) warnings.warn(msg.format(method_name=method_name), FutureWarning, stacklevel=4) - out[data._AXIS_NAMES[0]] = args[0] - out[data._AXIS_NAMES[1]] = args[1] + out[data._get_axis_name(0)] = args[0] + out[data._get_axis_name(1)] = args[1] else: msg = f"Cannot specify all of '{arg_name}', 'index', 'columns'." raise TypeError(msg) From 28e050e25a66a0cf52486844ee909c4067437cbc Mon Sep 17 00:00:00 2001 From: tp Date: Sat, 18 Apr 2020 21:50:42 +0100 Subject: [PATCH 2/5] clean _AXIS_NAMES --- pandas/io/pytables.py | 4 ++-- pandas/tests/generic/test_generic.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pandas/io/pytables.py b/pandas/io/pytables.py index 425118694fa02..311d8d0d55341 100644 --- a/pandas/io/pytables.py +++ b/pandas/io/pytables.py @@ -3712,7 +3712,7 @@ def _create_axes( # Now we can construct our new index axis idx = axes[0] a = obj.axes[idx] - axis_name = obj._AXIS_NAMES[idx] + axis_name = obj._get_axis_name(idx) new_index = _convert_index(axis_name, a, self.encoding, self.errors) new_index.axis = idx @@ -3919,7 +3919,7 @@ def process_axes(self, obj, selection: "Selection", columns=None): def process_filter(field, filt): - for axis_name in obj._AXIS_NAMES.values(): + for axis_name in obj._AXIS_ORDERS: axis_number = obj._get_axis_number(axis_name) axis_values = obj._get_axis(axis_name) assert axis_number is not None diff --git a/pandas/tests/generic/test_generic.py b/pandas/tests/generic/test_generic.py index 7f255aa83495f..05588ead54be4 100644 --- a/pandas/tests/generic/test_generic.py +++ b/pandas/tests/generic/test_generic.py @@ -86,7 +86,9 @@ def test_rename(self): def test_get_numeric_data(self): n = 4 - kwargs = {self._typ._AXIS_NAMES[i]: list(range(n)) for i in range(self._ndim)} + kwargs = { + self._typ._get_axis_name(i): list(range(n)) for i in range(self._ndim) + } # get the numeric data o = self._construct(n, **kwargs) From 79328227352c95debbb145ff08cb4b6dfb8d7ff6 Mon Sep 17 00:00:00 2001 From: tp Date: Mon, 20 Apr 2020 16:36:20 +0100 Subject: [PATCH 3/5] Simpler warning --- pandas/core/generic.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 43881904d6bc7..ccf344a0a1ece 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -315,9 +315,7 @@ def _data(self): def _AXIS_NUMBERS(self) -> Dict[str, int]: """.. deprecated:: 1.1.0""" warnings.warn( - "_AXIS_NUMBERS has been deprecated. Call ._get_axis_number instead", - FutureWarning, - stacklevel=3, + "_AXIS_NUMBERS has been deprecated.", FutureWarning, stacklevel=3, ) return {"index": 0} @@ -325,9 +323,7 @@ def _AXIS_NUMBERS(self) -> Dict[str, int]: def _AXIS_NAMES(self) -> Dict[int, str]: """.. deprecated:: 1.1.0""" warnings.warn( - "_AXIS_NAMES has been deprecated. Call ._get_axis_name instead", - FutureWarning, - stacklevel=3, + "_AXIS_NAMES has been deprecated.", FutureWarning, stacklevel=3, ) return {0: "index"} From c6bc5ebdd4cc0e01931d7fa43165fb536ce25d74 Mon Sep 17 00:00:00 2001 From: tp Date: Mon, 20 Apr 2020 19:48:08 +0100 Subject: [PATCH 4/5] fix cookbook.rst --- doc/source/user_guide/cookbook.rst | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/doc/source/user_guide/cookbook.rst b/doc/source/user_guide/cookbook.rst index 992cdfa5d7332..7d6d3d007bb01 100644 --- a/doc/source/user_guide/cookbook.rst +++ b/doc/source/user_guide/cookbook.rst @@ -1336,21 +1336,27 @@ Values can be set to NaT using np.nan, similar to datetime Aliasing axis names ------------------- +.. versionchanged:: 1.1.0 + + This recipe changed in pandas 1.1 because of changes in pandas internals. + To globally provide aliases for axis names, one can define these 2 functions: .. ipython:: python def set_axis_alias(cls, axis, alias): - if axis not in cls._AXIS_NUMBERS: - raise Exception("invalid axis [%s] for alias [%s]" % (axis, alias)) - cls._AXIS_ALIASES[alias] = axis + if axis not in cls._AXIS_TO_AXIS_NUMBER: + raise ValueError(f"invalid axis [{axis}] for alias [{alias}]") + if alias in cls._AXIS_TO_AXIS_NUMBER: + raise ValueError(f"invalid alias [{alias}] for axis [{axis}]") + cls._AXIS_TO_AXIS_NUMBER[alias] = cls._AXIS_TO_AXIS_NUMBER[axis] .. ipython:: python def clear_axis_alias(cls, axis, alias): - if axis not in cls._AXIS_NUMBERS: - raise Exception("invalid axis [%s] for alias [%s]" % (axis, alias)) - cls._AXIS_ALIASES.pop(alias, None) + if axis not in cls._AXIS_TO_AXIS_NUMBER: + raise ValueError(f"invalid axis [{axis}] for alias [{alias}]") + cls._AXIS_TO_AXIS_NUMBER.pop(alias, None) .. ipython:: python From 6ffa2f5be6d10a98df012094d27ad921f8d8f2d3 Mon Sep 17 00:00:00 2001 From: tp Date: Mon, 20 Apr 2020 22:57:45 +0100 Subject: [PATCH 5/5] DOC: remove alias section from cookbook.rst --- doc/source/user_guide/cookbook.rst | 33 ------------------------------ 1 file changed, 33 deletions(-) diff --git a/doc/source/user_guide/cookbook.rst b/doc/source/user_guide/cookbook.rst index 7d6d3d007bb01..56ef6fc479f2c 100644 --- a/doc/source/user_guide/cookbook.rst +++ b/doc/source/user_guide/cookbook.rst @@ -1333,39 +1333,6 @@ Values can be set to NaT using np.nan, similar to datetime y[1] = np.nan y -Aliasing axis names -------------------- - -.. versionchanged:: 1.1.0 - - This recipe changed in pandas 1.1 because of changes in pandas internals. - -To globally provide aliases for axis names, one can define these 2 functions: - -.. ipython:: python - - def set_axis_alias(cls, axis, alias): - if axis not in cls._AXIS_TO_AXIS_NUMBER: - raise ValueError(f"invalid axis [{axis}] for alias [{alias}]") - if alias in cls._AXIS_TO_AXIS_NUMBER: - raise ValueError(f"invalid alias [{alias}] for axis [{axis}]") - cls._AXIS_TO_AXIS_NUMBER[alias] = cls._AXIS_TO_AXIS_NUMBER[axis] - -.. ipython:: python - - def clear_axis_alias(cls, axis, alias): - if axis not in cls._AXIS_TO_AXIS_NUMBER: - raise ValueError(f"invalid axis [{axis}] for alias [{alias}]") - cls._AXIS_TO_AXIS_NUMBER.pop(alias, None) - -.. ipython:: python - - set_axis_alias(pd.DataFrame, 'columns', 'myaxis2') - df2 = pd.DataFrame(np.random.randn(3, 2), columns=['c1', 'c2'], - index=['i1', 'i2', 'i3']) - df2.sum(axis='myaxis2') - clear_axis_alias(pd.DataFrame, 'columns', 'myaxis2') - Creating example data ---------------------