Skip to content

ENH: GH11128 add weekday_name to DatetimeIndex and .dt #11813

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ These can be accessed like ``Series.dt.<property>``.
Series.dt.days_in_month
Series.dt.tz
Series.dt.freq
Series.dt.weekday_name

**Datetime Methods**

Expand Down Expand Up @@ -1477,6 +1478,7 @@ Time/Date Components
DatetimeIndex.is_year_start
DatetimeIndex.is_year_end
DatetimeIndex.inferred_freq
DatetimeIndex.weekday_name

Selecting
~~~~~~~~~
Expand Down
1 change: 1 addition & 0 deletions doc/source/timeseries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <basics.dt_accessors>`

Expand Down
1 change: 1 addition & 0 deletions doc/source/whatsnew/v0.18.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
3 changes: 2 additions & 1 deletion pandas/tests/test_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down
9 changes: 8 additions & 1 deletion pandas/tseries/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion pandas/tseries/tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
10 changes: 10 additions & 0 deletions pandas/tseries/tests/test_timeseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
41 changes: 41 additions & 0 deletions pandas/tslib.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just do this in the above property, not need to have another function

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can just do:
return ["Monday","Tue...][self.weekday()]
in the property, no?
Although, I did find f_weekday in LocaleTime,.. not sure how to access that...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what you mean. the code can be moved directly w/o creating another function

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')
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put np.nan in if its NaT

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs some tests for NaT in the data as well (and test pd.NaT) as well

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're returning strings, isn't an empty string better for NaT? or str(np.nan)?
Where do we best put tests with NaT? Maybe just add a case to an existing test? How do I run these tests, where is the beginning of all this spagetti? (bear with me, I'm on a steep curve).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no np.nan is the empty indicator in non-datetimelike objects.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests go in tseries/tests_timeseries.py look where things like is_year_end properties are tested


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)
Expand Down