Skip to content

BUG: frequencies.get_freq_code raises an error against offset with n != 1 #10350

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

Merged
merged 1 commit into from
Jun 15, 2015
Merged
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
3 changes: 1 addition & 2 deletions pandas/tseries/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,7 @@ def resolution(self):
"""
Returns day, hour, minute, second, millisecond or microsecond
"""
from pandas.tseries.frequencies import get_reso_string
return get_reso_string(self._resolution)
return Resolution.get_str(self._resolution)

def _convert_scalar_indexer(self, key, kind=None):
"""
Expand Down
132 changes: 119 additions & 13 deletions pandas/tseries/frequencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class FreqGroup(object):

class Resolution(object):

# defined in period.pyx
# note that these are different from freq codes
RESO_US = period.US_RESO
RESO_MS = period.MS_RESO
RESO_SEC = period.S_RESO
Expand Down Expand Up @@ -65,30 +67,104 @@ class Resolution(object):

@classmethod
def get_str(cls, reso):
"""
Return resolution str against resolution code.

Example
-------
>>> Resolution.get_str(Resolution.RESO_SEC)
'second'
"""
return cls._reso_str_map.get(reso, 'day')

@classmethod
def get_reso(cls, resostr):
"""
Return resolution str against resolution code.

Example
-------
>>> Resolution.get_reso('second')
2

>>> Resolution.get_reso('second') == Resolution.RESO_SEC
True
"""
return cls._str_reso_map.get(resostr, cls.RESO_DAY)

@classmethod
def get_freq_group(cls, resostr):
"""
Return frequency str against resolution str.

Example
-------
>>> f.Resolution.get_freq_group('day')
4000
"""
return get_freq_group(cls.get_freq(resostr))

@classmethod
def get_freq(cls, resostr):
"""
Return frequency str against resolution str.

Example
-------
>>> f.Resolution.get_freq('day')
'D'
"""
return cls._reso_freq_map[resostr]

@classmethod
def get_str_from_freq(cls, freq):
"""
Return resolution str against frequency str.

Example
-------
>>> Resolution.get_str_from_freq('H')
'hour'
"""
return cls._freq_reso_map.get(freq, 'day')

@classmethod
def get_reso_from_freq(cls, freq):
return cls.get_reso(cls.get_str_from_freq(freq))
"""
Return resolution code against frequency str.

Example
-------
>>> Resolution.get_reso_from_freq('H')
4

def get_reso_string(reso):
return Resolution.get_str(reso)
>>> Resolution.get_reso_from_freq('H') == Resolution.RESO_HR
True
"""
return cls.get_reso(cls.get_str_from_freq(freq))


def get_to_timestamp_base(base):
"""
Return frequency code group used for base of to_timestamp against
frequency code.

Example
-------
# Return day freq code against longer freq than day
>>> get_to_timestamp_base(get_freq_code('D')[0])
6000
>>> get_to_timestamp_base(get_freq_code('W')[0])
6000
>>> get_to_timestamp_base(get_freq_code('M')[0])
6000

# Return second freq code against hour between second
>>> get_to_timestamp_base(get_freq_code('H')[0])
9000
>>> get_to_timestamp_base(get_freq_code('S')[0])
9000
"""
if base < FreqGroup.FR_BUS:
return FreqGroup.FR_DAY
if FreqGroup.FR_HR <= base <= FreqGroup.FR_SEC:
Expand All @@ -97,13 +173,36 @@ def get_to_timestamp_base(base):


def get_freq_group(freq):
"""
Return frequency code group of given frequency str.

Example
-------
>>> get_freq_group('W-MON')
4000

>>> get_freq_group('W-FRI')
4000
"""
if isinstance(freq, compat.string_types):
base, mult = get_freq_code(freq)
freq = base
return (freq // 1000) * 1000


def get_freq(freq):
"""
Return frequency code of given frequency str.
If input is not string, return input as it is.

Example
-------
>>> get_freq('A')
1000

>>> get_freq('3A')
1000
"""
if isinstance(freq, compat.string_types):
base, mult = get_freq_code(freq)
freq = base
Expand All @@ -112,15 +211,29 @@ def get_freq(freq):

def get_freq_code(freqstr):
"""
Return freq str or tuple to freq code and stride (mult)

Parameters
----------
freqstr : str or tuple

Returns
-------
return : tuple of base frequency code and stride (mult)

Example
-------
>>> get_freq_code('3D')
(6000, 3)

>>> get_freq_code('D')
(6000, 1)

>>> get_freq_code(('D', 3))
(6000, 3)
"""
if isinstance(freqstr, DateOffset):
freqstr = (get_offset_name(freqstr), freqstr.n)
freqstr = (freqstr.rule_code, freqstr.n)

if isinstance(freqstr, tuple):
if (com.is_integer(freqstr[0]) and
Expand Down Expand Up @@ -386,6 +499,7 @@ def get_base_alias(freqstr):
"""
return _base_and_stride(freqstr)[0]


_dont_uppercase = set(('MS', 'ms'))


Expand Down Expand Up @@ -637,14 +751,6 @@ def _period_alias_dictionary():
return alias_dict


def _infer_period_group(freqstr):
return _period_group(Resolution._reso_freq_map[freqstr])


def _period_group(freqstr):
base, mult = get_freq_code(freqstr)
return base // 1000 * 1000

_period_alias_dict = _period_alias_dictionary()


Expand All @@ -671,7 +777,7 @@ def _period_str_to_code(freqstr):
def infer_freq(index, warn=True):
"""
Infer the most likely frequency given the input index. If the frequency is
uncertain, a warning will be printed.
uncertain, a warning will be printed.

Parameters
----------
Expand Down
8 changes: 4 additions & 4 deletions pandas/tseries/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,8 +528,8 @@ def get_value(self, series, key):
except (KeyError, IndexError):
try:
asdt, parsed, reso = parse_time_string(key, self.freq)
grp = frequencies._infer_period_group(reso)
freqn = frequencies._period_group(self.freq)
grp = frequencies.Resolution.get_freq_group(reso)
freqn = frequencies.get_freq_group(self.freq)

vals = self.values

Expand Down Expand Up @@ -655,8 +655,8 @@ def _get_string_slice(self, key):

key, parsed, reso = parse_time_string(key, self.freq)

grp = frequencies._infer_period_group(reso)
freqn = frequencies._period_group(self.freq)
grp = frequencies.Resolution.get_freq_group(reso)
freqn = frequencies.get_freq_group(self.freq)
if reso in ['day', 'hour', 'minute', 'second'] and not grp < freqn:
raise KeyError(key)

Expand Down
112 changes: 111 additions & 1 deletion pandas/tseries/tests/test_frequencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,117 @@ def test_anchored_shortcuts():
expected = frequencies.to_offset('Q-DEC')
assert(result == expected)

class TestFrequencyCode(tm.TestCase):

def test_freq_code(self):
self.assertEqual(frequencies.get_freq('A'), 1000)
self.assertEqual(frequencies.get_freq('3A'), 1000)
self.assertEqual(frequencies.get_freq('-1A'), 1000)

self.assertEqual(frequencies.get_freq('W'), 4000)
self.assertEqual(frequencies.get_freq('W-MON'), 4001)
self.assertEqual(frequencies.get_freq('W-FRI'), 4005)

for freqstr, code in compat.iteritems(frequencies._period_code_map):
result = frequencies.get_freq(freqstr)
self.assertEqual(result, code)

result = frequencies.get_freq_group(freqstr)
self.assertEqual(result, code // 1000 * 1000)

result = frequencies.get_freq_group(code)
self.assertEqual(result, code // 1000 * 1000)

def test_get_to_timestamp_base(self):
tsb = frequencies.get_to_timestamp_base

self.assertEqual(tsb(frequencies.get_freq_code('D')[0]),
frequencies.get_freq_code('D')[0])
self.assertEqual(tsb(frequencies.get_freq_code('W')[0]),
frequencies.get_freq_code('D')[0])
self.assertEqual(tsb(frequencies.get_freq_code('M')[0]),
frequencies.get_freq_code('D')[0])

self.assertEqual(tsb(frequencies.get_freq_code('S')[0]),
frequencies.get_freq_code('S')[0])
self.assertEqual(tsb(frequencies.get_freq_code('T')[0]),
frequencies.get_freq_code('S')[0])
self.assertEqual(tsb(frequencies.get_freq_code('H')[0]),
frequencies.get_freq_code('S')[0])


def test_freq_to_reso(self):
Reso = frequencies.Resolution

self.assertEqual(Reso.get_str_from_freq('A'), 'year')
self.assertEqual(Reso.get_str_from_freq('Q'), 'quarter')
self.assertEqual(Reso.get_str_from_freq('M'), 'month')
self.assertEqual(Reso.get_str_from_freq('D'), 'day')
self.assertEqual(Reso.get_str_from_freq('H'), 'hour')
self.assertEqual(Reso.get_str_from_freq('T'), 'minute')
self.assertEqual(Reso.get_str_from_freq('S'), 'second')
self.assertEqual(Reso.get_str_from_freq('L'), 'millisecond')
self.assertEqual(Reso.get_str_from_freq('U'), 'microsecond')
self.assertEqual(Reso.get_str_from_freq('N'), 'nanosecond')

for freq in ['A', 'Q', 'M', 'D', 'H', 'T', 'S', 'L', 'U', 'N']:
# check roundtrip
result = Reso.get_freq(Reso.get_str_from_freq(freq))
self.assertEqual(freq, result)

for freq in ['D', 'H', 'T', 'S', 'L', 'U']:
result = Reso.get_freq(Reso.get_str(Reso.get_reso_from_freq(freq)))
self.assertEqual(freq, result)

def test_get_freq_code(self):
# freqstr
self.assertEqual(frequencies.get_freq_code('A'),
(frequencies.get_freq('A'), 1))
self.assertEqual(frequencies.get_freq_code('3D'),
(frequencies.get_freq('D'), 3))
self.assertEqual(frequencies.get_freq_code('-2M'),
(frequencies.get_freq('M'), -2))

# tuple
self.assertEqual(frequencies.get_freq_code(('D', 1)),
(frequencies.get_freq('D'), 1))
self.assertEqual(frequencies.get_freq_code(('A', 3)),
(frequencies.get_freq('A'), 3))
self.assertEqual(frequencies.get_freq_code(('M', -2)),
(frequencies.get_freq('M'), -2))
# numeric tuple
self.assertEqual(frequencies.get_freq_code((1000, 1)), (1000, 1))

# offsets
self.assertEqual(frequencies.get_freq_code(offsets.Day()),
(frequencies.get_freq('D'), 1))
self.assertEqual(frequencies.get_freq_code(offsets.Day(3)),
(frequencies.get_freq('D'), 3))
self.assertEqual(frequencies.get_freq_code(offsets.Day(-2)),
(frequencies.get_freq('D'), -2))

self.assertEqual(frequencies.get_freq_code(offsets.MonthEnd()),
(frequencies.get_freq('M'), 1))
self.assertEqual(frequencies.get_freq_code(offsets.MonthEnd(3)),
(frequencies.get_freq('M'), 3))
self.assertEqual(frequencies.get_freq_code(offsets.MonthEnd(-2)),
(frequencies.get_freq('M'), -2))

self.assertEqual(frequencies.get_freq_code(offsets.Week()),
(frequencies.get_freq('W'), 1))
self.assertEqual(frequencies.get_freq_code(offsets.Week(3)),
(frequencies.get_freq('W'), 3))
self.assertEqual(frequencies.get_freq_code(offsets.Week(-2)),
(frequencies.get_freq('W'), -2))

# monday is weekday=0
self.assertEqual(frequencies.get_freq_code(offsets.Week(weekday=1)),
(frequencies.get_freq('W-TUE'), 1))
self.assertEqual(frequencies.get_freq_code(offsets.Week(3, weekday=0)),
(frequencies.get_freq('W-MON'), 3))
self.assertEqual(frequencies.get_freq_code(offsets.Week(-2, weekday=4)),
(frequencies.get_freq('W-FRI'), -2))


_dti = DatetimeIndex

Expand Down Expand Up @@ -333,7 +444,6 @@ def test_infer_freq_tz_transition(self):
for date_pair in date_pairs:
for freq in freqs:
idx = date_range(date_pair[0], date_pair[1], freq=freq, tz=tz)
print(idx)
self.assertEqual(idx.inferred_freq, freq)

index = date_range("2013-11-03", periods=5, freq="3H").tz_localize("America/Chicago")
Expand Down
Loading