4747 Axis ,
4848 Dtype ,
4949 FilePathOrBuffer ,
50+ IndexKeyFunc ,
5051 Label ,
5152 Level ,
5253 Renamer ,
54+ ValueKeyFunc ,
5355)
5456from pandas .compat import PY37
5557from pandas .compat ._optional import import_optional_dependency
139141)
140142from pandas .core .ops .missing import dispatch_fill_zeros
141143from pandas .core .series import Series
144+ from pandas .core .sorting import ensure_key_mapped
142145
143146from pandas .io .common import get_filepath_or_buffer
144147from pandas .io .formats import console , format as fmt
@@ -5054,10 +5057,10 @@ def f(vals):
50545057
50555058 # ----------------------------------------------------------------------
50565059 # Sorting
5057-
5060+ # TODO: Just move the sort_values doc here.
50585061 @Substitution (** _shared_doc_kwargs )
50595062 @Appender (NDFrame .sort_values .__doc__ )
5060- def sort_values (
5063+ def sort_values ( # type: ignore[override] # NOQA # issue 27237
50615064 self ,
50625065 by ,
50635066 axis = 0 ,
@@ -5066,6 +5069,7 @@ def sort_values(
50665069 kind = "quicksort" ,
50675070 na_position = "last" ,
50685071 ignore_index = False ,
5072+ key : ValueKeyFunc = None ,
50695073 ):
50705074 inplace = validate_bool_kwarg (inplace , "inplace" )
50715075 axis = self ._get_axis_number (axis )
@@ -5080,19 +5084,30 @@ def sort_values(
50805084 from pandas .core .sorting import lexsort_indexer
50815085
50825086 keys = [self ._get_label_or_level_values (x , axis = axis ) for x in by ]
5083- indexer = lexsort_indexer (keys , orders = ascending , na_position = na_position )
5087+
5088+ # need to rewrap columns in Series to apply key function
5089+ if key is not None :
5090+ keys = [Series (k , name = name ) for (k , name ) in zip (keys , by )]
5091+
5092+ indexer = lexsort_indexer (
5093+ keys , orders = ascending , na_position = na_position , key = key
5094+ )
50845095 indexer = ensure_platform_int (indexer )
50855096 else :
50865097 from pandas .core .sorting import nargsort
50875098
50885099 by = by [0 ]
50895100 k = self ._get_label_or_level_values (by , axis = axis )
50905101
5102+ # need to rewrap column in Series to apply key function
5103+ if key is not None :
5104+ k = Series (k , name = by )
5105+
50915106 if isinstance (ascending , (tuple , list )):
50925107 ascending = ascending [0 ]
50935108
50945109 indexer = nargsort (
5095- k , kind = kind , ascending = ascending , na_position = na_position
5110+ k , kind = kind , ascending = ascending , na_position = na_position , key = key
50965111 )
50975112
50985113 new_data = self ._mgr .take (
@@ -5118,6 +5133,7 @@ def sort_index(
51185133 na_position : str = "last" ,
51195134 sort_remaining : bool = True ,
51205135 ignore_index : bool = False ,
5136+ key : IndexKeyFunc = None ,
51215137 ):
51225138 """
51235139 Sort object by labels (along an axis).
@@ -5153,6 +5169,16 @@ def sort_index(
51535169
51545170 .. versionadded:: 1.0.0
51555171
5172+ key : callable, optional
5173+ If not None, apply the key function to the index values
5174+ before sorting. This is similar to the `key` argument in the
5175+ builtin :meth:`sorted` function, with the notable difference that
5176+ this `key` function should be *vectorized*. It should expect an
5177+ ``Index`` and return an ``Index`` of the same shape. For MultiIndex
5178+ inputs, the key is applied *per level*.
5179+
5180+ .. versionadded:: 1.1.0
5181+
51565182 Returns
51575183 -------
51585184 DataFrame
@@ -5186,6 +5212,17 @@ def sort_index(
51865212 100 1
51875213 29 2
51885214 1 4
5215+
5216+ A key function can be specified which is applied to the index before
5217+ sorting. For a ``MultiIndex`` this is applied to each level separately.
5218+
5219+ >>> df = pd.DataFrame({"a": [1, 2, 3, 4]}, index=['A', 'b', 'C', 'd'])
5220+ >>> df.sort_index(key=lambda x: x.str.lower())
5221+ a
5222+ A 1
5223+ b 2
5224+ C 3
5225+ d 4
51895226 """
51905227 # TODO: this can be combined with Series.sort_index impl as
51915228 # almost identical
@@ -5194,12 +5231,12 @@ def sort_index(
51945231
51955232 axis = self ._get_axis_number (axis )
51965233 labels = self ._get_axis (axis )
5234+ labels = ensure_key_mapped (labels , key , levels = level )
51975235
51985236 # make sure that the axis is lexsorted to start
51995237 # if not we need to reconstruct to get the correct indexer
52005238 labels = labels ._sort_levels_monotonic ()
52015239 if level is not None :
5202-
52035240 new_axis , indexer = labels .sortlevel (
52045241 level , ascending = ascending , sort_remaining = sort_remaining
52055242 )
0 commit comments