Repository: pandas-dev/pandas

Issue:
ENH date_range yearly frequency flag 'A' is confusing, 'Y' should be accepted
to get a yearly frequency from date range the flag is actually `freq="A"` (for "Annual" i guess) rather than `freq="Y"` for yearly.

It would seem more consistent if it accepted "Y" as well/instead of "A", following the pattern:

hour -> hourly -> H
day -> daily -> D
week -> weekly -> W
year -> yearly -> Y



Discussion:
Anecdotally, I do `freq='Y'` almost every time. Same for resample.

The referenced issue, https://github.com/pandas-dev/pandas/issues/9312#issuecomment-71404429, concludes `freq=Y` should be an alias for `freq='AS'`
Hi, 
The aliases are here:
 https://github.com/pandas-dev/pandas/blob/master/pandas/tseries/frequencies.py#L425

are we fine with `freq='Y'` being an alias for the beginning of the year while `freq='A'` being an alias for the end of the year? 

Additionally, should other composed freqs containing `'Y'` ( 'BYA', 'BYE', 'YA', 'YE') also be added? 
so these should following the same convention as Q and M

so Y would be the same as A e.g. end of year and

YS would be year start

Resolving patch:
diff --git a/pandas/tseries/frequencies.py b/pandas/tseries/frequencies.py
--- a/pandas/tseries/frequencies.py
+++ b/pandas/tseries/frequencies.py
@@ -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)] = \
@@ -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',
@@ -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


Test patch:
diff --git a/pandas/tests/indexes/datetimes/test_date_range.py b/pandas/tests/indexes/datetimes/test_date_range.py
--- a/pandas/tests/indexes/datetimes/test_date_range.py
+++ b/pandas/tests/indexes/datetimes/test_date_range.py
@@ -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)
diff --git a/pandas/tests/tseries/test_frequencies.py b/pandas/tests/tseries/test_frequencies.py
--- a/pandas/tests/tseries/test_frequencies.py
+++ b/pandas/tests/tseries/test_frequencies.py
@@ -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: '):
@@ -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')
 
@@ -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)
@@ -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
@@ -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
@@ -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']:
@@ -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: