Skip to content

Commit 77ec4e8

Browse files
jbrockmendeljreback
authored andcommitted
Move frequencies functions to cython (#17746)
1 parent a355d5c commit 77ec4e8

15 files changed

+490
-393
lines changed

pandas/_libs/tslibs/frequencies.pxd

+6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
# -*- coding: utf-8 -*-
22
# cython: profile=False
33

4+
cpdef object get_rule_month(object source, object default=*)
5+
46
cpdef get_freq_code(freqstr)
7+
cpdef object get_freq(object freq)
8+
cpdef str get_base_alias(freqstr)
9+
cpdef int get_to_timestamp_base(int base)
10+
cpdef str get_freq_str(base, mult=*)

pandas/_libs/tslibs/frequencies.pyx

+311-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ cimport cython
66

77
import numpy as np
88
cimport numpy as np
9+
from numpy cimport int64_t
910
np.import_array()
1011

11-
from util cimport is_integer_object
12+
from util cimport is_integer_object, is_string_object
13+
14+
from ccalendar import MONTH_NUMBERS
1215

1316
# ----------------------------------------------------------------------
1417
# Constants
@@ -23,6 +26,22 @@ _INVALID_FREQ_ERROR = "Invalid frequency: {0}"
2326
# ---------------------------------------------------------------------
2427
# Period codes
2528

29+
30+
class FreqGroup(object):
31+
FR_ANN = 1000
32+
FR_QTR = 2000
33+
FR_MTH = 3000
34+
FR_WK = 4000
35+
FR_BUS = 5000
36+
FR_DAY = 6000
37+
FR_HR = 7000
38+
FR_MIN = 8000
39+
FR_SEC = 9000
40+
FR_MS = 10000
41+
FR_US = 11000
42+
FR_NS = 12000
43+
44+
2645
# period frequency constants corresponding to scikits timeseries
2746
# originals
2847
_period_code_map = {
@@ -125,8 +144,8 @@ cpdef get_freq_code(freqstr):
125144
-------
126145
return : tuple of base frequency code and stride (mult)
127146
128-
Example
129-
-------
147+
Examples
148+
--------
130149
>>> get_freq_code('3D')
131150
(6000, 3)
132151
@@ -203,3 +222,292 @@ cpdef _period_str_to_code(freqstr):
203222
return _period_code_map[freqstr]
204223
except KeyError:
205224
raise ValueError(_INVALID_FREQ_ERROR.format(freqstr))
225+
226+
227+
cpdef str get_freq_str(base, mult=1):
228+
"""
229+
Return the summary string associated with this offset code, possibly
230+
adjusted by a multiplier.
231+
232+
Parameters
233+
----------
234+
base : int (member of FreqGroup)
235+
236+
Returns
237+
-------
238+
freq_str : str
239+
240+
Examples
241+
--------
242+
>>> get_freq_str(1000)
243+
'A-DEC'
244+
245+
>>> get_freq_str(2000, 2)
246+
'2Q-DEC'
247+
248+
>>> get_freq_str("foo")
249+
"""
250+
code = _reverse_period_code_map.get(base)
251+
if mult == 1:
252+
return code
253+
return str(mult) + code
254+
255+
256+
cpdef str get_base_alias(freqstr):
257+
"""
258+
Returns the base frequency alias, e.g., '5D' -> 'D'
259+
260+
Parameters
261+
----------
262+
freqstr : str
263+
264+
Returns
265+
-------
266+
base_alias : str
267+
"""
268+
return _base_and_stride(freqstr)[0]
269+
270+
271+
cpdef int get_to_timestamp_base(int base):
272+
"""
273+
Return frequency code group used for base of to_timestamp against
274+
frequency code.
275+
276+
Parameters
277+
----------
278+
base : int (member of FreqGroup)
279+
280+
Returns
281+
-------
282+
base : int
283+
284+
Examples
285+
--------
286+
# Return day freq code against longer freq than day
287+
>>> get_to_timestamp_base(get_freq_code('D')[0])
288+
6000
289+
>>> get_to_timestamp_base(get_freq_code('W')[0])
290+
6000
291+
>>> get_to_timestamp_base(get_freq_code('M')[0])
292+
6000
293+
294+
# Return second freq code against hour between second
295+
>>> get_to_timestamp_base(get_freq_code('H')[0])
296+
9000
297+
>>> get_to_timestamp_base(get_freq_code('S')[0])
298+
9000
299+
"""
300+
if base < FreqGroup.FR_BUS:
301+
return FreqGroup.FR_DAY
302+
elif FreqGroup.FR_HR <= base <= FreqGroup.FR_SEC:
303+
return FreqGroup.FR_SEC
304+
return base
305+
306+
307+
cpdef object get_freq(object freq):
308+
"""
309+
Return frequency code of given frequency str.
310+
If input is not string, return input as it is.
311+
312+
Examples
313+
--------
314+
>>> get_freq('A')
315+
1000
316+
317+
>>> get_freq('3A')
318+
1000
319+
"""
320+
if is_string_object(freq):
321+
base, mult = get_freq_code(freq)
322+
freq = base
323+
return freq
324+
325+
326+
# ----------------------------------------------------------------------
327+
# Frequency comparison
328+
329+
cpdef bint is_subperiod(source, target):
330+
"""
331+
Returns True if downsampling is possible between source and target
332+
frequencies
333+
334+
Parameters
335+
----------
336+
source : string or DateOffset
337+
Frequency converting from
338+
target : string or DateOffset
339+
Frequency converting to
340+
341+
Returns
342+
-------
343+
is_subperiod : boolean
344+
"""
345+
346+
if target is None or source is None:
347+
return False
348+
source = _maybe_coerce_freq(source)
349+
target = _maybe_coerce_freq(target)
350+
351+
if _is_annual(target):
352+
if _is_quarterly(source):
353+
return _quarter_months_conform(get_rule_month(source),
354+
get_rule_month(target))
355+
return source in {'D', 'C', 'B', 'M', 'H', 'T', 'S', 'L', 'U', 'N'}
356+
elif _is_quarterly(target):
357+
return source in {'D', 'C', 'B', 'M', 'H', 'T', 'S', 'L', 'U', 'N'}
358+
elif _is_monthly(target):
359+
return source in {'D', 'C', 'B', 'H', 'T', 'S', 'L', 'U', 'N'}
360+
elif _is_weekly(target):
361+
return source in {target, 'D', 'C', 'B', 'H', 'T', 'S', 'L', 'U', 'N'}
362+
elif target == 'B':
363+
return source in {'B', 'H', 'T', 'S', 'L', 'U', 'N'}
364+
elif target == 'C':
365+
return source in {'C', 'H', 'T', 'S', 'L', 'U', 'N'}
366+
elif target == 'D':
367+
return source in {'D', 'H', 'T', 'S', 'L', 'U', 'N'}
368+
elif target == 'H':
369+
return source in {'H', 'T', 'S', 'L', 'U', 'N'}
370+
elif target == 'T':
371+
return source in {'T', 'S', 'L', 'U', 'N'}
372+
elif target == 'S':
373+
return source in {'S', 'L', 'U', 'N'}
374+
elif target == 'L':
375+
return source in {'L', 'U', 'N'}
376+
elif target == 'U':
377+
return source in {'U', 'N'}
378+
elif target == 'N':
379+
return source in {'N'}
380+
381+
382+
cpdef bint is_superperiod(source, target):
383+
"""
384+
Returns True if upsampling is possible between source and target
385+
frequencies
386+
387+
Parameters
388+
----------
389+
source : string
390+
Frequency converting from
391+
target : string
392+
Frequency converting to
393+
394+
Returns
395+
-------
396+
is_superperiod : boolean
397+
"""
398+
if target is None or source is None:
399+
return False
400+
source = _maybe_coerce_freq(source)
401+
target = _maybe_coerce_freq(target)
402+
403+
if _is_annual(source):
404+
if _is_annual(target):
405+
return get_rule_month(source) == get_rule_month(target)
406+
407+
if _is_quarterly(target):
408+
smonth = get_rule_month(source)
409+
tmonth = get_rule_month(target)
410+
return _quarter_months_conform(smonth, tmonth)
411+
return target in {'D', 'C', 'B', 'M', 'H', 'T', 'S', 'L', 'U', 'N'}
412+
elif _is_quarterly(source):
413+
return target in {'D', 'C', 'B', 'M', 'H', 'T', 'S', 'L', 'U', 'N'}
414+
elif _is_monthly(source):
415+
return target in {'D', 'C', 'B', 'H', 'T', 'S', 'L', 'U', 'N'}
416+
elif _is_weekly(source):
417+
return target in {source, 'D', 'C', 'B', 'H', 'T', 'S', 'L', 'U', 'N'}
418+
elif source == 'B':
419+
return target in {'D', 'C', 'B', 'H', 'T', 'S', 'L', 'U', 'N'}
420+
elif source == 'C':
421+
return target in {'D', 'C', 'B', 'H', 'T', 'S', 'L', 'U', 'N'}
422+
elif source == 'D':
423+
return target in {'D', 'C', 'B', 'H', 'T', 'S', 'L', 'U', 'N'}
424+
elif source == 'H':
425+
return target in {'H', 'T', 'S', 'L', 'U', 'N'}
426+
elif source == 'T':
427+
return target in {'T', 'S', 'L', 'U', 'N'}
428+
elif source == 'S':
429+
return target in {'S', 'L', 'U', 'N'}
430+
elif source == 'L':
431+
return target in {'L', 'U', 'N'}
432+
elif source == 'U':
433+
return target in {'U', 'N'}
434+
elif source == 'N':
435+
return target in {'N'}
436+
437+
438+
cdef str _maybe_coerce_freq(code):
439+
""" we might need to coerce a code to a rule_code
440+
and uppercase it
441+
442+
Parameters
443+
----------
444+
source : string or DateOffset
445+
Frequency converting from
446+
447+
Returns
448+
-------
449+
code : string
450+
"""
451+
assert code is not None
452+
if getattr(code, '_typ', None) == 'dateoffset':
453+
# i.e. isinstance(code, ABCDateOffset):
454+
code = code.rule_code
455+
return code.upper()
456+
457+
458+
cdef bint _quarter_months_conform(str source, str target):
459+
snum = MONTH_NUMBERS[source]
460+
tnum = MONTH_NUMBERS[target]
461+
return snum % 3 == tnum % 3
462+
463+
464+
cdef bint _is_annual(str rule):
465+
rule = rule.upper()
466+
return rule == 'A' or rule.startswith('A-')
467+
468+
469+
cdef bint _is_quarterly(str rule):
470+
rule = rule.upper()
471+
return rule == 'Q' or rule.startswith('Q-') or rule.startswith('BQ')
472+
473+
474+
cdef bint _is_monthly(str rule):
475+
rule = rule.upper()
476+
return rule == 'M' or rule == 'BM'
477+
478+
479+
cdef bint _is_weekly(str rule):
480+
rule = rule.upper()
481+
return rule == 'W' or rule.startswith('W-')
482+
483+
484+
# ----------------------------------------------------------------------
485+
486+
cpdef object get_rule_month(object source, object default='DEC'):
487+
"""
488+
Return starting month of given freq, default is December.
489+
490+
Parameters
491+
----------
492+
source : object
493+
default : object (default "DEC")
494+
495+
Returns
496+
-------
497+
rule_month: object (usually string)
498+
499+
Examples
500+
--------
501+
>>> get_rule_month('D')
502+
'DEC'
503+
504+
>>> get_rule_month('A-JAN')
505+
'JAN'
506+
"""
507+
if hasattr(source, 'freqstr'):
508+
source = source.freqstr
509+
source = source.upper()
510+
if '-' not in source:
511+
return default
512+
else:
513+
return source.split('-')[1]

0 commit comments

Comments
 (0)