Skip to content

Commit 22497ef

Browse files
mroeschkecodamuse
authored andcommitted
DEPR: Enforce deprecations in indexes/datetimes.py (pandas-dev#49607)
* DEPR: date_range(closed) * Disallow partial slicing of missing * Review + failed test
1 parent 2088f0e commit 22497ef

File tree

6 files changed

+28
-73
lines changed

6 files changed

+28
-73
lines changed

doc/source/whatsnew/v2.0.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -433,9 +433,11 @@ Removal of prior version deprecations/changes
433433
- Remove :meth:`DataFrameGroupBy.pad` and :meth:`DataFrameGroupBy.backfill` (:issue:`45076`)
434434
- Remove ``numpy`` argument from :func:`read_json` (:issue:`30636`)
435435
- Disallow passing abbreviations for ``orient`` in :meth:`DataFrame.to_dict` (:issue:`32516`)
436+
- Disallow partial slicing on an non-monotonic :class:`DatetimeIndex` with keys which are not in Index. This now raises a ``KeyError`` (:issue:`18531`)
436437
- Removed ``get_offset`` in favor of :func:`to_offset` (:issue:`30340`)
437438
- Removed the ``warn`` keyword in :func:`infer_freq` (:issue:`45947`)
438439
- Removed the ``include_start`` and ``include_end`` arguments in :meth:`DataFrame.between_time` in favor of ``inclusive`` (:issue:`43248`)
440+
- Removed the ``closed`` argument in :meth:`date_range` and :meth:`bdate_range` in favor of ``inclusive`` argument (:issue:`40245`)
439441
- Removed the ``center`` keyword in :meth:`DataFrame.expanding` (:issue:`20647`)
440442
- Removed the ``truediv`` keyword from :func:`eval` (:issue:`29812`)
441443
- Removed the ``pandas.datetime`` submodule (:issue:`30489`)

pandas/core/indexes/datetimes.py

+8-53
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from typing import (
1212
TYPE_CHECKING,
1313
Hashable,
14-
Literal,
1514
)
1615
import warnings
1716

@@ -37,7 +36,6 @@
3736
DtypeObj,
3837
Frequency,
3938
IntervalClosedType,
40-
IntervalLeftRight,
4139
TimeAmbiguous,
4240
TimeNonexistent,
4341
npt,
@@ -46,7 +44,6 @@
4644
cache_readonly,
4745
doc,
4846
)
49-
from pandas.util._exceptions import find_stack_level
5047

5148
from pandas.core.dtypes.common import (
5249
is_datetime64_dtype,
@@ -699,24 +696,21 @@ def check_str_or_none(point) -> bool:
699696
return Index.slice_indexer(self, start, end, step)
700697

701698
mask = np.array(True)
702-
deprecation_mask = np.array(True)
699+
raise_mask = np.array(True)
703700
if start is not None:
704701
start_casted = self._maybe_cast_slice_bound(start, "left")
705702
mask = start_casted <= self
706-
deprecation_mask = start_casted == self
703+
raise_mask = start_casted == self
707704

708705
if end is not None:
709706
end_casted = self._maybe_cast_slice_bound(end, "right")
710707
mask = (self <= end_casted) & mask
711-
deprecation_mask = (end_casted == self) | deprecation_mask
708+
raise_mask = (end_casted == self) | raise_mask
712709

713-
if not deprecation_mask.any():
714-
warnings.warn(
710+
if not raise_mask.any():
711+
raise KeyError(
715712
"Value based partial slicing on non-monotonic DatetimeIndexes "
716-
"with non-existing keys is deprecated and will raise a "
717-
"KeyError in a future Version.",
718-
FutureWarning,
719-
stacklevel=find_stack_level(),
713+
"with non-existing keys is not allowed.",
720714
)
721715
indexer = mask.nonzero()[0][::step]
722716
if len(indexer) == len(self):
@@ -829,8 +823,7 @@ def date_range(
829823
tz=None,
830824
normalize: bool = False,
831825
name: Hashable = None,
832-
closed: Literal["left", "right"] | None | lib.NoDefault = lib.no_default,
833-
inclusive: IntervalClosedType | None = None,
826+
inclusive: IntervalClosedType = "both",
834827
**kwargs,
835828
) -> DatetimeIndex:
836829
"""
@@ -865,13 +858,6 @@ def date_range(
865858
Normalize start/end dates to midnight before generating date range.
866859
name : str, default None
867860
Name of the resulting DatetimeIndex.
868-
closed : {None, 'left', 'right'}, optional
869-
Make the interval closed with respect to the given frequency to
870-
the 'left', 'right', or both sides (None, the default).
871-
872-
.. deprecated:: 1.4.0
873-
Argument `closed` has been deprecated to standardize boundary inputs.
874-
Use `inclusive` instead, to set each bound as closed or open.
875861
inclusive : {"both", "neither", "left", "right"}, default "both"
876862
Include boundaries; Whether to set each bound as closed or open.
877863
@@ -987,28 +973,6 @@ def date_range(
987973
DatetimeIndex(['2017-01-02', '2017-01-03', '2017-01-04'],
988974
dtype='datetime64[ns]', freq='D')
989975
"""
990-
if inclusive is not None and closed is not lib.no_default:
991-
raise ValueError(
992-
"Deprecated argument `closed` cannot be passed"
993-
"if argument `inclusive` is not None"
994-
)
995-
if closed is not lib.no_default:
996-
warnings.warn(
997-
"Argument `closed` is deprecated in favor of `inclusive`.",
998-
FutureWarning,
999-
stacklevel=find_stack_level(),
1000-
)
1001-
if closed is None:
1002-
inclusive = "both"
1003-
elif closed in ("left", "right"):
1004-
inclusive = closed
1005-
else:
1006-
raise ValueError(
1007-
"Argument `closed` has to be either 'left', 'right' or None"
1008-
)
1009-
elif inclusive is None:
1010-
inclusive = "both"
1011-
1012976
if freq is None and com.any_none(periods, start, end):
1013977
freq = "D"
1014978

@@ -1035,8 +999,7 @@ def bdate_range(
1035999
name: Hashable = None,
10361000
weekmask=None,
10371001
holidays=None,
1038-
closed: IntervalLeftRight | lib.NoDefault | None = lib.no_default,
1039-
inclusive: IntervalClosedType | None = None,
1002+
inclusive: IntervalClosedType = "both",
10401003
**kwargs,
10411004
) -> DatetimeIndex:
10421005
"""
@@ -1068,13 +1031,6 @@ def bdate_range(
10681031
Dates to exclude from the set of valid business days, passed to
10691032
``numpy.busdaycalendar``, only used when custom frequency strings
10701033
are passed.
1071-
closed : str, default None
1072-
Make the interval closed with respect to the given frequency to
1073-
the 'left', 'right', or both sides (None).
1074-
1075-
.. deprecated:: 1.4.0
1076-
Argument `closed` has been deprecated to standardize boundary inputs.
1077-
Use `inclusive` instead, to set each bound as closed or open.
10781034
inclusive : {"both", "neither", "left", "right"}, default "both"
10791035
Include boundaries; Whether to set each bound as closed or open.
10801036
@@ -1131,7 +1087,6 @@ def bdate_range(
11311087
tz=tz,
11321088
normalize=normalize,
11331089
name=name,
1134-
closed=closed,
11351090
inclusive=inclusive,
11361091
**kwargs,
11371092
)

pandas/tests/frame/indexing/test_getitem.py

+4-9
Original file line numberDiff line numberDiff line change
@@ -455,15 +455,10 @@ def test_getitem_datetime_slice(self):
455455
]
456456
),
457457
)
458-
with tm.assert_produces_warning(FutureWarning):
459-
result = df["2011-01-01":"2011-11-01"]
460-
expected = DataFrame(
461-
{"a": 0},
462-
index=DatetimeIndex(
463-
["11.01.2011 22:00", "11.01.2011 23:00", "2011-01-13 00:00"]
464-
),
465-
)
466-
tm.assert_frame_equal(result, expected)
458+
with pytest.raises(
459+
KeyError, match="Value based partial slicing on non-monotonic"
460+
):
461+
df["2011-01-01":"2011-11-01"]
467462

468463

469464
class TestGetitemDeprecatedIndexers:

pandas/tests/indexes/datetimes/test_partial_slicing.py

+10-9
Original file line numberDiff line numberDiff line change
@@ -376,23 +376,24 @@ def test_partial_slicing_with_multiindex_series(self):
376376
result = df2.loc[Timestamp("2000-1-4")]
377377
tm.assert_frame_equal(result, expected)
378378

379-
def test_partial_slice_doesnt_require_monotonicity(self):
380-
# For historical reasons.
379+
def test_partial_slice_requires_monotonicity(self):
380+
# Disallowed since 2.0 (GH 37819)
381381
ser = Series(np.arange(10), date_range("2014-01-01", periods=10))
382382

383383
nonmonotonic = ser[[3, 5, 4]]
384-
expected = nonmonotonic.iloc[:0]
385384
timestamp = Timestamp("2014-01-10")
386-
with tm.assert_produces_warning(FutureWarning):
387-
result = nonmonotonic["2014-01-10":]
388-
tm.assert_series_equal(result, expected)
385+
with pytest.raises(
386+
KeyError, match="Value based partial slicing on non-monotonic"
387+
):
388+
nonmonotonic["2014-01-10":]
389389

390390
with pytest.raises(KeyError, match=r"Timestamp\('2014-01-10 00:00:00'\)"):
391391
nonmonotonic[timestamp:]
392392

393-
with tm.assert_produces_warning(FutureWarning):
394-
result = nonmonotonic.loc["2014-01-10":]
395-
tm.assert_series_equal(result, expected)
393+
with pytest.raises(
394+
KeyError, match="Value based partial slicing on non-monotonic"
395+
):
396+
nonmonotonic.loc["2014-01-10":]
396397

397398
with pytest.raises(KeyError, match=r"Timestamp\('2014-01-10 00:00:00'\)"):
398399
nonmonotonic.loc[timestamp:]

pandas/tests/indexing/test_loc.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -2460,7 +2460,9 @@ def test_loc_getitem_slice_unordered_dt_index(self, frame_or_series, start):
24602460
[1, 2, 3],
24612461
index=[Timestamp("2016"), Timestamp("2019"), Timestamp("2017")],
24622462
)
2463-
with tm.assert_produces_warning(FutureWarning):
2463+
with pytest.raises(
2464+
KeyError, match="Value based partial slicing on non-monotonic"
2465+
):
24642466
obj.loc[start:"2022"]
24652467

24662468
@pytest.mark.parametrize("value", [1, 1.5])

pandas/tests/series/indexing/test_datetime.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ def compare(slobj):
385385
tm.assert_series_equal(result, expected)
386386

387387
compare(slice("2011-01-01", "2011-01-15"))
388-
with tm.assert_produces_warning(FutureWarning):
388+
with pytest.raises(KeyError, match="Value based partial slicing on non-monotonic"):
389389
compare(slice("2010-12-30", "2011-01-15"))
390390
compare(slice("2011-01-01", "2011-01-16"))
391391

0 commit comments

Comments
 (0)