diff --git a/doc/source/api.rst b/doc/source/api.rst index 3a6b31ceeeece..bfacd220ce6f2 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -469,6 +469,7 @@ These can be accessed like ``Series.dt.``. Series.dt.days_in_month Series.dt.tz Series.dt.freq + Series.dt.weekday_name **Datetime Methods** @@ -1477,6 +1478,7 @@ Time/Date Components DatetimeIndex.is_year_start DatetimeIndex.is_year_end DatetimeIndex.inferred_freq + DatetimeIndex.weekday_name Selecting ~~~~~~~~~ diff --git a/doc/source/timeseries.rst b/doc/source/timeseries.rst index 80a4774e02e69..90589a0643446 100644 --- a/doc/source/timeseries.rst +++ b/doc/source/timeseries.rst @@ -491,6 +491,7 @@ There are several time/date properties that one can access from ``Timestamp`` or is_quarter_end,"Logical indicating if last day of quarter (defined by frequency)" is_year_start,"Logical indicating if first day of year (defined by frequency)" is_year_end,"Logical indicating if last day of year (defined by frequency)" + weekday_name,"The name of day in a week (ex: Friday)" Furthermore, if you have a ``Series`` with datetimelike values, then you can access these properties via the ``.dt`` accessor, see the :ref:`docs ` diff --git a/doc/source/whatsnew/v0.18.0.txt b/doc/source/whatsnew/v0.18.0.txt index 193d8f83ded79..de8276e283cb5 100644 --- a/doc/source/whatsnew/v0.18.0.txt +++ b/doc/source/whatsnew/v0.18.0.txt @@ -152,6 +152,7 @@ Other enhancements - ``Series`` gained an ``is_unique`` attribute (:issue:`11946`) - ``DataFrame.quantile`` and ``Series.quantile`` now accept ``interpolation`` keyword (:issue:`10174`). - ``DataFrame.select_dtypes`` now allows the ``np.float16`` typecode (:issue:`11990`) +- Added ``weekday_name`` as a component to ``DatetimeIndex`` and ``.dt`` accessor. (:issue:`11128`) .. _whatsnew_0180.enhancements.rounding: diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py index dff7aead5bdba..0c6254b94b9cf 100644 --- a/pandas/tests/test_series.py +++ b/pandas/tests/test_series.py @@ -88,7 +88,8 @@ def test_dt_namespace_accessor(self): ok_for_dt = ok_for_base + ['date', 'time', 'microsecond', 'nanosecond', 'is_month_start', 'is_month_end', 'is_quarter_start', 'is_quarter_end', - 'is_year_start', 'is_year_end', 'tz'] + 'is_year_start', 'is_year_end', 'tz', + 'weekday_name'] ok_for_dt_methods = ['to_period', 'to_pydatetime', 'tz_localize', 'tz_convert', 'normalize', 'strftime', 'round', 'floor', 'ceil'] diff --git a/pandas/tseries/index.py b/pandas/tseries/index.py index bb4f878157595..8d1fc86b2f327 100644 --- a/pandas/tseries/index.py +++ b/pandas/tseries/index.py @@ -60,6 +60,9 @@ def f(self): result = tslib.get_start_end_field( values, field, self.freqstr, month_kw) + elif field in ['weekday_name']: + result = tslib.get_date_name_field(values, field) + return self._maybe_mask_results(result) else: result = tslib.get_date_field(values, field) @@ -207,7 +210,7 @@ def _join_i8_wrapper(joinf, **kwargs): 'daysinmonth', 'date', 'time', 'microsecond', 'nanosecond', 'is_month_start', 'is_month_end', 'is_quarter_start', 'is_quarter_end', 'is_year_start', - 'is_year_end', 'tz', 'freq'] + 'is_year_end', 'tz', 'freq', 'weekday_name'] _is_numeric_dtype = False _infer_as_myclass = True @@ -1564,6 +1567,10 @@ def _set_freq(self, value): 'is_year_end', 'is_year_end', "Logical indicating if last day of year (defined by frequency)") + weekday_name = _field_accessor( + 'weekday_name', + 'weekday_name', + "The name of day in a week (ex: Friday)\n\n.. versionadded:: 0.18.0") @property def time(self): diff --git a/pandas/tseries/tests/test_base.py b/pandas/tseries/tests/test_base.py index 2f28c55ae520f..130cbd01820c8 100644 --- a/pandas/tseries/tests/test_base.py +++ b/pandas/tseries/tests/test_base.py @@ -31,7 +31,7 @@ def test_ops_properties(self): 'is_month_start', 'is_month_end', 'is_quarter_start', 'is_quarter_end', 'is_year_start', - 'is_year_end'], + 'is_year_end', 'weekday_name'], lambda x: isinstance(x, DatetimeIndex)) def test_ops_properties_basic(self): diff --git a/pandas/tseries/tests/test_timeseries.py b/pandas/tseries/tests/test_timeseries.py index 84065c0340aad..97983d8dc25cf 100644 --- a/pandas/tseries/tests/test_timeseries.py +++ b/pandas/tseries/tests/test_timeseries.py @@ -3369,6 +3369,15 @@ def test_datetimeindex_accessors(self): self.assertEqual(dti.is_year_end[0], False) self.assertEqual(dti.is_year_end[364], True) + # GH 11128 + self.assertEqual(dti.weekday_name[4], u'Monday') + self.assertEqual(dti.weekday_name[5], u'Tuesday') + self.assertEqual(dti.weekday_name[6], u'Wednesday') + self.assertEqual(dti.weekday_name[7], u'Thursday') + self.assertEqual(dti.weekday_name[8], u'Friday') + self.assertEqual(dti.weekday_name[9], u'Saturday') + self.assertEqual(dti.weekday_name[10], u'Sunday') + self.assertEqual(len(dti.year), 365) self.assertEqual(len(dti.month), 365) self.assertEqual(len(dti.day), 365) @@ -3386,6 +3395,7 @@ def test_datetimeindex_accessors(self): self.assertEqual(len(dti.is_quarter_end), 365) self.assertEqual(len(dti.is_year_start), 365) self.assertEqual(len(dti.is_year_end), 365) + self.assertEqual(len(dti.weekday_name), 365) dti = DatetimeIndex(freq='BQ-FEB', start=datetime(1998, 1, 1), periods=4) diff --git a/pandas/tslib.pyx b/pandas/tslib.pyx index f737ac8178a68..48cd858c7f868 100644 --- a/pandas/tslib.pyx +++ b/pandas/tslib.pyx @@ -494,6 +494,10 @@ class Timestamp(_Timestamp): def is_year_end(self): return self._get_start_end_field('is_year_end') + @property + def weekday_name(self): + return self._get_date_name_field('weekday_name') + def tz_localize(self, tz, ambiguous='raise'): """ Convert naive Timestamp to local time zone, or remove @@ -1098,6 +1102,10 @@ cdef class _Timestamp(datetime): out = get_start_end_field(np.array([self.value], dtype=np.int64), field, freqstr, month_kw) return out[0] + cpdef _get_date_name_field(self, field): + out = get_date_name_field(np.array([self.value], dtype=np.int64), field) + return out[0] + property asm8: def __get__(self): return np.datetime64(self.value, 'ns') @@ -4363,6 +4371,39 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field, object freqstr=N raise ValueError("Field %s not supported" % field) +@cython.wraparound(False) +@cython.boundscheck(False) +def get_date_name_field(ndarray[int64_t] dtindex, object field): + ''' + Given a int64-based datetime index, return array of strings of date + name based on requested field (e.g. weekday_name) + ''' + cdef: + _TSObject ts + Py_ssize_t i, count = 0 + ndarray[object] out + pandas_datetimestruct dts + int dow + + _dayname = np.array( + ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'], + dtype=np.str ) + + count = len(dtindex) + out = np.empty(count, dtype=object) + + if field == 'weekday_name': + for i in range(count): + if dtindex[i] == NPY_NAT: out[i] = -1; continue + + pandas_datetime_to_datetimestruct(dtindex[i], PANDAS_FR_ns, &dts) + dow = dayofweek(dts.year, dts.month, dts.day) + out[i] = _dayname[dow] + return out + + raise ValueError("Field %s not supported" % field) + + cdef inline int m8_weekday(int64_t val): ts = convert_to_tsobject(val, None, None) return ts_dayofweek(ts)