Skip to content

Commit 2edc5b3

Browse files
committed
DEPR: Deprecate range-based PeriodIndex construction
Closes pandas-dev#20535
1 parent c230f29 commit 2edc5b3

File tree

11 files changed

+113
-80
lines changed

11 files changed

+113
-80
lines changed

doc/source/whatsnew/v0.24.0.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1142,7 +1142,7 @@ Deprecations
11421142
- Timezone converting a tz-aware ``datetime.datetime`` or :class:`Timestamp` with :class:`Timestamp` and the ``tz`` argument is now deprecated. Instead, use :meth:`Timestamp.tz_convert` (:issue:`23579`)
11431143
- :func:`pandas.api.types.is_period` is deprecated in favor of `pandas.api.types.is_period_dtype` (:issue:`23917`)
11441144
- :func:`pandas.api.types.is_datetimetz` is deprecated in favor of `pandas.api.types.is_datetime64tz` (:issue:`23917`)
1145-
- Creating a :class:`TimedeltaIndex` or :class:`DatetimeIndex` by passing range arguments `start`, `end`, and `periods` is deprecated in favor of :func:`timedelta_range` and :func:`date_range` (:issue:`23919`)
1145+
- Creating a :class:`TimedeltaIndex`, :class:`DatetimeIndex`, or :class:`PeriodIndex` by passing range arguments `start`, `end`, and `periods` is deprecated in favor of :func:`timedelta_range`, :func:`date_range`, or :func:`period_range` (:issue:`23919`)
11461146
- Passing a string alias like ``'datetime64[ns, UTC]'`` as the ``unit`` parameter to :class:`DatetimeTZDtype` is deprecated. Use :class:`DatetimeTZDtype.construct_from_string` instead (:issue:`23990`).
11471147
- In :meth:`Series.where` with Categorical data, providing an ``other`` that is not present in the categories is deprecated. Convert the categorical to a different dtype or add the ``other`` to the categories first (:issue:`24077`).
11481148
- :meth:`Series.clip_lower`, :meth:`Series.clip_upper`, :meth:`DataFrame.clip_lower` and :meth:`DataFrame.clip_upper` are deprecated and will be removed in a future version. Use ``Series.clip(lower=threshold)``, ``Series.clip(upper=threshold)`` and the equivalent ``DataFrame`` methods (:issue:`24203`)
@@ -1313,6 +1313,7 @@ Datetimelike
13131313
- Bug in :meth:`Series.combine_first` not properly aligning categoricals, so that missing values in ``self`` where not filled by valid values from ``other`` (:issue:`24147`)
13141314
- Bug in :func:`DataFrame.combine` with datetimelike values raising a TypeError (:issue:`23079`)
13151315
- Bug in :func:`date_range` with frequency of ``Day`` or higher where dates sufficiently far in the future could wrap around to the past instead of raising ``OutOfBoundsDatetime`` (:issue:`14187`)
1316+
- Bug in :func:`period_range` ignoring the frequency of ``start`` and ``end`` when those are provided as :class:`Period` objects (:issue:`20535`).
13161317
- Bug in :class:`PeriodIndex` with attribute ``freq.n`` greater than 1 where adding a :class:`DateOffset` object would return incorrect results (:issue:`23215`)
13171318
- Bug in :class:`Series` that interpreted string indices as lists of characters when setting datetimelike values (:issue:`23451`)
13181319
- Bug in :class:`Timestamp` constructor which would drop the frequency of an input :class:`Timestamp` (:issue:`22311`)

pandas/core/indexes/datetimes.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,13 @@ def __new__(cls, data=None,
223223
verify_integrity = True
224224

225225
if data is None:
226+
result = cls._generate_range(start, end, periods,
227+
freq=freq, tz=tz, normalize=normalize,
228+
closed=closed, ambiguous=ambiguous)
226229
warnings.warn("Creating a DatetimeIndex by passing range "
227230
"endpoints is deprecated. Use "
228231
"`pandas.date_range` instead.",
229232
FutureWarning, stacklevel=2)
230-
result = cls._generate_range(start, end, periods,
231-
freq=freq, tz=tz, normalize=normalize,
232-
closed=closed, ambiguous=ambiguous)
233233
result.name = name
234234
return result
235235

pandas/core/indexes/period.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,14 @@ def __new__(cls, data=None, ordinal=None, freq=None, start=None, end=None,
183183
# range-based.
184184
data, freq = PeriodArray._generate_range(start, end, periods,
185185
freq, fields)
186+
# PeriodArray._generate range does validate that fields is
187+
# empty when really using the range-based constructor.
188+
if not fields:
189+
warnings.warn("Creating a PeriodIndex by passing range "
190+
"endpoints is deprecated. Use "
191+
"`pandas.period_range` instead.",
192+
FutureWarning, stacklevel=2)
193+
186194
data = PeriodArray(data, freq=freq)
187195
else:
188196
freq = validate_dtype_freq(dtype, freq)
@@ -983,7 +991,7 @@ def base(self):
983991
PeriodIndex._add_datetimelike_methods()
984992

985993

986-
def period_range(start=None, end=None, periods=None, freq='D', name=None):
994+
def period_range(start=None, end=None, periods=None, freq=None, name=None):
987995
"""
988996
Return a fixed frequency PeriodIndex, with day (calendar) as the default
989997
frequency
@@ -996,8 +1004,11 @@ def period_range(start=None, end=None, periods=None, freq='D', name=None):
9961004
Right bound for generating periods
9971005
periods : integer, default None
9981006
Number of periods to generate
999-
freq : string or DateOffset, default 'D'
1000-
Frequency alias
1007+
freq : string or DateOffset, optional
1008+
Frequency alias. By default the freq is taken from `start` or `end`
1009+
if those are Period objects. Otherwise, the default is ``"D"`` for
1010+
daily frequency.
1011+
10011012
name : string, default None
10021013
Name of the resulting PeriodIndex
10031014
@@ -1035,5 +1046,7 @@ def period_range(start=None, end=None, periods=None, freq='D', name=None):
10351046
raise ValueError('Of the three parameters: start, end, and periods, '
10361047
'exactly two must be specified')
10371048

1038-
return PeriodIndex(start=start, end=end, periods=periods,
1039-
freq=freq, name=name)
1049+
data, freq = PeriodArray._generate_range(start, end, periods, freq,
1050+
fields={})
1051+
data = PeriodArray(data, freq=freq)
1052+
return PeriodIndex(data, name=name)

pandas/tests/indexes/period/test_arithmetic.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pytest
55

66
import pandas as pd
7-
from pandas import PeriodIndex
7+
from pandas import PeriodIndex, period_range
88
import pandas.util.testing as tm
99

1010

@@ -26,36 +26,36 @@ def test_pi_shift_ndarray(self):
2626
tm.assert_index_equal(result, expected)
2727

2828
def test_shift(self):
29-
pi1 = PeriodIndex(freq='A', start='1/1/2001', end='12/1/2009')
30-
pi2 = PeriodIndex(freq='A', start='1/1/2002', end='12/1/2010')
29+
pi1 = period_range(freq='A', start='1/1/2001', end='12/1/2009')
30+
pi2 = period_range(freq='A', start='1/1/2002', end='12/1/2010')
3131

3232
tm.assert_index_equal(pi1.shift(0), pi1)
3333

3434
assert len(pi1) == len(pi2)
3535
tm.assert_index_equal(pi1.shift(1), pi2)
3636

37-
pi1 = PeriodIndex(freq='A', start='1/1/2001', end='12/1/2009')
38-
pi2 = PeriodIndex(freq='A', start='1/1/2000', end='12/1/2008')
37+
pi1 = period_range(freq='A', start='1/1/2001', end='12/1/2009')
38+
pi2 = period_range(freq='A', start='1/1/2000', end='12/1/2008')
3939
assert len(pi1) == len(pi2)
4040
tm.assert_index_equal(pi1.shift(-1), pi2)
4141

42-
pi1 = PeriodIndex(freq='M', start='1/1/2001', end='12/1/2009')
43-
pi2 = PeriodIndex(freq='M', start='2/1/2001', end='1/1/2010')
42+
pi1 = period_range(freq='M', start='1/1/2001', end='12/1/2009')
43+
pi2 = period_range(freq='M', start='2/1/2001', end='1/1/2010')
4444
assert len(pi1) == len(pi2)
4545
tm.assert_index_equal(pi1.shift(1), pi2)
4646

47-
pi1 = PeriodIndex(freq='M', start='1/1/2001', end='12/1/2009')
48-
pi2 = PeriodIndex(freq='M', start='12/1/2000', end='11/1/2009')
47+
pi1 = period_range(freq='M', start='1/1/2001', end='12/1/2009')
48+
pi2 = period_range(freq='M', start='12/1/2000', end='11/1/2009')
4949
assert len(pi1) == len(pi2)
5050
tm.assert_index_equal(pi1.shift(-1), pi2)
5151

52-
pi1 = PeriodIndex(freq='D', start='1/1/2001', end='12/1/2009')
53-
pi2 = PeriodIndex(freq='D', start='1/2/2001', end='12/2/2009')
52+
pi1 = period_range(freq='D', start='1/1/2001', end='12/1/2009')
53+
pi2 = period_range(freq='D', start='1/2/2001', end='12/2/2009')
5454
assert len(pi1) == len(pi2)
5555
tm.assert_index_equal(pi1.shift(1), pi2)
5656

57-
pi1 = PeriodIndex(freq='D', start='1/1/2001', end='12/1/2009')
58-
pi2 = PeriodIndex(freq='D', start='12/31/2000', end='11/30/2009')
57+
pi1 = period_range(freq='D', start='1/1/2001', end='12/1/2009')
58+
pi2 = period_range(freq='D', start='12/31/2000', end='11/30/2009')
5959
assert len(pi1) == len(pi2)
6060
tm.assert_index_equal(pi1.shift(-1), pi2)
6161

@@ -100,7 +100,7 @@ def test_shift_gh8083(self):
100100

101101
def test_shift_periods(self):
102102
# GH #22458 : argument 'n' was deprecated in favor of 'periods'
103-
idx = PeriodIndex(freq='A', start='1/1/2001', end='12/1/2009')
103+
idx = period_range(freq='A', start='1/1/2001', end='12/1/2009')
104104
tm.assert_index_equal(idx.shift(periods=0), idx)
105105
tm.assert_index_equal(idx.shift(0), idx)
106106
with tm.assert_produces_warning(FutureWarning,

pandas/tests/indexes/period/test_asfreq.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@
22
import pytest
33

44
import pandas as pd
5-
from pandas import DataFrame, PeriodIndex, Series
5+
from pandas import DataFrame, PeriodIndex, Series, period_range
66
from pandas.util import testing as tm
77

88

99
class TestPeriodIndex(object):
1010

1111
def test_asfreq(self):
12-
pi1 = PeriodIndex(freq='A', start='1/1/2001', end='1/1/2001')
13-
pi2 = PeriodIndex(freq='Q', start='1/1/2001', end='1/1/2001')
14-
pi3 = PeriodIndex(freq='M', start='1/1/2001', end='1/1/2001')
15-
pi4 = PeriodIndex(freq='D', start='1/1/2001', end='1/1/2001')
16-
pi5 = PeriodIndex(freq='H', start='1/1/2001', end='1/1/2001 00:00')
17-
pi6 = PeriodIndex(freq='Min', start='1/1/2001', end='1/1/2001 00:00')
18-
pi7 = PeriodIndex(freq='S', start='1/1/2001', end='1/1/2001 00:00:00')
12+
pi1 = period_range(freq='A', start='1/1/2001', end='1/1/2001')
13+
pi2 = period_range(freq='Q', start='1/1/2001', end='1/1/2001')
14+
pi3 = period_range(freq='M', start='1/1/2001', end='1/1/2001')
15+
pi4 = period_range(freq='D', start='1/1/2001', end='1/1/2001')
16+
pi5 = period_range(freq='H', start='1/1/2001', end='1/1/2001 00:00')
17+
pi6 = period_range(freq='Min', start='1/1/2001', end='1/1/2001 00:00')
18+
pi7 = period_range(freq='S', start='1/1/2001', end='1/1/2001 00:00:00')
1919

2020
assert pi1.asfreq('Q', 'S') == pi2
2121
assert pi1.asfreq('Q', 's') == pi2
@@ -70,7 +70,7 @@ def test_asfreq(self):
7070
pytest.raises(ValueError, pi7.asfreq, 'T', 'foo')
7171
result1 = pi1.asfreq('3M')
7272
result2 = pi1.asfreq('M')
73-
expected = PeriodIndex(freq='M', start='2001-12', end='2001-12')
73+
expected = period_range(freq='M', start='2001-12', end='2001-12')
7474
tm.assert_numpy_array_equal(result1.asi8, expected.asi8)
7575
assert result1.freqstr == '3M'
7676
tm.assert_numpy_array_equal(result2.asi8, expected.asi8)
@@ -126,7 +126,7 @@ def test_asfreq_combined_pi(self):
126126
assert result.freq == exp.freq
127127

128128
def test_asfreq_ts(self):
129-
index = PeriodIndex(freq='A', start='1/1/2001', end='12/31/2010')
129+
index = period_range(freq='A', start='1/1/2001', end='12/31/2010')
130130
ts = Series(np.random.randn(len(index)), index=index)
131131
df = DataFrame(np.random.randn(len(index), 3), index=index)
132132

pandas/tests/indexes/period/test_construction.py

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,12 @@ def test_construction_base_constructor(self):
4040
def test_constructor_use_start_freq(self):
4141
# GH #1118
4242
p = Period('4/2/2012', freq='B')
43-
index = PeriodIndex(start=p, periods=10)
44-
expected = PeriodIndex(start='4/2/2012', periods=10, freq='B')
43+
with tm.assert_produces_warning(FutureWarning):
44+
index = PeriodIndex(start=p, periods=10)
45+
expected = period_range(start='4/2/2012', periods=10, freq='B')
46+
tm.assert_index_equal(index, expected)
47+
48+
index = period_range(start=p, periods=10)
4549
tm.assert_index_equal(index, expected)
4650

4751
def test_constructor_field_arrays(self):
@@ -93,7 +97,7 @@ def test_constructor_arrays_negative_year(self):
9397
years = np.arange(1960, 2000, dtype=np.int64).repeat(4)
9498
quarters = np.tile(np.array([1, 2, 3, 4], dtype=np.int64), 40)
9599

96-
pindex = PeriodIndex(year=years, quarter=quarters)
100+
pindex = period_range(year=years, quarter=quarters)
97101

98102
tm.assert_index_equal(pindex.year, pd.Index(years))
99103
tm.assert_index_equal(pindex.quarter, pd.Index(quarters))
@@ -320,19 +324,28 @@ def test_constructor_year_and_quarter(self):
320324
def test_constructor_freq_mult(self):
321325
# GH #7811
322326
for func in [PeriodIndex, period_range]:
323-
# must be the same, but for sure...
324-
pidx = func(start='2014-01', freq='2M', periods=4)
327+
328+
if func is PeriodIndex:
329+
warning = FutureWarning
330+
else:
331+
warning = None
332+
333+
with tm.assert_produces_warning(warning):
334+
# must be the same, but for sure...
335+
pidx = func(start='2014-01', freq='2M', periods=4)
325336
expected = PeriodIndex(['2014-01', '2014-03',
326337
'2014-05', '2014-07'], freq='2M')
327338
tm.assert_index_equal(pidx, expected)
328339

329-
pidx = func(start='2014-01-02', end='2014-01-15', freq='3D')
340+
with tm.assert_produces_warning(warning):
341+
pidx = func(start='2014-01-02', end='2014-01-15', freq='3D')
330342
expected = PeriodIndex(['2014-01-02', '2014-01-05',
331343
'2014-01-08', '2014-01-11',
332344
'2014-01-14'], freq='3D')
333345
tm.assert_index_equal(pidx, expected)
334346

335-
pidx = func(end='2014-01-01 17:00', freq='4H', periods=3)
347+
with tm.assert_produces_warning(warning):
348+
pidx = func(end='2014-01-01 17:00', freq='4H', periods=3)
336349
expected = PeriodIndex(['2014-01-01 09:00', '2014-01-01 13:00',
337350
'2014-01-01 17:00'], freq='4H')
338351
tm.assert_index_equal(pidx, expected)
@@ -354,7 +367,7 @@ def test_constructor_freq_mult(self):
354367
@pytest.mark.parametrize('mult', [1, 2, 3, 4, 5])
355368
def test_constructor_freq_mult_dti_compat(self, mult, freq):
356369
freqstr = str(mult) + freq
357-
pidx = PeriodIndex(start='2014-04-01', freq=freqstr, periods=10)
370+
pidx = period_range(start='2014-04-01', freq=freqstr, periods=10)
358371
expected = date_range(start='2014-04-01', freq=freqstr,
359372
periods=10).to_period(freqstr)
360373
tm.assert_index_equal(pidx, expected)
@@ -364,63 +377,68 @@ def test_constructor_freq_combined(self):
364377
pidx = PeriodIndex(['2016-01-01', '2016-01-02'], freq=freq)
365378
expected = PeriodIndex(['2016-01-01 00:00', '2016-01-02 00:00'],
366379
freq='25H')
367-
for freq, func in zip(['1D1H', '1H1D'], [PeriodIndex, period_range]):
368-
pidx = func(start='2016-01-01', periods=2, freq=freq)
380+
for freq in ['1D1H', '1H1D']:
381+
pidx = period_range(start='2016-01-01', periods=2, freq=freq)
369382
expected = PeriodIndex(['2016-01-01 00:00', '2016-01-02 01:00'],
370383
freq='25H')
371384
tm.assert_index_equal(pidx, expected)
372385

386+
def test_constructor_range_based_deprecated(self):
387+
with tm.assert_produces_warning(FutureWarning):
388+
pi = PeriodIndex(freq='A', start='1/1/2001', end='12/1/2009')
389+
assert len(pi) == 9
390+
373391
def test_constructor(self):
374-
pi = PeriodIndex(freq='A', start='1/1/2001', end='12/1/2009')
392+
pi = period_range(freq='A', start='1/1/2001', end='12/1/2009')
375393
assert len(pi) == 9
376394

377-
pi = PeriodIndex(freq='Q', start='1/1/2001', end='12/1/2009')
395+
pi = period_range(freq='Q', start='1/1/2001', end='12/1/2009')
378396
assert len(pi) == 4 * 9
379397

380-
pi = PeriodIndex(freq='M', start='1/1/2001', end='12/1/2009')
398+
pi = period_range(freq='M', start='1/1/2001', end='12/1/2009')
381399
assert len(pi) == 12 * 9
382400

383-
pi = PeriodIndex(freq='D', start='1/1/2001', end='12/31/2009')
401+
pi = period_range(freq='D', start='1/1/2001', end='12/31/2009')
384402
assert len(pi) == 365 * 9 + 2
385403

386-
pi = PeriodIndex(freq='B', start='1/1/2001', end='12/31/2009')
404+
pi = period_range(freq='B', start='1/1/2001', end='12/31/2009')
387405
assert len(pi) == 261 * 9
388406

389-
pi = PeriodIndex(freq='H', start='1/1/2001', end='12/31/2001 23:00')
407+
pi = period_range(freq='H', start='1/1/2001', end='12/31/2001 23:00')
390408
assert len(pi) == 365 * 24
391409

392-
pi = PeriodIndex(freq='Min', start='1/1/2001', end='1/1/2001 23:59')
410+
pi = period_range(freq='Min', start='1/1/2001', end='1/1/2001 23:59')
393411
assert len(pi) == 24 * 60
394412

395-
pi = PeriodIndex(freq='S', start='1/1/2001', end='1/1/2001 23:59:59')
413+
pi = period_range(freq='S', start='1/1/2001', end='1/1/2001 23:59:59')
396414
assert len(pi) == 24 * 60 * 60
397415

398416
start = Period('02-Apr-2005', 'B')
399-
i1 = PeriodIndex(start=start, periods=20)
417+
i1 = period_range(start=start, periods=20)
400418
assert len(i1) == 20
401419
assert i1.freq == start.freq
402420
assert i1[0] == start
403421

404422
end_intv = Period('2006-12-31', 'W')
405-
i1 = PeriodIndex(end=end_intv, periods=10)
423+
i1 = period_range(end=end_intv, periods=10)
406424
assert len(i1) == 10
407425
assert i1.freq == end_intv.freq
408426
assert i1[-1] == end_intv
409427

410428
end_intv = Period('2006-12-31', '1w')
411-
i2 = PeriodIndex(end=end_intv, periods=10)
429+
i2 = period_range(end=end_intv, periods=10)
412430
assert len(i1) == len(i2)
413431
assert (i1 == i2).all()
414432
assert i1.freq == i2.freq
415433

416434
end_intv = Period('2006-12-31', ('w', 1))
417-
i2 = PeriodIndex(end=end_intv, periods=10)
435+
i2 = period_range(end=end_intv, periods=10)
418436
assert len(i1) == len(i2)
419437
assert (i1 == i2).all()
420438
assert i1.freq == i2.freq
421439

422440
end_intv = Period('2005-05-01', 'B')
423-
i1 = PeriodIndex(start=start, end=end_intv)
441+
i1 = period_range(start=start, end=end_intv)
424442

425443
# infer freq from first element
426444
i2 = PeriodIndex([end_intv, Period('2005-05-05', 'B')])
@@ -453,7 +471,7 @@ def test_constructor_error(self):
453471
@pytest.mark.parametrize('freq', ['M', 'Q', 'A', 'D', 'B',
454472
'T', 'S', 'L', 'U', 'N', 'H'])
455473
def test_recreate_from_data(self, freq):
456-
org = PeriodIndex(start='2001/04/01', freq=freq, periods=1)
474+
org = period_range(start='2001/04/01', freq=freq, periods=1)
457475
idx = PeriodIndex(org.values, freq=freq)
458476
tm.assert_index_equal(idx, org)
459477

pandas/tests/indexes/period/test_indexing.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ def test_getitem_seconds(self):
147147
# GH#6716
148148
didx = pd.date_range(start='2013/01/01 09:00:00', freq='S',
149149
periods=4000)
150-
pidx = PeriodIndex(start='2013/01/01 09:00:00', freq='S', periods=4000)
150+
pidx = period_range(start='2013/01/01 09:00:00', freq='S',
151+
periods=4000)
151152

152153
for idx in [didx, pidx]:
153154
# getitem against index should raise ValueError
@@ -171,7 +172,7 @@ def test_getitem_day(self):
171172
# GH#6716
172173
# Confirm DatetimeIndex and PeriodIndex works identically
173174
didx = pd.date_range(start='2013/01/01', freq='D', periods=400)
174-
pidx = PeriodIndex(start='2013/01/01', freq='D', periods=400)
175+
pidx = period_range(start='2013/01/01', freq='D', periods=400)
175176

176177
for idx in [didx, pidx]:
177178
# getitem against index should raise ValueError
@@ -281,8 +282,8 @@ def test_take(self):
281282
assert result.freq == 'D'
282283

283284
def test_take_misc(self):
284-
index = PeriodIndex(start='1/1/10', end='12/31/12', freq='D',
285-
name='idx')
285+
index = period_range(start='1/1/10', end='12/31/12', freq='D',
286+
name='idx')
286287
expected = PeriodIndex([datetime(2010, 1, 6), datetime(2010, 1, 7),
287288
datetime(2010, 1, 9), datetime(2010, 1, 13)],
288289
freq='D', name='idx')

0 commit comments

Comments
 (0)