Skip to content

Commit

Permalink
ENH: Use 'Y' as an alias for end of year (#16978)
Browse files Browse the repository at this point in the history
Closes gh-9313
Redo of gh-16958
  • Loading branch information
gfyoung authored Jul 19, 2017
1 parent e5de21a commit aead041
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 18 deletions.
4 changes: 2 additions & 2 deletions doc/source/timeseries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1092,9 +1092,9 @@ frequencies. We will refer to these aliases as *offset aliases*
"BQ", "business quarter endfrequency"
"QS", "quarter start frequency"
"BQS", "business quarter start frequency"
"A", "year end frequency"
"A, Y", "year end frequency"
"BA", "business year end frequency"
"AS", "year start frequency"
"AS, YS", "year start frequency"
"BAS", "business year start frequency"
"BH", "business hour frequency"
"H", "hourly frequency"
Expand Down
2 changes: 2 additions & 0 deletions doc/source/whatsnew/v0.21.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ Other Enhancements
- :func:`DataFrame.clip()` and :func:`Series.clip()` have gained an ``inplace`` argument. (:issue:`15388`)
- :func:`crosstab` has gained a ``margins_name`` parameter to define the name of the row / column that will contain the totals when ``margins=True``. (:issue:`15972`)
- :func:`DataFrame.select_dtypes` now accepts scalar values for include/exclude as well as list-like. (:issue:`16855`)
- :func:`date_range` now accepts 'YS' in addition to 'AS' as an alias for start of year (:issue:`9313`)
- :func:`date_range` now accepts 'Y' in addition to 'A' as an alias for end of year (:issue:`9313`)

.. _whatsnew_0210.api_breaking:

Expand Down
25 changes: 25 additions & 0 deletions pandas/tests/indexes/datetimes/test_date_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,31 @@ def test_date_range_gen_error(self):
rng = date_range('1/1/2000 00:00', '1/1/2000 00:18', freq='5min')
assert len(rng) == 4

@pytest.mark.parametrize("freq", ["AS", "YS"])
def test_begin_year_alias(self, freq):
# see gh-9313
rng = date_range("1/1/2013", "7/1/2017", freq=freq)
exp = pd.DatetimeIndex(["2013-01-01", "2014-01-01",
"2015-01-01", "2016-01-01",
"2017-01-01"], freq=freq)
tm.assert_index_equal(rng, exp)

@pytest.mark.parametrize("freq", ["A", "Y"])
def test_end_year_alias(self, freq):
# see gh-9313
rng = date_range("1/1/2013", "7/1/2017", freq=freq)
exp = pd.DatetimeIndex(["2013-12-31", "2014-12-31",
"2015-12-31", "2016-12-31"], freq=freq)
tm.assert_index_equal(rng, exp)

@pytest.mark.parametrize("freq", ["BA", "BY"])
def test_business_end_year_alias(self, freq):
# see gh-9313
rng = date_range("1/1/2013", "7/1/2017", freq=freq)
exp = pd.DatetimeIndex(["2013-12-31", "2014-12-31",
"2015-12-31", "2016-12-30"], freq=freq)
tm.assert_index_equal(rng, exp)

def test_date_range_negative_freq(self):
# GH 11018
rng = date_range('2011-12-31', freq='-2A', periods=3)
Expand Down
41 changes: 28 additions & 13 deletions pandas/tests/tseries/test_frequencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,10 @@ def test_anchored_shortcuts(self):

# ensure invalid cases fail as expected
invalid_anchors = ['SM-0', 'SM-28', 'SM-29',
'SM-FOO', 'BSM', 'SM--1'
'SM-FOO', 'BSM', 'SM--1',
'SMS-1', 'SMS-28', 'SMS-30',
'SMS-BAR', 'BSMS', 'SMS--2']
'SMS-BAR', 'SMS-BYR' 'BSMS',
'SMS--2']
for invalid_anchor in invalid_anchors:
with tm.assert_raises_regex(ValueError,
'Invalid frequency: '):
Expand Down Expand Up @@ -292,11 +293,15 @@ def test_get_rule_month():

result = frequencies._get_rule_month('A-DEC')
assert (result == 'DEC')
result = frequencies._get_rule_month('Y-DEC')
assert (result == 'DEC')
result = frequencies._get_rule_month(offsets.YearEnd())
assert (result == 'DEC')

result = frequencies._get_rule_month('A-MAY')
assert (result == 'MAY')
result = frequencies._get_rule_month('Y-MAY')
assert (result == 'MAY')
result = frequencies._get_rule_month(offsets.YearEnd(month=5))
assert (result == 'MAY')

Expand All @@ -305,6 +310,10 @@ def test_period_str_to_code():
assert (frequencies._period_str_to_code('A') == 1000)
assert (frequencies._period_str_to_code('A-DEC') == 1000)
assert (frequencies._period_str_to_code('A-JAN') == 1001)
assert (frequencies._period_str_to_code('Y') == 1000)
assert (frequencies._period_str_to_code('Y-DEC') == 1000)
assert (frequencies._period_str_to_code('Y-JAN') == 1001)

assert (frequencies._period_str_to_code('Q') == 2000)
assert (frequencies._period_str_to_code('Q-DEC') == 2000)
assert (frequencies._period_str_to_code('Q-FEB') == 2002)
Expand Down Expand Up @@ -349,6 +358,10 @@ def test_freq_code(self):
assert frequencies.get_freq('3A') == 1000
assert frequencies.get_freq('-1A') == 1000

assert frequencies.get_freq('Y') == 1000
assert frequencies.get_freq('3Y') == 1000
assert frequencies.get_freq('-1Y') == 1000

assert frequencies.get_freq('W') == 4000
assert frequencies.get_freq('W-MON') == 4001
assert frequencies.get_freq('W-FRI') == 4005
Expand All @@ -369,6 +382,13 @@ def test_freq_group(self):
assert frequencies.get_freq_group('-1A') == 1000
assert frequencies.get_freq_group('A-JAN') == 1000
assert frequencies.get_freq_group('A-MAY') == 1000

assert frequencies.get_freq_group('Y') == 1000
assert frequencies.get_freq_group('3Y') == 1000
assert frequencies.get_freq_group('-1Y') == 1000
assert frequencies.get_freq_group('Y-JAN') == 1000
assert frequencies.get_freq_group('Y-MAY') == 1000

assert frequencies.get_freq_group(offsets.YearEnd()) == 1000
assert frequencies.get_freq_group(offsets.YearEnd(month=1)) == 1000
assert frequencies.get_freq_group(offsets.YearEnd(month=5)) == 1000
Expand Down Expand Up @@ -790,12 +810,6 @@ def test_series(self):
for freq in [None, 'L']:
s = Series(period_range('2013', periods=10, freq=freq))
pytest.raises(TypeError, lambda: frequencies.infer_freq(s))
for freq in ['Y']:

msg = frequencies._INVALID_FREQ_ERROR
with tm.assert_raises_regex(ValueError, msg):
s = Series(period_range('2013', periods=10, freq=freq))
pytest.raises(TypeError, lambda: frequencies.infer_freq(s))

# DateTimeIndex
for freq in ['M', 'L', 'S']:
Expand All @@ -812,11 +826,12 @@ def test_legacy_offset_warnings(self):
'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']
'Y@JAN', '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:
Expand Down
24 changes: 21 additions & 3 deletions pandas/tseries/frequencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,10 +399,14 @@ def _get_freq_str(base, mult=1):
'Q': 'Q',
'A': 'A',
'W': 'W',
'M': 'M'
'M': 'M',
'Y': 'A',
'BY': 'A',
'YS': 'A',
'BYS': 'A',
}

need_suffix = ['QS', 'BQ', 'BQS', 'AS', 'BA', 'BAS']
need_suffix = ['QS', 'BQ', 'BQS', 'YS', 'AS', 'BY', 'BA', 'BYS', 'BAS']
for __prefix in need_suffix:
for _m in tslib._MONTHS:
_offset_to_period_map['%s-%s' % (__prefix, _m)] = \
Expand All @@ -427,9 +431,13 @@ def get_period_alias(offset_str):
'Q': 'Q-DEC',

'A': 'A-DEC', # YearEnd(month=12),
'Y': 'A-DEC',
'AS': 'AS-JAN', # YearBegin(month=1),
'YS': 'AS-JAN',
'BA': 'BA-DEC', # BYearEnd(month=12),
'BY': 'BA-DEC',
'BAS': 'BAS-JAN', # BYearBegin(month=1),
'BYS': 'BAS-JAN',

'Min': 'T',
'min': 'T',
Expand Down Expand Up @@ -708,7 +716,17 @@ def get_standard_freq(freq):
for _k, _v in compat.iteritems(_period_code_map):
_reverse_period_code_map[_v] = _k

# Additional aliases
# Yearly aliases
year_aliases = {}

for k, v in compat.iteritems(_period_code_map):
if k.startswith("A-"):
alias = "Y" + k[1:]
year_aliases[alias] = v

_period_code_map.update(**year_aliases)
del year_aliases

_period_code_map.update({
"Q": 2000, # Quarterly - December year end (default quarterly)
"A": 1000, # Annual
Expand Down

0 comments on commit aead041

Please sign in to comment.