Skip to content

Commit 6929e26

Browse files
authored
Move sort index to generic (#36177)
1 parent 3833dc5 commit 6929e26

File tree

4 files changed

+134
-112
lines changed

4 files changed

+134
-112
lines changed

pandas/core/frame.py

+11-57
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,6 @@
143143
)
144144
from pandas.core.reshape.melt import melt
145145
from pandas.core.series import Series
146-
from pandas.core.sorting import ensure_key_mapped
147146

148147
from pandas.io.common import get_filepath_or_buffer
149148
from pandas.io.formats import console, format as fmt
@@ -5448,62 +5447,17 @@ def sort_index(
54485447
C 3
54495448
d 4
54505449
"""
5451-
# TODO: this can be combined with Series.sort_index impl as
5452-
# almost identical
5453-
5454-
inplace = validate_bool_kwarg(inplace, "inplace")
5455-
5456-
axis = self._get_axis_number(axis)
5457-
labels = self._get_axis(axis)
5458-
labels = ensure_key_mapped(labels, key, levels=level)
5459-
5460-
# make sure that the axis is lexsorted to start
5461-
# if not we need to reconstruct to get the correct indexer
5462-
labels = labels._sort_levels_monotonic()
5463-
if level is not None:
5464-
new_axis, indexer = labels.sortlevel(
5465-
level, ascending=ascending, sort_remaining=sort_remaining
5466-
)
5467-
5468-
elif isinstance(labels, MultiIndex):
5469-
from pandas.core.sorting import lexsort_indexer
5470-
5471-
indexer = lexsort_indexer(
5472-
labels._get_codes_for_sorting(),
5473-
orders=ascending,
5474-
na_position=na_position,
5475-
)
5476-
else:
5477-
from pandas.core.sorting import nargsort
5478-
5479-
# Check monotonic-ness before sort an index
5480-
# GH11080
5481-
if (ascending and labels.is_monotonic_increasing) or (
5482-
not ascending and labels.is_monotonic_decreasing
5483-
):
5484-
if inplace:
5485-
return
5486-
else:
5487-
return self.copy()
5488-
5489-
indexer = nargsort(
5490-
labels, kind=kind, ascending=ascending, na_position=na_position
5491-
)
5492-
5493-
baxis = self._get_block_manager_axis(axis)
5494-
new_data = self._mgr.take(indexer, axis=baxis, verify=False)
5495-
5496-
# reconstruct axis if needed
5497-
new_data.axes[baxis] = new_data.axes[baxis]._sort_levels_monotonic()
5498-
5499-
if ignore_index:
5500-
new_data.axes[1] = ibase.default_index(len(indexer))
5501-
5502-
result = self._constructor(new_data)
5503-
if inplace:
5504-
return self._update_inplace(result)
5505-
else:
5506-
return result.__finalize__(self, method="sort_index")
5450+
return super().sort_index(
5451+
axis,
5452+
level,
5453+
ascending,
5454+
inplace,
5455+
kind,
5456+
na_position,
5457+
sort_remaining,
5458+
ignore_index,
5459+
key,
5460+
)
55075461

55085462
def value_counts(
55095463
self,

pandas/core/generic.py

+47
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
CompressionOptions,
4141
FilePathOrBuffer,
4242
FrameOrSeries,
43+
IndexKeyFunc,
4344
IndexLabel,
4445
JSONSerializable,
4546
Label,
@@ -92,6 +93,7 @@
9293
import pandas.core.common as com
9394
from pandas.core.construction import create_series_with_explicit_dtype
9495
from pandas.core.flags import Flags
96+
from pandas.core.indexes import base as ibase
9597
from pandas.core.indexes.api import Index, MultiIndex, RangeIndex, ensure_index
9698
from pandas.core.indexes.datetimes import DatetimeIndex
9799
from pandas.core.indexes.period import Period, PeriodIndex
@@ -100,6 +102,7 @@
100102
from pandas.core.missing import find_valid_index
101103
from pandas.core.ops import align_method_FRAME
102104
from pandas.core.shared_docs import _shared_docs
105+
from pandas.core.sorting import get_indexer_indexer
103106
from pandas.core.window import Expanding, ExponentialMovingWindow, Rolling, Window
104107

105108
from pandas.io.formats import format as fmt
@@ -4409,6 +4412,50 @@ def sort_values(
44094412
"""
44104413
raise AbstractMethodError(self)
44114414

4415+
def sort_index(
4416+
self,
4417+
axis=0,
4418+
level=None,
4419+
ascending: bool_t = True,
4420+
inplace: bool_t = False,
4421+
kind: str = "quicksort",
4422+
na_position: str = "last",
4423+
sort_remaining: bool_t = True,
4424+
ignore_index: bool_t = False,
4425+
key: IndexKeyFunc = None,
4426+
):
4427+
4428+
inplace = validate_bool_kwarg(inplace, "inplace")
4429+
axis = self._get_axis_number(axis)
4430+
target = self._get_axis(axis)
4431+
4432+
indexer = get_indexer_indexer(
4433+
target, level, ascending, kind, na_position, sort_remaining, key
4434+
)
4435+
4436+
if indexer is None:
4437+
if inplace:
4438+
return
4439+
else:
4440+
return self.copy()
4441+
4442+
baxis = self._get_block_manager_axis(axis)
4443+
new_data = self._mgr.take(indexer, axis=baxis, verify=False)
4444+
4445+
# reconstruct axis if needed
4446+
new_data.axes[baxis] = new_data.axes[baxis]._sort_levels_monotonic()
4447+
4448+
if ignore_index:
4449+
axis = 1 if isinstance(self, ABCDataFrame) else 0
4450+
new_data.axes[axis] = ibase.default_index(len(indexer))
4451+
4452+
result = self._constructor(new_data)
4453+
4454+
if inplace:
4455+
return self._update_inplace(result)
4456+
else:
4457+
return result.__finalize__(self, method="sort_index")
4458+
44124459
@doc(
44134460
klass=_shared_doc_kwargs["klass"],
44144461
axes=_shared_doc_kwargs["axes"],

pandas/core/series.py

+11-53
Original file line numberDiff line numberDiff line change
@@ -3463,59 +3463,17 @@ def sort_index(
34633463
dtype: int64
34643464
"""
34653465

3466-
# TODO: this can be combined with DataFrame.sort_index impl as
3467-
# almost identical
3468-
inplace = validate_bool_kwarg(inplace, "inplace")
3469-
# Validate the axis parameter
3470-
self._get_axis_number(axis)
3471-
index = ensure_key_mapped(self.index, key, levels=level)
3472-
3473-
if level is not None:
3474-
new_index, indexer = index.sortlevel(
3475-
level, ascending=ascending, sort_remaining=sort_remaining
3476-
)
3477-
3478-
elif isinstance(index, MultiIndex):
3479-
from pandas.core.sorting import lexsort_indexer
3480-
3481-
labels = index._sort_levels_monotonic()
3482-
3483-
indexer = lexsort_indexer(
3484-
labels._get_codes_for_sorting(),
3485-
orders=ascending,
3486-
na_position=na_position,
3487-
)
3488-
else:
3489-
from pandas.core.sorting import nargsort
3490-
3491-
# Check monotonic-ness before sort an index
3492-
# GH11080
3493-
if (ascending and index.is_monotonic_increasing) or (
3494-
not ascending and index.is_monotonic_decreasing
3495-
):
3496-
if inplace:
3497-
return
3498-
else:
3499-
return self.copy()
3500-
3501-
indexer = nargsort(
3502-
index, kind=kind, ascending=ascending, na_position=na_position
3503-
)
3504-
3505-
indexer = ensure_platform_int(indexer)
3506-
new_index = self.index.take(indexer)
3507-
new_index = new_index._sort_levels_monotonic()
3508-
3509-
new_values = self._values.take(indexer)
3510-
result = self._constructor(new_values, index=new_index)
3511-
3512-
if ignore_index:
3513-
result.index = ibase.default_index(len(result))
3514-
3515-
if inplace:
3516-
self._update_inplace(result)
3517-
else:
3518-
return result.__finalize__(self, method="sort_index")
3466+
return super().sort_index(
3467+
axis,
3468+
level,
3469+
ascending,
3470+
inplace,
3471+
kind,
3472+
na_position,
3473+
sort_remaining,
3474+
ignore_index,
3475+
key,
3476+
)
35193477

35203478
def argsort(self, axis=0, kind="quicksort", order=None) -> "Series":
35213479
"""

pandas/core/sorting.py

+65-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
""" miscellaneous sorting / groupby utilities """
22
from collections import defaultdict
3-
from typing import TYPE_CHECKING, Callable, DefaultDict, Iterable, List, Optional, Tuple
3+
from typing import (
4+
TYPE_CHECKING,
5+
Callable,
6+
DefaultDict,
7+
Iterable,
8+
List,
9+
Optional,
10+
Tuple,
11+
Union,
12+
)
413

514
import numpy as np
615

716
from pandas._libs import algos, hashtable, lib
817
from pandas._libs.hashtable import unique_label_indices
18+
from pandas._typing import IndexKeyFunc
919

1020
from pandas.core.dtypes.common import (
1121
ensure_int64,
@@ -20,11 +30,64 @@
2030
from pandas.core.construction import extract_array
2131

2232
if TYPE_CHECKING:
23-
from pandas.core.indexes.base import Index # noqa:F401
33+
from pandas.core.indexes.base import Index
2434

2535
_INT64_MAX = np.iinfo(np.int64).max
2636

2737

38+
def get_indexer_indexer(
39+
target: "Index",
40+
level: Union[str, int, List[str], List[int]],
41+
ascending: bool,
42+
kind: str,
43+
na_position: str,
44+
sort_remaining: bool,
45+
key: IndexKeyFunc,
46+
) -> Optional[np.array]:
47+
"""
48+
Helper method that return the indexer according to input parameters for
49+
the sort_index method of DataFrame and Series.
50+
51+
Parameters
52+
----------
53+
target : Index
54+
level : int or level name or list of ints or list of level names
55+
ascending : bool or list of bools, default True
56+
kind : {'quicksort', 'mergesort', 'heapsort'}, default 'quicksort'
57+
na_position : {'first', 'last'}, default 'last'
58+
sort_remaining : bool, default True
59+
key : callable, optional
60+
61+
Returns
62+
-------
63+
Optional[ndarray]
64+
The indexer for the new index.
65+
"""
66+
67+
target = ensure_key_mapped(target, key, levels=level)
68+
target = target._sort_levels_monotonic()
69+
70+
if level is not None:
71+
_, indexer = target.sortlevel(
72+
level, ascending=ascending, sort_remaining=sort_remaining
73+
)
74+
elif isinstance(target, ABCMultiIndex):
75+
indexer = lexsort_indexer(
76+
target._get_codes_for_sorting(), orders=ascending, na_position=na_position,
77+
)
78+
else:
79+
# Check monotonic-ness before sort an index (GH 11080)
80+
if (ascending and target.is_monotonic_increasing) or (
81+
not ascending and target.is_monotonic_decreasing
82+
):
83+
return None
84+
85+
indexer = nargsort(
86+
target, kind=kind, ascending=ascending, na_position=na_position
87+
)
88+
return indexer
89+
90+
2891
def get_group_index(labels, shape, sort: bool, xnull: bool):
2992
"""
3093
For the particular label_list, gets the offsets into the hypothetical list

0 commit comments

Comments
 (0)