From ca96e15ebae665be4a11ee822c1c9d6c63e6824e Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sat, 25 Nov 2017 13:15:33 -0800 Subject: [PATCH 1/5] implement business_start, business_end for shift_months --- pandas/_libs/tslibs/ccalendar.pxd | 13 +++++++ pandas/_libs/tslibs/ccalendar.pyx | 65 +++++++++++++++++++++++++++++++ pandas/_libs/tslibs/offsets.pyx | 63 +++++++++++++++++++++++++----- pandas/tseries/offsets.py | 60 ++++++++++++---------------- setup.py | 4 ++ 5 files changed, 160 insertions(+), 45 deletions(-) create mode 100644 pandas/_libs/tslibs/ccalendar.pxd create mode 100644 pandas/_libs/tslibs/ccalendar.pyx diff --git a/pandas/_libs/tslibs/ccalendar.pxd b/pandas/_libs/tslibs/ccalendar.pxd new file mode 100644 index 0000000000000..929f74d3ee0bf --- /dev/null +++ b/pandas/_libs/tslibs/ccalendar.pxd @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# cython: profile=False + +from cython cimport Py_ssize_t + +from numpy cimport int64_t, int32_t + + +cpdef monthrange(int64_t year, Py_ssize_t month) + +cdef int dayofweek(int y, int m, int d) nogil +cdef int is_leapyear(int64_t year) nogil +cdef int32_t get_days_in_month(int year, int month) nogil diff --git a/pandas/_libs/tslibs/ccalendar.pyx b/pandas/_libs/tslibs/ccalendar.pyx new file mode 100644 index 0000000000000..2cd73bd852ff6 --- /dev/null +++ b/pandas/_libs/tslibs/ccalendar.pyx @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# cython: profile=False +# cython: boundscheck=False +""" +Cython implementations of functions resembling the stdlib calendar module +""" + +cimport cython +from cython cimport Py_ssize_t + +import numpy as np +cimport numpy as np +from numpy cimport int64_t, int32_t +np.import_array() + + +# ---------------------------------------------------------------------- +# Constants + +# Slightly more performant cython lookups than a 2D table +cdef int32_t* days_per_month_array = [ + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, + 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + +# ---------------------------------------------------------------------- + + +@cython.wraparound(False) +@cython.boundscheck(False) +cdef inline int32_t get_days_in_month(int year, int month) nogil: + return days_per_month_array[12 * is_leapyear(year) + month - 1] + + +@cython.wraparound(False) +@cython.boundscheck(False) +cpdef monthrange(int64_t year, Py_ssize_t month): + cdef: + int32_t days + + if month < 1 or month > 12: + raise ValueError("bad month number 0; must be 1-12") + + days = get_days_in_month(year, month) + return (dayofweek(year, month, 1), days) + + +@cython.wraparound(False) +@cython.boundscheck(False) +@cython.cdivision +cdef int dayofweek(int y, int m, int d) nogil: + """Sakamoto's method, from wikipedia""" + cdef: + int day + int* sakamoto_arr = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4] + + y -= m < 3 + day = (y + y / 4 - y / 100 + y / 400 + sakamoto_arr[m - 1] + d) % 7 + # convert to python day + return (day + 6) % 7 + + +cdef int is_leapyear(int64_t year) nogil: + """Returns 1 if the given year is a leap year, 0 otherwise.""" + return ((year & 0x3) == 0 and # year % 4 == 0 + ((year % 100) != 0 or (year % 400) == 0)) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 4ed4d4a9b7b99..267f9b4861cb8 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -17,14 +17,12 @@ np.import_array() from util cimport is_string_object, is_integer_object -from pandas._libs.tslib import monthrange - +from ccalendar cimport get_days_in_month, monthrange from conversion cimport tz_convert_single, pydt_to_i8 from frequencies cimport get_freq_code from nattype cimport NPY_NAT from np_datetime cimport (pandas_datetimestruct, - dtstruct_to_dt64, dt64_to_dtstruct, - is_leapyear, days_per_month_table) + dtstruct_to_dt64, dt64_to_dtstruct) # --------------------------------------------------------------------- # Constants @@ -452,11 +450,6 @@ class BaseOffset(_BaseOffset): # ---------------------------------------------------------------------- # RelativeDelta Arithmetic -@cython.wraparound(False) -@cython.boundscheck(False) -cdef inline int get_days_in_month(int year, int month) nogil: - return days_per_month_table[is_leapyear(year)][month - 1] - cdef inline int year_add_months(pandas_datetimestruct dts, int months) nogil: """new year number after shifting pandas_datetimestruct number of months""" @@ -554,8 +547,58 @@ def shift_months(int64_t[:] dtindex, int months, object day=None): dts.day = get_days_in_month(dts.year, dts.month) out[i] = dtstruct_to_dt64(&dts) + + elif day == 'business_start': + for i in range(count): + if dtindex[i] == NPY_NAT: + out[i] = NPY_NAT + continue + + dt64_to_dtstruct(dtindex[i], &dts) + months_to_roll = months + wkday, days_in_month = monthrange(dts.year, dts.month) + compare_day = get_firstbday(wkday, days_in_month) + + if months_to_roll > 0 and dts.day < compare_day: + months_to_roll -= 1 + elif months_to_roll <= 0 and dts.day > compare_day: + # as if rolled forward already + months_to_roll += 1 + + dts.year = year_add_months(dts, months_to_roll) + dts.month = month_add_months(dts, months_to_roll) + + wkday, days_in_month = monthrange(dts.year, dts.month) + dts.day = get_firstbday(wkday, days_in_month) + out[i] = dtstruct_to_dt64(&dts) + + elif day == 'business_end': + for i in range(count): + if dtindex[i] == NPY_NAT: + out[i] = NPY_NAT + continue + + dt64_to_dtstruct(dtindex[i], &dts) + months_to_roll = months + wkday, days_in_month = monthrange(dts.year, dts.month) + compare_day = get_lastbday(wkday, days_in_month) + + if months_to_roll > 0 and dts.day < compare_day: + months_to_roll -= 1 + elif months_to_roll <= 0 and dts.day > compare_day: + # as if rolled forward already + months_to_roll += 1 + + dts.year = year_add_months(dts, months_to_roll) + dts.month = month_add_months(dts, months_to_roll) + + wkday, days_in_month = monthrange(dts.year, dts.month) + dts.day = get_lastbday(wkday, days_in_month) + out[i] = dtstruct_to_dt64(&dts) + else: - raise ValueError("day must be None, 'start' or 'end'") + raise ValueError("day must be None, 'start', 'end', " + "'business_start', or 'business_end'") return np.asarray(out) diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index a307b7e5817a8..8e1ead5dfbe9e 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -929,8 +929,9 @@ def name(self): if self.isAnchored: return self.rule_code else: + month = liboffsets._int_to_month[self.n] return "{code}-{month}".format(code=self.rule_code, - month=_int_to_month[self.n]) + month=month) def onOffset(self, dt): if self.normalize and not _is_normalized(dt): @@ -950,28 +951,23 @@ def apply(self, other): return shift_month(other, n, self._day_opt) + @apply_index_wraps + def apply_index(self, i): + shifted = liboffsets.shift_months(i.asi8, self.n, self._day_opt) + return i._shallow_copy(shifted) + class MonthEnd(MonthOffset): """DateOffset of one month end""" _prefix = 'M' _day_opt = 'end' - @apply_index_wraps - def apply_index(self, i): - shifted = liboffsets.shift_months(i.asi8, self.n, self._day_opt) - return i._shallow_copy(shifted) - class MonthBegin(MonthOffset): """DateOffset of one month at beginning""" _prefix = 'MS' _day_opt = 'start' - @apply_index_wraps - def apply_index(self, i): - shifted = liboffsets.shift_months(i.asi8, self.n, self._day_opt) - return i._shallow_copy(shifted) - class BusinessMonthEnd(MonthOffset): """DateOffset increments between business EOM dates""" @@ -1008,6 +1004,7 @@ class CustomBusinessMonthEnd(BusinessMixin, MonthOffset): _prefix = 'CBM' onOffset = DateOffset.onOffset # override MonthOffset method + apply_index = DateOffset.apply_index # override MonthOffset method def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri', holidays=None, calendar=None, offset=timedelta(0)): @@ -1083,6 +1080,7 @@ class CustomBusinessMonthBegin(BusinessMixin, MonthOffset): _prefix = 'CBMS' onOffset = DateOffset.onOffset # override MonthOffset method + apply_index = DateOffset.apply_index # override MonthOffset method def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri', holidays=None, calendar=None, offset=timedelta(0)): @@ -1603,7 +1601,7 @@ def isAnchored(self): def _from_name(cls, suffix=None): kwargs = {} if suffix: - kwargs['startingMonth'] = _month_to_int[suffix] + kwargs['startingMonth'] = liboffsets._month_to_int[suffix] else: if cls._from_name_startingMonth is not None: kwargs['startingMonth'] = cls._from_name_startingMonth @@ -1611,7 +1609,7 @@ def _from_name(cls, suffix=None): @property def rule_code(self): - month = _int_to_month[self.startingMonth] + month = liboffsets._int_to_month[self.startingMonth] return '{prefix}-{month}'.format(prefix=self._prefix, month=month) @apply_wraps @@ -1631,6 +1629,12 @@ def apply(self, other): return shift_month(other, 3 * n - months_since, self._day_opt) + def onOffset(self, dt): + if self.normalize and not _is_normalized(dt): + return False + modMonth = (dt.month - self.startingMonth) % 3 + return modMonth == 0 and dt.day == self._get_offset_day(dt) + class BQuarterEnd(QuarterOffset): """DateOffset increments between business Quarter dates @@ -1644,16 +1648,6 @@ class BQuarterEnd(QuarterOffset): _prefix = 'BQ' _day_opt = 'business_end' - def onOffset(self, dt): - if self.normalize and not _is_normalized(dt): - return False - modMonth = (dt.month - self.startingMonth) % 3 - return modMonth == 0 and dt.day == self._get_offset_day(dt) - - -_int_to_month = tslib._MONTH_ALIASES -_month_to_int = {v: k for k, v in _int_to_month.items()} - # TODO: This is basically the same as BQuarterEnd class BQuarterBegin(QuarterOffset): @@ -1680,12 +1674,6 @@ class QuarterEnd(EndMixin, QuarterOffset): def apply_index(self, i): return self._end_apply_index(i, self.freqstr) - def onOffset(self, dt): - if self.normalize and not _is_normalized(dt): - return False - modMonth = (dt.month - self.startingMonth) % 3 - return modMonth == 0 and dt.day == self._get_offset_day(dt) - class QuarterBegin(BeginMixin, QuarterOffset): _outputName = 'QuarterBegin' @@ -1697,7 +1685,8 @@ class QuarterBegin(BeginMixin, QuarterOffset): @apply_index_wraps def apply_index(self, i): freq_month = 12 if self.startingMonth == 1 else self.startingMonth - 1 - freqstr = 'Q-{month}'.format(month=_int_to_month[freq_month]) + month = liboffsets._int_to_month[freq_month] + freqstr = 'Q-{month}'.format(month=month) return self._beg_apply_index(i, freqstr) @@ -1738,12 +1727,12 @@ def __init__(self, n=1, normalize=False, month=None): def _from_name(cls, suffix=None): kwargs = {} if suffix: - kwargs['month'] = _month_to_int[suffix] + kwargs['month'] = liboffsets._month_to_int[suffix] return cls(**kwargs) @property def rule_code(self): - month = _int_to_month[self.month] + month = liboffsets._int_to_month[self.month] return '{prefix}-{month}'.format(prefix=self._prefix, month=month) @@ -1784,7 +1773,8 @@ class YearBegin(BeginMixin, YearOffset): @apply_index_wraps def apply_index(self, i): freq_month = 12 if self.month == 1 else self.month - 1 - freqstr = 'A-{month}'.format(month=_int_to_month[freq_month]) + month = liboffsets._int_to_month[freq_month] + freqstr = 'A-{month}'.format(month=month) return self._beg_apply_index(i, freqstr) @@ -1969,7 +1959,7 @@ def _get_suffix_prefix(self): def get_rule_code_suffix(self): prefix = self._get_suffix_prefix() - month = _int_to_month[self.startingMonth] + month = liboffsets._int_to_month[self.startingMonth] weekday = _int_to_weekday[self.weekday] return '{prefix}-{month}-{weekday}'.format(prefix=prefix, month=month, weekday=weekday) @@ -1984,7 +1974,7 @@ def _parse_suffix(cls, varion_code, startingMonth_code, weekday_code): raise ValueError("Unable to parse varion_code: " "{code}".format(code=varion_code)) - startingMonth = _month_to_int[startingMonth_code] + startingMonth = liboffsets._month_to_int[startingMonth_code] weekday = _weekday_to_int[weekday_code] return {"weekday": weekday, diff --git a/setup.py b/setup.py index 7e56298d1b20b..306545f40f4af 100755 --- a/setup.py +++ b/setup.py @@ -343,6 +343,7 @@ class CheckSDist(sdist_class): 'pandas/_libs/window.pyx', 'pandas/_libs/sparse.pyx', 'pandas/_libs/parsers.pyx', + 'pandas/_libs/tslibs/ccalendar.pyx', 'pandas/_libs/tslibs/strptime.pyx', 'pandas/_libs/tslibs/np_datetime.pyx', 'pandas/_libs/tslibs/timedeltas.pyx', @@ -558,6 +559,8 @@ def pxd(name): '_libs/tslibs/nattype'], 'depends': tseries_depends, 'sources': np_datetime_sources}, + '_libs.tslibs.ccalendar': { + 'pyxfile': '_libs/tslibs/ccalendar'}, '_libs.tslibs.conversion': { 'pyxfile': '_libs/tslibs/conversion', 'pxdfiles': ['_libs/src/util', @@ -584,6 +587,7 @@ def pxd(name): '_libs.tslibs.offsets': { 'pyxfile': '_libs/tslibs/offsets', 'pxdfiles': ['_libs/src/util', + '_libs/tslibs/ccalendar', '_libs/tslibs/conversion', '_libs/tslibs/frequencies', '_libs/tslibs/nattype'], From 19231e11e61782b6ec62de7097070ad41abb422a Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sun, 26 Nov 2017 11:53:52 -0800 Subject: [PATCH 2/5] revert ccalendar, just implement shift_months cases --- pandas/_libs/tslibs/ccalendar.pxd | 13 ------- pandas/_libs/tslibs/ccalendar.pyx | 65 ------------------------------- pandas/_libs/tslibs/offsets.pyx | 11 +++++- setup.py | 4 -- 4 files changed, 9 insertions(+), 84 deletions(-) delete mode 100644 pandas/_libs/tslibs/ccalendar.pxd delete mode 100644 pandas/_libs/tslibs/ccalendar.pyx diff --git a/pandas/_libs/tslibs/ccalendar.pxd b/pandas/_libs/tslibs/ccalendar.pxd deleted file mode 100644 index 929f74d3ee0bf..0000000000000 --- a/pandas/_libs/tslibs/ccalendar.pxd +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -# cython: profile=False - -from cython cimport Py_ssize_t - -from numpy cimport int64_t, int32_t - - -cpdef monthrange(int64_t year, Py_ssize_t month) - -cdef int dayofweek(int y, int m, int d) nogil -cdef int is_leapyear(int64_t year) nogil -cdef int32_t get_days_in_month(int year, int month) nogil diff --git a/pandas/_libs/tslibs/ccalendar.pyx b/pandas/_libs/tslibs/ccalendar.pyx deleted file mode 100644 index 2cd73bd852ff6..0000000000000 --- a/pandas/_libs/tslibs/ccalendar.pyx +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -# cython: profile=False -# cython: boundscheck=False -""" -Cython implementations of functions resembling the stdlib calendar module -""" - -cimport cython -from cython cimport Py_ssize_t - -import numpy as np -cimport numpy as np -from numpy cimport int64_t, int32_t -np.import_array() - - -# ---------------------------------------------------------------------- -# Constants - -# Slightly more performant cython lookups than a 2D table -cdef int32_t* days_per_month_array = [ - 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, - 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - -# ---------------------------------------------------------------------- - - -@cython.wraparound(False) -@cython.boundscheck(False) -cdef inline int32_t get_days_in_month(int year, int month) nogil: - return days_per_month_array[12 * is_leapyear(year) + month - 1] - - -@cython.wraparound(False) -@cython.boundscheck(False) -cpdef monthrange(int64_t year, Py_ssize_t month): - cdef: - int32_t days - - if month < 1 or month > 12: - raise ValueError("bad month number 0; must be 1-12") - - days = get_days_in_month(year, month) - return (dayofweek(year, month, 1), days) - - -@cython.wraparound(False) -@cython.boundscheck(False) -@cython.cdivision -cdef int dayofweek(int y, int m, int d) nogil: - """Sakamoto's method, from wikipedia""" - cdef: - int day - int* sakamoto_arr = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4] - - y -= m < 3 - day = (y + y / 4 - y / 100 + y / 400 + sakamoto_arr[m - 1] + d) % 7 - # convert to python day - return (day + 6) % 7 - - -cdef int is_leapyear(int64_t year) nogil: - """Returns 1 if the given year is a leap year, 0 otherwise.""" - return ((year & 0x3) == 0 and # year % 4 == 0 - ((year % 100) != 0 or (year % 400) == 0)) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 267f9b4861cb8..654c51f0ca842 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -17,12 +17,14 @@ np.import_array() from util cimport is_string_object, is_integer_object -from ccalendar cimport get_days_in_month, monthrange +from pandas._libs.tslib import monthrange + from conversion cimport tz_convert_single, pydt_to_i8 from frequencies cimport get_freq_code from nattype cimport NPY_NAT from np_datetime cimport (pandas_datetimestruct, - dtstruct_to_dt64, dt64_to_dtstruct) + dtstruct_to_dt64, dt64_to_dtstruct, + is_leapyear, days_per_month_table) # --------------------------------------------------------------------- # Constants @@ -450,6 +452,11 @@ class BaseOffset(_BaseOffset): # ---------------------------------------------------------------------- # RelativeDelta Arithmetic +@cython.wraparound(False) +@cython.boundscheck(False) +cdef inline int get_days_in_month(int year, int month) nogil: + return days_per_month_table[is_leapyear(year)][month - 1] + cdef inline int year_add_months(pandas_datetimestruct dts, int months) nogil: """new year number after shifting pandas_datetimestruct number of months""" diff --git a/setup.py b/setup.py index 306545f40f4af..7e56298d1b20b 100755 --- a/setup.py +++ b/setup.py @@ -343,7 +343,6 @@ class CheckSDist(sdist_class): 'pandas/_libs/window.pyx', 'pandas/_libs/sparse.pyx', 'pandas/_libs/parsers.pyx', - 'pandas/_libs/tslibs/ccalendar.pyx', 'pandas/_libs/tslibs/strptime.pyx', 'pandas/_libs/tslibs/np_datetime.pyx', 'pandas/_libs/tslibs/timedeltas.pyx', @@ -559,8 +558,6 @@ def pxd(name): '_libs/tslibs/nattype'], 'depends': tseries_depends, 'sources': np_datetime_sources}, - '_libs.tslibs.ccalendar': { - 'pyxfile': '_libs/tslibs/ccalendar'}, '_libs.tslibs.conversion': { 'pyxfile': '_libs/tslibs/conversion', 'pxdfiles': ['_libs/src/util', @@ -587,7 +584,6 @@ def pxd(name): '_libs.tslibs.offsets': { 'pyxfile': '_libs/tslibs/offsets', 'pxdfiles': ['_libs/src/util', - '_libs/tslibs/ccalendar', '_libs/tslibs/conversion', '_libs/tslibs/frequencies', '_libs/tslibs/nattype'], From d3ff62863ca3d468ac2ac1e6b4bcb41d393dbb28 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sun, 26 Nov 2017 12:07:31 -0800 Subject: [PATCH 3/5] add tests and asv for newly implemented methods --- asv_bench/benchmarks/offset.py | 19 ++++++++++++ .../tests/tseries/offsets/test_yqm_offsets.py | 31 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/asv_bench/benchmarks/offset.py b/asv_bench/benchmarks/offset.py index ea826e8270ace..849776bf9a591 100644 --- a/asv_bench/benchmarks/offset.py +++ b/asv_bench/benchmarks/offset.py @@ -38,6 +38,25 @@ def time_apply_series(self, param): self.ser + self.offset +class OnOffset(object): + goal_time = 0.2 + + params = [pd.offsets.QuarterBegin(), pd.offsets.QuarterEnd(), + pd.offsets.BQuarterBegin(), pd.offsets.BQuarterEnd()] + param_names = ['offset'] + + def setup(self, offset): + self.offset = offset + self.dates = [datetime(2016, m, d) + for m in [10, 11, 12] + for d in [1, 2, 3, 28, 29, 30, 31] + if not (m == 11 and d == 31)] + + def time_on_offset(self, offset): + for date in self.dates: + self.offset.onOffset(date) + + class DatetimeIndexArithmetic(object): goal_time = 0.2 diff --git a/pandas/tests/tseries/offsets/test_yqm_offsets.py b/pandas/tests/tseries/offsets/test_yqm_offsets.py index 1d47cf67c6e55..90c1ec7adfe9e 100644 --- a/pandas/tests/tseries/offsets/test_yqm_offsets.py +++ b/pandas/tests/tseries/offsets/test_yqm_offsets.py @@ -6,6 +6,7 @@ import pytest +import pandas as pd from pandas import Timestamp from pandas import compat @@ -32,6 +33,36 @@ def test_quarterly_dont_normalize(): assert (result.time() == date.time()) + +@pytest.mark.parametrize('offset', [MonthBegin(), MonthEnd(), + BMonthBegin(), BMonthEnd()]) +def test_apply_index(offset): + rng = pd.date_range(start='1/1/2000', periods=100000, freq='T') + ser = pd.Series(rng) + + res = rng + offset + res_v2 = offset.apply_index(rng) + assert (res == res_v2).all() + assert res[0] == rng[0] + offset + assert res[-1] == rng[-1] + offset + res2 = ser + offset + # apply_index is only for indexes, not series, so no res2_v2 + assert res2.iloc[0] == ser.iloc[0] + offset + assert res2.iloc[-1] == ser.iloc[-1] + offset + + +@pytest.mark.parametrize('offset', [QuarterBegin(), QuarterEnd(), + BQuarterBegin(), BQuarterEnd()]) +def test_on_offset(offset): + dates = [datetime(2016, m, d) + for m in [10, 11, 12] + for d in [1, 2, 3, 28, 29, 30, 31] if not (m == 11 and d == 31)] + for date in dates: + res = offset.onOffset(date) + slow_version = date == (date + offset) - offset + assert res == slow_version + + # -------------------------------------------------------------------- # Months From 5978807cf1f096e13e816a6efab44e0330607ad8 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sun, 26 Nov 2017 15:30:39 -0800 Subject: [PATCH 4/5] flake8 whitespace fixup --- pandas/tests/tseries/offsets/test_yqm_offsets.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/tests/tseries/offsets/test_yqm_offsets.py b/pandas/tests/tseries/offsets/test_yqm_offsets.py index 90c1ec7adfe9e..292dd5eba938e 100644 --- a/pandas/tests/tseries/offsets/test_yqm_offsets.py +++ b/pandas/tests/tseries/offsets/test_yqm_offsets.py @@ -33,7 +33,6 @@ def test_quarterly_dont_normalize(): assert (result.time() == date.time()) - @pytest.mark.parametrize('offset', [MonthBegin(), MonthEnd(), BMonthBegin(), BMonthEnd()]) def test_apply_index(offset): From c503c535e406edf54b6137b1c2362ded1f0a3e47 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sun, 26 Nov 2017 16:46:57 -0800 Subject: [PATCH 5/5] whatsnew perf note --- doc/source/whatsnew/v0.22.0.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index 78b8ca8d5a480..d0b27e1d22a89 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -116,6 +116,7 @@ Performance Improvements - ``Series`` construction will reduce the number of copies made of the input data in certain cases (:issue:`17449`) - Improved performance of :func:`Series.dt.date` and :func:`DatetimeIndex.date` (:issue:`18058`) - Improved performance of ``IntervalIndex.symmetric_difference()`` (:issue:`18475`) +- Improved performance of ``DatetimeIndex`` and ``Series`` arithmetic operations with Business-Month and Business-Quarter frequencies (:issue:`18489`) .. _whatsnew_0220.docs: