Skip to content

DEPR: is_all_dates #36697

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Sep 30, 2020
2 changes: 2 additions & 0 deletions doc/source/whatsnew/v1.2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`)

.. ---------------------------------------------------------------------------

Expand Down
8 changes: 7 additions & 1 deletion pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
15 changes: 14 additions & 1 deletion pandas/core/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion pandas/core/indexes/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class DatetimeIndexOpsMixin(ExtensionIndex):
_hasnans = hasnans # for index / array -agnostic code

@property
def is_all_dates(self) -> bool:
def _is_all_dates(self) -> bool:
return True

# ------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion pandas/core/indexes/interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pandas/core/indexes/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion pandas/core/indexes/numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand Down
4 changes: 2 additions & 2 deletions pandas/core/missing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down Expand Up @@ -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")

Expand Down
10 changes: 8 additions & 2 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pandas/plotting/_matplotlib/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
)

Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/indexes/interval/test_interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/indexes/multi/test_equivalence.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
3 changes: 2 additions & 1 deletion pandas/tests/indexes/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 5 additions & 3 deletions pandas/tests/indexing/test_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down
10 changes: 8 additions & 2 deletions pandas/tests/io/pytables/test_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/series/test_alter_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 4 additions & 4 deletions pandas/tests/series/test_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"):
Expand Down
4 changes: 3 additions & 1 deletion pandas/tests/series/test_repr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions pandas/tests/series/test_timeseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
34 changes: 20 additions & 14 deletions pandas/tests/window/moments/test_moments_rolling_apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this case raising a warning?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ping

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

series.reindex(list(series.index) + s) has an object-dtype Index that can be cast to DatetimeIndex

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbrockmendel Thanks for the follow-up.

Now, the user is not actively creating a Seriew with an object-dtype index themselves, but it is happening under the hood in the rolling apply.
So I am not sure the user should see this warning (and it's actually not a single warning, but a flood of warnings, one for each window ..). Or what is the recommended way in this case to get rid of the warning?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could suppress it inside roll_apply?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly. The warning only shows with raw=False, so I suppose this comes from creating a Series for each window. We might also be able to avoid the warning altogether (without the need to suppress it) by modifying the series construction to be explicit about the index dtype?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should address this before the release

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opened #38182 for this

)
series_rs = series.rolling(window=25, min_periods=minp, center=True).apply(
f, raw=raw
)
Expand All @@ -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)