Skip to content

Commit

Permalink
DEPR: Deprecate DatetimeIndex.offset in favor of DatetimeIndex.freq (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
jschendel authored and jorisvandenbossche committed Apr 19, 2018
1 parent ede11af commit 78fee04
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 85 deletions.
1 change: 1 addition & 0 deletions doc/source/whatsnew/v0.23.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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() <pandas.core.window.Rolling.apply>`, :func:`DataFrame.rolling().apply() <pandas.core.window.Rolling.apply>`,
:func:`Series.expanding().apply() <pandas.core.window.Expanding.apply>`, and :func:`DataFrame.expanding().apply() <pandas.core.window.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:

Expand Down
122 changes: 69 additions & 53 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 '
Expand Down Expand Up @@ -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:
Expand All @@ -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:

Expand All @@ -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,
Expand All @@ -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 """
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -747,54 +747,54 @@ 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
elif end is 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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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

Expand All @@ -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')
Expand All @@ -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):
"""
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))


Expand Down
6 changes: 3 additions & 3 deletions pandas/tests/indexes/datetimes/test_construction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading

0 comments on commit 78fee04

Please sign in to comment.