diff --git a/doc/source/user_guide/scale.rst b/doc/source/user_guide/scale.rst index 7f2419bc7f19d..71aef4fdd75f6 100644 --- a/doc/source/user_guide/scale.rst +++ b/doc/source/user_guide/scale.rst @@ -345,6 +345,7 @@ we need to supply the divisions manually. Now we can do things like fast random access with ``.loc``. .. ipython:: python + :okwarning: ddf.loc["2002-01-01 12:01":"2002-01-01 12:05"].compute() diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 84f1245299d53..539881deff705 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -3685,7 +3685,7 @@ def is_int(v): ) indexer = key else: - indexer = self.slice_indexer(start, stop, step, kind=kind) + indexer = self.slice_indexer(start, stop, step) return indexer @@ -5648,7 +5648,7 @@ def slice_indexer( >>> idx.slice_indexer(start='b', end=('c', 'g')) slice(1, 3, None) """ - start_slice, end_slice = self.slice_locs(start, end, step=step, kind=kind) + start_slice, end_slice = self.slice_locs(start, end, step=step) # return a slice if not is_scalar(start_slice): @@ -5678,7 +5678,7 @@ def _validate_indexer(self, form: str_t, key, kind: str_t): if key is not None and not is_integer(key): raise self._invalid_indexer(form, key) - def _maybe_cast_slice_bound(self, label, side: str_t, kind): + def _maybe_cast_slice_bound(self, label, side: str_t, kind=no_default): """ This function should be overloaded in subclasses that allow non-trivial casting on label-slice bounds, e.g. datetime-like indices allowing @@ -5698,7 +5698,8 @@ def _maybe_cast_slice_bound(self, label, side: str_t, kind): ----- Value of `side` parameter should be validated in caller. """ - assert kind in ["loc", "getitem", None] + assert kind in ["loc", "getitem", None, no_default] + self._deprecated_arg(kind, "kind", "_maybe_cast_slice_bound") # We are a plain index here (sub-class override this method if they # wish to have special treatment for floats/ints, e.g. Float64Index and @@ -5723,7 +5724,7 @@ def _searchsorted_monotonic(self, label, side: str_t = "left"): raise ValueError("index must be monotonic increasing or decreasing") - def get_slice_bound(self, label, side: str_t, kind) -> int: + def get_slice_bound(self, label, side: str_t, kind=None) -> int: """ Calculate slice bound that corresponds to given label. @@ -5753,7 +5754,7 @@ def get_slice_bound(self, label, side: str_t, kind) -> int: # For datetime indices label may be a string that has to be converted # to datetime boundary according to its resolution. - label = self._maybe_cast_slice_bound(label, side, kind) + label = self._maybe_cast_slice_bound(label, side) # we need to look up the label try: @@ -5843,13 +5844,13 @@ def slice_locs(self, start=None, end=None, step=None, kind=None): start_slice = None if start is not None: - start_slice = self.get_slice_bound(start, "left", kind) + start_slice = self.get_slice_bound(start, "left") if start_slice is None: start_slice = 0 end_slice = None if end is not None: - end_slice = self.get_slice_bound(end, "right", kind) + end_slice = self.get_slice_bound(end, "right") if end_slice is None: end_slice = len(self) @@ -6181,6 +6182,18 @@ def shape(self) -> Shape: # See GH#27775, GH#27384 for history/reasoning in how this is defined. return (len(self),) + def _deprecated_arg(self, value, name: str_t, methodname: str_t) -> None: + """ + Issue a FutureWarning if the arg/kwarg is not no_default. + """ + if value is not no_default: + warnings.warn( + f"'{name}' argument in {methodname} is deprecated " + "and will be removed in a future version. Do not pass it.", + FutureWarning, + stacklevel=3, + ) + def ensure_index_from_sequences(sequences, names=None): """ diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index f77f28deecf57..e8b21f3cec668 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -724,7 +724,7 @@ def _maybe_cast_for_get_loc(self, key) -> Timestamp: key = key.tz_convert(self.tz) return key - def _maybe_cast_slice_bound(self, label, side: str, kind): + def _maybe_cast_slice_bound(self, label, side: str, kind=lib.no_default): """ If label is a string, cast it to datetime according to resolution. @@ -742,7 +742,8 @@ def _maybe_cast_slice_bound(self, label, side: str, kind): ----- Value of `side` parameter should be validated in caller. """ - assert kind in ["loc", "getitem", None] + assert kind in ["loc", "getitem", None, lib.no_default] + self._deprecated_arg(kind, "kind", "_maybe_cast_slice_bound") if isinstance(label, str): freq = getattr(self, "freqstr", getattr(self, "inferred_freq", None)) @@ -823,12 +824,12 @@ def check_str_or_none(point): mask = np.array(True) deprecation_mask = np.array(True) if start is not None: - start_casted = self._maybe_cast_slice_bound(start, "left", kind) + start_casted = self._maybe_cast_slice_bound(start, "left") mask = start_casted <= self deprecation_mask = start_casted == self if end is not None: - end_casted = self._maybe_cast_slice_bound(end, "right", kind) + end_casted = self._maybe_cast_slice_bound(end, "right") mask = (self <= end_casted) & mask deprecation_mask = (end_casted == self) | deprecation_mask diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py index d7b5f66bd385f..fc92a1b3afe53 100644 --- a/pandas/core/indexes/interval.py +++ b/pandas/core/indexes/interval.py @@ -825,8 +825,9 @@ def _should_fallback_to_positional(self) -> bool: # positional in this case return self.dtype.subtype.kind in ["m", "M"] - def _maybe_cast_slice_bound(self, label, side: str, kind): - return getattr(self, side)._maybe_cast_slice_bound(label, side, kind) + def _maybe_cast_slice_bound(self, label, side: str, kind=lib.no_default): + self._deprecated_arg(kind, "kind", "_maybe_cast_slice_bound") + return getattr(self, side)._maybe_cast_slice_bound(label, side) @Appender(Index._convert_list_indexer.__doc__) def _convert_list_indexer(self, keyarr): diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index a68238af003e4..d143af4e53c60 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -2716,7 +2716,7 @@ def _get_indexer( return ensure_platform_int(indexer) def get_slice_bound( - self, label: Hashable | Sequence[Hashable], side: str, kind: str + self, label: Hashable | Sequence[Hashable], side: str, kind: str | None = None ) -> int: """ For an ordered MultiIndex, compute slice bound @@ -2729,7 +2729,7 @@ def get_slice_bound( ---------- label : object or tuple of objects side : {'left', 'right'} - kind : {'loc', 'getitem'} + kind : {'loc', 'getitem', None} Returns ------- @@ -2747,13 +2747,13 @@ def get_slice_bound( Get the locations from the leftmost 'b' in the first level until the end of the multiindex: - >>> mi.get_slice_bound('b', side="left", kind="loc") + >>> mi.get_slice_bound('b', side="left") 1 Like above, but if you get the locations from the rightmost 'b' in the first level and 'f' in the second level: - >>> mi.get_slice_bound(('b','f'), side="right", kind="loc") + >>> mi.get_slice_bound(('b','f'), side="right") 3 See Also @@ -2820,7 +2820,7 @@ def slice_locs(self, start=None, end=None, step=None, kind=None): """ # This function adds nothing to its parent implementation (the magic # happens in get_slice_bound method), but it adds meaningful doc. - return super().slice_locs(start, end, step, kind=kind) + return super().slice_locs(start, end, step) def _partial_tup_index(self, tup, side="left"): if len(tup) > self._lexsort_depth: @@ -3206,9 +3206,7 @@ def convert_indexer(start, stop, step, indexer=indexer, codes=level_codes): # we have a partial slice (like looking up a partial date # string) - start = stop = level_index.slice_indexer( - key.start, key.stop, key.step, kind="loc" - ) + start = stop = level_index.slice_indexer(key.start, key.stop, key.step) step = start.step if isinstance(start, slice) or isinstance(stop, slice): diff --git a/pandas/core/indexes/numeric.py b/pandas/core/indexes/numeric.py index 28f563764ef10..88b0b019324ea 100644 --- a/pandas/core/indexes/numeric.py +++ b/pandas/core/indexes/numeric.py @@ -112,8 +112,9 @@ def _validate_dtype(cls, dtype: Dtype | None) -> None: # Indexing Methods @doc(Index._maybe_cast_slice_bound) - def _maybe_cast_slice_bound(self, label, side: str, kind): - assert kind in ["loc", "getitem", None] + def _maybe_cast_slice_bound(self, label, side: str, kind=lib.no_default): + assert kind in ["loc", "getitem", None, lib.no_default] + self._deprecated_arg(kind, "kind", "_maybe_cast_slice_bound") # we will try to coerce to integers return self._maybe_cast_indexer(label) @@ -346,7 +347,7 @@ def _convert_slice_indexer(self, key: slice, kind: str): # We always treat __getitem__ slicing as label-based # translate to locations - return self.slice_indexer(key.start, key.stop, key.step, kind=kind) + return self.slice_indexer(key.start, key.stop, key.step) # ---------------------------------------------------------------- diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 18e441ef165c9..136843938b683 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -531,7 +531,7 @@ def get_loc(self, key, method=None, tolerance=None): except KeyError as err: raise KeyError(orig_key) from err - def _maybe_cast_slice_bound(self, label, side: str, kind: str): + def _maybe_cast_slice_bound(self, label, side: str, kind=lib.no_default): """ If label is a string or a datetime, cast it to Period.ordinal according to resolution. @@ -540,7 +540,7 @@ def _maybe_cast_slice_bound(self, label, side: str, kind: str): ---------- label : object side : {'left', 'right'} - kind : {'loc', 'getitem'} + kind : {'loc', 'getitem'}, or None Returns ------- @@ -551,7 +551,8 @@ def _maybe_cast_slice_bound(self, label, side: str, kind: str): Value of `side` parameter should be validated in caller. """ - assert kind in ["loc", "getitem"] + assert kind in ["loc", "getitem", None, lib.no_default] + self._deprecated_arg(kind, "kind", "_maybe_cast_slice_bound") if isinstance(label, datetime): return Period(label, freq=self.freq) diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index a23dd10bc3c0e..ec97fa1e05851 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -192,7 +192,7 @@ def get_loc(self, key, method=None, tolerance=None): return Index.get_loc(self, key, method, tolerance) - def _maybe_cast_slice_bound(self, label, side: str, kind): + def _maybe_cast_slice_bound(self, label, side: str, kind=lib.no_default): """ If label is a string, cast it to timedelta according to resolution. @@ -206,7 +206,8 @@ def _maybe_cast_slice_bound(self, label, side: str, kind): ------- label : object """ - assert kind in ["loc", "getitem", None] + assert kind in ["loc", "getitem", None, lib.no_default] + self._deprecated_arg(kind, "kind", "_maybe_cast_slice_bound") if isinstance(label, str): parsed = Timedelta(label) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 96aeda955df01..3d36387588e73 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -1170,9 +1170,7 @@ def _get_slice_axis(self, slice_obj: slice, axis: int): return obj.copy(deep=False) labels = obj._get_axis(axis) - indexer = labels.slice_indexer( - slice_obj.start, slice_obj.stop, slice_obj.step, kind="loc" - ) + indexer = labels.slice_indexer(slice_obj.start, slice_obj.stop, slice_obj.step) if isinstance(indexer, slice): return self.obj._slice(indexer, axis=axis) diff --git a/pandas/tests/indexes/datetimes/test_indexing.py b/pandas/tests/indexes/datetimes/test_indexing.py index 3da6414332cb8..2773543b74764 100644 --- a/pandas/tests/indexes/datetimes/test_indexing.py +++ b/pandas/tests/indexes/datetimes/test_indexing.py @@ -679,18 +679,18 @@ def test_maybe_cast_slice_bounds_empty(self): # GH#14354 empty_idx = date_range(freq="1H", periods=0, end="2015") - right = empty_idx._maybe_cast_slice_bound("2015-01-02", "right", "loc") + right = empty_idx._maybe_cast_slice_bound("2015-01-02", "right") exp = Timestamp("2015-01-02 23:59:59.999999999") assert right == exp - left = empty_idx._maybe_cast_slice_bound("2015-01-02", "left", "loc") + left = empty_idx._maybe_cast_slice_bound("2015-01-02", "left") exp = Timestamp("2015-01-02 00:00:00") assert left == exp def test_maybe_cast_slice_duplicate_monotonic(self): # https://github.com/pandas-dev/pandas/issues/16515 idx = DatetimeIndex(["2017", "2017"]) - result = idx._maybe_cast_slice_bound("2017-01-01", "left", "loc") + result = idx._maybe_cast_slice_bound("2017-01-01", "left") expected = Timestamp("2017-01-01") assert result == expected diff --git a/pandas/tests/indexes/period/test_partial_slicing.py b/pandas/tests/indexes/period/test_partial_slicing.py index b0e573250d02e..148999d90d554 100644 --- a/pandas/tests/indexes/period/test_partial_slicing.py +++ b/pandas/tests/indexes/period/test_partial_slicing.py @@ -110,9 +110,9 @@ def test_maybe_cast_slice_bound(self, make_range, frame_or_series): # Check the lower-level calls are raising where expected. with pytest.raises(TypeError, match=msg): - idx._maybe_cast_slice_bound("foo", "left", "loc") + idx._maybe_cast_slice_bound("foo", "left") with pytest.raises(TypeError, match=msg): - idx.get_slice_bound("foo", "left", "loc") + idx.get_slice_bound("foo", "left") with pytest.raises(TypeError, match=msg): obj["2013/09/30":"foo"] diff --git a/pandas/tests/indexes/test_indexing.py b/pandas/tests/indexes/test_indexing.py index 8d2637e4a06f6..379c766b94d6c 100644 --- a/pandas/tests/indexes/test_indexing.py +++ b/pandas/tests/indexes/test_indexing.py @@ -259,3 +259,16 @@ def test_getitem_deprecated_float(idx): expected = idx[1] assert result == expected + + +def test_maybe_cast_slice_bound_kind_deprecated(index): + if not len(index): + return + + with tm.assert_produces_warning(FutureWarning): + # passed as keyword + index._maybe_cast_slice_bound(index[0], "left", kind="loc") + + with tm.assert_produces_warning(FutureWarning): + # pass as positional + index._maybe_cast_slice_bound(index[0], "left", "loc")