diff --git a/doc/source/whatsnew/v0.17.0.txt b/doc/source/whatsnew/v0.17.0.txt index f88e5c0a11f9f..b861bf51b80eb 100644 --- a/doc/source/whatsnew/v0.17.0.txt +++ b/doc/source/whatsnew/v0.17.0.txt @@ -1009,4 +1009,6 @@ Bug Fixes - Bug in ``.var()`` causing roundoff errors for highly similar values (:issue:`10242`) - Bug in ``DataFrame.plot(subplots=True)`` with duplicated columns outputs incorrect result (:issue:`10962`) - Bug in ``Index`` arithmetic may result in incorrect class (:issue:`10638`) +- Bug in ``date_range`` results in empty if freq is negative annualy, quarterly and monthly (:issue:`11018`) +- Bug in ``DatetimeIndex`` cannot infer negative freq (:issue:`11018`) diff --git a/pandas/tests/test_index.py b/pandas/tests/test_index.py index 36bc0755f9a6a..1ec73edbb82e5 100644 --- a/pandas/tests/test_index.py +++ b/pandas/tests/test_index.py @@ -3329,7 +3329,7 @@ def test_ufunc_coercions(self): exp = TimedeltaIndex(['-2H', '-4H', '-6H', '-8H', '-10H'], freq='-2H', name='x') tm.assert_index_equal(result, exp) - self.assertEqual(result.freq, None) + self.assertEqual(result.freq, '-2H') idx = TimedeltaIndex(['-2H', '-1H', '0H', '1H', '2H'], freq='H', name='x') diff --git a/pandas/tseries/frequencies.py b/pandas/tseries/frequencies.py index d7eaab5a5a186..6a4257d101473 100644 --- a/pandas/tseries/frequencies.py +++ b/pandas/tseries/frequencies.py @@ -887,7 +887,8 @@ def __init__(self, index, warn=True): if len(index) < 3: raise ValueError('Need at least 3 dates to infer frequency') - self.is_monotonic = self.index.is_monotonic + self.is_monotonic = (self.index.is_monotonic_increasing or + self.index.is_monotonic_decreasing) @cache_readonly def deltas(self): @@ -971,7 +972,6 @@ def month_position_check(self): from calendar import monthrange for y, m, d, wd in zip(years, months, days, weekdays): - wd = datetime(y, m, d).weekday() if calendar_start: calendar_start &= d == 1 @@ -1025,7 +1025,7 @@ def _infer_daily_rule(self): monthly_rule = self._get_monthly_rule() if monthly_rule: - return monthly_rule + return _maybe_add_count(monthly_rule, self.mdiffs[0]) if self.is_unique: days = self.deltas[0] / _ONE_DAY @@ -1111,7 +1111,7 @@ def _infer_daily_rule(self): def _maybe_add_count(base, count): - if count > 1: + if count != 1: return '%d%s' % (count, base) else: return base diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index fb6929c77f6b0..e15be45ef305a 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -2615,15 +2615,24 @@ def generate_range(start=None, end=None, periods=None, start = end - (periods - 1) * offset cur = start - - while cur <= end: - yield cur - - # faster than cur + offset - next_date = offset.apply(cur) - if next_date <= cur: - raise ValueError('Offset %s did not increment date' % offset) - cur = next_date + if offset.n >= 0: + while cur <= end: + yield cur + + # faster than cur + offset + next_date = offset.apply(cur) + if next_date <= cur: + raise ValueError('Offset %s did not increment date' % offset) + cur = next_date + else: + while cur >= end: + yield cur + + # faster than cur + offset + next_date = offset.apply(cur) + if next_date >= cur: + raise ValueError('Offset %s did not decrement date' % offset) + cur = next_date prefix_mapping = dict((offset._prefix, offset) for offset in [ YearBegin, # 'AS' diff --git a/pandas/tseries/tests/test_base.py b/pandas/tseries/tests/test_base.py index 4a72b094917b5..24edc54582ec1 100644 --- a/pandas/tseries/tests/test_base.py +++ b/pandas/tseries/tests/test_base.py @@ -494,6 +494,15 @@ def test_take(self): self.assert_index_equal(result, expected) self.assertIsNone(result.freq) + def test_infer_freq(self): + # GH 11018 + for freq in ['A', '2A', '-2A', 'Q', '-1Q', 'M', '-1M', 'D', '3D', '-3D', + 'W', '-1W', 'H', '2H', '-2H', 'T', '2T', 'S', '-3S']: + idx = pd.date_range('2011-01-01 09:00:00', freq=freq, periods=10) + result = pd.DatetimeIndex(idx.asi8, freq='infer') + tm.assert_index_equal(idx, result) + self.assertEqual(result.freq, freq) + class TestTimedeltaIndexOps(Ops): @@ -1108,6 +1117,14 @@ def test_take(self): self.assert_index_equal(result, expected) self.assertIsNone(result.freq) + def test_infer_freq(self): + # GH 11018 + for freq in ['D', '3D', '-3D', 'H', '2H', '-2H', 'T', '2T', 'S', '-3S']: + idx = pd.timedelta_range('1', freq=freq, periods=10) + result = pd.TimedeltaIndex(idx.asi8, freq='infer') + tm.assert_index_equal(idx, result) + self.assertEqual(result.freq, freq) + class TestPeriodIndexOps(Ops): diff --git a/pandas/tseries/tests/test_frequencies.py b/pandas/tseries/tests/test_frequencies.py index a642c12786940..d9bc64136e390 100644 --- a/pandas/tseries/tests/test_frequencies.py +++ b/pandas/tseries/tests/test_frequencies.py @@ -539,7 +539,7 @@ def test_infer_freq_businesshour(self): def test_not_monotonic(self): rng = _dti(['1/31/2000', '1/31/2001', '1/31/2002']) rng = rng[::-1] - self.assertIsNone(rng.inferred_freq) + self.assertEqual(rng.inferred_freq, '-1A-JAN') def test_non_datetimeindex(self): rng = _dti(['1/31/2000', '1/31/2001', '1/31/2002']) diff --git a/pandas/tseries/tests/test_timedeltas.py b/pandas/tseries/tests/test_timedeltas.py index d3d09356648b0..69dc70698ca28 100644 --- a/pandas/tseries/tests/test_timedeltas.py +++ b/pandas/tseries/tests/test_timedeltas.py @@ -1539,8 +1539,7 @@ def test_tdi_ops_attributes(self): result = - rng exp = timedelta_range('-2 days', periods=5, freq='-2D', name='x') tm.assert_index_equal(result, exp) - # tdi doesn't infer negative freq - self.assertEqual(result.freq, None) + self.assertEqual(result.freq, '-2D') rng = pd.timedelta_range('-2 days', periods=5, freq='D', name='x') @@ -1548,7 +1547,6 @@ def test_tdi_ops_attributes(self): exp = TimedeltaIndex(['2 days', '1 days', '0 days', '1 days', '2 days'], name='x') tm.assert_index_equal(result, exp) - # tdi doesn't infer negative freq self.assertEqual(result.freq, None) diff --git a/pandas/tseries/tests/test_timeseries.py b/pandas/tseries/tests/test_timeseries.py index a021195ea6c04..565ce43dc46a1 100644 --- a/pandas/tseries/tests/test_timeseries.py +++ b/pandas/tseries/tests/test_timeseries.py @@ -1260,6 +1260,18 @@ def test_date_range_gen_error(self): rng = date_range('1/1/2000 00:00', '1/1/2000 00:18', freq='5min') self.assertEqual(len(rng), 4) + def test_date_range_negative_freq(self): + # GH 11018 + rng = date_range('2011-12-31', freq='-2A', periods=3) + exp = pd.DatetimeIndex(['2011-12-31', '2009-12-31', '2007-12-31'], freq='-2A') + self.assert_index_equal(rng, exp) + self.assertEqual(rng.freq, '-2A') + + rng = date_range('2011-01-31', freq='-2M', periods=3) + exp = pd.DatetimeIndex(['2011-01-31', '2010-11-30', '2010-09-30'], freq='-2M') + self.assert_index_equal(rng, exp) + self.assertEqual(rng.freq, '-2M') + def test_first_subset(self): ts = _simple_ts('1/1/2000', '1/1/2010', freq='12h') result = ts.first('10d')