@@ -1959,23 +1959,99 @@ def slice_indexer(self, start=None, end=None, step=None):
1959
1959
-----
1960
1960
This function assumes that the data is sorted, so use at your own peril
1961
1961
"""
1962
- start_slice , end_slice = self .slice_locs (start , end )
1962
+ start_slice , end_slice = self .slice_locs (start , end , step = step )
1963
1963
1964
1964
# return a slice
1965
- if np .isscalar (start_slice ) and np .isscalar (end_slice ):
1965
+ if not lib .isscalar (start_slice ):
1966
+ raise AssertionError ("Start slice bound is non-scalar" )
1967
+ if not lib .isscalar (end_slice ):
1968
+ raise AssertionError ("End slice bound is non-scalar" )
1966
1969
1967
- # degenerate cases
1968
- if start is None and end is None :
1969
- return slice (None , None , step )
1970
+ return slice (start_slice , end_slice , step )
1970
1971
1971
- return slice (start_slice , end_slice , step )
1972
+ def _maybe_cast_slice_bound (self , label , side ):
1973
+ """
1974
+ This function should be overloaded in subclasses that allow non-trivial
1975
+ casting on label-slice bounds, e.g. datetime-like indices allowing
1976
+ strings containing formatted datetimes.
1972
1977
1973
- # loc indexers
1974
- return (Index (start_slice ) & Index (end_slice )).values
1978
+ Parameters
1979
+ ----------
1980
+ label : object
1981
+ side : {'left', 'right'}
1982
+
1983
+ Notes
1984
+ -----
1985
+ Value of `side` parameter should be validated in caller.
1975
1986
1976
- def slice_locs (self , start = None , end = None ):
1977
1987
"""
1978
- For an ordered Index, compute the slice locations for input labels
1988
+ return label
1989
+
1990
+ def get_slice_bound (self , label , side ):
1991
+ """
1992
+ Calculate slice bound that corresponds to given label.
1993
+
1994
+ Returns leftmost (one-past-the-rightmost if ``side=='right'``) position
1995
+ of given label.
1996
+
1997
+ Parameters
1998
+ ----------
1999
+ label : object
2000
+ side : {'left', 'right'}
2001
+
2002
+ """
2003
+ if side not in ('left' , 'right' ):
2004
+ raise ValueError (
2005
+ "Invalid value for side kwarg,"
2006
+ " must be either 'left' or 'right': %s" % (side ,))
2007
+
2008
+ original_label = label
2009
+ # For datetime indices label may be a string that has to be converted
2010
+ # to datetime boundary according to its resolution.
2011
+ label = self ._maybe_cast_slice_bound (label , side )
2012
+
2013
+ try :
2014
+ slc = self .get_loc (label )
2015
+ except KeyError :
2016
+ if self .is_monotonic_increasing :
2017
+ return self .searchsorted (label , side = side )
2018
+ elif self .is_monotonic_decreasing :
2019
+ # np.searchsorted expects ascending sort order, have to reverse
2020
+ # everything for it to work (element ordering, search side and
2021
+ # resulting value).
2022
+ pos = self [::- 1 ].searchsorted (
2023
+ label , side = 'right' if side == 'left' else 'right' )
2024
+ return len (self ) - pos
2025
+
2026
+ # In all other cases, just re-raise the KeyError
2027
+ raise
2028
+
2029
+ if isinstance (slc , np .ndarray ):
2030
+ # get_loc may return a boolean array or an array of indices, which
2031
+ # is OK as long as they are representable by a slice.
2032
+ if com .is_bool_dtype (slc ):
2033
+ slc = lib .maybe_booleans_to_slice (slc .view ('u1' ))
2034
+ else :
2035
+ slc = lib .maybe_indices_to_slice (slc .astype ('i8' ))
2036
+ if isinstance (slc , np .ndarray ):
2037
+ raise KeyError (
2038
+ "Cannot get %s slice bound for non-unique label:"
2039
+ " %r" % (side , original_label ))
2040
+
2041
+ if isinstance (slc , slice ):
2042
+ if side == 'left' :
2043
+ return slc .start
2044
+ else :
2045
+ return slc .stop
2046
+ else :
2047
+ if side == 'right' :
2048
+ return slc + 1
2049
+ else :
2050
+ return slc
2051
+
2052
+ def slice_locs (self , start = None , end = None , step = None ):
2053
+ """
2054
+ Compute slice locations for input labels.
1979
2055
1980
2056
Parameters
1981
2057
----------
@@ -1986,51 +2062,51 @@ def slice_locs(self, start=None, end=None):
1986
2062
1987
2063
Returns
1988
2064
-------
1989
- ( start, end) : ( int, int)
2065
+ start, end : int
1990
2066
1991
- Notes
1992
- -----
1993
- This function assumes that the data is sorted, so use at your own peril
1994
2067
"""
2068
+ inc = (step is None or step >= 0 )
1995
2069
1996
- is_unique = self .is_unique
1997
-
1998
- def _get_slice (starting_value , offset , search_side , slice_property ,
1999
- search_value ):
2000
- if search_value is None :
2001
- return starting_value
2070
+ if not inc :
2071
+ # If it's a reverse slice, temporarily swap bounds.
2072
+ start , end = end , start
2002
2073
2003
- try :
2004
- slc = self .get_loc (search_value )
2005
-
2006
- if not is_unique :
2007
-
2008
- # get_loc will return a boolean array for non_uniques
2009
- # if we are not monotonic
2010
- if isinstance (slc , (np .ndarray , Index )):
2011
- raise KeyError ("cannot peform a slice operation "
2012
- "on a non-unique non-monotonic index" )
2013
-
2014
- if isinstance (slc , slice ):
2015
- slc = getattr (slc , slice_property )
2016
- else :
2017
- slc += offset
2074
+ start_slice = None
2075
+ if start is not None :
2076
+ start_slice = self .get_slice_bound (start , 'left' )
2077
+ if start_slice is None :
2078
+ start_slice = 0
2018
2079
2019
- except KeyError :
2020
- if self .is_monotonic_increasing :
2021
- slc = self .searchsorted (search_value , side = search_side )
2022
- elif self .is_monotonic_decreasing :
2023
- search_side = 'right' if search_side == 'left' else 'left'
2024
- slc = len (self ) - self [::- 1 ].searchsorted (search_value ,
2025
- side = search_side )
2026
- else :
2027
- raise
2028
- return slc
2080
+ end_slice = None
2081
+ if end is not None :
2082
+ end_slice = self .get_slice_bound (end , 'right' )
2083
+ if end_slice is None :
2084
+ end_slice = len (self )
2029
2085
2030
- start_slice = _get_slice (0 , offset = 0 , search_side = 'left' ,
2031
- slice_property = 'start' , search_value = start )
2032
- end_slice = _get_slice (len (self ), offset = 1 , search_side = 'right' ,
2033
- slice_property = 'stop' , search_value = end )
2086
+ if not inc :
2087
+ # Bounds at this moment are swapped, swap them back and shift by 1.
2088
+ #
2089
+ # slice_locs('B', 'A', step=-1): s='B', e='A'
2090
+ #
2091
+ # s='A' e='B'
2092
+ # AFTER SWAP: | |
2093
+ # v ------------------> V
2094
+ # -----------------------------------
2095
+ # | | |A|A|A|A| | | | | |B|B| | | | |
2096
+ # -----------------------------------
2097
+ # ^ <------------------ ^
2098
+ # SHOULD BE: | |
2099
+ # end=s-1 start=e-1
2100
+ #
2101
+ end_slice , start_slice = start_slice - 1 , end_slice - 1
2102
+
2103
+ # i == -1 triggers ``len(self) + i`` selection that points to the
2104
+ # last element, not before-the-first one, subtracting len(self)
2105
+ # compensates that.
2106
+ if end_slice == - 1 :
2107
+ end_slice -= len (self )
2108
+ if start_slice == - 1 :
2109
+ start_slice -= len (self )
2034
2110
2035
2111
return start_slice , end_slice
2036
2112
@@ -3887,7 +3963,12 @@ def _tuple_index(self):
3887
3963
"""
3888
3964
return Index (self .values )
3889
3965
3890
- def slice_locs (self , start = None , end = None , strict = False ):
3966
+ def get_slice_bound (self , label , side ):
3967
+ if not isinstance (label , tuple ):
3968
+ label = label ,
3969
+ return self ._partial_tup_index (label , side = side )
3970
+
3971
+ def slice_locs (self , start = None , end = None , step = None ):
3891
3972
"""
3892
3973
For an ordered MultiIndex, compute the slice locations for input
3893
3974
labels. They can be tuples representing partial levels, e.g. for a
@@ -3900,7 +3981,8 @@ def slice_locs(self, start=None, end=None, strict=False):
3900
3981
If None, defaults to the beginning
3901
3982
end : label or tuple
3902
3983
If None, defaults to the end
3903
- strict : boolean,
3984
+ step : int or None
3985
+ Slice step
3904
3986
3905
3987
Returns
3906
3988
-------
@@ -3910,21 +3992,9 @@ def slice_locs(self, start=None, end=None, strict=False):
3910
3992
-----
3911
3993
This function assumes that the data is sorted by the first level
3912
3994
"""
3913
- if start is None :
3914
- start_slice = 0
3915
- else :
3916
- if not isinstance (start , tuple ):
3917
- start = start ,
3918
- start_slice = self ._partial_tup_index (start , side = 'left' )
3919
-
3920
- if end is None :
3921
- end_slice = len (self )
3922
- else :
3923
- if not isinstance (end , tuple ):
3924
- end = end ,
3925
- end_slice = self ._partial_tup_index (end , side = 'right' )
3926
-
3927
- return start_slice , end_slice
3995
+ # This function adds nothing to its parent implementation (the magic
3996
+ # happens in get_slice_bound method), but it adds meaningful doc.
3997
+ return super (MultiIndex , self ).slice_locs (start , end , step )
3928
3998
3929
3999
def _partial_tup_index (self , tup , side = 'left' ):
3930
4000
if len (tup ) > self .lexsort_depth :
0 commit comments