From 09288e890479a4f52f1e460f2d1dc54d6209a144 Mon Sep 17 00:00:00 2001 From: Brock Date: Sun, 27 Sep 2020 18:27:05 -0700 Subject: [PATCH 1/5] DEPR: is_all_dates --- pandas/core/generic.py | 8 +++++++- pandas/core/indexes/base.py | 15 ++++++++++++++- pandas/core/indexes/datetimelike.py | 4 +--- pandas/core/indexes/interval.py | 2 +- pandas/core/indexes/multi.py | 2 +- pandas/core/indexes/numeric.py | 2 +- pandas/core/series.py | 10 ++++++++-- pandas/plotting/_matplotlib/core.py | 2 +- pandas/tests/indexes/interval/test_interval.py | 2 +- pandas/tests/indexes/multi/test_equivalence.py | 2 +- pandas/tests/indexes/test_base.py | 3 ++- pandas/tests/indexing/test_indexing.py | 8 +++++--- pandas/tests/series/test_alter_axes.py | 2 +- pandas/tests/series/test_constructors.py | 8 ++++---- pandas/tests/series/test_timeseries.py | 6 ++++-- 15 files changed, 52 insertions(+), 24 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index bd720151fb15e..5ef4a4d7d68fd 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -9528,7 +9528,13 @@ def truncate( # if we have a date index, convert to dates, otherwise # treat like a slice - if ax.is_all_dates: + if ax._is_all_dates: + if is_object_dtype(ax.dtype): + warnings.warn( + "Treating object-dtype Index of date objects as DatetimeIndex " + "is deprecated, will be removed in a future version.", + FutureWarning, + ) from pandas.core.tools.datetimes import to_datetime before = to_datetime(before) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 84489c1033d8c..c449b9b53a53e 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2086,12 +2086,25 @@ def inferred_type(self) -> str_t: return lib.infer_dtype(self._values, skipna=False) @cache_readonly - def is_all_dates(self) -> bool: + def _is_all_dates(self) -> bool: """ Whether or not the index values only consist of dates. """ return is_datetime_array(ensure_object(self._values)) + @cache_readonly + def is_all_dates(self): + """ + Whether or not the index values only consist of dates. + """ + warnings.warn( + "Index.is_all_dates is deprecated, will be removed in a future version. " + "check index.inferred_type instead", + FutureWarning, + stacklevel=2, + ) + return self._is_all_dates + # -------------------------------------------------------------------- # Pickle Methods diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index e2f59ceb41db5..30fc48d5e46c0 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -97,9 +97,7 @@ class DatetimeIndexOpsMixin(ExtensionIndex): ) _hasnans = hasnans # for index / array -agnostic code - @property - def is_all_dates(self) -> bool: - return True + _is_all_dates = True # ------------------------------------------------------------------------ # Abstract data attributes diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py index 3fcc40c90b98e..c0c61982f2267 100644 --- a/pandas/core/indexes/interval.py +++ b/pandas/core/indexes/interval.py @@ -1092,7 +1092,7 @@ def func(self, other, sort=sort): # -------------------------------------------------------------------- @property - def is_all_dates(self) -> bool: + def _is_all_dates(self) -> bool: """ This is False even when left/right contain datetime-like objects, as the check is done on the Interval itself diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index cd3e384837280..7e81362387f48 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -1733,7 +1733,7 @@ def to_flat_index(self): return Index(self._values, tupleize_cols=False) @property - def is_all_dates(self) -> bool: + def _is_all_dates(self) -> bool: return False def is_lexsorted(self) -> bool: diff --git a/pandas/core/indexes/numeric.py b/pandas/core/indexes/numeric.py index 574c9adc31808..34bbaca06cc08 100644 --- a/pandas/core/indexes/numeric.py +++ b/pandas/core/indexes/numeric.py @@ -149,7 +149,7 @@ def _assert_safe_casting(cls, data, subarr): pass @property - def is_all_dates(self) -> bool: + def _is_all_dates(self) -> bool: """ Checks that all the labels are datetime objects. """ diff --git a/pandas/core/series.py b/pandas/core/series.py index 41c3e8fa9d246..d2c702d924136 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -409,14 +409,20 @@ def _set_axis(self, axis: int, labels, fastpath: bool = False) -> None: if not fastpath: labels = ensure_index(labels) - is_all_dates = labels.is_all_dates - if is_all_dates: + if labels._is_all_dates: if not isinstance(labels, (DatetimeIndex, PeriodIndex, TimedeltaIndex)): try: labels = DatetimeIndex(labels) # need to set here because we changed the index if fastpath: self._mgr.set_axis(axis, labels) + warnings.warn( + "Automatically casting object-dtype Index of datetimes to " + "DatetimeIndex is deprecated and will be removed in a " + "future version. Explicitly cast to DatetimeIndex instead.", + FutureWarning, + stacklevel=3, + ) except (tslibs.OutOfBoundsDatetime, ValueError): # labels may exceeds datetime bounds, # or not be a DatetimeIndex diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 0c64ea824996f..f806325d60eca 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -1238,7 +1238,7 @@ def get_label(i): # would be too close together. condition = ( not self._use_dynamic_x() - and (data.index.is_all_dates and self.use_index) + and (data.index._is_all_dates and self.use_index) and (not self.subplots or (self.subplots and self.sharex)) ) diff --git a/pandas/tests/indexes/interval/test_interval.py b/pandas/tests/indexes/interval/test_interval.py index b81f0f27e60ad..17a1c69858c11 100644 --- a/pandas/tests/indexes/interval/test_interval.py +++ b/pandas/tests/indexes/interval/test_interval.py @@ -871,7 +871,7 @@ def test_is_all_dates(self): pd.Timestamp("2017-01-01 00:00:00"), pd.Timestamp("2018-01-01 00:00:00") ) year_2017_index = pd.IntervalIndex([year_2017]) - assert not year_2017_index.is_all_dates + assert not year_2017_index._is_all_dates @pytest.mark.parametrize("key", [[5], (2, 3)]) def test_get_value_non_scalar_errors(self, key): diff --git a/pandas/tests/indexes/multi/test_equivalence.py b/pandas/tests/indexes/multi/test_equivalence.py index b48f09457b96c..184cedea7dc5c 100644 --- a/pandas/tests/indexes/multi/test_equivalence.py +++ b/pandas/tests/indexes/multi/test_equivalence.py @@ -202,7 +202,7 @@ def test_is_(): def test_is_all_dates(idx): - assert not idx.is_all_dates + assert not idx._is_all_dates def test_is_numeric(idx): diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 7cafdb61fcb31..77585f4003fe9 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -1153,7 +1153,8 @@ def test_is_object(self, index, expected): indirect=["index"], ) def test_is_all_dates(self, index, expected): - assert index.is_all_dates is expected + with tm.assert_produces_warning(FutureWarning): + assert index.is_all_dates is expected def test_summary(self, index): self._check_method_works(Index._summary, index) diff --git a/pandas/tests/indexing/test_indexing.py b/pandas/tests/indexing/test_indexing.py index 0cc61cd7df389..7d5fea232817d 100644 --- a/pandas/tests/indexing/test_indexing.py +++ b/pandas/tests/indexing/test_indexing.py @@ -554,15 +554,17 @@ def test_string_slice(self): # string indexing against datetimelike with object # dtype should properly raises KeyError df = DataFrame([1], Index([pd.Timestamp("2011-01-01")], dtype=object)) - assert df.index.is_all_dates + assert df.index._is_all_dates with pytest.raises(KeyError, match="'2011'"): df["2011"] with pytest.raises(KeyError, match="'2011'"): - df.loc["2011", 0] + with tm.assert_produces_warning(FutureWarning): + # This does an is_all_dates check + df.loc["2011", 0] df = DataFrame() - assert not df.index.is_all_dates + assert not df.index._is_all_dates with pytest.raises(KeyError, match="'2011'"): df["2011"] diff --git a/pandas/tests/series/test_alter_axes.py b/pandas/tests/series/test_alter_axes.py index 203750757e28d..181d7de43d945 100644 --- a/pandas/tests/series/test_alter_axes.py +++ b/pandas/tests/series/test_alter_axes.py @@ -52,4 +52,4 @@ def test_set_index_makes_timeseries(self): s = Series(range(10)) s.index = idx - assert s.index.is_all_dates + assert s.index._is_all_dates diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 1b5fddaf14335..4ad4917533422 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -94,11 +94,11 @@ def test_scalar_conversion(self): def test_constructor(self, datetime_series): with tm.assert_produces_warning(DeprecationWarning, check_stacklevel=False): empty_series = Series() - assert datetime_series.index.is_all_dates + assert datetime_series.index._is_all_dates # Pass in Series derived = Series(datetime_series) - assert derived.index.is_all_dates + assert derived.index._is_all_dates assert tm.equalContents(derived.index, datetime_series.index) # Ensure new index is not created @@ -109,9 +109,9 @@ def test_constructor(self, datetime_series): assert mixed.dtype == np.object_ assert mixed[1] is np.NaN - assert not empty_series.index.is_all_dates + assert not empty_series.index._is_all_dates with tm.assert_produces_warning(DeprecationWarning, check_stacklevel=False): - assert not Series().index.is_all_dates + assert not Series().index._is_all_dates # exception raised is of type Exception with pytest.raises(Exception, match="Data must be 1-dimensional"): diff --git a/pandas/tests/series/test_timeseries.py b/pandas/tests/series/test_timeseries.py index 15b6481c08a61..bab3853e3bd1d 100644 --- a/pandas/tests/series/test_timeseries.py +++ b/pandas/tests/series/test_timeseries.py @@ -9,8 +9,10 @@ class TestTimeSeries: def test_timeseries_coercion(self): idx = tm.makeDateIndex(10000) - ser = Series(np.random.randn(len(idx)), idx.astype(object)) - assert ser.index.is_all_dates + with tm.assert_produces_warning(FutureWarning): + ser = Series(np.random.randn(len(idx)), idx.astype(object)) + with tm.assert_produces_warning(FutureWarning): + assert ser.index.is_all_dates assert isinstance(ser.index, DatetimeIndex) def test_contiguous_boolean_preserve_freq(self): From 0eb17efae42b3e66405179ddc3a96870ab1b6d97 Mon Sep 17 00:00:00 2001 From: Brock Date: Sun, 27 Sep 2020 18:29:34 -0700 Subject: [PATCH 2/5] whatsnew --- doc/source/whatsnew/v1.2.0.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 031c74b1cc367..5c91f1849d271 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -214,6 +214,8 @@ Deprecations - :meth:`DataFrame.lookup` is deprecated and will be removed in a future version, use :meth:`DataFrame.melt` and :meth:`DataFrame.loc` instead (:issue:`18682`) - The :meth:`Index.to_native_types` is deprecated. Use ``.astype(str)`` instead (:issue:`28867`) - Deprecated indexing :class:`DataFrame` rows with datetime-like strings ``df[string]``, use ``df.loc[string]`` instead (:issue:`36179`) +- Deprecated casting an object-dtype index of ``datetime`` objects to :class:`DatetimeIndex` in the :class:`Series` constructor (:issue:`23598`) +- Deprecated :meth:`Index.is_all_dates` (:issue:`27744`) .. --------------------------------------------------------------------------- From 26ac0ec6e4e726c40c4fd9bae696e786d7f5413c Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 28 Sep 2020 09:01:32 -0700 Subject: [PATCH 3/5] mypy fixup --- pandas/core/indexes/datetimelike.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 30fc48d5e46c0..3d2820976a6af 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -97,7 +97,9 @@ class DatetimeIndexOpsMixin(ExtensionIndex): ) _hasnans = hasnans # for index / array -agnostic code - _is_all_dates = True + @property + def _is_all_dates(self) -> bool: + return True # ------------------------------------------------------------------------ # Abstract data attributes From 17d29d637c8d08feaf19160c903602c3f3df7d52 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 28 Sep 2020 18:34:44 -0700 Subject: [PATCH 4/5] catch more warnings --- pandas/core/missing.py | 4 ++-- pandas/tests/io/pytables/test_store.py | 10 ++++++++-- pandas/tests/series/test_repr.py | 4 +++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pandas/core/missing.py b/pandas/core/missing.py index f4182027e9e04..c2926debcb6d6 100644 --- a/pandas/core/missing.py +++ b/pandas/core/missing.py @@ -199,7 +199,7 @@ def interpolate_1d( return yvalues if method == "time": - if not getattr(xvalues, "is_all_dates", None): + if not getattr(xvalues, "_is_all_dates", None): # if not issubclass(xvalues.dtype.type, np.datetime64): raise ValueError( "time-weighted interpolation only works " @@ -327,7 +327,7 @@ def _interpolate_scipy_wrapper( "piecewise_polynomial": _from_derivatives, } - if getattr(x, "is_all_dates", False): + if getattr(x, "_is_all_dates", False): # GH 5975, scipy.interp1d can't handle datetime64s x, new_x = x._values.astype("i8"), new_x.astype("i8") diff --git a/pandas/tests/io/pytables/test_store.py b/pandas/tests/io/pytables/test_store.py index 0942c79837e7c..10c9475401059 100644 --- a/pandas/tests/io/pytables/test_store.py +++ b/pandas/tests/io/pytables/test_store.py @@ -2384,10 +2384,16 @@ def test_series(self, setup_path): ts = tm.makeTimeSeries() self._check_roundtrip(ts, tm.assert_series_equal, path=setup_path) - ts2 = Series(ts.index, Index(ts.index, dtype=object)) + with tm.assert_produces_warning(FutureWarning): + # auto-casting object->DatetimeIndex deprecated + ts2 = Series(ts.index, Index(ts.index, dtype=object)) self._check_roundtrip(ts2, tm.assert_series_equal, path=setup_path) - ts3 = Series(ts.values, Index(np.asarray(ts.index, dtype=object), dtype=object)) + with tm.assert_produces_warning(FutureWarning): + # auto-casting object->DatetimeIndex deprecated + ts3 = Series( + ts.values, Index(np.asarray(ts.index, dtype=object), dtype=object) + ) self._check_roundtrip( ts3, tm.assert_series_equal, path=setup_path, check_index_type=False ) diff --git a/pandas/tests/series/test_repr.py b/pandas/tests/series/test_repr.py index b861b37b49f89..3aaecc37df56c 100644 --- a/pandas/tests/series/test_repr.py +++ b/pandas/tests/series/test_repr.py @@ -184,7 +184,9 @@ def test_timeseries_repr_object_dtype(self): index = Index( [datetime(2000, 1, 1) + timedelta(i) for i in range(1000)], dtype=object ) - ts = Series(np.random.randn(len(index)), index) + with tm.assert_produces_warning(FutureWarning): + # Index.is_all_dates deprecated + ts = Series(np.random.randn(len(index)), index) repr(ts) ts = tm.makeTimeSeries(1000) From eaaca8fb9cda69c9b93c9f702f68b2b32cd68028 Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 29 Sep 2020 18:10:16 -0700 Subject: [PATCH 5/5] catch more --- .../moments/test_moments_rolling_apply.py | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/pandas/tests/window/moments/test_moments_rolling_apply.py b/pandas/tests/window/moments/test_moments_rolling_apply.py index e48d88b365d8d..e9e672e1d3dae 100644 --- a/pandas/tests/window/moments/test_moments_rolling_apply.py +++ b/pandas/tests/window/moments/test_moments_rolling_apply.py @@ -122,13 +122,16 @@ def test_center_reindex_series(raw, series): s = [f"x{x:d}" for x in range(12)] minp = 10 - series_xp = ( - series.reindex(list(series.index) + s) - .rolling(window=25, min_periods=minp) - .apply(f, raw=raw) - .shift(-12) - .reindex(series.index) - ) + warn = None if raw else FutureWarning + with tm.assert_produces_warning(warn, check_stacklevel=False): + # GH#36697 is_all_dates deprecated + series_xp = ( + series.reindex(list(series.index) + s) + .rolling(window=25, min_periods=minp) + .apply(f, raw=raw) + .shift(-12) + .reindex(series.index) + ) series_rs = series.rolling(window=25, min_periods=minp, center=True).apply( f, raw=raw ) @@ -140,12 +143,15 @@ def test_center_reindex_frame(raw, frame): s = [f"x{x:d}" for x in range(12)] minp = 10 - frame_xp = ( - frame.reindex(list(frame.index) + s) - .rolling(window=25, min_periods=minp) - .apply(f, raw=raw) - .shift(-12) - .reindex(frame.index) - ) + warn = None if raw else FutureWarning + with tm.assert_produces_warning(warn, check_stacklevel=False): + # GH#36697 is_all_dates deprecated + frame_xp = ( + frame.reindex(list(frame.index) + s) + .rolling(window=25, min_periods=minp) + .apply(f, raw=raw) + .shift(-12) + .reindex(frame.index) + ) frame_rs = frame.rolling(window=25, min_periods=minp, center=True).apply(f, raw=raw) tm.assert_frame_equal(frame_xp, frame_rs)