Skip to content

DEPR: Enforce datetimelike deprecations #57999

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 14 commits into from
Apr 5, 2024
2 changes: 1 addition & 1 deletion asv_bench/benchmarks/categoricals.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def setup(self):
self.codes = np.tile(range(len(self.categories)), N)

self.datetimes = pd.Series(
pd.date_range("1995-01-01 00:00:00", periods=N / 10, freq="s")
pd.date_range("1995-01-01 00:00:00", periods=N // 10, freq="s")
)
self.datetimes_with_nat = self.datetimes.copy()
self.datetimes_with_nat.iloc[-1] = pd.NaT
Expand Down
2 changes: 1 addition & 1 deletion asv_bench/benchmarks/timeseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def setup(self, index_type):
"dst": date_range(
start="10/29/2000 1:00:00", end="10/29/2000 1:59:59", freq="s"
),
"repeated": date_range(start="2000", periods=N / 10, freq="s").repeat(10),
"repeated": date_range(start="2000", periods=N // 10, freq="s").repeat(10),
"tz_aware": date_range(start="2000", periods=N, freq="s", tz="US/Eastern"),
"tz_local": date_range(
start="2000", periods=N, freq="s", tz=dateutil.tz.tzlocal()
Expand Down
3 changes: 2 additions & 1 deletion doc/source/whatsnew/v3.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ Removal of prior version deprecations/changes
- All arguments in :meth:`Series.to_dict` are now keyword only (:issue:`56493`)
- Changed the default value of ``observed`` in :meth:`DataFrame.groupby` and :meth:`Series.groupby` to ``True`` (:issue:`51811`)
- Enforce deprecation in :func:`testing.assert_series_equal` and :func:`testing.assert_frame_equal` with object dtype and mismatched null-like values, which are now considered not-equal (:issue:`18463`)
- Enforced deprecation ``all`` and ``any`` reductions with ``datetime64`` and :class:`DatetimeTZDtype` dtypes (:issue:`58029`)
- Enforced deprecation ``all`` and ``any`` reductions with ``datetime64``, :class:`DatetimeTZDtype`, and :class:`PeriodDtype` dtypes (:issue:`58029`)
- Enforced deprecation disallowing ``float`` "periods" in :func:`date_range`, :func:`period_range`, :func:`timedelta_range`, :func:`interval_range`, (:issue:`56036`)
- Enforced deprecation disallowing parsing datetimes with mixed time zones unless user passes ``utc=True`` to :func:`to_datetime` (:issue:`57275`)
- Enforced deprecation in :meth:`Series.value_counts` and :meth:`Index.value_counts` with object dtype performing dtype inference on the ``.index`` of the result (:issue:`56161`)
- Enforced deprecation of :meth:`.DataFrameGroupBy.get_group` and :meth:`.SeriesGroupBy.get_group` allowing the ``name`` argument to be a non-tuple when grouping by a list of length 1 (:issue:`54155`)
Expand Down
43 changes: 19 additions & 24 deletions pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -1661,20 +1661,24 @@ def _groupby_op(
dtype = self.dtype
if dtype.kind == "M":
# Adding/multiplying datetimes is not valid
if how in ["any", "all", "sum", "prod", "cumsum", "cumprod", "var", "skew"]:
if how in ["sum", "prod", "cumsum", "cumprod", "var", "skew"]:
raise TypeError(f"datetime64 type does not support operation '{how}'")
if how in ["any", "all"]:
# GH#34479
raise TypeError(
f"'{how}' with datetime64 dtypes is no longer supported. "
f"Use (obj != pd.Timestamp(0)).{how}() instead."
)

elif isinstance(dtype, PeriodDtype):
# Adding/multiplying Periods is not valid
if how in ["sum", "prod", "cumsum", "cumprod", "var", "skew"]:
raise TypeError(f"Period type does not support {how} operations")
if how in ["any", "all"]:
# GH#34479
warnings.warn(
f"'{how}' with PeriodDtype is deprecated and will raise in a "
f"future version. Use (obj != pd.Period(0, freq)).{how}() instead.",
FutureWarning,
stacklevel=find_stack_level(),
raise TypeError(
f"'{how}' with PeriodDtype is no longer supported. "
f"Use (obj != pd.Period(0, freq)).{how}() instead."
)
else:
# timedeltas we can add but not multiply
Expand Down Expand Up @@ -2424,17 +2428,17 @@ def validate_periods(periods: None) -> None: ...


@overload
def validate_periods(periods: int | float) -> int: ...
def validate_periods(periods: int) -> int: ...


def validate_periods(periods: int | float | None) -> int | None:
def validate_periods(periods: int | None) -> int | None:
"""
If a `periods` argument is passed to the Datetime/Timedelta Array/Index
constructor, cast it to an integer.

Parameters
----------
periods : None, float, int
periods : None, int

Returns
-------
Expand All @@ -2443,22 +2447,13 @@ def validate_periods(periods: int | float | None) -> int | None:
Raises
------
TypeError
if periods is None, float, or int
if periods is not None or int
"""
if periods is not None:
if lib.is_float(periods):
warnings.warn(
# GH#56036
"Non-integer 'periods' in pd.date_range, pd.timedelta_range, "
"pd.period_range, and pd.interval_range are deprecated and "
"will raise in a future version.",
FutureWarning,
stacklevel=find_stack_level(),
)
periods = int(periods)
elif not lib.is_integer(periods):
raise TypeError(f"periods must be a number, got {periods}")
return periods
if periods is not None and not lib.is_integer(periods):
raise TypeError(f"periods must be an integer, got {periods}")
# error: Incompatible return value type (got "int | integer[Any] | None",
# expected "int | None")
return periods # type: ignore[return-value]


def _validate_inferred_freq(
Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/groupby/test_raises.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,8 @@ def test_groupby_raises_datetime(
return

klass, msg = {
"all": (TypeError, "datetime64 type does not support operation 'all'"),
"any": (TypeError, "datetime64 type does not support operation 'any'"),
"all": (TypeError, "'all' with datetime64 dtypes is no longer supported"),
"any": (TypeError, "'any' with datetime64 dtypes is no longer supported"),
"bfill": (None, ""),
"corrwith": (TypeError, "cannot perform __mul__ with this index type"),
"count": (None, ""),
Expand Down
14 changes: 6 additions & 8 deletions pandas/tests/indexes/datetimes/test_date_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,14 @@ def test_date_range_name(self):
assert idx.name == "TEST"

def test_date_range_invalid_periods(self):
msg = "periods must be a number, got foo"
msg = "periods must be an integer, got foo"
with pytest.raises(TypeError, match=msg):
date_range(start="1/1/2000", periods="foo", freq="D")

def test_date_range_fractional_period(self):
msg = "Non-integer 'periods' in pd.date_range, pd.timedelta_range"
with tm.assert_produces_warning(FutureWarning, match=msg):
rng = date_range("1/1/2000", periods=10.5)
exp = date_range("1/1/2000", periods=10)
tm.assert_index_equal(rng, exp)
msg = "periods must be an integer"
with pytest.raises(TypeError, match=msg):
date_range("1/1/2000", periods=10.5)

@pytest.mark.parametrize(
"freq,freq_depr",
Expand Down Expand Up @@ -1042,7 +1040,7 @@ def test_constructor(self):
bdate_range(START, periods=20, freq=BDay())
bdate_range(end=START, periods=20, freq=BDay())

msg = "periods must be a number, got B"
msg = "periods must be an integer, got B"
with pytest.raises(TypeError, match=msg):
date_range("2011-1-1", "2012-1-1", "B")

Expand Down Expand Up @@ -1120,7 +1118,7 @@ def test_constructor(self):
bdate_range(START, periods=20, freq=CDay())
bdate_range(end=START, periods=20, freq=CDay())

msg = "periods must be a number, got C"
msg = "periods must be an integer, got C"
with pytest.raises(TypeError, match=msg):
date_range("2011-1-1", "2012-1-1", "C")

Expand Down
11 changes: 5 additions & 6 deletions pandas/tests/indexes/interval/test_interval_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,10 @@ def test_interval_dtype(self, start, end, expected):

def test_interval_range_fractional_period(self):
# float value for periods
expected = interval_range(start=0, periods=10)
msg = "Non-integer 'periods' in pd.date_range, .* pd.interval_range"
with tm.assert_produces_warning(FutureWarning, match=msg):
result = interval_range(start=0, periods=10.5)
tm.assert_index_equal(result, expected)
msg = "periods must be an integer, got 10.5"
ts = Timestamp("2024-03-25")
with pytest.raises(TypeError, match=msg):
interval_range(ts, periods=10.5)

def test_constructor_coverage(self):
# equivalent timestamp-like start/end
Expand Down Expand Up @@ -340,7 +339,7 @@ def test_errors(self):
interval_range(start=Timedelta("1 day"), end=Timedelta("10 days"), freq=2)

# invalid periods
msg = "periods must be a number, got foo"
msg = "periods must be an integer, got foo"
with pytest.raises(TypeError, match=msg):
interval_range(start=0, periods="foo")

Expand Down
8 changes: 3 additions & 5 deletions pandas/tests/indexes/period/test_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,9 @@ def test_constructor_invalid_quarters(self):
)

def test_period_range_fractional_period(self):
msg = "Non-integer 'periods' in pd.date_range, pd.timedelta_range"
with tm.assert_produces_warning(FutureWarning, match=msg):
result = period_range("2007-01", periods=10.5, freq="M")
exp = period_range("2007-01", periods=10, freq="M")
tm.assert_index_equal(result, exp)
msg = "periods must be an integer, got 10.5"
with pytest.raises(TypeError, match=msg):
period_range("2007-01", periods=10.5, freq="M")

def test_constructor_with_without_freq(self):
# GH53687
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/indexes/period/test_period_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def test_start_end_non_nat(self):

def test_periods_requires_integer(self):
# invalid periods param
msg = "periods must be a number, got foo"
msg = "periods must be an integer, got foo"
with pytest.raises(TypeError, match=msg):
period_range(start="2017Q1", periods="foo")

Expand Down
10 changes: 4 additions & 6 deletions pandas/tests/indexes/timedeltas/test_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,12 @@ def test_constructor_iso(self):
tm.assert_index_equal(result, expected)

def test_timedelta_range_fractional_period(self):
msg = "Non-integer 'periods' in pd.date_range, pd.timedelta_range"
with tm.assert_produces_warning(FutureWarning, match=msg):
rng = timedelta_range("1 days", periods=10.5)
exp = timedelta_range("1 days", periods=10)
tm.assert_index_equal(rng, exp)
msg = "periods must be an integer"
with pytest.raises(TypeError, match=msg):
timedelta_range("1 days", periods=10.5)

def test_constructor_coverage(self):
msg = "periods must be a number, got foo"
msg = "periods must be an integer, got foo"
with pytest.raises(TypeError, match=msg):
timedelta_range(start="1 days", periods="foo", freq="D")

Expand Down
Loading