Skip to content

Commit

Permalink
API: NaT boolean accessors
Browse files Browse the repository at this point in the history
TST: add pandas/tests/scalar/test_nat

TST: revise testing of tseries accessors

closes pandas-dev#15781
  • Loading branch information
jreback committed Mar 22, 2017
1 parent 79581ff commit 4f4aff2
Show file tree
Hide file tree
Showing 17 changed files with 426 additions and 379 deletions.
3 changes: 2 additions & 1 deletion doc/source/whatsnew/v0.20.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,8 @@ Other API Changes
- ``Series.sort_values()`` accepts a one element list of bool for consistency with the behavior of ``DataFrame.sort_values()`` (:issue:`15604`)
- ``.merge()`` and ``.join()`` on ``category`` dtype columns will now preserve the category dtype when possible (:issue:`10409`)
- ``SparseDataFrame.default_fill_value`` will be 0, previously was ``nan`` in the return from ``pd.get_dummies(..., sparse=True)`` (:issue:`15594`)
- ``NaT`` will now correctly report ``False`` for datetimelike boolean operations such as ``is_month_start`` (:issue:`15781`)
- ``NaT`` will now correctly return ``np.nan`` for ``Timedelta`` and ``Period`` accessors such as ``days`` and ``quarter`` (:issue:`15782`)

.. _whatsnew_0200.deprecations:

Expand Down Expand Up @@ -936,4 +938,3 @@ Bug Fixes
- Bug in ``pd.melt()`` where passing a tuple value for ``value_vars`` caused a ``TypeError`` (:issue:`15348`)
- Bug in ``.eval()`` which caused multiline evals to fail with local variables not on the first line (:issue:`15342`)
- Bug in ``pd.read_msgpack`` which did not allow to load dataframe with an index of type ``CategoricalIndex`` (:issue:`15487`)

53 changes: 39 additions & 14 deletions pandas/_libs/tslib.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,30 @@ class NaTType(_NaT):
def is_leap_year(self):
return False

@property
def is_month_start(self):
return False

@property
def is_quarter_start(self):
return False

@property
def is_year_start(self):
return False

@property
def is_month_end(self):
return False

@property
def is_quarter_end(self):
return False

@property
def is_year_end(self):
return False

def __rdiv__(self, other):
return _nat_rdivide_op(self, other)

Expand Down Expand Up @@ -3799,8 +3823,9 @@ def array_strptime(ndarray[object] values, object fmt,
# these by definition return np.nan
fields = ['year', 'quarter', 'month', 'day', 'hour',
'minute', 'second', 'millisecond', 'microsecond', 'nanosecond',
'week', 'dayofyear', 'days_in_month', 'daysinmonth', 'dayofweek',
'weekday_name']
'week', 'dayofyear', 'weekofyear', 'days_in_month', 'daysinmonth',
'dayofweek', 'weekday_name', 'days', 'seconds', 'microseconds',
'nanoseconds', 'qyear', 'quarter']
for field in fields:
prop = property(fget=lambda self: np.nan)
setattr(NaTType, field, prop)
Expand Down Expand Up @@ -4810,7 +4835,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field,
if field == 'is_month_start':
if is_business:
for i in range(count):
if dtindex[i] == NPY_NAT: out[i] = -1; continue
if dtindex[i] == NPY_NAT: out[i] = 0; continue

pandas_datetime_to_datetimestruct(
dtindex[i], PANDAS_FR_ns, &dts)
Expand All @@ -4823,7 +4848,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field,
return out.view(bool)
else:
for i in range(count):
if dtindex[i] == NPY_NAT: out[i] = -1; continue
if dtindex[i] == NPY_NAT: out[i] = 0; continue

pandas_datetime_to_datetimestruct(
dtindex[i], PANDAS_FR_ns, &dts)
Expand All @@ -4836,7 +4861,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field,
elif field == 'is_month_end':
if is_business:
for i in range(count):
if dtindex[i] == NPY_NAT: out[i] = -1; continue
if dtindex[i] == NPY_NAT: out[i] = 0; continue

pandas_datetime_to_datetimestruct(
dtindex[i], PANDAS_FR_ns, &dts)
Expand All @@ -4854,7 +4879,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field,
return out.view(bool)
else:
for i in range(count):
if dtindex[i] == NPY_NAT: out[i] = -1; continue
if dtindex[i] == NPY_NAT: out[i] = 0; continue

pandas_datetime_to_datetimestruct(
dtindex[i], PANDAS_FR_ns, &dts)
Expand All @@ -4871,7 +4896,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field,
elif field == 'is_quarter_start':
if is_business:
for i in range(count):
if dtindex[i] == NPY_NAT: out[i] = -1; continue
if dtindex[i] == NPY_NAT: out[i] = 0; continue

pandas_datetime_to_datetimestruct(
dtindex[i], PANDAS_FR_ns, &dts)
Expand All @@ -4885,7 +4910,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field,
return out.view(bool)
else:
for i in range(count):
if dtindex[i] == NPY_NAT: out[i] = -1; continue
if dtindex[i] == NPY_NAT: out[i] = 0; continue

pandas_datetime_to_datetimestruct(
dtindex[i], PANDAS_FR_ns, &dts)
Expand All @@ -4898,7 +4923,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field,
elif field == 'is_quarter_end':
if is_business:
for i in range(count):
if dtindex[i] == NPY_NAT: out[i] = -1; continue
if dtindex[i] == NPY_NAT: out[i] = 0; continue

pandas_datetime_to_datetimestruct(
dtindex[i], PANDAS_FR_ns, &dts)
Expand All @@ -4917,7 +4942,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field,
return out.view(bool)
else:
for i in range(count):
if dtindex[i] == NPY_NAT: out[i] = -1; continue
if dtindex[i] == NPY_NAT: out[i] = 0; continue

pandas_datetime_to_datetimestruct(
dtindex[i], PANDAS_FR_ns, &dts)
Expand All @@ -4934,7 +4959,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field,
elif field == 'is_year_start':
if is_business:
for i in range(count):
if dtindex[i] == NPY_NAT: out[i] = -1; continue
if dtindex[i] == NPY_NAT: out[i] = 0; continue

pandas_datetime_to_datetimestruct(
dtindex[i], PANDAS_FR_ns, &dts)
Expand All @@ -4948,7 +4973,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field,
return out.view(bool)
else:
for i in range(count):
if dtindex[i] == NPY_NAT: out[i] = -1; continue
if dtindex[i] == NPY_NAT: out[i] = 0; continue

pandas_datetime_to_datetimestruct(
dtindex[i], PANDAS_FR_ns, &dts)
Expand All @@ -4961,7 +4986,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field,
elif field == 'is_year_end':
if is_business:
for i in range(count):
if dtindex[i] == NPY_NAT: out[i] = -1; continue
if dtindex[i] == NPY_NAT: out[i] = 0; continue

pandas_datetime_to_datetimestruct(
dtindex[i], PANDAS_FR_ns, &dts)
Expand All @@ -4980,7 +5005,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field,
return out.view(bool)
else:
for i in range(count):
if dtindex[i] == NPY_NAT: out[i] = -1; continue
if dtindex[i] == NPY_NAT: out[i] = 0; continue

pandas_datetime_to_datetimestruct(
dtindex[i], PANDAS_FR_ns, &dts)
Expand Down
13 changes: 4 additions & 9 deletions pandas/tests/indexes/datetimes/test_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,10 @@ def setUp(self):
self.not_valid_objs = [o for o in self.objs if not mask(o)]

def test_ops_properties(self):
self.check_ops_properties(
['year', 'month', 'day', 'hour', 'minute', 'second', 'weekofyear',
'week', 'dayofweek', 'dayofyear', 'quarter'])
self.check_ops_properties(['date', 'time', 'microsecond', 'nanosecond',
'is_month_start', 'is_month_end',
'is_quarter_start',
'is_quarter_end', 'is_year_start',
'is_year_end', 'weekday_name'],
lambda x: isinstance(x, DatetimeIndex))
f = lambda x: isinstance(x, DatetimeIndex)
self.check_ops_properties(DatetimeIndex._field_ops, f)
self.check_ops_properties(DatetimeIndex._object_ops, f)
self.check_ops_properties(DatetimeIndex._bool_ops, f)

def test_ops_properties_basic(self):

Expand Down
9 changes: 4 additions & 5 deletions pandas/tests/indexes/period/test_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ def setUp(self):
self.not_valid_objs = [o for o in self.objs if not mask(o)]

def test_ops_properties(self):
self.check_ops_properties(
['year', 'month', 'day', 'hour', 'minute', 'second', 'weekofyear',
'week', 'dayofweek', 'dayofyear', 'quarter'])
self.check_ops_properties(['qyear'],
lambda x: isinstance(x, PeriodIndex))
f = lambda x: isinstance(x, PeriodIndex)
self.check_ops_properties(PeriodIndex._field_ops, f)
self.check_ops_properties(PeriodIndex._object_ops, f)
self.check_ops_properties(PeriodIndex._bool_ops, f)

def test_asobject_tolist(self):
idx = pd.period_range(start='2013-01-01', periods=4, freq='M',
Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/indexes/period/test_period.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,8 @@ def test_fields(self):

def _check_all_fields(self, periodindex):
fields = ['year', 'month', 'day', 'hour', 'minute', 'second',
'weekofyear', 'week', 'dayofweek', 'weekday', 'dayofyear',
'quarter', 'qyear', 'days_in_month', 'is_leap_year']
'weekofyear', 'week', 'dayofweek', 'dayofyear',
'quarter', 'qyear', 'days_in_month']

periods = list(periodindex)
s = pd.Series(periodindex)
Expand Down
6 changes: 3 additions & 3 deletions pandas/tests/indexes/timedeltas/test_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ def setUp(self):
self.not_valid_objs = []

def test_ops_properties(self):
self.check_ops_properties(['days', 'hours', 'minutes', 'seconds',
'milliseconds'])
self.check_ops_properties(['microseconds', 'nanoseconds'])
f = lambda x: isinstance(x, TimedeltaIndex)
self.check_ops_properties(TimedeltaIndex._field_ops, f)
self.check_ops_properties(TimedeltaIndex._object_ops, f)

def test_asobject_tolist(self):
idx = timedelta_range(start='1 days', periods=4, freq='D', name='idx')
Expand Down
Loading

0 comments on commit 4f4aff2

Please sign in to comment.