Skip to content

Commit b2b0f97

Browse files
authored
DEPR: fractional periods in date_range, timedelta_range, period_range… (#56036)
* DEPR: fractional periods in date_range, timedelta_range, period_range, interval_range * GH ref * DEPR: non-integer periods
1 parent acf5d7d commit b2b0f97

File tree

7 files changed

+29
-14
lines changed

7 files changed

+29
-14
lines changed

doc/source/whatsnew/v2.2.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ Other Deprecations
272272
- Deprecated :meth:`Index.format`, use ``index.astype(str)`` or ``index.map(formatter)`` instead (:issue:`55413`)
273273
- Deprecated ``core.internals`` members ``Block``, ``ExtensionBlock``, and ``DatetimeTZBlock``, use public APIs instead (:issue:`55139`)
274274
- Deprecated ``year``, ``month``, ``quarter``, ``day``, ``hour``, ``minute``, and ``second`` keywords in the :class:`PeriodIndex` constructor, use :meth:`PeriodIndex.from_fields` instead (:issue:`55960`)
275+
- Deprecated allowing non-integer ``periods`` argument in :func:`date_range`, :func:`timedelta_range`, :func:`period_range`, and :func:`interval_range` (:issue:`56036`)
275276
- Deprecated allowing non-keyword arguments in :meth:`DataFrame.to_clipboard`. (:issue:`54229`)
276277
- Deprecated allowing non-keyword arguments in :meth:`DataFrame.to_csv` except ``path_or_buf``. (:issue:`54229`)
277278
- Deprecated allowing non-keyword arguments in :meth:`DataFrame.to_dict`. (:issue:`54229`)

pandas/core/arrays/datetimelike.py

+8
Original file line numberDiff line numberDiff line change
@@ -2454,6 +2454,14 @@ def validate_periods(periods: int | float | None) -> int | None:
24542454
"""
24552455
if periods is not None:
24562456
if lib.is_float(periods):
2457+
warnings.warn(
2458+
# GH#56036
2459+
"Non-integer 'periods' in pd.date_range, pd.timedelta_range, "
2460+
"pd.period_range, and pd.interval_range are deprecated and "
2461+
"will raise in a future version.",
2462+
FutureWarning,
2463+
stacklevel=find_stack_level(),
2464+
)
24572465
periods = int(periods)
24582466
elif not lib.is_integer(periods):
24592467
raise TypeError(f"periods must be a number, got {periods}")

pandas/core/indexes/interval.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
)
4444
from pandas.core.dtypes.common import (
4545
ensure_platform_int,
46-
is_float,
4746
is_float_dtype,
4847
is_integer,
4948
is_integer_dtype,
@@ -60,6 +59,7 @@
6059
from pandas.core.dtypes.missing import is_valid_na_for_dtype
6160

6261
from pandas.core.algorithms import unique
62+
from pandas.core.arrays.datetimelike import validate_periods
6363
from pandas.core.arrays.interval import (
6464
IntervalArray,
6565
_interval_shared_docs,
@@ -1076,10 +1076,7 @@ def interval_range(
10761076
if not _is_valid_endpoint(end):
10771077
raise ValueError(f"end must be numeric or datetime-like, got {end}")
10781078

1079-
if is_float(periods):
1080-
periods = int(periods)
1081-
elif not is_integer(periods) and periods is not None:
1082-
raise TypeError(f"periods must be a number, got {periods}")
1079+
periods = validate_periods(periods)
10831080

10841081
if freq is not None and not is_number(freq):
10851082
try:

pandas/tests/indexes/datetimes/test_date_range.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,10 @@ def test_date_range_invalid_periods(self):
139139
with pytest.raises(TypeError, match=msg):
140140
date_range(start="1/1/2000", periods="foo", freq="D")
141141

142-
def test_date_range_float_periods(self):
143-
# TODO: reconsider allowing this?
144-
rng = date_range("1/1/2000", periods=10.5)
142+
def test_date_range_fractional_period(self):
143+
msg = "Non-integer 'periods' in pd.date_range, pd.timedelta_range"
144+
with tm.assert_produces_warning(FutureWarning, match=msg):
145+
rng = date_range("1/1/2000", periods=10.5)
145146
exp = date_range("1/1/2000", periods=10)
146147
tm.assert_index_equal(rng, exp)
147148

pandas/tests/indexes/interval/test_interval_range.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -219,12 +219,15 @@ def test_float_subtype(self, start, end, freq):
219219
expected = "int64" if is_integer(start + end) else "float64"
220220
assert result == expected
221221

222-
def test_constructor_coverage(self):
222+
def test_interval_range_fractional_period(self):
223223
# float value for periods
224224
expected = interval_range(start=0, periods=10)
225-
result = interval_range(start=0, periods=10.5)
225+
msg = "Non-integer 'periods' in pd.date_range, .* pd.interval_range"
226+
with tm.assert_produces_warning(FutureWarning, match=msg):
227+
result = interval_range(start=0, periods=10.5)
226228
tm.assert_index_equal(result, expected)
227229

230+
def test_constructor_coverage(self):
228231
# equivalent timestamp-like start/end
229232
start, end = Timestamp("2017-01-01"), Timestamp("2017-01-15")
230233
expected = interval_range(start=start, end=end)

pandas/tests/indexes/period/test_constructors.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,10 @@ def test_constructor_invalid_quarters(self):
176176
year=range(2000, 2004), quarter=list(range(4)), freq="Q-DEC"
177177
)
178178

179-
def test_constructor_corner(self):
180-
result = period_range("2007-01", periods=10.5, freq="M")
179+
def test_period_range_fractional_period(self):
180+
msg = "Non-integer 'periods' in pd.date_range, pd.timedelta_range"
181+
with tm.assert_produces_warning(FutureWarning, match=msg):
182+
result = period_range("2007-01", periods=10.5, freq="M")
181183
exp = period_range("2007-01", periods=10, freq="M")
182184
tm.assert_index_equal(result, exp)
183185

pandas/tests/indexes/timedeltas/test_constructors.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,14 @@ def test_constructor_iso(self):
174174
result = to_timedelta(durations)
175175
tm.assert_index_equal(result, expected)
176176

177-
def test_constructor_coverage(self):
178-
rng = timedelta_range("1 days", periods=10.5)
177+
def test_timedelta_range_fractional_period(self):
178+
msg = "Non-integer 'periods' in pd.date_range, pd.timedelta_range"
179+
with tm.assert_produces_warning(FutureWarning, match=msg):
180+
rng = timedelta_range("1 days", periods=10.5)
179181
exp = timedelta_range("1 days", periods=10)
180182
tm.assert_index_equal(rng, exp)
181183

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

0 commit comments

Comments
 (0)