diff --git a/doc/source/whatsnew/v0.23.0.txt b/doc/source/whatsnew/v0.23.0.txt index 641214550a3b7..2975b2a53c3a8 100644 --- a/doc/source/whatsnew/v0.23.0.txt +++ b/doc/source/whatsnew/v0.23.0.txt @@ -877,6 +877,7 @@ Deprecations - The ``convert_datetime64`` parameter in :func:`DataFrame.to_records` has been deprecated and will be removed in a future version. The NumPy bug motivating this parameter has been resolved. The default value for this parameter has also changed from ``True`` to ``None`` (:issue:`18160`). - :func:`Series.rolling().apply() `, :func:`DataFrame.rolling().apply() `, :func:`Series.expanding().apply() `, and :func:`DataFrame.expanding().apply() ` have deprecated passing an ``np.array`` by default. One will need to pass the new ``raw`` parameter to be explicit about what is passed (:issue:`20584`) +- ``DatetimeIndex.offset`` is deprecated. Use ``DatetimeIndex.freq`` instead (:issue:`20716`) .. _whatsnew_0230.prior_deprecations: diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 88ea3511d4ee3..e0e7ba3e8b518 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -302,7 +302,7 @@ def _add_comparison_methods(cls): _engine_type = libindex.DatetimeEngine tz = None - offset = None + _freq = None _comparables = ['name', 'freqstr', 'tz'] _attributes = ['name', 'freq', 'tz'] @@ -415,7 +415,7 @@ def __new__(cls, data=None, subarr = data.values if freq is None: - freq = data.offset + freq = data.freq verify_integrity = False else: if data.dtype != _NS_DTYPE: @@ -467,12 +467,12 @@ def __new__(cls, data=None, if freq_infer: inferred = subarr.inferred_freq if inferred: - subarr.offset = to_offset(inferred) + subarr.freq = to_offset(inferred) return subarr._deepcopy_if_needed(ref_to_data, copy) @classmethod - def _generate(cls, start, end, periods, name, offset, + def _generate(cls, start, end, periods, name, freq, tz=None, normalize=False, ambiguous='raise', closed=None): if com._count_not_none(start, end, periods) != 2: raise ValueError('Of the three parameters: start, end, and ' @@ -535,7 +535,7 @@ def _generate(cls, start, end, periods, name, offset, else: _normalized = _normalized and end.time() == _midnight - if hasattr(offset, 'delta') and offset != offsets.Day(): + if hasattr(freq, 'delta') and freq != offsets.Day(): if inferred_tz is None and tz is not None: # naive dates if start is not None and start.tz is None: @@ -551,11 +551,11 @@ def _generate(cls, start, end, periods, name, offset, if end.tz is None and start.tz is not None: end = end.tz_localize(start.tz, ambiguous=False) - if _use_cached_range(offset, _normalized, start, end): + if _use_cached_range(freq, _normalized, start, end): index = cls._cached_range(start, end, periods=periods, - offset=offset, name=name) + freq=freq, name=name) else: - index = _generate_regular_range(start, end, periods, offset) + index = _generate_regular_range(start, end, periods, freq) else: @@ -574,11 +574,11 @@ def _generate(cls, start, end, periods, name, offset, if end.tz is None and start.tz is not None: start = start.replace(tzinfo=None) - if _use_cached_range(offset, _normalized, start, end): + if _use_cached_range(freq, _normalized, start, end): index = cls._cached_range(start, end, periods=periods, - offset=offset, name=name) + freq=freq, name=name) else: - index = _generate_regular_range(start, end, periods, offset) + index = _generate_regular_range(start, end, periods, freq) if tz is not None and getattr(index, 'tz', None) is None: index = conversion.tz_localize_to_utc(_ensure_int64(index), tz, @@ -596,12 +596,12 @@ def _generate(cls, start, end, periods, name, offset, index = index[1:] if not right_closed and len(index) and index[-1] == end: index = index[:-1] - index = cls._simple_new(index, name=name, freq=offset, tz=tz) + index = cls._simple_new(index, name=name, freq=freq, tz=tz) return index @property def _box_func(self): - return lambda x: Timestamp(x, freq=self.offset, tz=self.tz) + return lambda x: Timestamp(x, freq=self.freq, tz=self.tz) def _convert_for_op(self, value): """ Convert value to be insertable to ndarray """ @@ -647,7 +647,7 @@ def _simple_new(cls, values, name=None, freq=None, tz=None, result = object.__new__(cls) result._data = values result.name = name - result.offset = freq + result._freq = freq result._tz = timezones.maybe_get_tz(tz) result._tz = timezones.tz_standardize(result._tz) result._reset_identity() @@ -734,7 +734,7 @@ def _has_same_tz(self, other): return zzone == vzone @classmethod - def _cached_range(cls, start=None, end=None, periods=None, offset=None, + def _cached_range(cls, start=None, end=None, periods=None, freq=None, name=None): if start is None and end is None: # I somewhat believe this should never be raised externally @@ -747,30 +747,30 @@ def _cached_range(cls, start=None, end=None, periods=None, offset=None, raise TypeError( 'Must either specify period or provide both start and end.') - if offset is None: + if freq is None: # This can't happen with external-facing code - raise TypeError('Must provide offset.') + raise TypeError('Must provide freq.') drc = _daterange_cache - if offset not in _daterange_cache: - xdr = generate_range(offset=offset, start=_CACHE_START, + if freq not in _daterange_cache: + xdr = generate_range(offset=freq, start=_CACHE_START, end=_CACHE_END) arr = tools.to_datetime(list(xdr), box=False) cachedRange = DatetimeIndex._simple_new(arr) - cachedRange.offset = offset + cachedRange.freq = freq cachedRange = cachedRange.tz_localize(None) cachedRange.name = None - drc[offset] = cachedRange + drc[freq] = cachedRange else: - cachedRange = drc[offset] + cachedRange = drc[freq] if start is None: if not isinstance(end, Timestamp): raise AssertionError('end must be an instance of Timestamp') - end = offset.rollback(end) + end = freq.rollback(end) endLoc = cachedRange.get_loc(end) + 1 startLoc = endLoc - periods @@ -778,23 +778,23 @@ def _cached_range(cls, start=None, end=None, periods=None, offset=None, if not isinstance(start, Timestamp): raise AssertionError('start must be an instance of Timestamp') - start = offset.rollforward(start) + start = freq.rollforward(start) startLoc = cachedRange.get_loc(start) endLoc = startLoc + periods else: - if not offset.onOffset(start): - start = offset.rollforward(start) + if not freq.onOffset(start): + start = freq.rollforward(start) - if not offset.onOffset(end): - end = offset.rollback(end) + if not freq.onOffset(end): + end = freq.rollback(end) startLoc = cachedRange.get_loc(start) endLoc = cachedRange.get_loc(end) + 1 indexSlice = cachedRange[startLoc:endLoc] indexSlice.name = name - indexSlice.offset = offset + indexSlice.freq = freq return indexSlice @@ -836,7 +836,7 @@ def __setstate__(self, state): np.ndarray.__setstate__(data, nd_state) self.name = own_state[0] - self.offset = own_state[1] + self.freq = own_state[1] self._tz = timezones.tz_standardize(own_state[2]) # provide numpy < 1.7 compat @@ -1184,7 +1184,7 @@ def union(self, other): result._tz = timezones.tz_standardize(this.tz) if (result.freq is None and (this.freq is not None or other.freq is not None)): - result.offset = to_offset(result.inferred_freq) + result.freq = to_offset(result.inferred_freq) return result def to_perioddelta(self, freq): @@ -1232,7 +1232,7 @@ def union_many(self, others): this._tz = timezones.tz_standardize(tz) if this.freq is None: - this.offset = to_offset(this.inferred_freq) + this.freq = to_offset(this.inferred_freq) return this def join(self, other, how='left', level=None, return_indexers=False, @@ -1271,7 +1271,7 @@ def _maybe_utc_convert(self, other): def _wrap_joined_index(self, joined, other): name = self.name if self.name == other.name else None if (isinstance(other, DatetimeIndex) and - self.offset == other.offset and + self.freq == other.freq and self._can_fast_union(other)): joined = self._shallow_copy(joined) joined.name = name @@ -1284,9 +1284,9 @@ def _can_fast_union(self, other): if not isinstance(other, DatetimeIndex): return False - offset = self.offset + freq = self.freq - if offset is None or offset != other.offset: + if freq is None or freq != other.freq: return False if not self.is_monotonic or not other.is_monotonic: @@ -1306,10 +1306,10 @@ def _can_fast_union(self, other): # Only need to "adjoin", not overlap try: - return (right_start == left_end + offset) or right_start in left + return (right_start == left_end + freq) or right_start in left except (ValueError): - # if we are comparing an offset that does not propagate timezones + # if we are comparing a freq that does not propagate timezones # this will raise return False @@ -1329,7 +1329,7 @@ def _fast_union(self, other): left_start, left_end = left[0], left[-1] right_end = right[-1] - if not self.offset._should_cache(): + if not self.freq._should_cache(): # concatenate dates if left_end < right_end: loc = right.searchsorted(left_end, side='right') @@ -1341,7 +1341,7 @@ def _fast_union(self, other): else: return type(self)(start=left_start, end=max(left_end, right_end), - freq=left.offset) + freq=left.freq) def __iter__(self): """ @@ -1393,18 +1393,18 @@ def intersection(self, other): result = Index.intersection(self, other) if isinstance(result, DatetimeIndex): if result.freq is None: - result.offset = to_offset(result.inferred_freq) + result.freq = to_offset(result.inferred_freq) return result - elif (other.offset is None or self.offset is None or - other.offset != self.offset or - not other.offset.isAnchored() or + elif (other.freq is None or self.freq is None or + other.freq != self.freq or + not other.freq.isAnchored() or (not self.is_monotonic or not other.is_monotonic)): result = Index.intersection(self, other) result = self._shallow_copy(result._values, name=result.name, tz=result.tz, freq=None) if result.freq is None: - result.offset = to_offset(result.inferred_freq) + result.freq = to_offset(result.inferred_freq) return result if len(self) == 0: @@ -1729,12 +1729,28 @@ def slice_indexer(self, start=None, end=None, step=None, kind=None): @property def freq(self): """get/set the frequency of the Index""" - return self.offset + return self._freq @freq.setter def freq(self, value): """get/set the frequency of the Index""" - self.offset = value + self._freq = value + + @property + def offset(self): + """get/set the frequency of the Index""" + msg = ('DatetimeIndex.offset has been deprecated and will be removed ' + 'in a future version; use DatetimeIndex.freq instead.') + warnings.warn(msg, FutureWarning, stacklevel=2) + return self.freq + + @offset.setter + def offset(self, value): + """get/set the frequency of the Index""" + msg = ('DatetimeIndex.offset has been deprecated and will be removed ' + 'in a future version; use DatetimeIndex.freq instead.') + warnings.warn(msg, FutureWarning, stacklevel=2) + self.freq = value year = _field_accessor('year', 'Y', "The year of the datetime") month = _field_accessor('month', 'M', @@ -2525,9 +2541,9 @@ def day_name(self, locale=None): DatetimeIndex._add_datetimelike_methods() -def _generate_regular_range(start, end, periods, offset): - if isinstance(offset, Tick): - stride = offset.nanos +def _generate_regular_range(start, end, periods, freq): + if isinstance(freq, Tick): + stride = freq.nanos if periods is None: b = Timestamp(start).value # cannot just use e = Timestamp(end) + 1 because arange breaks when @@ -2558,7 +2574,7 @@ def _generate_regular_range(start, end, periods, offset): end = end.to_pydatetime() xdr = generate_range(start=start, end=end, - periods=periods, offset=offset) + periods=periods, offset=freq) dates = list(xdr) # utc = len(dates) > 0 and dates[0].tzinfo is not None @@ -2855,9 +2871,9 @@ def _in_range(start, end, rng_start, rng_end): return start > rng_start and end < rng_end -def _use_cached_range(offset, _normalized, start, end): - return (offset._should_cache() and - not (offset._normalize_cache and not _normalized) and +def _use_cached_range(freq, _normalized, start, end): + return (freq._should_cache() and + not (freq._normalize_cache and not _normalized) and _naive_in_cache_range(start, end)) diff --git a/pandas/tests/indexes/datetimes/test_construction.py b/pandas/tests/indexes/datetimes/test_construction.py index 1cf854ad4a926..dae69a86910af 100644 --- a/pandas/tests/indexes/datetimes/test_construction.py +++ b/pandas/tests/indexes/datetimes/test_construction.py @@ -598,16 +598,16 @@ def test_datetimeindex_constructor_misc(self): idx2 = DatetimeIndex(start=sdate, end=edate, freq=offsets.Week(weekday=6)) assert len(idx1) == len(idx2) - assert idx1.offset == idx2.offset + assert idx1.freq == idx2.freq idx1 = DatetimeIndex(start=sdate, end=edate, freq='QS') idx2 = DatetimeIndex(start=sdate, end=edate, freq=offsets.QuarterBegin(startingMonth=1)) assert len(idx1) == len(idx2) - assert idx1.offset == idx2.offset + assert idx1.freq == idx2.freq idx1 = DatetimeIndex(start=sdate, end=edate, freq='BQ') idx2 = DatetimeIndex(start=sdate, end=edate, freq=offsets.BQuarterEnd(startingMonth=12)) assert len(idx1) == len(idx2) - assert idx1.offset == idx2.offset + assert idx1.freq == idx2.freq diff --git a/pandas/tests/indexes/datetimes/test_date_range.py b/pandas/tests/indexes/datetimes/test_date_range.py index d2ec465468dfb..2dfd4ae3e6e3a 100644 --- a/pandas/tests/indexes/datetimes/test_date_range.py +++ b/pandas/tests/indexes/datetimes/test_date_range.py @@ -331,21 +331,21 @@ def test_naive_aware_conflicts(self): aware.join(naive) def test_cached_range(self): - DatetimeIndex._cached_range(START, END, offset=BDay()) - DatetimeIndex._cached_range(START, periods=20, offset=BDay()) - DatetimeIndex._cached_range(end=START, periods=20, offset=BDay()) + DatetimeIndex._cached_range(START, END, freq=BDay()) + DatetimeIndex._cached_range(START, periods=20, freq=BDay()) + DatetimeIndex._cached_range(end=START, periods=20, freq=BDay()) - with tm.assert_raises_regex(TypeError, "offset"): + with tm.assert_raises_regex(TypeError, "freq"): DatetimeIndex._cached_range(START, END) with tm.assert_raises_regex(TypeError, "specify period"): - DatetimeIndex._cached_range(START, offset=BDay()) + DatetimeIndex._cached_range(START, freq=BDay()) with tm.assert_raises_regex(TypeError, "specify period"): - DatetimeIndex._cached_range(end=END, offset=BDay()) + DatetimeIndex._cached_range(end=END, freq=BDay()) with tm.assert_raises_regex(TypeError, "start or end"): - DatetimeIndex._cached_range(periods=20, offset=BDay()) + DatetimeIndex._cached_range(periods=20, freq=BDay()) def test_cached_range_bug(self): rng = date_range('2010-09-01 05:00:00', periods=50, @@ -393,7 +393,7 @@ def test_daterange_bug_456(self): # GH #456 rng1 = bdate_range('12/5/2011', '12/5/2011') rng2 = bdate_range('12/2/2011', '12/5/2011') - rng2.offset = BDay() + rng2.freq = BDay() result = rng1.union(rng2) assert isinstance(result, DatetimeIndex) @@ -605,27 +605,27 @@ def test_constructor(self): bdate_range('2011-1-1', '2012-1-1', 'C') def test_cached_range(self): - DatetimeIndex._cached_range(START, END, offset=CDay()) + DatetimeIndex._cached_range(START, END, freq=CDay()) DatetimeIndex._cached_range(START, periods=20, - offset=CDay()) + freq=CDay()) DatetimeIndex._cached_range(end=START, periods=20, - offset=CDay()) + freq=CDay()) # with pytest.raises(TypeError): - with tm.assert_raises_regex(TypeError, "offset"): + with tm.assert_raises_regex(TypeError, "freq"): DatetimeIndex._cached_range(START, END) # with pytest.raises(TypeError): with tm.assert_raises_regex(TypeError, "specify period"): - DatetimeIndex._cached_range(START, offset=CDay()) + DatetimeIndex._cached_range(START, freq=CDay()) # with pytest.raises(TypeError): with tm.assert_raises_regex(TypeError, "specify period"): - DatetimeIndex._cached_range(end=END, offset=CDay()) + DatetimeIndex._cached_range(end=END, freq=CDay()) # with pytest.raises(TypeError): with tm.assert_raises_regex(TypeError, "start or end"): - DatetimeIndex._cached_range(periods=20, offset=CDay()) + DatetimeIndex._cached_range(periods=20, freq=CDay()) def test_misc(self): end = datetime(2009, 5, 13) @@ -640,7 +640,7 @@ def test_daterange_bug_456(self): # GH #456 rng1 = bdate_range('12/5/2011', '12/5/2011', freq='C') rng2 = bdate_range('12/2/2011', '12/5/2011', freq='C') - rng2.offset = CDay() + rng2.freq = CDay() result = rng1.union(rng2) assert isinstance(result, DatetimeIndex) diff --git a/pandas/tests/indexes/datetimes/test_indexing.py b/pandas/tests/indexes/datetimes/test_indexing.py index af65a8618d30f..dd192db4b0eb3 100644 --- a/pandas/tests/indexes/datetimes/test_indexing.py +++ b/pandas/tests/indexes/datetimes/test_indexing.py @@ -53,10 +53,10 @@ def test_dti_business_getitem(self): exp = DatetimeIndex(rng.view(np.ndarray)[:5]) tm.assert_index_equal(smaller, exp) - assert smaller.offset == rng.offset + assert smaller.freq == rng.freq sliced = rng[::5] - assert sliced.offset == BDay() * 5 + assert sliced.freq == BDay() * 5 fancy_indexed = rng[[4, 3, 2, 1, 0]] assert len(fancy_indexed) == 5 @@ -77,10 +77,10 @@ def test_dti_custom_getitem(self): smaller = rng[:5] exp = DatetimeIndex(rng.view(np.ndarray)[:5]) tm.assert_index_equal(smaller, exp) - assert smaller.offset == rng.offset + assert smaller.freq == rng.freq sliced = rng[::5] - assert sliced.offset == CDay() * 5 + assert sliced.freq == CDay() * 5 fancy_indexed = rng[[4, 3, 2, 1, 0]] assert len(fancy_indexed) == 5 diff --git a/pandas/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index 8986828399a98..3c7d5d37e98f3 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -405,6 +405,18 @@ def test_equals(self): assert not idx.equals(list(idx3)) assert not idx.equals(pd.Series(idx3)) + def test_offset_deprecated(self): + # GH 20716 + idx = pd.DatetimeIndex(['20180101', '20180102']) + + # getter deprecated + with tm.assert_produces_warning(FutureWarning): + idx.offset + + # setter deprecated + with tm.assert_produces_warning(FutureWarning): + idx.offset = BDay() + class TestBusinessDatetimeIndex(object): @@ -420,7 +432,7 @@ def test_comparison(self): def test_pickle_unpickle(self): unpickled = tm.round_trip_pickle(self.rng) - assert unpickled.offset is not None + assert unpickled.freq is not None def test_copy(self): cp = self.rng.copy() @@ -430,15 +442,15 @@ def test_copy(self): def test_shift(self): shifted = self.rng.shift(5) assert shifted[0] == self.rng[5] - assert shifted.offset == self.rng.offset + assert shifted.freq == self.rng.freq shifted = self.rng.shift(-5) assert shifted[5] == self.rng[0] - assert shifted.offset == self.rng.offset + assert shifted.freq == self.rng.freq shifted = self.rng.shift(0) assert shifted[0] == self.rng[0] - assert shifted.offset == self.rng.offset + assert shifted.freq == self.rng.freq rng = date_range(START, END, freq=BMonthEnd()) shifted = rng.shift(1, freq=BDay()) @@ -485,15 +497,15 @@ def test_shift(self): shifted = self.rng.shift(5) assert shifted[0] == self.rng[5] - assert shifted.offset == self.rng.offset + assert shifted.freq == self.rng.freq shifted = self.rng.shift(-5) assert shifted[5] == self.rng[0] - assert shifted.offset == self.rng.offset + assert shifted.freq == self.rng.freq shifted = self.rng.shift(0) assert shifted[0] == self.rng[0] - assert shifted.offset == self.rng.offset + assert shifted.freq == self.rng.freq # PerformanceWarning with warnings.catch_warnings(record=True): @@ -503,7 +515,7 @@ def test_shift(self): def test_pickle_unpickle(self): unpickled = tm.round_trip_pickle(self.rng) - assert unpickled.offset is not None + assert unpickled.freq is not None def test_equals(self): assert not self.rng.equals(list(self.rng)) diff --git a/pandas/tests/indexes/datetimes/test_setops.py b/pandas/tests/indexes/datetimes/test_setops.py index 84632e59e2bfb..cb9364edc0cc3 100644 --- a/pandas/tests/indexes/datetimes/test_setops.py +++ b/pandas/tests/indexes/datetimes/test_setops.py @@ -357,7 +357,7 @@ def test_intersection(self): expected = rng[10:25] tm.assert_index_equal(the_int, expected) assert isinstance(the_int, DatetimeIndex) - assert the_int.offset == rng.offset + assert the_int.freq == rng.freq the_int = rng1.intersection(rng2.view(DatetimeIndex)) tm.assert_index_equal(the_int, expected)