From 4482c66776d72ca1ee1785b5745f28a82a122295 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Wed, 9 Nov 2022 14:00:38 -0800 Subject: [PATCH 1/3] DEPR: date_range(closed) --- doc/source/whatsnew/v2.0.0.rst | 1 + pandas/core/indexes/datetimes.py | 45 ++------------------------------ 2 files changed, 3 insertions(+), 43 deletions(-) diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index 05c76a2836cd2..dae057a33d354 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -436,6 +436,7 @@ Removal of prior version deprecations/changes - Removed ``get_offset`` in favor of :func:`to_offset` (:issue:`30340`) - Removed the ``warn`` keyword in :func:`infer_freq` (:issue:`45947`) - Removed the ``include_start`` and ``include_end`` arguments in :meth:`DataFrame.between_time` in favor of ``inclusive`` (:issue:`43248`) +- Removed the ``closed`` argument in :meth:`date_range` and :meth:`bdate_range` in favor of ``inclusive`` argument (:issue:`40245`) - Removed the ``center`` keyword in :meth:`DataFrame.expanding` (:issue:`20647`) - Removed the ``truediv`` keyword from :func:`eval` (:issue:`29812`) - Removed the ``pandas.datetime`` submodule (:issue:`30489`) diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 6fcad23e4b4c3..c6daefb83aec4 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -11,7 +11,6 @@ from typing import ( TYPE_CHECKING, Hashable, - Literal, ) import warnings @@ -37,7 +36,6 @@ DtypeObj, Frequency, IntervalClosedType, - IntervalLeftRight, TimeAmbiguous, TimeNonexistent, npt, @@ -829,8 +827,7 @@ def date_range( tz=None, normalize: bool = False, name: Hashable = None, - closed: Literal["left", "right"] | None | lib.NoDefault = lib.no_default, - inclusive: IntervalClosedType | None = None, + inclusive: IntervalClosedType = "both", **kwargs, ) -> DatetimeIndex: """ @@ -865,13 +862,6 @@ def date_range( Normalize start/end dates to midnight before generating date range. name : str, default None Name of the resulting DatetimeIndex. - closed : {None, 'left', 'right'}, optional - Make the interval closed with respect to the given frequency to - the 'left', 'right', or both sides (None, the default). - - .. deprecated:: 1.4.0 - Argument `closed` has been deprecated to standardize boundary inputs. - Use `inclusive` instead, to set each bound as closed or open. inclusive : {"both", "neither", "left", "right"}, default "both" Include boundaries; Whether to set each bound as closed or open. @@ -987,28 +977,6 @@ def date_range( DatetimeIndex(['2017-01-02', '2017-01-03', '2017-01-04'], dtype='datetime64[ns]', freq='D') """ - if inclusive is not None and closed is not lib.no_default: - raise ValueError( - "Deprecated argument `closed` cannot be passed" - "if argument `inclusive` is not None" - ) - if closed is not lib.no_default: - warnings.warn( - "Argument `closed` is deprecated in favor of `inclusive`.", - FutureWarning, - stacklevel=find_stack_level(), - ) - if closed is None: - inclusive = "both" - elif closed in ("left", "right"): - inclusive = closed - else: - raise ValueError( - "Argument `closed` has to be either 'left', 'right' or None" - ) - elif inclusive is None: - inclusive = "both" - if freq is None and com.any_none(periods, start, end): freq = "D" @@ -1035,8 +1003,7 @@ def bdate_range( name: Hashable = None, weekmask=None, holidays=None, - closed: IntervalLeftRight | lib.NoDefault | None = lib.no_default, - inclusive: IntervalClosedType | None = None, + inclusive: IntervalClosedType = "both", **kwargs, ) -> DatetimeIndex: """ @@ -1068,13 +1035,6 @@ def bdate_range( Dates to exclude from the set of valid business days, passed to ``numpy.busdaycalendar``, only used when custom frequency strings are passed. - closed : str, default None - Make the interval closed with respect to the given frequency to - the 'left', 'right', or both sides (None). - - .. deprecated:: 1.4.0 - Argument `closed` has been deprecated to standardize boundary inputs. - Use `inclusive` instead, to set each bound as closed or open. inclusive : {"both", "neither", "left", "right"}, default "both" Include boundaries; Whether to set each bound as closed or open. @@ -1131,7 +1091,6 @@ def bdate_range( tz=tz, normalize=normalize, name=name, - closed=closed, inclusive=inclusive, **kwargs, ) From d165eab26fa679f30b28ea981b0cbdd5c05e9149 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Wed, 9 Nov 2022 14:32:47 -0800 Subject: [PATCH 2/3] Disallow partial slicing of missing --- doc/source/whatsnew/v2.0.0.rst | 1 + pandas/core/indexes/datetimes.py | 16 ++++++---------- .../indexes/datetimes/test_partial_slicing.py | 19 ++++++++++--------- pandas/tests/indexing/test_loc.py | 4 +++- pandas/tests/series/indexing/test_datetime.py | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index dae057a33d354..a816fe95a94f4 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -433,6 +433,7 @@ Removal of prior version deprecations/changes - Remove :meth:`DataFrameGroupBy.pad` and :meth:`DataFrameGroupBy.backfill` (:issue:`45076`) - Remove ``numpy`` argument from :func:`read_json` (:issue:`30636`) - Disallow passing abbreviations for ``orient`` in :meth:`DataFrame.to_dict` (:issue:`32516`) +- Disallow partial slicing on an unordered :class:`DatetimeIndex` with keys, which are not in Index. This now raises a ``KeyError`` (:issue:`18531`) - Removed ``get_offset`` in favor of :func:`to_offset` (:issue:`30340`) - Removed the ``warn`` keyword in :func:`infer_freq` (:issue:`45947`) - Removed the ``include_start`` and ``include_end`` arguments in :meth:`DataFrame.between_time` in favor of ``inclusive`` (:issue:`43248`) diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index c6daefb83aec4..3a00301fbc042 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -44,7 +44,6 @@ cache_readonly, doc, ) -from pandas.util._exceptions import find_stack_level from pandas.core.dtypes.common import ( is_datetime64_dtype, @@ -697,24 +696,21 @@ def check_str_or_none(point) -> bool: return Index.slice_indexer(self, start, end, step) mask = np.array(True) - deprecation_mask = np.array(True) + raise_mask = np.array(True) if start is not None: start_casted = self._maybe_cast_slice_bound(start, "left") mask = start_casted <= self - deprecation_mask = start_casted == self + raise_mask = start_casted == self if end is not None: end_casted = self._maybe_cast_slice_bound(end, "right") mask = (self <= end_casted) & mask - deprecation_mask = (end_casted == self) | deprecation_mask + raise_mask = (end_casted == self) | raise_mask - if not deprecation_mask.any(): - warnings.warn( + if not raise_mask.any(): + raise KeyError( "Value based partial slicing on non-monotonic DatetimeIndexes " - "with non-existing keys is deprecated and will raise a " - "KeyError in a future Version.", - FutureWarning, - stacklevel=find_stack_level(), + "with non-existing keys is not allowed.", ) indexer = mask.nonzero()[0][::step] if len(indexer) == len(self): diff --git a/pandas/tests/indexes/datetimes/test_partial_slicing.py b/pandas/tests/indexes/datetimes/test_partial_slicing.py index cdf78c97c45b5..b4ef62604d888 100644 --- a/pandas/tests/indexes/datetimes/test_partial_slicing.py +++ b/pandas/tests/indexes/datetimes/test_partial_slicing.py @@ -376,23 +376,24 @@ def test_partial_slicing_with_multiindex_series(self): result = df2.loc[Timestamp("2000-1-4")] tm.assert_frame_equal(result, expected) - def test_partial_slice_doesnt_require_monotonicity(self): - # For historical reasons. + def test_partial_slice_requires_monotonicity(self): + # Disallowed since 2.0 (GH 37819) ser = Series(np.arange(10), date_range("2014-01-01", periods=10)) nonmonotonic = ser[[3, 5, 4]] - expected = nonmonotonic.iloc[:0] timestamp = Timestamp("2014-01-10") - with tm.assert_produces_warning(FutureWarning): - result = nonmonotonic["2014-01-10":] - tm.assert_series_equal(result, expected) + with pytest.raises( + KeyError, match="Value based partial slicing on non-monotonic" + ): + nonmonotonic["2014-01-10":] with pytest.raises(KeyError, match=r"Timestamp\('2014-01-10 00:00:00'\)"): nonmonotonic[timestamp:] - with tm.assert_produces_warning(FutureWarning): - result = nonmonotonic.loc["2014-01-10":] - tm.assert_series_equal(result, expected) + with pytest.raises( + KeyError, match="Value based partial slicing on non-monotonic" + ): + nonmonotonic.loc["2014-01-10":] with pytest.raises(KeyError, match=r"Timestamp\('2014-01-10 00:00:00'\)"): nonmonotonic.loc[timestamp:] diff --git a/pandas/tests/indexing/test_loc.py b/pandas/tests/indexing/test_loc.py index 3b75f9d7ce1be..0068a0a0ded67 100644 --- a/pandas/tests/indexing/test_loc.py +++ b/pandas/tests/indexing/test_loc.py @@ -2460,7 +2460,9 @@ def test_loc_getitem_slice_unordered_dt_index(self, frame_or_series, start): [1, 2, 3], index=[Timestamp("2016"), Timestamp("2019"), Timestamp("2017")], ) - with tm.assert_produces_warning(FutureWarning): + with pytest.raises( + KeyError, match="Value based partial slicing on non-monotonic" + ): obj.loc[start:"2022"] @pytest.mark.parametrize("value", [1, 1.5]) diff --git a/pandas/tests/series/indexing/test_datetime.py b/pandas/tests/series/indexing/test_datetime.py index 54b9d26b614c8..b977c78c635da 100644 --- a/pandas/tests/series/indexing/test_datetime.py +++ b/pandas/tests/series/indexing/test_datetime.py @@ -385,7 +385,7 @@ def compare(slobj): tm.assert_series_equal(result, expected) compare(slice("2011-01-01", "2011-01-15")) - with tm.assert_produces_warning(FutureWarning): + with pytest.raises(KeyError, match="Value based partial slicing on non-monotonic"): compare(slice("2010-12-30", "2011-01-15")) compare(slice("2011-01-01", "2011-01-16")) From e60ca7ddb976b7d684d871522884b5b731753107 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Wed, 9 Nov 2022 17:30:53 -0800 Subject: [PATCH 3/3] Review + failed test --- doc/source/whatsnew/v2.0.0.rst | 2 +- pandas/tests/frame/indexing/test_getitem.py | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index 66eeb2ac6ff6f..2ae50cfe4e137 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -433,7 +433,7 @@ Removal of prior version deprecations/changes - Remove :meth:`DataFrameGroupBy.pad` and :meth:`DataFrameGroupBy.backfill` (:issue:`45076`) - Remove ``numpy`` argument from :func:`read_json` (:issue:`30636`) - Disallow passing abbreviations for ``orient`` in :meth:`DataFrame.to_dict` (:issue:`32516`) -- Disallow partial slicing on an unordered :class:`DatetimeIndex` with keys, which are not in Index. This now raises a ``KeyError`` (:issue:`18531`) +- Disallow partial slicing on an non-monotonic :class:`DatetimeIndex` with keys which are not in Index. This now raises a ``KeyError`` (:issue:`18531`) - Removed ``get_offset`` in favor of :func:`to_offset` (:issue:`30340`) - Removed the ``warn`` keyword in :func:`infer_freq` (:issue:`45947`) - Removed the ``include_start`` and ``include_end`` arguments in :meth:`DataFrame.between_time` in favor of ``inclusive`` (:issue:`43248`) diff --git a/pandas/tests/frame/indexing/test_getitem.py b/pandas/tests/frame/indexing/test_getitem.py index 499c5c2afed4c..f17e2a197a82b 100644 --- a/pandas/tests/frame/indexing/test_getitem.py +++ b/pandas/tests/frame/indexing/test_getitem.py @@ -455,15 +455,10 @@ def test_getitem_datetime_slice(self): ] ), ) - with tm.assert_produces_warning(FutureWarning): - result = df["2011-01-01":"2011-11-01"] - expected = DataFrame( - {"a": 0}, - index=DatetimeIndex( - ["11.01.2011 22:00", "11.01.2011 23:00", "2011-01-13 00:00"] - ), - ) - tm.assert_frame_equal(result, expected) + with pytest.raises( + KeyError, match="Value based partial slicing on non-monotonic" + ): + df["2011-01-01":"2011-11-01"] class TestGetitemDeprecatedIndexers: