Skip to content

Commit 1e1e9b3

Browse files
sinhrksjreback
authored andcommitted
DEPR: Remove legacy offsets
Follow-up of pandas-dev#10951. Remove legacy offsets deprecated in 0.17.0. Author: sinhrks <[email protected]> Closes pandas-dev#13590 from sinhrks/depr_legacy_offset and squashes the following commits: 2593b1f [sinhrks] DEPR: Remove legacy offsets
1 parent 361a2b4 commit 1e1e9b3

File tree

8 files changed

+118
-311
lines changed

8 files changed

+118
-311
lines changed

doc/source/timeseries.rst

+3-43
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,7 @@ calculate significantly slower and will raise a ``PerformanceWarning``
752752
rng + BQuarterEnd()
753753
754754
755-
.. _timeseries.alias:
755+
.. _timeseries.custombusinessdays:
756756

757757
Custom Business Days (Experimental)
758758
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -953,6 +953,8 @@ You can use keyword arguments suported by either ``BusinessHour`` and ``CustomBu
953953
# Monday is skipped because it's a holiday, business hour starts from 10:00
954954
dt + bhour_mon * 2
955955
956+
.. _timeseries.alias:
957+
956958
Offset Aliases
957959
~~~~~~~~~~~~~~
958960

@@ -1103,48 +1105,6 @@ it is rolled forward to the next anchor point.
11031105
pd.Timestamp('2014-01-01') + MonthBegin(n=0)
11041106
pd.Timestamp('2014-01-31') + MonthEnd(n=0)
11051107
1106-
.. _timeseries.legacyaliases:
1107-
1108-
Legacy Aliases
1109-
~~~~~~~~~~~~~~
1110-
Note that prior to v0.8.0, time rules had a slightly different look. These are
1111-
deprecated in v0.17.0, and removed in future version.
1112-
1113-
.. csv-table::
1114-
:header: "Legacy Time Rule", "Offset Alias"
1115-
:widths: 15, 65
1116-
1117-
"WEEKDAY", "B"
1118-
"EOM", "BM"
1119-
"W\@MON", "W\-MON"
1120-
"W\@TUE", "W\-TUE"
1121-
"W\@WED", "W\-WED"
1122-
"W\@THU", "W\-THU"
1123-
"W\@FRI", "W\-FRI"
1124-
"W\@SAT", "W\-SAT"
1125-
"W\@SUN", "W\-SUN"
1126-
"Q\@JAN", "BQ\-JAN"
1127-
"Q\@FEB", "BQ\-FEB"
1128-
"Q\@MAR", "BQ\-MAR"
1129-
"A\@JAN", "BA\-JAN"
1130-
"A\@FEB", "BA\-FEB"
1131-
"A\@MAR", "BA\-MAR"
1132-
"A\@APR", "BA\-APR"
1133-
"A\@MAY", "BA\-MAY"
1134-
"A\@JUN", "BA\-JUN"
1135-
"A\@JUL", "BA\-JUL"
1136-
"A\@AUG", "BA\-AUG"
1137-
"A\@SEP", "BA\-SEP"
1138-
"A\@OCT", "BA\-OCT"
1139-
"A\@NOV", "BA\-NOV"
1140-
"A\@DEC", "BA\-DEC"
1141-
1142-
1143-
As you can see, legacy quarterly and annual frequencies are business quarters
1144-
and business year ends. Please also note the legacy time rule for milliseconds
1145-
``ms`` versus the new offset alias for month start ``MS``. This means that
1146-
offset alias parsing is case sensitive.
1147-
11481108
.. _timeseries.holiday:
11491109

11501110
Holidays / Holiday Calendars

doc/source/whatsnew/v0.19.0.txt

+11
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,17 @@ Removal of prior version deprecations/changes
510510
- ``DataFrame.to_dict()`` has dropped the ``outtype`` parameter in favor of ``orient`` (:issue:`13627`, :issue:`8486`)
511511
- ``pd.Categorical`` has dropped the ``levels`` attribute in favour of ``categories`` (:issue:`8376`)
512512

513+
- Removal of the legacy time rules (offset aliases), deprecated since 0.17.0 (this has been alias since 0.8.0) (:issue:`13590`)
514+
515+
Previous Behavior:
516+
517+
.. code-block:: ipython
518+
519+
In [2]: pd.date_range('2016-07-01', freq='W@MON', periods=3)
520+
pandas/tseries/frequencies.py:465: FutureWarning: Freq "W@MON" is deprecated, use "W-MON" as alternative.
521+
Out[2]: DatetimeIndex(['2016-07-04', '2016-07-11', '2016-07-18'], dtype='datetime64[ns]', freq='W-MON')
522+
523+
Now legacy time rules raises ``ValueError``. For the list of currently supported offsets, see :ref:`here <timeseries.alias>`
513524

514525
.. _whatsnew_0190.performance:
515526

pandas/tseries/frequencies.py

+11-97
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from datetime import timedelta
2-
from pandas.compat import range, long, zip
2+
from pandas.compat import long, zip
33
from pandas import compat
44
import re
55
import warnings
@@ -356,34 +356,6 @@ def get_period_alias(offset_str):
356356
""" alias to closest period strings BQ->Q etc"""
357357
return _offset_to_period_map.get(offset_str, None)
358358

359-
_rule_aliases = {
360-
# Legacy rules that will continue to map to their original values
361-
# essentially for the rest of time
362-
'WEEKDAY': 'B',
363-
'EOM': 'BM',
364-
'W@MON': 'W-MON',
365-
'W@TUE': 'W-TUE',
366-
'W@WED': 'W-WED',
367-
'W@THU': 'W-THU',
368-
'W@FRI': 'W-FRI',
369-
'W@SAT': 'W-SAT',
370-
'W@SUN': 'W-SUN',
371-
'Q@JAN': 'BQ-JAN',
372-
'Q@FEB': 'BQ-FEB',
373-
'Q@MAR': 'BQ-MAR',
374-
'A@JAN': 'BA-JAN',
375-
'A@FEB': 'BA-FEB',
376-
'A@MAR': 'BA-MAR',
377-
'A@APR': 'BA-APR',
378-
'A@MAY': 'BA-MAY',
379-
'A@JUN': 'BA-JUN',
380-
'A@JUL': 'BA-JUL',
381-
'A@AUG': 'BA-AUG',
382-
'A@SEP': 'BA-SEP',
383-
'A@OCT': 'BA-OCT',
384-
'A@NOV': 'BA-NOV',
385-
'A@DEC': 'BA-DEC',
386-
}
387359

388360
_lite_rule_alias = {
389361
'W': 'W-SUN',
@@ -401,17 +373,6 @@ def get_period_alias(offset_str):
401373
'ns': 'N'
402374
}
403375

404-
# TODO: Can this be killed?
405-
for _i, _weekday in enumerate(['MON', 'TUE', 'WED', 'THU', 'FRI']):
406-
for _iweek in range(4):
407-
_name = 'WOM-%d%s' % (_iweek + 1, _weekday)
408-
_rule_aliases[_name.replace('-', '@')] = _name
409-
410-
# Note that _rule_aliases is not 1:1 (d[BA]==d[A@DEC]), and so traversal
411-
# order matters when constructing an inverse. we pick one. #2331
412-
# Used in get_legacy_offset_name
413-
_legacy_reverse_map = dict((v, k) for k, v in
414-
reversed(sorted(compat.iteritems(_rule_aliases))))
415376

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

424385

386+
_INVALID_FREQ_ERROR = "Invalid frequency: {0}"
387+
388+
425389
def to_offset(freqstr):
426390
"""
427391
Return DateOffset object from string representation or
@@ -460,7 +424,7 @@ def to_offset(freqstr):
460424
else:
461425
delta = delta + offset
462426
except Exception:
463-
raise ValueError("Could not evaluate %s" % freqstr)
427+
raise ValueError(_INVALID_FREQ_ERROR.format(freqstr))
464428

465429
else:
466430
delta = None
@@ -479,10 +443,10 @@ def to_offset(freqstr):
479443
else:
480444
delta = delta + offset
481445
except Exception:
482-
raise ValueError("Could not evaluate %s" % freqstr)
446+
raise ValueError(_INVALID_FREQ_ERROR.format(freqstr))
483447

484448
if delta is None:
485-
raise ValueError('Unable to understand %s as a frequency' % freqstr)
449+
raise ValueError(_INVALID_FREQ_ERROR.format(freqstr))
486450

487451
return delta
488452

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

528492

529-
_LEGACY_FREQ_WARNING = 'Freq "{0}" is deprecated, use "{1}" as alternative.'
530-
531-
532493
def get_offset(name):
533494
"""
534495
Return DateOffset object associated with rule name
@@ -539,27 +500,9 @@ def get_offset(name):
539500
"""
540501
if name not in _dont_uppercase:
541502
name = name.upper()
542-
543-
if name in _rule_aliases:
544-
new = _rule_aliases[name]
545-
warnings.warn(_LEGACY_FREQ_WARNING.format(name, new),
546-
FutureWarning, stacklevel=2)
547-
name = new
548-
elif name.lower() in _rule_aliases:
549-
new = _rule_aliases[name.lower()]
550-
warnings.warn(_LEGACY_FREQ_WARNING.format(name, new),
551-
FutureWarning, stacklevel=2)
552-
name = new
553-
554503
name = _lite_rule_alias.get(name, name)
555504
name = _lite_rule_alias.get(name.lower(), name)
556-
557505
else:
558-
if name in _rule_aliases:
559-
new = _rule_aliases[name]
560-
warnings.warn(_LEGACY_FREQ_WARNING.format(name, new),
561-
FutureWarning, stacklevel=2)
562-
name = new
563506
name = _lite_rule_alias.get(name, name)
564507

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

597540

598-
def get_legacy_offset_name(offset):
599-
"""
600-
Return the pre pandas 0.8.0 name for the date offset
601-
"""
602-
603-
# This only used in test_timeseries_legacy.py
604-
605-
name = offset.name
606-
return _legacy_reverse_map.get(name, name)
607-
608-
609541
def get_standard_freq(freq):
610542
"""
611543
Return the standardized frequency string
@@ -796,36 +728,18 @@ def _period_alias_dictionary():
796728

797729

798730
def _period_str_to_code(freqstr):
799-
# hack
800-
if freqstr in _rule_aliases:
801-
new = _rule_aliases[freqstr]
802-
warnings.warn(_LEGACY_FREQ_WARNING.format(freqstr, new),
803-
FutureWarning, stacklevel=3)
804-
freqstr = new
805731
freqstr = _lite_rule_alias.get(freqstr, freqstr)
806732

807733
if freqstr not in _dont_uppercase:
808734
lower = freqstr.lower()
809-
if lower in _rule_aliases:
810-
new = _rule_aliases[lower]
811-
warnings.warn(_LEGACY_FREQ_WARNING.format(lower, new),
812-
FutureWarning, stacklevel=3)
813-
freqstr = new
814735
freqstr = _lite_rule_alias.get(lower, freqstr)
815736

737+
if freqstr not in _dont_uppercase:
738+
freqstr = freqstr.upper()
816739
try:
817-
if freqstr not in _dont_uppercase:
818-
freqstr = freqstr.upper()
819740
return _period_code_map[freqstr]
820741
except KeyError:
821-
try:
822-
alias = _period_alias_dict[freqstr]
823-
warnings.warn(_LEGACY_FREQ_WARNING.format(freqstr, alias),
824-
FutureWarning, stacklevel=3)
825-
except KeyError:
826-
raise ValueError("Unknown freqstr: %s" % freqstr)
827-
828-
return _period_code_map[alias]
742+
raise ValueError(_INVALID_FREQ_ERROR.format(freqstr))
829743

830744

831745
def infer_freq(index, warn=True):

pandas/tseries/tests/test_base.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,11 @@ def test_round(self):
160160
tm.assert_index_equal(rng.round(freq='H'), expected_rng)
161161
self.assertEqual(elt.round(freq='H'), expected_elt)
162162

163-
msg = "Could not evaluate foo"
164-
tm.assertRaisesRegexp(ValueError, msg, rng.round, freq='foo')
165-
tm.assertRaisesRegexp(ValueError, msg, elt.round, freq='foo')
163+
msg = pd.tseries.frequencies._INVALID_FREQ_ERROR
164+
with tm.assertRaisesRegexp(ValueError, msg):
165+
rng.round(freq='foo')
166+
with tm.assertRaisesRegexp(ValueError, msg):
167+
elt.round(freq='foo')
166168

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

850-
msg = "Could not evaluate foo"
851-
tm.assertRaisesRegexp(ValueError, msg, td.round, freq='foo')
852-
tm.assertRaisesRegexp(ValueError, msg, elt.round, freq='foo')
852+
msg = pd.tseries.frequencies._INVALID_FREQ_ERROR
853+
with self.assertRaisesRegexp(ValueError, msg):
854+
td.round(freq='foo')
855+
with tm.assertRaisesRegexp(ValueError, msg):
856+
elt.round(freq='foo')
853857

854858
msg = "<MonthEnd> is a non-fixed frequency"
855859
tm.assertRaisesRegexp(ValueError, msg, td.round, freq='M')

pandas/tseries/tests/test_frequencies.py

+23-16
Original file line numberDiff line numberDiff line change
@@ -245,10 +245,10 @@ def _assert_depr(freq, expected, aliases):
245245
assert isinstance(aliases, list)
246246
assert (frequencies._period_str_to_code(freq) == expected)
247247

248+
msg = frequencies._INVALID_FREQ_ERROR
248249
for alias in aliases:
249-
with tm.assert_produces_warning(FutureWarning,
250-
check_stacklevel=False):
251-
assert (frequencies._period_str_to_code(alias) == expected)
250+
with tm.assertRaisesRegexp(ValueError, msg):
251+
frequencies._period_str_to_code(alias)
252252

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

@@ -699,8 +699,9 @@ def test_series(self):
699699
s = Series(period_range('2013', periods=10, freq=freq))
700700
self.assertRaises(TypeError, lambda: frequencies.infer_freq(s))
701701
for freq in ['Y']:
702-
with tm.assert_produces_warning(FutureWarning,
703-
check_stacklevel=False):
702+
703+
msg = frequencies._INVALID_FREQ_ERROR
704+
with tm.assertRaisesRegexp(ValueError, msg):
704705
s = Series(period_range('2013', periods=10, freq=freq))
705706
self.assertRaises(TypeError, lambda: frequencies.infer_freq(s))
706707

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

717718
def test_legacy_offset_warnings(self):
718-
for k, v in compat.iteritems(frequencies._rule_aliases):
719-
with tm.assert_produces_warning(FutureWarning):
720-
result = frequencies.get_offset(k)
721-
exp = frequencies.get_offset(v)
722-
self.assertEqual(result, exp)
723-
724-
with tm.assert_produces_warning(FutureWarning,
725-
check_stacklevel=False):
726-
idx = date_range('2011-01-01', periods=5, freq=k)
727-
exp = date_range('2011-01-01', periods=5, freq=v)
728-
self.assert_index_equal(idx, exp)
719+
freqs = ['WEEKDAY', 'EOM', 'W@MON', 'W@TUE', 'W@WED', 'W@THU',
720+
'W@FRI', 'W@SAT', 'W@SUN', 'Q@JAN', 'Q@FEB', 'Q@MAR',
721+
'A@JAN', 'A@FEB', 'A@MAR', 'A@APR', 'A@MAY', 'A@JUN',
722+
'A@JUL', 'A@AUG', 'A@SEP', 'A@OCT', 'A@NOV', 'A@DEC',
723+
'WOM@1MON', 'WOM@2MON', 'WOM@3MON', 'WOM@4MON',
724+
'WOM@1TUE', 'WOM@2TUE', 'WOM@3TUE', 'WOM@4TUE',
725+
'WOM@1WED', 'WOM@2WED', 'WOM@3WED', 'WOM@4WED',
726+
'WOM@1THU', 'WOM@2THU', 'WOM@3THU', 'WOM@4THU'
727+
'WOM@1FRI', 'WOM@2FRI', 'WOM@3FRI', 'WOM@4FRI']
728+
729+
msg = frequencies._INVALID_FREQ_ERROR
730+
for freq in freqs:
731+
with tm.assertRaisesRegexp(ValueError, msg):
732+
frequencies.get_offset(freq)
733+
734+
with tm.assertRaisesRegexp(ValueError, msg):
735+
date_range('2011-01-01', periods=5, freq=freq)
729736

730737

731738
MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT',

0 commit comments

Comments
 (0)