Skip to content

Commit 0b858f0

Browse files
committed
Merge pull request #11018 from sinhrks/negative_freq
BUG: Unable to infer negative freq
2 parents 77d9c92 + ebf539a commit 0b858f0

File tree

8 files changed

+56
-18
lines changed

8 files changed

+56
-18
lines changed

doc/source/whatsnew/v0.17.0.txt

+2
Original file line numberDiff line numberDiff line change
@@ -1011,4 +1011,6 @@ Bug Fixes
10111011
- Bug in ``.var()`` causing roundoff errors for highly similar values (:issue:`10242`)
10121012
- Bug in ``DataFrame.plot(subplots=True)`` with duplicated columns outputs incorrect result (:issue:`10962`)
10131013
- Bug in ``Index`` arithmetic may result in incorrect class (:issue:`10638`)
1014+
- Bug in ``date_range`` results in empty if freq is negative annualy, quarterly and monthly (:issue:`11018`)
1015+
- Bug in ``DatetimeIndex`` cannot infer negative freq (:issue:`11018`)
10141016

pandas/tests/test_index.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3329,7 +3329,7 @@ def test_ufunc_coercions(self):
33293329
exp = TimedeltaIndex(['-2H', '-4H', '-6H', '-8H', '-10H'],
33303330
freq='-2H', name='x')
33313331
tm.assert_index_equal(result, exp)
3332-
self.assertEqual(result.freq, None)
3332+
self.assertEqual(result.freq, '-2H')
33333333

33343334
idx = TimedeltaIndex(['-2H', '-1H', '0H', '1H', '2H'],
33353335
freq='H', name='x')

pandas/tseries/frequencies.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,8 @@ def __init__(self, index, warn=True):
887887
if len(index) < 3:
888888
raise ValueError('Need at least 3 dates to infer frequency')
889889

890-
self.is_monotonic = self.index.is_monotonic
890+
self.is_monotonic = (self.index.is_monotonic_increasing or
891+
self.index.is_monotonic_decreasing)
891892

892893
@cache_readonly
893894
def deltas(self):
@@ -971,7 +972,6 @@ def month_position_check(self):
971972

972973
from calendar import monthrange
973974
for y, m, d, wd in zip(years, months, days, weekdays):
974-
wd = datetime(y, m, d).weekday()
975975

976976
if calendar_start:
977977
calendar_start &= d == 1
@@ -1025,7 +1025,7 @@ def _infer_daily_rule(self):
10251025

10261026
monthly_rule = self._get_monthly_rule()
10271027
if monthly_rule:
1028-
return monthly_rule
1028+
return _maybe_add_count(monthly_rule, self.mdiffs[0])
10291029

10301030
if self.is_unique:
10311031
days = self.deltas[0] / _ONE_DAY
@@ -1111,7 +1111,7 @@ def _infer_daily_rule(self):
11111111

11121112

11131113
def _maybe_add_count(base, count):
1114-
if count > 1:
1114+
if count != 1:
11151115
return '%d%s' % (count, base)
11161116
else:
11171117
return base

pandas/tseries/offsets.py

+18-9
Original file line numberDiff line numberDiff line change
@@ -2615,15 +2615,24 @@ def generate_range(start=None, end=None, periods=None,
26152615
start = end - (periods - 1) * offset
26162616

26172617
cur = start
2618-
2619-
while cur <= end:
2620-
yield cur
2621-
2622-
# faster than cur + offset
2623-
next_date = offset.apply(cur)
2624-
if next_date <= cur:
2625-
raise ValueError('Offset %s did not increment date' % offset)
2626-
cur = next_date
2618+
if offset.n >= 0:
2619+
while cur <= end:
2620+
yield cur
2621+
2622+
# faster than cur + offset
2623+
next_date = offset.apply(cur)
2624+
if next_date <= cur:
2625+
raise ValueError('Offset %s did not increment date' % offset)
2626+
cur = next_date
2627+
else:
2628+
while cur >= end:
2629+
yield cur
2630+
2631+
# faster than cur + offset
2632+
next_date = offset.apply(cur)
2633+
if next_date >= cur:
2634+
raise ValueError('Offset %s did not decrement date' % offset)
2635+
cur = next_date
26272636

26282637
prefix_mapping = dict((offset._prefix, offset) for offset in [
26292638
YearBegin, # 'AS'

pandas/tseries/tests/test_base.py

+17
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,15 @@ def test_take(self):
494494
self.assert_index_equal(result, expected)
495495
self.assertIsNone(result.freq)
496496

497+
def test_infer_freq(self):
498+
# GH 11018
499+
for freq in ['A', '2A', '-2A', 'Q', '-1Q', 'M', '-1M', 'D', '3D', '-3D',
500+
'W', '-1W', 'H', '2H', '-2H', 'T', '2T', 'S', '-3S']:
501+
idx = pd.date_range('2011-01-01 09:00:00', freq=freq, periods=10)
502+
result = pd.DatetimeIndex(idx.asi8, freq='infer')
503+
tm.assert_index_equal(idx, result)
504+
self.assertEqual(result.freq, freq)
505+
497506

498507
class TestTimedeltaIndexOps(Ops):
499508

@@ -1108,6 +1117,14 @@ def test_take(self):
11081117
self.assert_index_equal(result, expected)
11091118
self.assertIsNone(result.freq)
11101119

1120+
def test_infer_freq(self):
1121+
# GH 11018
1122+
for freq in ['D', '3D', '-3D', 'H', '2H', '-2H', 'T', '2T', 'S', '-3S']:
1123+
idx = pd.timedelta_range('1', freq=freq, periods=10)
1124+
result = pd.TimedeltaIndex(idx.asi8, freq='infer')
1125+
tm.assert_index_equal(idx, result)
1126+
self.assertEqual(result.freq, freq)
1127+
11111128

11121129
class TestPeriodIndexOps(Ops):
11131130

pandas/tseries/tests/test_frequencies.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ def test_infer_freq_businesshour(self):
539539
def test_not_monotonic(self):
540540
rng = _dti(['1/31/2000', '1/31/2001', '1/31/2002'])
541541
rng = rng[::-1]
542-
self.assertIsNone(rng.inferred_freq)
542+
self.assertEqual(rng.inferred_freq, '-1A-JAN')
543543

544544
def test_non_datetimeindex(self):
545545
rng = _dti(['1/31/2000', '1/31/2001', '1/31/2002'])

pandas/tseries/tests/test_timedeltas.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -1539,16 +1539,14 @@ def test_tdi_ops_attributes(self):
15391539
result = - rng
15401540
exp = timedelta_range('-2 days', periods=5, freq='-2D', name='x')
15411541
tm.assert_index_equal(result, exp)
1542-
# tdi doesn't infer negative freq
1543-
self.assertEqual(result.freq, None)
1542+
self.assertEqual(result.freq, '-2D')
15441543

15451544
rng = pd.timedelta_range('-2 days', periods=5, freq='D', name='x')
15461545

15471546
result = abs(rng)
15481547
exp = TimedeltaIndex(['2 days', '1 days', '0 days', '1 days',
15491548
'2 days'], name='x')
15501549
tm.assert_index_equal(result, exp)
1551-
# tdi doesn't infer negative freq
15521550
self.assertEqual(result.freq, None)
15531551

15541552

pandas/tseries/tests/test_timeseries.py

+12
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,18 @@ def test_date_range_gen_error(self):
12601260
rng = date_range('1/1/2000 00:00', '1/1/2000 00:18', freq='5min')
12611261
self.assertEqual(len(rng), 4)
12621262

1263+
def test_date_range_negative_freq(self):
1264+
# GH 11018
1265+
rng = date_range('2011-12-31', freq='-2A', periods=3)
1266+
exp = pd.DatetimeIndex(['2011-12-31', '2009-12-31', '2007-12-31'], freq='-2A')
1267+
self.assert_index_equal(rng, exp)
1268+
self.assertEqual(rng.freq, '-2A')
1269+
1270+
rng = date_range('2011-01-31', freq='-2M', periods=3)
1271+
exp = pd.DatetimeIndex(['2011-01-31', '2010-11-30', '2010-09-30'], freq='-2M')
1272+
self.assert_index_equal(rng, exp)
1273+
self.assertEqual(rng.freq, '-2M')
1274+
12631275
def test_first_subset(self):
12641276
ts = _simple_ts('1/1/2000', '1/1/2010', freq='12h')
12651277
result = ts.first('10d')

0 commit comments

Comments
 (0)