Skip to content

Commit 677eb75

Browse files
committed
ENH: PeriodIndex can accept freq with mult
1 parent e331a87 commit 677eb75

File tree

4 files changed

+73
-32
lines changed

4 files changed

+73
-32
lines changed

doc/source/timeseries.rst

+6
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,12 @@ The ``PeriodIndex`` constructor can also be used directly:
11521152
11531153
PeriodIndex(['2011-1', '2011-2', '2011-3'], freq='M')
11541154
1155+
Passing multiplied freq results in specified interval with normal freq:
1156+
1157+
.. ipython:: python
1158+
1159+
PeriodIndex(start='2014-01', freq='3M', periods=4)
1160+
11551161
Just like ``DatetimeIndex``, a ``PeriodIndex`` can also be used to index pandas
11561162
objects:
11571163

doc/source/whatsnew/v0.15.2.txt

+2
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ Enhancements
6767
- Added Google Analytics (`pandas.io.ga`) basic documentation (:issue:`8835`). See :ref:`here<remote_data.ga>`.
6868
- Added flag ``order_categoricals`` to ``StataReader`` and ``read_stata`` to select whether to order imported categorical data (:issue:`8836`). See :ref:`here <io.stata-categorical>` for more information on importing categorical variables from Stata data files.
6969

70+
- ``PeriodIndex`` and ``period_range`` can now accept multiplied freq (:issue:`7811`)
71+
7072
.. _whatsnew_0152.performance:
7173

7274
Performance

pandas/tseries/period.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -1275,10 +1275,14 @@ def tz_localize(self, tz, infer_dst=False):
12751275
PeriodIndex._add_datetimelike_methods()
12761276

12771277

1278-
def _get_ordinal_range(start, end, periods, freq):
1278+
def _get_ordinal_range(start, end, periods, freq, mult=1):
12791279
if com._count_not_none(start, end, periods) < 2:
12801280
raise ValueError('Must specify 2 of start, end, periods')
12811281

1282+
if freq is not None:
1283+
_, mult = _gfc(freq)
1284+
freq = frequencies.get_base_alias(freq)
1285+
12821286
if start is not None:
12831287
start = Period(start, freq)
12841288
if end is not None:
@@ -1302,15 +1306,15 @@ def _get_ordinal_range(start, end, periods, freq):
13021306
raise ValueError('Could not infer freq from start/end')
13031307

13041308
if periods is not None:
1309+
periods = periods * mult
13051310
if start is None:
1306-
data = np.arange(end.ordinal - periods + 1,
1307-
end.ordinal + 1,
1311+
data = np.arange(end.ordinal - periods + mult, end.ordinal + 1, mult,
13081312
dtype=np.int64)
13091313
else:
1310-
data = np.arange(start.ordinal, start.ordinal + periods,
1314+
data = np.arange(start.ordinal, start.ordinal + periods, mult,
13111315
dtype=np.int64)
13121316
else:
1313-
data = np.arange(start.ordinal, end.ordinal + 1, dtype=np.int64)
1317+
data = np.arange(start.ordinal, end.ordinal + 1, mult, dtype=np.int64)
13141318

13151319
return data, freq
13161320

@@ -1348,7 +1352,6 @@ def _range_from_fields(year=None, month=None, quarter=None, day=None,
13481352
base, mult = _gfc(freq)
13491353
if mult != 1:
13501354
raise ValueError('Only mult == 1 supported')
1351-
13521355
arrays = _make_field_arrays(year, month, day, hour, minute, second)
13531356
for y, mth, d, h, mn, s in zip(*arrays):
13541357
ordinals.append(tslib.period_ordinal(y, mth, d, h, mn, s, 0, 0, base))

pandas/tseries/tests/test_period.py

+56-26
Original file line numberDiff line numberDiff line change
@@ -1291,6 +1291,42 @@ def test_constructor_year_and_quarter(self):
12911291
p = PeriodIndex(lops)
12921292
tm.assert_index_equal(p, idx)
12931293

1294+
def test_constructor_freq_mult(self):
1295+
# GH #7811
1296+
for func in [PeriodIndex, period_range]:
1297+
# must be the same, but for sure...
1298+
pidx = func(start='2014-01', freq='2M', periods=4)
1299+
expected = PeriodIndex(['2014-01', '2014-03', '2014-05', '2014-07'], freq='M')
1300+
tm.assert_index_equal(pidx, expected)
1301+
1302+
pidx = func(start='2014-01-02', end='2014-01-15', freq='3D')
1303+
expected = PeriodIndex(['2014-01-02', '2014-01-05', '2014-01-08', '2014-01-11',
1304+
'2014-01-14'], freq='D')
1305+
tm.assert_index_equal(pidx, expected)
1306+
1307+
pidx = func(end='2014-01-01 17:00', freq='4H', periods=3)
1308+
expected = PeriodIndex(['2014-01-01 09:00', '2014-01-01 13:00',
1309+
'2014-01-01 17:00'], freq='H')
1310+
tm.assert_index_equal(pidx, expected)
1311+
1312+
def test_constructor_freq_mult_dti_compat(self):
1313+
import itertools
1314+
mults = [1, 2, 3, 4, 5] # -1, -2, -3, -4, -5]
1315+
freqs = ['A', 'M', 'D', 'T', 'S']
1316+
for mult, freq in itertools.product(mults, freqs):
1317+
freqstr = str(mult) + freq
1318+
pidx = PeriodIndex(start='2014-04-01', freq=freqstr, periods=10)
1319+
expected = date_range(start='2014-04-01', freq=freqstr, periods=10).to_period(freq)
1320+
tm.assert_index_equal(pidx, expected)
1321+
1322+
mults = [-1, -2, -3, -4, -5]
1323+
freqs = ['D', 'T', 'S']
1324+
for mult, freq in itertools.product(mults, freqs):
1325+
freqstr = str(mult) + freq
1326+
pidx = PeriodIndex(start='2014-04-01', freq=freqstr, periods=10)
1327+
expected = date_range(start='2014-04-01', freq=freqstr, periods=10).to_period(freq)
1328+
tm.assert_index_equal(pidx, expected)
1329+
12941330
def test_is_(self):
12951331
create_index = lambda: PeriodIndex(freq='A', start='1/1/2001',
12961332
end='12/1/2009')
@@ -2149,34 +2185,28 @@ def test_to_period_monthish(self):
21492185
self.assertEqual(prng.freq, 'M')
21502186

21512187
def test_no_multiples(self):
2152-
self.assertRaises(ValueError, period_range, '1989Q3', periods=10,
2153-
freq='2Q')
2154-
2155-
self.assertRaises(ValueError, period_range, '1989', periods=10,
2156-
freq='2A')
21572188
self.assertRaises(ValueError, Period, '1989', freq='2A')
21582189

2159-
# def test_pindex_multiples(self):
2160-
# pi = PeriodIndex(start='1/1/10', end='12/31/12', freq='2M')
2161-
# self.assertEqual(pi[0], Period('1/1/10', '2M'))
2162-
# self.assertEqual(pi[1], Period('3/1/10', '2M'))
2163-
2164-
# self.assertEqual(pi[0].asfreq('6M'), pi[2].asfreq('6M'))
2165-
# self.assertEqual(pi[0].asfreq('A'), pi[2].asfreq('A'))
2166-
2167-
# self.assertEqual(pi[0].asfreq('M', how='S'),
2168-
# Period('Jan 2010', '1M'))
2169-
# self.assertEqual(pi[0].asfreq('M', how='E'),
2170-
# Period('Feb 2010', '1M'))
2171-
# self.assertEqual(pi[1].asfreq('M', how='S'),
2172-
# Period('Mar 2010', '1M'))
2173-
2174-
# i = Period('1/1/2010 12:05:18', '5S')
2175-
# self.assertEqual(i, Period('1/1/2010 12:05:15', '5S'))
2176-
2177-
# i = Period('1/1/2010 12:05:18', '5S')
2178-
# self.assertEqual(i.asfreq('1S', how='E'),
2179-
# Period('1/1/2010 12:05:19', '1S'))
2190+
def test_pindex_multiples(self):
2191+
pi = PeriodIndex(start='1/1/11', end='12/31/11', freq='2M')
2192+
expected = PeriodIndex(['2011-01', '2011-03', '2011-05', '2011-07',
2193+
'2011-09', '2011-11'], freq='M')
2194+
tm.assert_index_equal(pi, expected)
2195+
self.assertEqual(pi.freq, 'M')
2196+
2197+
pi = period_range(start='1/1/11', end='12/31/11', freq='2M')
2198+
tm.assert_index_equal(pi, expected)
2199+
self.assertEqual(pi.freq, 'M')
2200+
2201+
pi = period_range(start='1/1/11', periods=6, freq='2M')
2202+
tm.assert_index_equal(pi, expected)
2203+
self.assertEqual(pi.freq, 'M')
2204+
2205+
pi = period_range(start='2011-11-30', periods=6, freq='-2M')
2206+
expected = PeriodIndex(['2011-11', '2011-09', '2011-07', '2011-05',
2207+
'2011-03', '2011-01'], freq='M')
2208+
tm.assert_index_equal(pi, expected)
2209+
self.assertEqual(pi.freq, 'M')
21802210

21812211
def test_iteration(self):
21822212
index = PeriodIndex(start='1/1/10', periods=4, freq='B')

0 commit comments

Comments
 (0)