From ae1c968e673e772558ef3cbd37d0c3ef46df1382 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 27 Nov 2017 18:01:49 -0800 Subject: [PATCH 1/3] fastpaths for timestamp properties --- pandas/_libs/tslibs/timestamps.pyx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 8fdded0bcb07a..37683772be8b9 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -304,10 +304,12 @@ cdef class _Timestamp(datetime): out = get_date_field(np.array([val], dtype=np.int64), field) return int(out[0]) - cpdef _get_start_end_field(self, field): + cpdef bint _get_start_end_field(self, str field): cdef: int64_t val dict kwds + ndarray out + int month_kw freq = self.freq if freq: @@ -713,7 +715,7 @@ class Timestamp(_Timestamp): @property def quarter(self): - return self._get_field('q') + return ((self.month - 1) // 3) + 1 @property def days_in_month(self): @@ -727,6 +729,9 @@ class Timestamp(_Timestamp): @property def is_month_start(self): + if self.freq is None: + # fast-path for non-business frequencies + return self.day == 1 return self._get_start_end_field('is_month_start') @property @@ -735,6 +740,9 @@ class Timestamp(_Timestamp): @property def is_quarter_start(self): + if self.freq is None: + # fast-path for non-business frequencies + return self.day == 1 and self.month % 3 == 1 return self._get_start_end_field('is_quarter_start') @property @@ -743,10 +751,16 @@ class Timestamp(_Timestamp): @property def is_year_start(self): + if self.freq is None: + # fast-path for non-business frequencies + return self.day == self.month == 1 return self._get_start_end_field('is_year_start') @property def is_year_end(self): + if self.freq is None: + # fast-path for non-business frequencies + return self.month == 12 and self.day == 31 return self._get_start_end_field('is_year_end') @property From 5fc79fb09656d3fe5662c0df2e1f1e6218f7ad48 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 27 Nov 2017 18:24:47 -0800 Subject: [PATCH 2/3] more fastpaths --- pandas/_libs/tslibs/timestamps.pyx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 37683772be8b9..cf0c0e2c01d60 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -736,6 +736,9 @@ class Timestamp(_Timestamp): @property def is_month_end(self): + if self.freq is None: + # fast-path for non-business frequencies + return self.day == self.days_in_month return self._get_start_end_field('is_month_end') @property @@ -747,6 +750,9 @@ class Timestamp(_Timestamp): @property def is_quarter_end(self): + if self.freq is None: + # fast-path for non-business frequencies + return (self.month % 3) == 0 and self.day == self.days_in_month return self._get_start_end_field('is_quarter_end') @property From 9baeb6db0bc459e3431fd42ff16884bceaf61ff9 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Tue, 28 Nov 2017 07:30:11 -0800 Subject: [PATCH 3/3] tests+asvs for business Timestamp properties --- asv_bench/benchmarks/timestamp.py | 46 ++++++++++++++------------- pandas/tests/scalar/test_timestamp.py | 22 +++++++++++++ 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/asv_bench/benchmarks/timestamp.py b/asv_bench/benchmarks/timestamp.py index fc5e6dc8c06d6..9d7d6d2998a8b 100644 --- a/asv_bench/benchmarks/timestamp.py +++ b/asv_bench/benchmarks/timestamp.py @@ -1,4 +1,3 @@ -from .pandas_vb_common import * from pandas import to_timedelta, Timestamp import pytz import datetime @@ -7,61 +6,64 @@ class TimestampProperties(object): goal_time = 0.2 - params = [None, pytz.timezone('Europe/Amsterdam')] - param_names = ['tz'] + params = [(None, None), + (pytz.timezone('Europe/Amsterdam'), None), + (None, 'B'), + (pytz.timezone('Europe/Amsterdam'), 'B')] + param_names = ['tz', 'freq'] - def setup(self, tz): - self.ts = Timestamp('2017-08-25 08:16:14', tzinfo=tz) + def setup(self, tz, freq): + self.ts = Timestamp('2017-08-25 08:16:14', tzinfo=tz, freq=freq) - def time_tz(self, tz): + def time_tz(self, tz, freq): self.ts.tz - def time_offset(self, tz): + def time_offset(self, tz, freq): self.ts.offset - def time_dayofweek(self, tz): + def time_dayofweek(self, tz, freq): self.ts.dayofweek - def time_weekday_name(self, tz): + def time_weekday_name(self, tz, freq): self.ts.weekday_name - def time_dayofyear(self, tz): + def time_dayofyear(self, tz, freq): self.ts.dayofyear - def time_week(self, tz): + def time_week(self, tz, freq): self.ts.week - def time_quarter(self, tz): + def time_quarter(self, tz, freq): self.ts.quarter - def time_days_in_month(self, tz): + def time_days_in_month(self, tz, freq): self.ts.days_in_month - def time_freqstr(self, tz): + def time_freqstr(self, tz, freq): self.ts.freqstr - def time_is_month_start(self, tz): + def time_is_month_start(self, tz, freq): self.ts.is_month_start - def time_is_month_end(self, tz): + def time_is_month_end(self, tz, freq): self.ts.is_month_end - def time_is_quarter_start(self, tz): + def time_is_quarter_start(self, tz, freq): self.ts.is_quarter_start - def time_is_quarter_end(self, tz): + def time_is_quarter_end(self, tz, freq): self.ts.is_quarter_end - def time_is_year_start(self, tz): + def time_is_year_start(self, tz, freq): self.ts.is_quarter_end - def time_is_year_end(self, tz): + def time_is_year_end(self, tz, freq): self.ts.is_quarter_end - def time_is_leap_year(self, tz): + def time_is_leap_year(self, tz, freq): self.ts.is_quarter_end - def time_microsecond(self, tz): + def time_microsecond(self, tz, freq): self.ts.microsecond diff --git a/pandas/tests/scalar/test_timestamp.py b/pandas/tests/scalar/test_timestamp.py index 545ed7f1ebbf3..992f211229441 100644 --- a/pandas/tests/scalar/test_timestamp.py +++ b/pandas/tests/scalar/test_timestamp.py @@ -47,6 +47,28 @@ def test_overflow_offset(self): stamp - offset +class TestTimestampProperties(object): + + def test_properties_business(self): + ts = Timestamp('2017-10-01', freq='B') + control = Timestamp('2017-10-01') + assert ts.dayofweek == 6 + assert not ts.is_month_start # not a weekday + assert not ts.is_quarter_start # not a weekday + # Control case: non-business is month/qtr start + assert control.is_month_start + assert control.is_quarter_start + + ts = Timestamp('2017-09-30', freq='B') + control = Timestamp('2017-09-30') + assert ts.dayofweek == 5 + assert not ts.is_month_end # not a weekday + assert not ts.is_quarter_end # not a weekday + # Control case: non-business is month/qtr start + assert control.is_month_end + assert control.is_quarter_end + + class TestTimestamp(object): def test_constructor(self):