diff --git a/doc/source/api.rst b/doc/source/api.rst index d6402100a296f..b4105eaa23dca 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -457,6 +457,7 @@ These can be accessed like ``Series.dt.``. Series.dt.weekofyear Series.dt.dayofweek Series.dt.weekday + Series.dt.weekday_name Series.dt.dayofyear Series.dt.quarter Series.dt.is_month_start @@ -1476,6 +1477,7 @@ Time/Date Components DatetimeIndex.week DatetimeIndex.dayofweek DatetimeIndex.weekday + DatetimeIndex.weekday_name DatetimeIndex.quarter DatetimeIndex.tz DatetimeIndex.freq diff --git a/doc/source/timeseries.rst b/doc/source/timeseries.rst index 92b904bc683f4..40ef3b5cd6593 100644 --- a/doc/source/timeseries.rst +++ b/doc/source/timeseries.rst @@ -523,6 +523,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.1.txt b/doc/source/whatsnew/v0.18.1.txt index 87525e6edfba0..8151765ec2281 100644 --- a/doc/source/whatsnew/v0.18.1.txt +++ b/doc/source/whatsnew/v0.18.1.txt @@ -59,6 +59,7 @@ Other Enhancements - ``pd.read_csv()`` now supports opening files using xz compression, via extension inference or explicit ``compression='xz'`` is specified; ``xz`` compressions is also supported by ``DataFrame.to_csv`` in the same way (:issue:`11852`) - ``pd.read_msgpack()`` now always gives writeable ndarrays even when compression is used (:issue:`12359`). - ``Index.take`` now handles ``allow_fill`` and ``fill_value`` consistently (:issue:`12631`) +- Added ``weekday_name`` as a component to ``DatetimeIndex`` and ``.dt`` accessor. (:issue:`11128`) .. ipython:: python diff --git a/pandas/tests/series/test_datetime_values.py b/pandas/tests/series/test_datetime_values.py index 6e9df1661d139..90ee834aaf9c2 100644 --- a/pandas/tests/series/test_datetime_values.py +++ b/pandas/tests/series/test_datetime_values.py @@ -25,7 +25,7 @@ class TestSeriesDatetimeValues(TestData, tm.TestCase): def test_dt_namespace_accessor(self): - # GH 7207 + # GH 7207, 11128 # test .dt namespace accessor ok_for_base = ['year', 'month', 'day', 'hour', 'minute', 'second', @@ -37,10 +37,11 @@ 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'] + 'floor', 'ceil', 'weekday_name'] ok_for_td = ['days', 'seconds', 'microseconds', 'nanoseconds'] ok_for_td_methods = ['components', 'to_pytimedelta', 'total_seconds', 'round', 'floor', 'ceil'] diff --git a/pandas/tseries/index.py b/pandas/tseries/index.py index 50171c3ae4fe3..17b339027245d 100644 --- a/pandas/tseries/index.py +++ b/pandas/tseries/index.py @@ -61,6 +61,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) @@ -208,7 +211,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,12 @@ def _set_freq(self, value): '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.. versionadded:: 0.18.1") + dayofyear = _field_accessor( 'dayofyear', 'doy', diff --git a/pandas/tseries/tests/test_base.py b/pandas/tseries/tests/test_base.py index 3c35fc8299517..1820e39fd69b5 100644 --- a/pandas/tseries/tests/test_base.py +++ b/pandas/tseries/tests/test_base.py @@ -32,7 +32,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 abcf4244ba91f..bfd32844b055d 100644 --- a/pandas/tseries/tests/test_timeseries.py +++ b/pandas/tseries/tests/test_timeseries.py @@ -959,7 +959,7 @@ def test_nat_vector_field_access(self): def test_nat_scalar_field_access(self): fields = ['year', 'quarter', 'month', 'day', 'hour', 'minute', 'second', 'microsecond', 'nanosecond', 'week', 'dayofyear', - 'days_in_month', 'daysinmonth', 'dayofweek'] + 'days_in_month', 'daysinmonth', 'dayofweek', 'weekday_name'] for field in fields: result = getattr(NaT, field) self.assertTrue(np.isnan(result)) @@ -1852,7 +1852,7 @@ def test_timestamp_fields(self): fields = ['dayofweek', 'dayofyear', 'week', 'weekofyear', 'quarter', 'days_in_month', 'is_month_start', 'is_month_end', 'is_quarter_start', 'is_quarter_end', 'is_year_start', - 'is_year_end'] + 'is_year_end', 'weekday_name'] for f in fields: expected = getattr(idx, f)[-1] result = getattr(Timestamp(idx[-1]), f) @@ -3541,6 +3541,23 @@ 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(Timestamp('2016-04-04').weekday_name, u'Monday') + self.assertEqual(Timestamp('2016-04-05').weekday_name, u'Tuesday') + self.assertEqual(Timestamp('2016-04-06').weekday_name, u'Wednesday') + self.assertEqual(Timestamp('2016-04-07').weekday_name, u'Thursday') + self.assertEqual(Timestamp('2016-04-08').weekday_name, u'Friday') + self.assertEqual(Timestamp('2016-04-09').weekday_name, u'Saturday') + self.assertEqual(Timestamp('2016-04-10').weekday_name, u'Sunday') + self.assertEqual(len(dti.year), 365) self.assertEqual(len(dti.month), 365) self.assertEqual(len(dti.day), 365) @@ -3558,6 +3575,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 98e6f1d1c53f4..262d83d6a50b2 100644 --- a/pandas/tslib.pyx +++ b/pandas/tslib.pyx @@ -398,6 +398,11 @@ class Timestamp(_Timestamp): def dayofweek(self): return self.weekday() + @property + def weekday_name(self): + out = get_date_name_field(np.array([self.value], dtype=np.int64), 'weekday_name') + return out[0] + @property def dayofyear(self): return self._get_field('doy') @@ -667,7 +672,7 @@ class NaTType(_NaT): fields = ['year', 'quarter', 'month', 'day', 'hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond', - 'week', 'dayofyear', 'days_in_month', 'daysinmonth', 'dayofweek'] + 'week', 'dayofyear', 'days_in_month', 'daysinmonth', 'dayofweek', 'weekday_name'] for field in fields: prop = property(fget=lambda self: np.nan) setattr(NaTType, field, prop) @@ -4390,6 +4395,38 @@ 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.object_ ) + + 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] = np.nan; 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)