Skip to content

Commit f0055c5

Browse files
committed
Merge pull request #6592 from bjonen/master
Improve performance for custom business days (GH6584)
2 parents dd5c112 + 889fe1e commit f0055c5

File tree

3 files changed

+84
-44
lines changed

3 files changed

+84
-44
lines changed

doc/source/v0.14.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ Enhancements
343343
and data_label which allow the time stamp and dataset label to be set when creating a
344344
file. (:issue:`6545`)
345345
- ``pandas.io.gbq`` now handles reading unicode strings properly. (:issue:`5940`)
346+
- Improve performance of ``CustomBusinessDay`` (:issue:`6584`)
346347

347348
Performance
348349
~~~~~~~~~~~

pandas/tseries/offsets.py

+69-44
Original file line numberDiff line numberDiff line change
@@ -453,32 +453,38 @@ class CustomBusinessDay(BusinessDay):
453453
_prefix = 'C'
454454

455455
def __init__(self, n=1, **kwds):
456-
# Check we have the required numpy version
457-
from distutils.version import LooseVersion
458-
459-
if LooseVersion(np.__version__) < '1.7.0':
460-
raise NotImplementedError("CustomBusinessDay requires numpy >= "
461-
"1.7.0. Current version: " +
462-
np.__version__)
463-
464456
self.n = int(n)
465457
self.kwds = kwds
466458
self.offset = kwds.get('offset', timedelta(0))
467459
self.normalize = kwds.get('normalize', False)
468460
self.weekmask = kwds.get('weekmask', 'Mon Tue Wed Thu Fri')
469-
470461
holidays = kwds.get('holidays', [])
462+
471463
holidays = [self._to_dt64(dt, dtype='datetime64[D]') for dt in
472464
holidays]
473465
self.holidays = tuple(sorted(holidays))
474466
self.kwds['holidays'] = self.holidays
467+
475468
self._set_busdaycalendar()
476469

477470
def _set_busdaycalendar(self):
478-
holidays = np.array(self.holidays, dtype='datetime64[D]')
479-
self.busdaycalendar = np.busdaycalendar(holidays=holidays,
480-
weekmask=self.weekmask)
481-
471+
if self.holidays:
472+
kwargs = {'weekmask':self.weekmask,'holidays':self.holidays}
473+
else:
474+
kwargs = {'weekmask':self.weekmask}
475+
try:
476+
self.busdaycalendar = np.busdaycalendar(**kwargs)
477+
except:
478+
# Check we have the required numpy version
479+
from distutils.version import LooseVersion
480+
481+
if LooseVersion(np.__version__) < '1.7.0':
482+
raise NotImplementedError("CustomBusinessDay requires numpy >= "
483+
"1.7.0. Current version: " +
484+
np.__version__)
485+
else:
486+
raise
487+
482488
def __getstate__(self):
483489
""""Return a pickleable state"""
484490
state = self.__dict__.copy()
@@ -490,52 +496,71 @@ def __setstate__(self, state):
490496
self.__dict__ = state
491497
self._set_busdaycalendar()
492498

493-
@staticmethod
494-
def _to_dt64(dt, dtype='datetime64'):
495-
if isinstance(dt, (datetime, compat.string_types)):
496-
dt = np.datetime64(dt, dtype=dtype)
497-
if isinstance(dt, np.datetime64):
498-
dt = dt.astype(dtype)
499+
def apply(self, other):
500+
if self.n <= 0:
501+
roll = 'forward'
499502
else:
500-
raise TypeError('dt must be datestring, datetime or datetime64')
501-
return dt
503+
roll = 'backward'
502504

503-
def apply(self, other):
505+
# Distinguish input cases to enhance performance
504506
if isinstance(other, datetime):
505507
dtype = type(other)
508+
date_in = other
509+
np_dt = np.datetime64(date_in.date())
510+
511+
np_incr_dt = np.busday_offset(np_dt, self.n, roll=roll,
512+
busdaycal=self.busdaycalendar)
513+
514+
dt_date = np_incr_dt.astype(datetime)
515+
if not self.normalize:
516+
result = datetime.combine(dt_date,date_in.time())
517+
else:
518+
result = dt_date
519+
520+
if self.offset:
521+
result = result + self.offset
522+
523+
return result
524+
506525
elif isinstance(other, np.datetime64):
507526
dtype = other.dtype
527+
date_in = other
528+
np_day = date_in.astype('datetime64[D]')
529+
np_time = date_in - np_day
530+
531+
np_incr_dt = np.busday_offset(np_day, self.n, roll=roll,
532+
busdaycal=self.busdaycalendar)
533+
534+
if not self.normalize:
535+
result = np_day_incr + np_time
536+
else:
537+
result = np_incr_dt
538+
539+
if self.offset:
540+
result = result + self.offset
541+
542+
return result
543+
508544
elif isinstance(other, (timedelta, Tick)):
509545
return BDay(self.n, offset=self.offset + other,
510546
normalize=self.normalize)
511547
else:
512548
raise ApplyTypeError('Only know how to combine trading day with '
513549
'datetime, datetime64 or timedelta.')
514-
dt64 = self._to_dt64(other)
515-
516-
day64 = dt64.astype('datetime64[D]')
517-
time = dt64 - day64
518-
519-
if self.n <= 0:
520-
roll = 'forward'
521-
else:
522-
roll = 'backward'
523-
524-
result = np.busday_offset(day64, self.n, roll=roll,
525-
busdaycal=self.busdaycalendar)
526550

527-
if not self.normalize:
528-
result = result + time
529-
530-
result = result.astype(dtype)
531-
532-
if self.offset:
533-
result = result + self.offset
534-
535-
return result
551+
@staticmethod
552+
def _to_dt64(dt, dtype='datetime64'):
553+
# Currently
554+
# > np.datetime64(dt.datetime(2013,5,1),dtype='datetime64[D]')
555+
# numpy.datetime64('2013-05-01T02:00:00.000000+0200')
556+
# Thus astype is needed to cast datetime to datetime64[D]
557+
dt = np.datetime64(dt)
558+
if dt.dtype.name != dtype:
559+
dt = dt.astype(dtype)
560+
return dt
536561

537562
def onOffset(self, dt):
538-
day64 = self._to_dt64(dt).astype('datetime64[D]')
563+
day64 = self._to_dt64(dt,'datetime64[D]')
539564
return np.is_busday(day64, busdaycal=self.busdaycalendar)
540565

541566

vb_suite/timeseries.py

+14
Original file line numberDiff line numberDiff line change
@@ -281,3 +281,17 @@ def date_range(start=None, end=None, periods=None, freq=None):
281281
Benchmark('DatetimeConverter.convert(rng, None, None)',
282282
setup, start_date=datetime(2013, 1, 1))
283283

284+
# Adding custom business day
285+
setup = common_setup + """
286+
import datetime as dt
287+
import pandas as pd
288+
289+
date = dt.datetime(2011,1,1)
290+
cday = pd.offsets.CustomBusinessDay()
291+
"""
292+
timeseries_custom_bday_incr = \
293+
Benchmark("date + cday",setup)
294+
295+
# Increment by n
296+
timeseries_custom_bday_incr_n = \
297+
Benchmark("date + 10 * cday",setup)

0 commit comments

Comments
 (0)