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

REF: _AXIS_TO_AXIS_NUMBER to simplify axis access #33637

Merged
merged 5 commits into from
Apr 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 0 additions & 27 deletions doc/source/user_guide/cookbook.rst
Expand Up @@ -1333,33 +1333,6 @@ Values can be set to NaT using np.nan, similar to datetime
y[1] = np.nan
y

Aliasing axis names
-------------------

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

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

.. 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
---------------------

Expand Down
2 changes: 1 addition & 1 deletion pandas/core/computation/align.py
Expand Up @@ -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


Expand Down
19 changes: 17 additions & 2 deletions pandas/core/frame.py
Expand Up @@ -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
Expand All @@ -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"}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Neither _AXIS_NUMBERS and _AXIS_NAMES are needed anymore. Just in case they are used downstream I've deprecated them instead of just removing them.

# ----------------------------------------------------------------------
# Add plotting methods to DataFrame
plot = CachedAccessor("plot", pandas.plotting.PlotAccessor)
Expand Down
69 changes: 34 additions & 35 deletions pandas/core/generic.py
Expand Up @@ -67,7 +67,6 @@
is_dict_like,
is_extension_array_dtype,
is_float,
is_integer,
is_list_like,
is_number,
is_numeric_dtype,
Expand Down Expand Up @@ -302,19 +301,32 @@ 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.", 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.", 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)}
Expand Down Expand Up @@ -353,37 +365,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:
Expand Down Expand Up @@ -448,11 +447,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
Expand Down Expand Up @@ -813,7 +812,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)
Expand Down Expand Up @@ -1156,7 +1155,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))
Expand Down
2 changes: 0 additions & 2 deletions pandas/core/series.py
Expand Up @@ -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
Expand Down
9 changes: 6 additions & 3 deletions pandas/io/json/_json.py
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions pandas/io/pytables.py
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
24 changes: 18 additions & 6 deletions pandas/tests/generic/test_generic.py
Expand Up @@ -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)
Expand Down Expand Up @@ -901,12 +903,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
6 changes: 3 additions & 3 deletions pandas/util/_validators.py
Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down