From f41c1161c23ba0efa7e102f71919007ab8448cdc Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 6 Jul 2018 11:39:00 -0500 Subject: [PATCH 1/2] move properties and funcs that are easily wrapped --- pandas/core/arrays/datetimes.py | 442 +++++++++++++++++++++++++++++- pandas/core/indexes/datetimes.py | 454 +++---------------------------- 2 files changed, 482 insertions(+), 414 deletions(-) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index e5159141d4b59..0e3e50278bbec 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -6,7 +6,7 @@ from pandas._libs import tslib from pandas._libs.tslib import Timestamp, NaT, iNaT -from pandas._libs.tslibs import conversion, timezones +from pandas._libs.tslibs import conversion, fields, timezones from pandas.util._decorators import cache_readonly @@ -16,6 +16,44 @@ from .datetimelike import DatetimeLikeArrayMixin +def _field_accessor(name, field, docstring=None): + def f(self): + values = self.asi8 + if self.tz is not None: + if self.tz is not utc: + values = self._local_timestamps() + + if field in self._bool_ops: + if field.endswith(('start', 'end')): + freq = self.freq + month_kw = 12 + if freq: + kwds = freq.kwds + month_kw = kwds.get('startingMonth', kwds.get('month', 12)) + + result = fields.get_start_end_field(values, field, + self.freqstr, month_kw) + else: + result = fields.get_date_field(values, field) + + # these return a boolean by-definition + return result + + if field in self._object_ops: + result = fields.get_date_name_field(values, field) + result = self._maybe_mask_results(result) + + else: + result = fields.get_date_field(values, field) + result = self._maybe_mask_results(result, convert='float64') + + return result + + f.__name__ = name + f.__doc__ = docstring + return property(f) + + class DatetimeArrayMixin(DatetimeLikeArrayMixin): """ Assumes that subclass __new__/__init__ defines: @@ -23,6 +61,10 @@ class DatetimeArrayMixin(DatetimeLikeArrayMixin): _freq _data """ + _bool_ops = ['is_month_start', 'is_month_end', + 'is_quarter_start', 'is_quarter_end', 'is_year_start', + 'is_year_end', 'is_leap_year'] + _object_ops = ['weekday_name', 'freq', 'tz'] # ----------------------------------------------------------------- # Descriptive Properties @@ -131,7 +173,7 @@ def _local_timestamps(self): return result.take(reverse) # ---------------------------------------------------------------- - # Conversion Methods - Vectorized analogues of Timedelta methods + # Conversion Methods - Vectorized analogues of Timestamp methods def to_pydatetime(self): """ @@ -143,3 +185,399 @@ def to_pydatetime(self): datetimes : ndarray """ return tslib.ints_to_pydatetime(self.asi8, tz=self.tz) + + # ----------------------------------------------------------------- + # Properties - Vectorized Timestamp Properties/Methods + + def month_name(self, locale=None): + """ + Return the month names of the DateTimeIndex with specified locale. + + Parameters + ---------- + locale : string, default None (English locale) + locale determining the language in which to return the month name + + Returns + ------- + month_names : Index + Index of month names + + .. versionadded:: 0.23.0 + """ + if self.tz is not None and self.tz is not utc: + values = self._local_timestamps() + else: + values = self.asi8 + + result = fields.get_date_name_field(values, 'month_name', + locale=locale) + result = self._maybe_mask_results(result) + return result + + def day_name(self, locale=None): + """ + Return the day names of the DateTimeIndex with specified locale. + + Parameters + ---------- + locale : string, default None (English locale) + locale determining the language in which to return the day name + + Returns + ------- + month_names : Index + Index of day names + + .. versionadded:: 0.23.0 + """ + if self.tz is not None and self.tz is not utc: + values = self._local_timestamps() + else: + values = self.asi8 + + result = fields.get_date_name_field(values, 'day_name', + locale=locale) + result = self._maybe_mask_results(result) + return result + + @property + def time(self): + """ + Returns numpy array of datetime.time. The time part of the Timestamps. + """ + # If the Timestamps have a timezone that is not UTC, + # convert them into their i8 representation while + # keeping their timezone and not using UTC + if self.tz is not None and self.tz is not utc: + timestamps = self._local_timestamps() + else: + timestamps = self.asi8 + + return tslib.ints_to_pydatetime(timestamps, box="time") + + @property + def date(self): + """ + Returns numpy array of python datetime.date objects (namely, the date + part of Timestamps without timezone information). + """ + # If the Timestamps have a timezone that is not UTC, + # convert them into their i8 representation while + # keeping their timezone and not using UTC + if self.tz is not None and self.tz is not utc: + timestamps = self._local_timestamps() + else: + timestamps = self.asi8 + + return tslib.ints_to_pydatetime(timestamps, box="date") + + year = _field_accessor('year', 'Y', "The year of the datetime") + month = _field_accessor('month', 'M', + "The month as January=1, December=12") + day = _field_accessor('day', 'D', "The days of the datetime") + hour = _field_accessor('hour', 'h', "The hours of the datetime") + minute = _field_accessor('minute', 'm', "The minutes of the datetime") + second = _field_accessor('second', 's', "The seconds of the datetime") + microsecond = _field_accessor('microsecond', 'us', + "The microseconds of the datetime") + nanosecond = _field_accessor('nanosecond', 'ns', + "The nanoseconds of the datetime") + weekofyear = _field_accessor('weekofyear', 'woy', + "The week ordinal of the year") + week = weekofyear + dayofweek = _field_accessor('dayofweek', 'dow', + "The day of the week with Monday=0, Sunday=6") + weekday = dayofweek + + weekday_name = _field_accessor( + 'weekday_name', + 'weekday_name', + "The name of day in a week (ex: Friday)\n\n.. deprecated:: 0.23.0") + + dayofyear = _field_accessor('dayofyear', 'doy', + "The ordinal day of the year") + quarter = _field_accessor('quarter', 'q', "The quarter of the date") + days_in_month = _field_accessor( + 'days_in_month', + 'dim', + "The number of days in the month") + daysinmonth = days_in_month + is_month_start = _field_accessor( + 'is_month_start', + 'is_month_start', + "Logical indicating if first day of month (defined by frequency)") + is_month_end = _field_accessor( + 'is_month_end', + 'is_month_end', + """ + Indicator for whether the date is the last day of the month. + + Returns + ------- + Series or array + For Series, returns a Series with boolean values. For + DatetimeIndex, returns a boolean array. + + See Also + -------- + is_month_start : Indicator for whether the date is the first day + of the month. + + Examples + -------- + This method is available on Series with datetime values under + the ``.dt`` accessor, and directly on DatetimeIndex. + + >>> dates = pd.Series(pd.date_range("2018-02-27", periods=3)) + >>> dates + 0 2018-02-27 + 1 2018-02-28 + 2 2018-03-01 + dtype: datetime64[ns] + >>> dates.dt.is_month_end + 0 False + 1 True + 2 False + dtype: bool + + >>> idx = pd.date_range("2018-02-27", periods=3) + >>> idx.is_month_end + array([False, True, False], dtype=bool) + """) + is_quarter_start = _field_accessor( + 'is_quarter_start', + 'is_quarter_start', + """ + Indicator for whether the date is the first day of a quarter. + + Returns + ------- + is_quarter_start : Series or DatetimeIndex + The same type as the original data with boolean values. Series will + have the same name and index. DatetimeIndex will have the same + name. + + See Also + -------- + quarter : Return the quarter of the date. + is_quarter_end : Similar property for indicating the quarter start. + + Examples + -------- + This method is available on Series with datetime values under + the ``.dt`` accessor, and directly on DatetimeIndex. + + >>> df = pd.DataFrame({'dates': pd.date_range("2017-03-30", + ... periods=4)}) + >>> df.assign(quarter=df.dates.dt.quarter, + ... is_quarter_start=df.dates.dt.is_quarter_start) + dates quarter is_quarter_start + 0 2017-03-30 1 False + 1 2017-03-31 1 False + 2 2017-04-01 2 True + 3 2017-04-02 2 False + + >>> idx = pd.date_range('2017-03-30', periods=4) + >>> idx + DatetimeIndex(['2017-03-30', '2017-03-31', '2017-04-01', '2017-04-02'], + dtype='datetime64[ns]', freq='D') + + >>> idx.is_quarter_start + array([False, False, True, False]) + """) + is_quarter_end = _field_accessor( + 'is_quarter_end', + 'is_quarter_end', + """ + Indicator for whether the date is the last day of a quarter. + + Returns + ------- + is_quarter_end : Series or DatetimeIndex + The same type as the original data with boolean values. Series will + have the same name and index. DatetimeIndex will have the same + name. + + See Also + -------- + quarter : Return the quarter of the date. + is_quarter_start : Similar property indicating the quarter start. + + Examples + -------- + This method is available on Series with datetime values under + the ``.dt`` accessor, and directly on DatetimeIndex. + + >>> df = pd.DataFrame({'dates': pd.date_range("2017-03-30", + ... periods=4)}) + >>> df.assign(quarter=df.dates.dt.quarter, + ... is_quarter_end=df.dates.dt.is_quarter_end) + dates quarter is_quarter_end + 0 2017-03-30 1 False + 1 2017-03-31 1 True + 2 2017-04-01 2 False + 3 2017-04-02 2 False + + >>> idx = pd.date_range('2017-03-30', periods=4) + >>> idx + DatetimeIndex(['2017-03-30', '2017-03-31', '2017-04-01', '2017-04-02'], + dtype='datetime64[ns]', freq='D') + + >>> idx.is_quarter_end + array([False, True, False, False]) + """) + is_year_start = _field_accessor( + 'is_year_start', + 'is_year_start', + """ + Indicate whether the date is the first day of a year. + + Returns + ------- + Series or DatetimeIndex + The same type as the original data with boolean values. Series will + have the same name and index. DatetimeIndex will have the same + name. + + See Also + -------- + is_year_end : Similar property indicating the last day of the year. + + Examples + -------- + This method is available on Series with datetime values under + the ``.dt`` accessor, and directly on DatetimeIndex. + + >>> dates = pd.Series(pd.date_range("2017-12-30", periods=3)) + >>> dates + 0 2017-12-30 + 1 2017-12-31 + 2 2018-01-01 + dtype: datetime64[ns] + + >>> dates.dt.is_year_start + 0 False + 1 False + 2 True + dtype: bool + + >>> idx = pd.date_range("2017-12-30", periods=3) + >>> idx + DatetimeIndex(['2017-12-30', '2017-12-31', '2018-01-01'], + dtype='datetime64[ns]', freq='D') + + >>> idx.is_year_start + array([False, False, True]) + """) + is_year_end = _field_accessor( + 'is_year_end', + 'is_year_end', + """ + Indicate whether the date is the last day of the year. + + Returns + ------- + Series or DatetimeIndex + The same type as the original data with boolean values. Series will + have the same name and index. DatetimeIndex will have the same + name. + + See Also + -------- + is_year_start : Similar property indicating the start of the year. + + Examples + -------- + This method is available on Series with datetime values under + the ``.dt`` accessor, and directly on DatetimeIndex. + + >>> dates = pd.Series(pd.date_range("2017-12-30", periods=3)) + >>> dates + 0 2017-12-30 + 1 2017-12-31 + 2 2018-01-01 + dtype: datetime64[ns] + + >>> dates.dt.is_year_end + 0 False + 1 True + 2 False + dtype: bool + + >>> idx = pd.date_range("2017-12-30", periods=3) + >>> idx + DatetimeIndex(['2017-12-30', '2017-12-31', '2018-01-01'], + dtype='datetime64[ns]', freq='D') + + >>> idx.is_year_end + array([False, True, False]) + """) + is_leap_year = _field_accessor( + 'is_leap_year', + 'is_leap_year', + """ + Boolean indicator if the date belongs to a leap year. + + A leap year is a year, which has 366 days (instead of 365) including + 29th of February as an intercalary day. + Leap years are years which are multiples of four with the exception + of years divisible by 100 but not by 400. + + Returns + ------- + Series or ndarray + Booleans indicating if dates belong to a leap year. + + Examples + -------- + This method is available on Series with datetime values under + the ``.dt`` accessor, and directly on DatetimeIndex. + + >>> idx = pd.date_range("2012-01-01", "2015-01-01", freq="Y") + >>> idx + DatetimeIndex(['2012-12-31', '2013-12-31', '2014-12-31'], + dtype='datetime64[ns]', freq='A-DEC') + >>> idx.is_leap_year + array([ True, False, False], dtype=bool) + + >>> dates = pd.Series(idx) + >>> dates_series + 0 2012-12-31 + 1 2013-12-31 + 2 2014-12-31 + dtype: datetime64[ns] + >>> dates_series.dt.is_leap_year + 0 True + 1 False + 2 False + dtype: bool + """) + + def to_julian_date(self): + """ + Convert DatetimeIndex to float64 ndarray of Julian Dates. + 0 Julian date is noon January 1, 4713 BC. + http://en.wikipedia.org/wiki/Julian_day + """ + + # http://mysite.verizon.net/aesir_research/date/jdalg2.htm + year = np.asarray(self.year) + month = np.asarray(self.month) + day = np.asarray(self.day) + testarr = month < 3 + year[testarr] -= 1 + month[testarr] += 12 + return (day + + np.fix((153 * month - 457) / 5) + + 365 * year + + np.floor(year / 4) - + np.floor(year / 100) + + np.floor(year / 400) + + 1721118.5 + + (self.hour + + self.minute / 60.0 + + self.second / 3600.0 + + self.microsecond / 3600.0 / 1e+6 + + self.nanosecond / 3600.0 / 1e+9 + ) / 24.0) diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 05f7af6383211..d782dd197dc2a 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -62,44 +62,29 @@ # -------- some conversion wrapper functions -def _field_accessor(name, field, docstring=None): +def _wrap_field_accessor(name): + fget = getattr(DatetimeArrayMixin, name).fget def f(self): - values = self.asi8 - if self.tz is not None: - if self.tz is not utc: - values = self._local_timestamps() - - if field in self._bool_ops: - if field in ['is_month_start', 'is_month_end', - 'is_quarter_start', 'is_quarter_end', - 'is_year_start', 'is_year_end']: - freq = self.freq - month_kw = 12 - if freq: - kwds = freq.kwds - month_kw = kwds.get('startingMonth', kwds.get('month', 12)) - - result = fields.get_start_end_field(values, field, - self.freqstr, month_kw) - else: - result = fields.get_date_field(values, field) - - # these return a boolean by-definition + result = fget(self) + if is_bool_dtype(result): return result + return Index(result, name=self.name) - if field in self._object_ops: - result = fields.get_date_name_field(values, field) - result = self._maybe_mask_results(result) + f.__name__ = name + f.__doc__ = fget.__doc__ + return property(f) - else: - result = fields.get_date_field(values, field) - result = self._maybe_mask_results(result, convert='float64') +def _wrap_in_index(name): + meth = getattr(DatetimeArrayMixin, name) + + def func(self, *args, **kwargs): + result = meth(self, *args, **kwargs) return Index(result, name=self.name) - f.__name__ = name - f.__doc__ = docstring - return property(f) + func.__doc__ = meth.__doc__ + func.__name__ = name + return func def _dt_index_cmp(opname, cls): @@ -1649,320 +1634,32 @@ def slice_indexer(self, start=None, end=None, step=None, kind=None): else: raise - year = _field_accessor('year', 'Y', "The year of the datetime") - month = _field_accessor('month', 'M', - "The month as January=1, December=12") - day = _field_accessor('day', 'D', "The days of the datetime") - hour = _field_accessor('hour', 'h', "The hours of the datetime") - minute = _field_accessor('minute', 'm', "The minutes of the datetime") - second = _field_accessor('second', 's', "The seconds of the datetime") - microsecond = _field_accessor('microsecond', 'us', - "The microseconds of the datetime") - nanosecond = _field_accessor('nanosecond', 'ns', - "The nanoseconds of the datetime") - weekofyear = _field_accessor('weekofyear', 'woy', - "The week ordinal of the year") + year = _wrap_field_accessor('year') + month = _wrap_field_accessor('month') + day = _wrap_field_accessor('day') + hour = _wrap_field_accessor('hour') + minute = _wrap_field_accessor('minute') + second = _wrap_field_accessor('second') + microsecond = _wrap_field_accessor('microsecond') + nanosecond = _wrap_field_accessor('nanosecond') + weekofyear = _wrap_field_accessor('weekofyear') week = weekofyear - dayofweek = _field_accessor('dayofweek', 'dow', - "The day of the week with Monday=0, Sunday=6") + dayofweek = _wrap_field_accessor('dayofweek') weekday = dayofweek - weekday_name = _field_accessor( - 'weekday_name', - 'weekday_name', - "The name of day in a week (ex: Friday)\n\n.. deprecated:: 0.23.0") - - dayofyear = _field_accessor('dayofyear', 'doy', - "The ordinal day of the year") - quarter = _field_accessor('quarter', 'q', "The quarter of the date") - days_in_month = _field_accessor( - 'days_in_month', - 'dim', - "The number of days in the month") - daysinmonth = days_in_month - is_month_start = _field_accessor( - 'is_month_start', - 'is_month_start', - "Logical indicating if first day of month (defined by frequency)") - is_month_end = _field_accessor( - 'is_month_end', - 'is_month_end', - """ - Indicator for whether the date is the last day of the month. - - Returns - ------- - Series or array - For Series, returns a Series with boolean values. For - DatetimeIndex, returns a boolean array. - - See Also - -------- - is_month_start : Indicator for whether the date is the first day - of the month. - - Examples - -------- - This method is available on Series with datetime values under - the ``.dt`` accessor, and directly on DatetimeIndex. - - >>> dates = pd.Series(pd.date_range("2018-02-27", periods=3)) - >>> dates - 0 2018-02-27 - 1 2018-02-28 - 2 2018-03-01 - dtype: datetime64[ns] - >>> dates.dt.is_month_end - 0 False - 1 True - 2 False - dtype: bool - - >>> idx = pd.date_range("2018-02-27", periods=3) - >>> idx.is_month_end - array([False, True, False], dtype=bool) - """) - is_quarter_start = _field_accessor( - 'is_quarter_start', - 'is_quarter_start', - """ - Indicator for whether the date is the first day of a quarter. - - Returns - ------- - is_quarter_start : Series or DatetimeIndex - The same type as the original data with boolean values. Series will - have the same name and index. DatetimeIndex will have the same - name. - - See Also - -------- - quarter : Return the quarter of the date. - is_quarter_end : Similar property for indicating the quarter start. - - Examples - -------- - This method is available on Series with datetime values under - the ``.dt`` accessor, and directly on DatetimeIndex. - - >>> df = pd.DataFrame({'dates': pd.date_range("2017-03-30", - ... periods=4)}) - >>> df.assign(quarter=df.dates.dt.quarter, - ... is_quarter_start=df.dates.dt.is_quarter_start) - dates quarter is_quarter_start - 0 2017-03-30 1 False - 1 2017-03-31 1 False - 2 2017-04-01 2 True - 3 2017-04-02 2 False - - >>> idx = pd.date_range('2017-03-30', periods=4) - >>> idx - DatetimeIndex(['2017-03-30', '2017-03-31', '2017-04-01', '2017-04-02'], - dtype='datetime64[ns]', freq='D') - - >>> idx.is_quarter_start - array([False, False, True, False]) - """) - is_quarter_end = _field_accessor( - 'is_quarter_end', - 'is_quarter_end', - """ - Indicator for whether the date is the last day of a quarter. - - Returns - ------- - is_quarter_end : Series or DatetimeIndex - The same type as the original data with boolean values. Series will - have the same name and index. DatetimeIndex will have the same - name. - - See Also - -------- - quarter : Return the quarter of the date. - is_quarter_start : Similar property indicating the quarter start. - - Examples - -------- - This method is available on Series with datetime values under - the ``.dt`` accessor, and directly on DatetimeIndex. - - >>> df = pd.DataFrame({'dates': pd.date_range("2017-03-30", - ... periods=4)}) - >>> df.assign(quarter=df.dates.dt.quarter, - ... is_quarter_end=df.dates.dt.is_quarter_end) - dates quarter is_quarter_end - 0 2017-03-30 1 False - 1 2017-03-31 1 True - 2 2017-04-01 2 False - 3 2017-04-02 2 False - - >>> idx = pd.date_range('2017-03-30', periods=4) - >>> idx - DatetimeIndex(['2017-03-30', '2017-03-31', '2017-04-01', '2017-04-02'], - dtype='datetime64[ns]', freq='D') - - >>> idx.is_quarter_end - array([False, True, False, False]) - """) - is_year_start = _field_accessor( - 'is_year_start', - 'is_year_start', - """ - Indicate whether the date is the first day of a year. - - Returns - ------- - Series or DatetimeIndex - The same type as the original data with boolean values. Series will - have the same name and index. DatetimeIndex will have the same - name. - - See Also - -------- - is_year_end : Similar property indicating the last day of the year. - - Examples - -------- - This method is available on Series with datetime values under - the ``.dt`` accessor, and directly on DatetimeIndex. - - >>> dates = pd.Series(pd.date_range("2017-12-30", periods=3)) - >>> dates - 0 2017-12-30 - 1 2017-12-31 - 2 2018-01-01 - dtype: datetime64[ns] - - >>> dates.dt.is_year_start - 0 False - 1 False - 2 True - dtype: bool + weekday_name = _wrap_field_accessor('weekday_name') - >>> idx = pd.date_range("2017-12-30", periods=3) - >>> idx - DatetimeIndex(['2017-12-30', '2017-12-31', '2018-01-01'], - dtype='datetime64[ns]', freq='D') - - >>> idx.is_year_start - array([False, False, True]) - """) - is_year_end = _field_accessor( - 'is_year_end', - 'is_year_end', - """ - Indicate whether the date is the last day of the year. - - Returns - ------- - Series or DatetimeIndex - The same type as the original data with boolean values. Series will - have the same name and index. DatetimeIndex will have the same - name. - - See Also - -------- - is_year_start : Similar property indicating the start of the year. - - Examples - -------- - This method is available on Series with datetime values under - the ``.dt`` accessor, and directly on DatetimeIndex. - - >>> dates = pd.Series(pd.date_range("2017-12-30", periods=3)) - >>> dates - 0 2017-12-30 - 1 2017-12-31 - 2 2018-01-01 - dtype: datetime64[ns] - - >>> dates.dt.is_year_end - 0 False - 1 True - 2 False - dtype: bool - - >>> idx = pd.date_range("2017-12-30", periods=3) - >>> idx - DatetimeIndex(['2017-12-30', '2017-12-31', '2018-01-01'], - dtype='datetime64[ns]', freq='D') - - >>> idx.is_year_end - array([False, True, False]) - """) - is_leap_year = _field_accessor( - 'is_leap_year', - 'is_leap_year', - """ - Boolean indicator if the date belongs to a leap year. - - A leap year is a year, which has 366 days (instead of 365) including - 29th of February as an intercalary day. - Leap years are years which are multiples of four with the exception - of years divisible by 100 but not by 400. - - Returns - ------- - Series or ndarray - Booleans indicating if dates belong to a leap year. - - Examples - -------- - This method is available on Series with datetime values under - the ``.dt`` accessor, and directly on DatetimeIndex. - - >>> idx = pd.date_range("2012-01-01", "2015-01-01", freq="Y") - >>> idx - DatetimeIndex(['2012-12-31', '2013-12-31', '2014-12-31'], - dtype='datetime64[ns]', freq='A-DEC') - >>> idx.is_leap_year - array([ True, False, False], dtype=bool) - - >>> dates = pd.Series(idx) - >>> dates_series - 0 2012-12-31 - 1 2013-12-31 - 2 2014-12-31 - dtype: datetime64[ns] - >>> dates_series.dt.is_leap_year - 0 True - 1 False - 2 False - dtype: bool - """) - - @property - def time(self): - """ - Returns numpy array of datetime.time. The time part of the Timestamps. - """ - - # If the Timestamps have a timezone that is not UTC, - # convert them into their i8 representation while - # keeping their timezone and not using UTC - if (self.tz is not None and self.tz is not utc): - timestamps = self._local_timestamps() - else: - timestamps = self.asi8 - - return libts.ints_to_pydatetime(timestamps, box="time") - - @property - def date(self): - """ - Returns numpy array of python datetime.date objects (namely, the date - part of Timestamps without timezone information). - """ - - # If the Timestamps have a timezone that is not UTC, - # convert them into their i8 representation while - # keeping their timezone and not using UTC - if (self.tz is not None and self.tz is not utc): - timestamps = self._local_timestamps() - else: - timestamps = self.asi8 - - return libts.ints_to_pydatetime(timestamps, box="date") + dayofyear = _wrap_field_accessor('dayofyear') + quarter = _wrap_field_accessor('quarter') + days_in_month = _wrap_field_accessor('days_in_month') + daysinmonth = days_in_month + is_month_start = _wrap_field_accessor('is_month_start') + is_month_end = _wrap_field_accessor('is_month_end') + is_quarter_start = _wrap_field_accessor('is_quarter_start') + is_quarter_end = _wrap_field_accessor('is_quarter_end') + is_year_start = _wrap_field_accessor('is_year_start') + is_year_end = _wrap_field_accessor('is_year_end') + is_leap_year = _wrap_field_accessor('is_leap_year') def normalize(self): """ @@ -2375,79 +2072,12 @@ def to_julian_date(self): 0 Julian date is noon January 1, 4713 BC. http://en.wikipedia.org/wiki/Julian_day """ + result = DatetimeArrayMixin.to_julian_date(self) + return Float64Index(result) - # http://mysite.verizon.net/aesir_research/date/jdalg2.htm - year = np.asarray(self.year) - month = np.asarray(self.month) - day = np.asarray(self.day) - testarr = month < 3 - year[testarr] -= 1 - month[testarr] += 12 - return Float64Index(day + - np.fix((153 * month - 457) / 5) + - 365 * year + - np.floor(year / 4) - - np.floor(year / 100) + - np.floor(year / 400) + - 1721118.5 + - (self.hour + - self.minute / 60.0 + - self.second / 3600.0 + - self.microsecond / 3600.0 / 1e+6 + - self.nanosecond / 3600.0 / 1e+9 - ) / 24.0) - - def month_name(self, locale=None): - """ - Return the month names of the DateTimeIndex with specified locale. - - Parameters - ---------- - locale : string, default None (English locale) - locale determining the language in which to return the month name + month_name = _wrap_in_index("month_name") + day_name = _wrap_in_index("day_name") - Returns - ------- - month_names : Index - Index of month names - - .. versionadded:: 0.23.0 - """ - values = self.asi8 - if self.tz is not None: - if self.tz is not utc: - values = self._local_timestamps() - - result = fields.get_date_name_field(values, 'month_name', - locale=locale) - result = self._maybe_mask_results(result) - return Index(result, name=self.name) - - def day_name(self, locale=None): - """ - Return the day names of the DateTimeIndex with specified locale. - - Parameters - ---------- - locale : string, default None (English locale) - locale determining the language in which to return the day name - - Returns - ------- - month_names : Index - Index of day names - - .. versionadded:: 0.23.0 - """ - values = self.asi8 - if self.tz is not None: - if self.tz is not utc: - values = self._local_timestamps() - - result = fields.get_date_name_field(values, 'day_name', - locale=locale) - result = self._maybe_mask_results(result) - return Index(result, name=self.name) DatetimeIndex._add_comparison_methods() From 74ac363810314216a757336d4a9cc9c8d99c1892 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 6 Jul 2018 12:09:25 -0500 Subject: [PATCH 2/2] move properties and methods to PeriodArray and TimedeltaArray mixins --- pandas/core/arrays/period.py | 36 ++++++++++++++++++++++++++++- pandas/core/arrays/timedelta.py | 27 ++++++++++++++++++++++ pandas/core/indexes/datetimes.py | 2 +- pandas/core/indexes/period.py | 38 +++++++++++++++---------------- pandas/core/indexes/timedeltas.py | 28 ++++++++--------------- 5 files changed, 90 insertions(+), 41 deletions(-) diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 01acaad228067..78012688673cf 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -7,7 +7,8 @@ from pandas._libs import lib from pandas._libs.tslib import NaT from pandas._libs.tslibs.period import ( - Period, IncompatibleFrequency, DIFFERENT_FREQ_INDEX) + Period, IncompatibleFrequency, DIFFERENT_FREQ_INDEX, + get_period_field_arr) from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds from pandas.util._decorators import cache_readonly @@ -20,6 +21,17 @@ from .datetimelike import DatetimeLikeArrayMixin +def _field_accessor(name, alias, docstring=None): + def f(self): + base, mult = frequencies.get_freq_code(self.freq) + result = get_period_field_arr(alias, self._ndarray_values, base) + return result + + f.__name__ = name + f.__doc__ = docstring + return property(f) + + class PeriodArrayMixin(DatetimeLikeArrayMixin): @property def _box_func(self): @@ -52,6 +64,28 @@ def freq(self, value): FutureWarning, stacklevel=2) self._freq = value + # -------------------------------------------------------------------- + # Vectorized analogues of Period properties + + year = _field_accessor('year', 0, "The year of the period") + month = _field_accessor('month', 3, "The month as January=1, December=12") + day = _field_accessor('day', 4, "The days of the period") + hour = _field_accessor('hour', 5, "The hour of the period") + minute = _field_accessor('minute', 6, "The minute of the period") + second = _field_accessor('second', 7, "The second of the period") + weekofyear = _field_accessor('week', 8, "The week ordinal of the year") + week = weekofyear + dayofweek = _field_accessor('dayofweek', 10, + "The day of the week with Monday=0, Sunday=6") + weekday = dayofweek + dayofyear = day_of_year = _field_accessor('dayofyear', 9, + "The ordinal day of the year") + quarter = _field_accessor('quarter', 2, "The quarter of the date") + qyear = _field_accessor('qyear', 1) + days_in_month = _field_accessor('days_in_month', 11, + "The number of days in the month") + daysinmonth = days_in_month + # ------------------------------------------------------------------ # Arithmetic Methods diff --git a/pandas/core/arrays/timedelta.py b/pandas/core/arrays/timedelta.py index cc3478e504efb..3bd2a0f0281e0 100644 --- a/pandas/core/arrays/timedelta.py +++ b/pandas/core/arrays/timedelta.py @@ -2,6 +2,7 @@ from pandas._libs import tslib from pandas._libs.tslib import Timedelta, NaT +from pandas._libs.tslibs.fields import get_timedelta_field from pandas.core.dtypes.common import _TD_DTYPE @@ -10,6 +11,20 @@ from .datetimelike import DatetimeLikeArrayMixin +def _field_accessor(name, alias, docstring=None): + def f(self): + values = self.asi8 + result = get_timedelta_field(values, alias) + if self.hasnans: + result = self._maybe_mask_results(result, convert='float64') + + return result + + f.__name__ = name + f.__doc__ = docstring + return property(f) + + class TimedeltaArrayMixin(DatetimeLikeArrayMixin): @property def _box_func(self): @@ -46,3 +61,15 @@ def to_pytimedelta(self): datetimes : ndarray """ return tslib.ints_to_pytimedelta(self.asi8) + + days = _field_accessor("days", "days", + " Number of days for each element. ") + seconds = _field_accessor("seconds", "seconds", + " Number of seconds (>= 0 and less than 1 day) " + "for each element. ") + microseconds = _field_accessor("microseconds", "microseconds", + "\nNumber of microseconds (>= 0 and less " + "than 1 second) for each\nelement. ") + nanoseconds = _field_accessor("nanoseconds", "nanoseconds", + "\nNumber of nanoseconds (>= 0 and less " + "than 1 microsecond) for each\nelement.\n") diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index d782dd197dc2a..1195bece809f3 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -64,6 +64,7 @@ def _wrap_field_accessor(name): fget = getattr(DatetimeArrayMixin, name).fget + def f(self): result = fget(self) if is_bool_dtype(result): @@ -2079,7 +2080,6 @@ def to_julian_date(self): day_name = _wrap_in_index("day_name") - DatetimeIndex._add_comparison_methods() DatetimeIndex._add_numeric_methods_disabled() DatetimeIndex._add_logical_methods_disabled() diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 143cb8cd3ff6e..16d003812f097 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -29,7 +29,6 @@ from pandas._libs.lib import infer_dtype from pandas._libs import tslib, index as libindex from pandas._libs.tslibs.period import (Period, IncompatibleFrequency, - get_period_field_arr, DIFFERENT_FREQ_INDEX, _validate_end_alias, _quarter_to_myear) from pandas._libs.tslibs.fields import isleapyear_arr @@ -51,13 +50,15 @@ dict(target_klass='PeriodIndex or list of Periods')) -def _field_accessor(name, alias, docstring=None): +def _wrap_field_accessor(name): + fget = getattr(PeriodArrayMixin, name).fget + def f(self): - base, mult = _gfc(self.freq) - result = get_period_field_arr(alias, self._ndarray_values, base) + result = fget(self) return Index(result, name=self.name) + f.__name__ = name - f.__doc__ = docstring + f.__doc__ = fget.__doc__ return property(f) @@ -608,23 +609,20 @@ def asfreq(self, freq=None, how='E'): return self._simple_new(new_data, self.name, freq=freq) - year = _field_accessor('year', 0, "The year of the period") - month = _field_accessor('month', 3, "The month as January=1, December=12") - day = _field_accessor('day', 4, "The days of the period") - hour = _field_accessor('hour', 5, "The hour of the period") - minute = _field_accessor('minute', 6, "The minute of the period") - second = _field_accessor('second', 7, "The second of the period") - weekofyear = _field_accessor('week', 8, "The week ordinal of the year") + year = _wrap_field_accessor('year') + month = _wrap_field_accessor('month') + day = _wrap_field_accessor('day') + hour = _wrap_field_accessor('hour') + minute = _wrap_field_accessor('minute') + second = _wrap_field_accessor('second') + weekofyear = _wrap_field_accessor('week') week = weekofyear - dayofweek = _field_accessor('dayofweek', 10, - "The day of the week with Monday=0, Sunday=6") + dayofweek = _wrap_field_accessor('dayofweek') weekday = dayofweek - dayofyear = day_of_year = _field_accessor('dayofyear', 9, - "The ordinal day of the year") - quarter = _field_accessor('quarter', 2, "The quarter of the date") - qyear = _field_accessor('qyear', 1) - days_in_month = _field_accessor('days_in_month', 11, - "The number of days in the month") + dayofyear = day_of_year = _wrap_field_accessor('dayofyear') + quarter = _wrap_field_accessor('quarter') + qyear = _wrap_field_accessor('qyear') + days_in_month = _wrap_field_accessor('days_in_month') daysinmonth = days_in_month @property diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index 520504affaa02..cd819191ef26e 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -37,20 +37,17 @@ from pandas._libs import (lib, index as libindex, join as libjoin, Timedelta, NaT, iNaT) from pandas._libs.tslibs.timedeltas import array_to_timedelta64 -from pandas._libs.tslibs.fields import get_timedelta_field -def _field_accessor(name, alias, docstring=None): - def f(self): - values = self.asi8 - result = get_timedelta_field(values, alias) - if self.hasnans: - result = self._maybe_mask_results(result, convert='float64') +def _wrap_field_accessor(name): + fget = getattr(TimedeltaArrayMixin, name).fget + def f(self): + result = fget(self) return Index(result, name=self.name) f.__name__ = name - f.__doc__ = docstring + f.__doc__ = fget.__doc__ return property(f) @@ -442,17 +439,10 @@ def _format_native_types(self, na_rep=u('NaT'), nat_rep=na_rep, justify='all').get_result() - days = _field_accessor("days", "days", - " Number of days for each element. ") - seconds = _field_accessor("seconds", "seconds", - " Number of seconds (>= 0 and less than 1 day) " - "for each element. ") - microseconds = _field_accessor("microseconds", "microseconds", - "\nNumber of microseconds (>= 0 and less " - "than 1 second) for each\nelement. ") - nanoseconds = _field_accessor("nanoseconds", "nanoseconds", - "\nNumber of nanoseconds (>= 0 and less " - "than 1 microsecond) for each\nelement.\n") + days = _wrap_field_accessor("days") + seconds = _wrap_field_accessor("seconds") + microseconds = _wrap_field_accessor("microseconds") + nanoseconds = _wrap_field_accessor("nanoseconds") @property def components(self):