Repository: modin-project/modin

Issue:
Use `get_axis` internal function instead of `axes` property
So as not to materialize both axes at once.


Discussion:

Resolving patch:
diff --git a/modin/core/dataframe/pandas/dataframe/dataframe.py b/modin/core/dataframe/pandas/dataframe/dataframe.py
--- a/modin/core/dataframe/pandas/dataframe/dataframe.py
+++ b/modin/core/dataframe/pandas/dataframe/dataframe.py
@@ -749,7 +749,7 @@ def _filter_empties(self, compute_metadata=True):
             # do not trigger the computations
             return
 
-        if len(self.axes[0]) == 0 or len(self.axes[1]) == 0:
+        if len(self.get_axis(0)) == 0 or len(self.get_axis(1)) == 0:
             # This is the case for an empty frame. We don't want to completely remove
             # all metadata and partitions so for the moment, we won't prune if the frame
             # is empty.
@@ -1067,7 +1067,7 @@ def _take_2d_positional(
         indexers = []
         for axis, indexer in enumerate((row_positions, col_positions)):
             if is_range_like(indexer):
-                if indexer.step == 1 and len(indexer) == len(self.axes[axis]):
+                if indexer.step == 1 and len(indexer) == len(self.get_axis(axis)):
                     # By this function semantics, `None` indexer is a full-axis access
                     indexer = None
                 elif indexer is not None and not isinstance(indexer, pandas.RangeIndex):
@@ -1660,12 +1660,12 @@ def _get_dict_of_block_index(self, axis, indices, are_indices_sorted=False):
         if isinstance(indices, slice) and (
             indices.step is not None and indices.step != 1
         ):
-            indices = range(*indices.indices(len(self.axes[axis])))
+            indices = range(*indices.indices(len(self.get_axis(axis))))
         # Fasttrack slices
         if isinstance(indices, slice) or (is_range_like(indices) and indices.step == 1):
             # Converting range-like indexer to slice
             indices = slice(indices.start, indices.stop, indices.step)
-            if is_full_grab_slice(indices, sequence_len=len(self.axes[axis])):
+            if is_full_grab_slice(indices, sequence_len=len(self.get_axis(axis))):
                 return OrderedDict(
                     zip(
                         range(self._partitions.shape[axis]),
@@ -1684,7 +1684,7 @@ def _get_dict_of_block_index(self, axis, indices, are_indices_sorted=False):
                 )
                 dict_of_slices.update({last_part: slice(last_idx[0])})
                 return dict_of_slices
-            elif indices.stop is None or indices.stop >= len(self.axes[axis]):
+            elif indices.stop is None or indices.stop >= len(self.get_axis(axis)):
                 first_part, first_idx = list(
                     self._get_dict_of_block_index(axis, [indices.start]).items()
                 )[0]
@@ -1741,7 +1741,7 @@ def _get_dict_of_block_index(self, axis, indices, are_indices_sorted=False):
                 if isinstance(indices, np.ndarray)
                 else np.array(indices, dtype=np.int64)
             )
-            indices[negative_mask] = indices[negative_mask] % len(self.axes[axis])
+            indices[negative_mask] = indices[negative_mask] % len(self.get_axis(axis))
         # If the `indices` array was modified because of the negative indices conversion
         # then the original order was broken and so we have to sort anyway:
         if has_negative or not are_indices_sorted:
@@ -1952,7 +1952,7 @@ def _compute_tree_reduce_metadata(self, axis, new_parts, dtypes=None):
         new_axes, new_axes_lengths = [0, 0], [0, 0]
 
         new_axes[axis] = [MODIN_UNNAMED_SERIES_LABEL]
-        new_axes[axis ^ 1] = self.axes[axis ^ 1]
+        new_axes[axis ^ 1] = self.get_axis(axis ^ 1)
 
         new_axes_lengths[axis] = [1]
         new_axes_lengths[axis ^ 1] = self._axes_lengths[axis ^ 1]
@@ -2541,7 +2541,7 @@ def sort_function(df):  # pragma: no cover
             return df
 
         # If this df is empty, we don't want to try and shuffle or sort.
-        if len(self.axes[0]) == 0 or len(self.axes[1]) == 0:
+        if len(self.get_axis(0)) == 0 or len(self.get_axis(1)) == 0:
             return self.copy()
 
         axis = Axis(axis)
@@ -2980,7 +2980,7 @@ def broadcast_apply(
                 axis,
                 other,
                 join_type,
-                sort=not self.axes[axis].equals(other.axes[axis]),
+                sort=not self.get_axis(axis).equals(other.get_axis(axis)),
             )
             # unwrap list returned by `copartition`.
             right_parts = right_parts[0]
@@ -3121,7 +3121,7 @@ def broadcast_apply_select_indices(
 
         if other is None:
             if apply_indices is None:
-                apply_indices = self.axes[axis][numeric_indices]
+                apply_indices = self.get_axis(axis)[numeric_indices]
             return self.apply_select_indices(
                 axis=axis,
                 func=func,
@@ -3221,7 +3221,7 @@ def broadcast_apply_full_axis(
             other = [o._partitions for o in other] if len(other) else None
 
         if apply_indices is not None:
-            numeric_indices = self.axes[axis ^ 1].get_indexer_for(apply_indices)
+            numeric_indices = self.get_axis(axis ^ 1).get_indexer_for(apply_indices)
             apply_indices = self._get_dict_of_block_index(
                 axis ^ 1, numeric_indices
             ).keys()
@@ -3377,8 +3377,8 @@ def _copartition(self, axis, other, how, sort, force_repartition=False):
         if isinstance(other, type(self)):
             other = [other]
 
-        self_index = self.axes[axis]
-        others_index = [o.axes[axis] for o in other]
+        self_index = self.get_axis(axis)
+        others_index = [o.get_axis(axis) for o in other]
         joined_index, make_reindexer = self._join_index_objects(
             axis, [self_index] + others_index, how, sort
         )
@@ -3404,7 +3404,7 @@ def _copartition(self, axis, other, how, sort, force_repartition=False):
 
         # Picking first non-empty frame
         base_frame = frames[non_empty_frames_idx[0]]
-        base_index = base_frame.axes[axis]
+        base_index = base_frame.get_axis(axis)
 
         # define conditions for reindexing and repartitioning `self` frame
         do_reindex_base = not base_index.equals(joined_index)
@@ -3431,7 +3431,7 @@ def _copartition(self, axis, other, how, sort, force_repartition=False):
 
         # define conditions for reindexing and repartitioning `other` frames
         do_reindex_others = [
-            not o.axes[axis].equals(joined_index) for o in other_frames
+            not o.get_axis(axis).equals(joined_index) for o in other_frames
         ]
 
         do_repartition_others = [None] * len(other_frames)
@@ -3912,7 +3912,7 @@ def groupby_reduce(
             self._propagate_index_objs(axis=0)
 
         if apply_indices is not None:
-            numeric_indices = self.axes[axis ^ 1].get_indexer_for(apply_indices)
+            numeric_indices = self.get_axis(axis ^ 1).get_indexer_for(apply_indices)
             apply_indices = list(
                 self._get_dict_of_block_index(axis ^ 1, numeric_indices).keys()
             )
diff --git a/modin/pandas/base.py b/modin/pandas/base.py
--- a/modin/pandas/base.py
+++ b/modin/pandas/base.py
@@ -340,7 +340,7 @@ def _validate_other(
         elif is_dict_like(other):
             other_dtypes = [
                 type(other[label])
-                for label in self._query_compiler.get_axis(axis)
+                for label in self._get_axis(axis)
                 # The binary operation is applied for intersection of axis labels
                 # and dictionary keys. So filtering out extra keys.
                 if label in other
@@ -359,9 +359,7 @@ def _validate_other(
                 # dictionary.
                 self_dtypes = [
                     dtype
-                    for label, dtype in zip(
-                        self._query_compiler.get_axis(axis), self._get_dtypes()
-                    )
+                    for label, dtype in zip(self._get_axis(axis), self._get_dtypes())
                     if label in other
                 ]
 
@@ -405,7 +403,7 @@ def error_raiser(msg, exception=Exception):
             [self._validate_function(fn, on_invalid) for fn in func.values()]
             return
             # We also could validate this, but it may be quite expensive for lazy-frames
-            # if not all(idx in self.axes[axis] for idx in func.keys()):
+            # if not all(idx in self._get_axis(axis) for idx in func.keys()):
             #     error_raiser("Invalid dict keys", KeyError)
 
         if not is_list_like(func):
@@ -626,6 +624,22 @@ def _get_index(self):
 
     index = property(_get_index, _set_index)
 
+    def _get_axis(self, axis):
+        """
+        Return index labels of the specified axis.
+
+        Parameters
+        ----------
+        axis : {0, 1}
+            Axis to return labels on.
+            0 is for index, when 1 is for columns.
+
+        Returns
+        -------
+        pandas.Index
+        """
+        return self.index if axis == 0 else self.columns
+
     def add(
         self, other, axis="columns", level=None, fill_value=None
     ):  # noqa: PR01, RT01, D200
@@ -2466,7 +2480,7 @@ def reorder_levels(self, order, axis=0):  # noqa: PR01, RT01, D200
         Rearrange index levels using input order.
         """
         axis = self._get_axis_number(axis)
-        new_labels = self.axes[axis].reorder_levels(order)
+        new_labels = self._get_axis(axis).reorder_levels(order)
         return self.set_axis(new_labels, axis=axis)
 
     def resample(
@@ -2727,7 +2741,7 @@ def sample(
             # Index of the weights Series should correspond to the index of the
             # Dataframe in order to sample
             if isinstance(weights, BasePandasDataset):
-                weights = weights.reindex(self.axes[axis])
+                weights = weights.reindex(self._get_axis(axis))
             # If weights arg is a string, the weights used for sampling will
             # the be values in the column corresponding to that string
             if isinstance(weights, str):
@@ -3509,15 +3523,15 @@ def truncate(
         """
         axis = self._get_axis_number(axis)
         if (
-            not self.axes[axis].is_monotonic_increasing
-            and not self.axes[axis].is_monotonic_decreasing
+            not self._get_axis(axis).is_monotonic_increasing
+            and not self._get_axis(axis).is_monotonic_decreasing
         ):
             raise ValueError("truncate requires a sorted index")
 
         if before is not None and after is not None and before > after:
             raise ValueError(f"Truncate: {after} must be after {before}")
 
-        s = slice(*self.axes[axis].slice_locs(before, after))
+        s = slice(*self._get_axis(axis).slice_locs(before, after))
         slice_obj = s if axis == 0 else (slice(None), s)
         return self.iloc[slice_obj]
 
diff --git a/modin/pandas/dataframe.py b/modin/pandas/dataframe.py
--- a/modin/pandas/dataframe.py
+++ b/modin/pandas/dataframe.py
@@ -516,7 +516,7 @@ def groupby(
                     (hashable(o) and (o in self))
                     or isinstance(o, Series)
                     or (isinstance(o, pandas.Grouper) and o.key in self)
-                    or (is_list_like(o) and len(o) == len(self.axes[axis]))
+                    or (is_list_like(o) and len(o) == len(self._get_axis(axis)))
                 )
                 for o in by
             ):
@@ -546,7 +546,7 @@ def groupby(
 
                 drop = True
             else:
-                mismatch = len(by) != len(self.axes[axis])
+                mismatch = len(by) != len(self._get_axis(axis))
                 if mismatch and all(
                     hashable(obj)
                     and (


Test patch: