Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DEPR: Remove legacy offsets #13590

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 3 additions & 43 deletions doc/source/timeseries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ calculate significantly slower and will raise a ``PerformanceWarning``
rng + BQuarterEnd()


.. _timeseries.alias:
.. _timeseries.custombusinessdays:

Custom Business Days (Experimental)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -953,6 +953,8 @@ You can use keyword arguments suported by either ``BusinessHour`` and ``CustomBu
# Monday is skipped because it's a holiday, business hour starts from 10:00
dt + bhour_mon * 2

.. _timeseries.alias:

Offset Aliases
~~~~~~~~~~~~~~

Expand Down Expand Up @@ -1103,48 +1105,6 @@ it is rolled forward to the next anchor point.
pd.Timestamp('2014-01-01') + MonthBegin(n=0)
pd.Timestamp('2014-01-31') + MonthEnd(n=0)

.. _timeseries.legacyaliases:

Legacy Aliases
~~~~~~~~~~~~~~
Note that prior to v0.8.0, time rules had a slightly different look. These are
deprecated in v0.17.0, and removed in future version.

.. csv-table::
:header: "Legacy Time Rule", "Offset Alias"
:widths: 15, 65

"WEEKDAY", "B"
"EOM", "BM"
"W\@MON", "W\-MON"
"W\@TUE", "W\-TUE"
"W\@WED", "W\-WED"
"W\@THU", "W\-THU"
"W\@FRI", "W\-FRI"
"W\@SAT", "W\-SAT"
"W\@SUN", "W\-SUN"
"Q\@JAN", "BQ\-JAN"
"Q\@FEB", "BQ\-FEB"
"Q\@MAR", "BQ\-MAR"
"A\@JAN", "BA\-JAN"
"A\@FEB", "BA\-FEB"
"A\@MAR", "BA\-MAR"
"A\@APR", "BA\-APR"
"A\@MAY", "BA\-MAY"
"A\@JUN", "BA\-JUN"
"A\@JUL", "BA\-JUL"
"A\@AUG", "BA\-AUG"
"A\@SEP", "BA\-SEP"
"A\@OCT", "BA\-OCT"
"A\@NOV", "BA\-NOV"
"A\@DEC", "BA\-DEC"


As you can see, legacy quarterly and annual frequencies are business quarters
and business year ends. Please also note the legacy time rule for milliseconds
``ms`` versus the new offset alias for month start ``MS``. This means that
offset alias parsing is case sensitive.

.. _timeseries.holiday:

Holidays / Holiday Calendars
Expand Down
11 changes: 11 additions & 0 deletions doc/source/whatsnew/v0.19.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,17 @@ Removal of prior version deprecations/changes
- ``DataFrame.to_csv()`` has dropped the ``engine`` parameter, as was deprecated in 0.17.1 (:issue:`11274`, :issue:`13419`)
- ``DataFrame.to_dict()`` has dropped the ``outtype`` parameter in favor of ``orient`` (:issue:`13627`, :issue:`8486`)

- Removal of the legacy time rules (offset aliases), deprecated since 0.17.0 (this has been alias since 0.8.0) (:issue:`13590`)

Previous Behavior:

.. code-block:: ipython

In [2]: pd.date_range('2016-07-01', freq='W@MON', periods=3)
pandas/tseries/frequencies.py:465: FutureWarning: Freq "W@MON" is deprecated, use "W-MON" as alternative.
Out[2]: DatetimeIndex(['2016-07-04', '2016-07-11', '2016-07-18'], dtype='datetime64[ns]', freq='W-MON')

Now legacy time rules raises ``ValueError``. For the list of currently supported offsets, see :ref:`here <timeseries.alias>`

.. _whatsnew_0190.performance:

Expand Down
108 changes: 11 additions & 97 deletions pandas/tseries/frequencies.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datetime import timedelta
from pandas.compat import range, long, zip
from pandas.compat import long, zip
from pandas import compat
import re
import warnings
Expand Down Expand Up @@ -356,34 +356,6 @@ def get_period_alias(offset_str):
""" alias to closest period strings BQ->Q etc"""
return _offset_to_period_map.get(offset_str, None)

_rule_aliases = {
# Legacy rules that will continue to map to their original values
# essentially for the rest of time
'WEEKDAY': 'B',
'EOM': 'BM',
'W@MON': 'W-MON',
'W@TUE': 'W-TUE',
'W@WED': 'W-WED',
'W@THU': 'W-THU',
'W@FRI': 'W-FRI',
'W@SAT': 'W-SAT',
'W@SUN': 'W-SUN',
'Q@JAN': 'BQ-JAN',
'Q@FEB': 'BQ-FEB',
'Q@MAR': 'BQ-MAR',
'A@JAN': 'BA-JAN',
'A@FEB': 'BA-FEB',
'A@MAR': 'BA-MAR',
'A@APR': 'BA-APR',
'A@MAY': 'BA-MAY',
'A@JUN': 'BA-JUN',
'A@JUL': 'BA-JUL',
'A@AUG': 'BA-AUG',
'A@SEP': 'BA-SEP',
'A@OCT': 'BA-OCT',
'A@NOV': 'BA-NOV',
'A@DEC': 'BA-DEC',
}

_lite_rule_alias = {
'W': 'W-SUN',
Expand All @@ -401,17 +373,6 @@ def get_period_alias(offset_str):
'ns': 'N'
}

# TODO: Can this be killed?
for _i, _weekday in enumerate(['MON', 'TUE', 'WED', 'THU', 'FRI']):
for _iweek in range(4):
_name = 'WOM-%d%s' % (_iweek + 1, _weekday)
_rule_aliases[_name.replace('-', '@')] = _name

# Note that _rule_aliases is not 1:1 (d[BA]==d[A@DEC]), and so traversal
# order matters when constructing an inverse. we pick one. #2331
# Used in get_legacy_offset_name
_legacy_reverse_map = dict((v, k) for k, v in
reversed(sorted(compat.iteritems(_rule_aliases))))

_name_to_offset_map = {'days': Day(1),
'hours': Hour(1),
Expand All @@ -422,6 +383,9 @@ def get_period_alias(offset_str):
'nanoseconds': Nano(1)}


_INVALID_FREQ_ERROR = "Invalid frequency: {0}"


def to_offset(freqstr):
"""
Return DateOffset object from string representation or
Expand Down Expand Up @@ -460,7 +424,7 @@ def to_offset(freqstr):
else:
delta = delta + offset
except Exception:
raise ValueError("Could not evaluate %s" % freqstr)
raise ValueError(_INVALID_FREQ_ERROR.format(freqstr))

else:
delta = None
Expand All @@ -479,10 +443,10 @@ def to_offset(freqstr):
else:
delta = delta + offset
except Exception:
raise ValueError("Could not evaluate %s" % freqstr)
raise ValueError(_INVALID_FREQ_ERROR.format(freqstr))

if delta is None:
raise ValueError('Unable to understand %s as a frequency' % freqstr)
raise ValueError(_INVALID_FREQ_ERROR.format(freqstr))

return delta

Expand Down Expand Up @@ -526,9 +490,6 @@ def get_base_alias(freqstr):
_dont_uppercase = set(('MS', 'ms'))


_LEGACY_FREQ_WARNING = 'Freq "{0}" is deprecated, use "{1}" as alternative.'


def get_offset(name):
"""
Return DateOffset object associated with rule name
Expand All @@ -539,27 +500,9 @@ def get_offset(name):
"""
if name not in _dont_uppercase:
name = name.upper()

if name in _rule_aliases:
new = _rule_aliases[name]
warnings.warn(_LEGACY_FREQ_WARNING.format(name, new),
FutureWarning, stacklevel=2)
name = new
elif name.lower() in _rule_aliases:
new = _rule_aliases[name.lower()]
warnings.warn(_LEGACY_FREQ_WARNING.format(name, new),
FutureWarning, stacklevel=2)
name = new

name = _lite_rule_alias.get(name, name)
name = _lite_rule_alias.get(name.lower(), name)

else:
if name in _rule_aliases:
new = _rule_aliases[name]
warnings.warn(_LEGACY_FREQ_WARNING.format(name, new),
FutureWarning, stacklevel=2)
name = new
name = _lite_rule_alias.get(name, name)

if name not in _offset_map:
Expand All @@ -571,7 +514,7 @@ def get_offset(name):
offset = klass._from_name(*split[1:])
except (ValueError, TypeError, KeyError):
# bad prefix or suffix
raise ValueError('Bad rule name requested: %s.' % name)
raise ValueError(_INVALID_FREQ_ERROR.format(name))
# cache
_offset_map[name] = offset
# do not return cache because it's mutable
Expand All @@ -595,17 +538,6 @@ def get_offset_name(offset):
return offset.freqstr


def get_legacy_offset_name(offset):
"""
Return the pre pandas 0.8.0 name for the date offset
"""

# This only used in test_timeseries_legacy.py

name = offset.name
return _legacy_reverse_map.get(name, name)


def get_standard_freq(freq):
"""
Return the standardized frequency string
Expand Down Expand Up @@ -796,36 +728,18 @@ def _period_alias_dictionary():


def _period_str_to_code(freqstr):
# hack
if freqstr in _rule_aliases:
new = _rule_aliases[freqstr]
warnings.warn(_LEGACY_FREQ_WARNING.format(freqstr, new),
FutureWarning, stacklevel=3)
freqstr = new
freqstr = _lite_rule_alias.get(freqstr, freqstr)

if freqstr not in _dont_uppercase:
lower = freqstr.lower()
if lower in _rule_aliases:
new = _rule_aliases[lower]
warnings.warn(_LEGACY_FREQ_WARNING.format(lower, new),
FutureWarning, stacklevel=3)
freqstr = new
freqstr = _lite_rule_alias.get(lower, freqstr)

if freqstr not in _dont_uppercase:
freqstr = freqstr.upper()
try:
if freqstr not in _dont_uppercase:
freqstr = freqstr.upper()
return _period_code_map[freqstr]
except KeyError:
try:
alias = _period_alias_dict[freqstr]
warnings.warn(_LEGACY_FREQ_WARNING.format(freqstr, alias),
FutureWarning, stacklevel=3)
except KeyError:
raise ValueError("Unknown freqstr: %s" % freqstr)

return _period_code_map[alias]
raise ValueError(_INVALID_FREQ_ERROR.format(freqstr))


def infer_freq(index, warn=True):
Expand Down
16 changes: 10 additions & 6 deletions pandas/tseries/tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,11 @@ def test_round(self):
tm.assert_index_equal(rng.round(freq='H'), expected_rng)
self.assertEqual(elt.round(freq='H'), expected_elt)

msg = "Could not evaluate foo"
tm.assertRaisesRegexp(ValueError, msg, rng.round, freq='foo')
tm.assertRaisesRegexp(ValueError, msg, elt.round, freq='foo')
msg = pd.tseries.frequencies._INVALID_FREQ_ERROR
with tm.assertRaisesRegexp(ValueError, msg):
rng.round(freq='foo')
with tm.assertRaisesRegexp(ValueError, msg):
elt.round(freq='foo')

msg = "<MonthEnd> is a non-fixed frequency"
tm.assertRaisesRegexp(ValueError, msg, rng.round, freq='M')
Expand Down Expand Up @@ -847,9 +849,11 @@ def test_round(self):
tm.assert_index_equal(td.round(freq='H'), expected_rng)
self.assertEqual(elt.round(freq='H'), expected_elt)

msg = "Could not evaluate foo"
tm.assertRaisesRegexp(ValueError, msg, td.round, freq='foo')
tm.assertRaisesRegexp(ValueError, msg, elt.round, freq='foo')
msg = pd.tseries.frequencies._INVALID_FREQ_ERROR
with self.assertRaisesRegexp(ValueError, msg):
td.round(freq='foo')
with tm.assertRaisesRegexp(ValueError, msg):
elt.round(freq='foo')

msg = "<MonthEnd> is a non-fixed frequency"
tm.assertRaisesRegexp(ValueError, msg, td.round, freq='M')
Expand Down
39 changes: 23 additions & 16 deletions pandas/tseries/tests/test_frequencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,10 @@ def _assert_depr(freq, expected, aliases):
assert isinstance(aliases, list)
assert (frequencies._period_str_to_code(freq) == expected)

msg = frequencies._INVALID_FREQ_ERROR
for alias in aliases:
with tm.assert_produces_warning(FutureWarning,
check_stacklevel=False):
assert (frequencies._period_str_to_code(alias) == expected)
with tm.assertRaisesRegexp(ValueError, msg):
frequencies._period_str_to_code(alias)

_assert_depr("M", 3000, ["MTH", "MONTH", "MONTHLY"])

Expand Down Expand Up @@ -699,8 +699,9 @@ def test_series(self):
s = Series(period_range('2013', periods=10, freq=freq))
self.assertRaises(TypeError, lambda: frequencies.infer_freq(s))
for freq in ['Y']:
with tm.assert_produces_warning(FutureWarning,
check_stacklevel=False):

msg = frequencies._INVALID_FREQ_ERROR
with tm.assertRaisesRegexp(ValueError, msg):
s = Series(period_range('2013', periods=10, freq=freq))
self.assertRaises(TypeError, lambda: frequencies.infer_freq(s))

Expand All @@ -715,17 +716,23 @@ def test_series(self):
self.assertEqual(inferred, 'D')

def test_legacy_offset_warnings(self):
for k, v in compat.iteritems(frequencies._rule_aliases):
with tm.assert_produces_warning(FutureWarning):
result = frequencies.get_offset(k)
exp = frequencies.get_offset(v)
self.assertEqual(result, exp)

with tm.assert_produces_warning(FutureWarning,
check_stacklevel=False):
idx = date_range('2011-01-01', periods=5, freq=k)
exp = date_range('2011-01-01', periods=5, freq=v)
self.assert_index_equal(idx, exp)
freqs = ['WEEKDAY', 'EOM', 'W@MON', 'W@TUE', 'W@WED', 'W@THU',
'W@FRI', 'W@SAT', 'W@SUN', 'Q@JAN', 'Q@FEB', 'Q@MAR',
'A@JAN', 'A@FEB', 'A@MAR', 'A@APR', 'A@MAY', 'A@JUN',
'A@JUL', 'A@AUG', 'A@SEP', 'A@OCT', 'A@NOV', 'A@DEC',
'WOM@1MON', 'WOM@2MON', 'WOM@3MON', 'WOM@4MON',
'WOM@1TUE', 'WOM@2TUE', 'WOM@3TUE', 'WOM@4TUE',
'WOM@1WED', 'WOM@2WED', 'WOM@3WED', 'WOM@4WED',
'WOM@1THU', 'WOM@2THU', 'WOM@3THU', 'WOM@4THU'
'WOM@1FRI', 'WOM@2FRI', 'WOM@3FRI', 'WOM@4FRI']

msg = frequencies._INVALID_FREQ_ERROR
for freq in freqs:
with tm.assertRaisesRegexp(ValueError, msg):
frequencies.get_offset(freq)

with tm.assertRaisesRegexp(ValueError, msg):
date_range('2011-01-01', periods=5, freq=freq)


MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT',
Expand Down
Loading