Skip to content

Commit 0dec193

Browse files
jrebackmattip
authored andcommitted
API: NaT boolean accessors now return False (pandas-dev#15782)
TST: add pandas/tests/scalar/test_nat TST: revise testing of tseries accessors closes pandas-dev#15781
1 parent 49a4287 commit 0dec193

18 files changed

+429
-402
lines changed

doc/source/whatsnew/v0.20.0.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,8 @@ Other API Changes
771771
since pandas version 0.13.0 and can be done with the ``Series.str.extract``
772772
method (:issue:`5224`). As a consequence, the ``as_indexer`` keyword is
773773
ignored (no longer needed to specify the new behaviour) and is deprecated.
774-
774+
- ``NaT`` will now correctly report ``False`` for datetimelike boolean operations such as ``is_month_start`` (:issue:`15781`)
775+
- ``NaT`` will now correctly return ``np.nan`` for ``Timedelta`` and ``Period`` accessors such as ``days`` and ``quarter`` (:issue:`15782`)
775776

776777
.. _whatsnew_0200.deprecations:
777778

pandas/_libs/tslib.pyx

+39-14
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,30 @@ class NaTType(_NaT):
849849
def is_leap_year(self):
850850
return False
851851

852+
@property
853+
def is_month_start(self):
854+
return False
855+
856+
@property
857+
def is_quarter_start(self):
858+
return False
859+
860+
@property
861+
def is_year_start(self):
862+
return False
863+
864+
@property
865+
def is_month_end(self):
866+
return False
867+
868+
@property
869+
def is_quarter_end(self):
870+
return False
871+
872+
@property
873+
def is_year_end(self):
874+
return False
875+
852876
def __rdiv__(self, other):
853877
return _nat_rdivide_op(self, other)
854878

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

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

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

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

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

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

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

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

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

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

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

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

49855010
pandas_datetime_to_datetimestruct(
49865011
dtindex[i], PANDAS_FR_ns, &dts)

pandas/tests/indexes/datetimes/test_misc.py

+2-7
Original file line numberDiff line numberDiff line change
@@ -259,19 +259,14 @@ def test_datetimeindex_accessors(self):
259259
dti.name = 'name'
260260

261261
# non boolean accessors -> return Index
262-
for accessor in ['year', 'month', 'day', 'hour', 'minute',
263-
'second', 'microsecond', 'nanosecond',
264-
'dayofweek', 'dayofyear', 'weekofyear',
265-
'quarter', 'weekday_name']:
262+
for accessor in DatetimeIndex._field_ops:
266263
res = getattr(dti, accessor)
267264
assert len(res) == 365
268265
assert isinstance(res, Index)
269266
assert res.name == 'name'
270267

271268
# boolean accessors -> return array
272-
for accessor in ['is_month_start', 'is_month_end',
273-
'is_quarter_start', 'is_quarter_end',
274-
'is_year_start', 'is_year_end']:
269+
for accessor in DatetimeIndex._bool_ops:
275270
res = getattr(dti, accessor)
276271
assert len(res) == 365
277272
assert isinstance(res, np.ndarray)

pandas/tests/indexes/datetimes/test_ops.py

+4-9
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,10 @@ def setUp(self):
3131
self.not_valid_objs = [o for o in self.objs if not mask(o)]
3232

3333
def test_ops_properties(self):
34-
self.check_ops_properties(
35-
['year', 'month', 'day', 'hour', 'minute', 'second', 'weekofyear',
36-
'week', 'dayofweek', 'dayofyear', 'quarter'])
37-
self.check_ops_properties(['date', 'time', 'microsecond', 'nanosecond',
38-
'is_month_start', 'is_month_end',
39-
'is_quarter_start',
40-
'is_quarter_end', 'is_year_start',
41-
'is_year_end', 'weekday_name'],
42-
lambda x: isinstance(x, DatetimeIndex))
34+
f = lambda x: isinstance(x, DatetimeIndex)
35+
self.check_ops_properties(DatetimeIndex._field_ops, f)
36+
self.check_ops_properties(DatetimeIndex._object_ops, f)
37+
self.check_ops_properties(DatetimeIndex._bool_ops, f)
4338

4439
def test_ops_properties_basic(self):
4540

pandas/tests/indexes/period/test_ops.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,10 @@ def setUp(self):
2121
self.not_valid_objs = [o for o in self.objs if not mask(o)]
2222

2323
def test_ops_properties(self):
24-
self.check_ops_properties(
25-
['year', 'month', 'day', 'hour', 'minute', 'second', 'weekofyear',
26-
'week', 'dayofweek', 'dayofyear', 'quarter'])
27-
self.check_ops_properties(['qyear'],
28-
lambda x: isinstance(x, PeriodIndex))
24+
f = lambda x: isinstance(x, PeriodIndex)
25+
self.check_ops_properties(PeriodIndex._field_ops, f)
26+
self.check_ops_properties(PeriodIndex._object_ops, f)
27+
self.check_ops_properties(PeriodIndex._bool_ops, f)
2928

3029
def test_asobject_tolist(self):
3130
idx = pd.period_range(start='2013-01-01', periods=4, freq='M',

pandas/tests/indexes/period/test_period.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -394,8 +394,8 @@ def test_fields(self):
394394

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

400400
periods = list(periodindex)
401401
s = pd.Series(periodindex)

pandas/tests/indexes/timedeltas/test_ops.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ def setUp(self):
2121
self.not_valid_objs = []
2222

2323
def test_ops_properties(self):
24-
self.check_ops_properties(['days', 'hours', 'minutes', 'seconds',
25-
'milliseconds'])
26-
self.check_ops_properties(['microseconds', 'nanoseconds'])
24+
f = lambda x: isinstance(x, TimedeltaIndex)
25+
self.check_ops_properties(TimedeltaIndex._field_ops, f)
26+
self.check_ops_properties(TimedeltaIndex._object_ops, f)
2727

2828
def test_asobject_tolist(self):
2929
idx = timedelta_range(start='1 days', periods=4, freq='D', name='idx')

0 commit comments

Comments
 (0)