Skip to content

Commit d0691e0

Browse files
jbrockmendelgfyoung
authored andcommitted
Fix+test timedelta64(nat) ops (#23425)
Follow-up to gh-23320.
1 parent cf4c0b6 commit d0691e0

File tree

6 files changed

+78
-6
lines changed

6 files changed

+78
-6
lines changed

pandas/core/arrays/datetimelike.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
is_object_dtype)
3737
from pandas.core.dtypes.generic import ABCSeries, ABCDataFrame, ABCIndexClass
3838
from pandas.core.dtypes.dtypes import DatetimeTZDtype
39+
from pandas.core.dtypes.missing import isna
3940

4041
import pandas.core.common as com
4142
from pandas.core.algorithms import checked_add_with_arr
@@ -370,6 +371,12 @@ def _add_timedeltalike_scalar(self, other):
370371
Add a delta of a timedeltalike
371372
return the i8 result view
372373
"""
374+
if isna(other):
375+
# i.e np.timedelta64("NaT"), not recognized by delta_to_nanoseconds
376+
new_values = np.empty(len(self), dtype='i8')
377+
new_values[:] = iNaT
378+
return new_values
379+
373380
inc = delta_to_nanoseconds(other)
374381
new_values = checked_add_with_arr(self.asi8, inc,
375382
arr_mask=self._isnan).view('i8')
@@ -442,7 +449,7 @@ def _sub_period_array(self, other):
442449
Array of DateOffset objects; nulls represented by NaT
443450
"""
444451
if not is_period_dtype(self):
445-
raise TypeError("cannot subtract {dtype}-dtype to {cls}"
452+
raise TypeError("cannot subtract {dtype}-dtype from {cls}"
446453
.format(dtype=other.dtype,
447454
cls=type(self).__name__))
448455

@@ -741,6 +748,11 @@ def __rsub__(self, other):
741748
raise TypeError("cannot subtract {cls} from {typ}"
742749
.format(cls=type(self).__name__,
743750
typ=type(other).__name__))
751+
elif is_period_dtype(self) and is_timedelta64_dtype(other):
752+
# TODO: Can we simplify/generalize these cases at all?
753+
raise TypeError("cannot subtract {cls} from {dtype}"
754+
.format(cls=type(self).__name__,
755+
dtype=other.dtype))
744756
return -(self - other)
745757
cls.__rsub__ = __rsub__
746758

pandas/core/arrays/period.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from pandas.core.dtypes.generic import (
3636
ABCSeries, ABCIndexClass, ABCPeriodIndex
3737
)
38-
from pandas.core.dtypes.missing import isna
38+
from pandas.core.dtypes.missing import isna, notna
3939
from pandas.core.missing import pad_1d, backfill_1d
4040

4141
import pandas.core.common as com
@@ -149,6 +149,8 @@ class PeriodArray(dtl.DatetimeLikeArrayMixin, ExtensionArray):
149149
period_array : Create a new PeriodArray
150150
pandas.PeriodIndex : Immutable Index for period data
151151
"""
152+
# array priority higher than numpy scalars
153+
__array_priority__ = 1000
152154
_attributes = ["freq"]
153155
_typ = "periodarray" # ABCPeriodArray
154156

@@ -761,12 +763,15 @@ def _add_timedeltalike_scalar(self, other):
761763
assert isinstance(self.freq, Tick) # checked by calling function
762764
assert isinstance(other, (timedelta, np.timedelta64, Tick))
763765

764-
delta = self._check_timedeltalike_freq_compat(other)
766+
if notna(other):
767+
# special handling for np.timedelta64("NaT"), avoid calling
768+
# _check_timedeltalike_freq_compat as that would raise TypeError
769+
other = self._check_timedeltalike_freq_compat(other)
765770

766771
# Note: when calling parent class's _add_timedeltalike_scalar,
767772
# it will call delta_to_nanoseconds(delta). Because delta here
768773
# is an integer, delta_to_nanoseconds will return it unchanged.
769-
ordinals = super(PeriodArray, self)._add_timedeltalike_scalar(delta)
774+
ordinals = super(PeriodArray, self)._add_timedeltalike_scalar(other)
770775
return ordinals
771776

772777
def _add_delta_tdi(self, other):

pandas/core/indexes/base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4707,7 +4707,7 @@ def _evaluate_with_timedelta_like(self, other, op):
47074707
'radd', 'rsub']:
47084708
raise TypeError("Operation {opname} between {cls} and {other} "
47094709
"is invalid".format(opname=op.__name__,
4710-
cls=type(self).__name__,
4710+
cls=self.dtype,
47114711
other=type(other).__name__))
47124712

47134713
other = Timedelta(other)

pandas/tests/arithmetic/test_datetime64.py

+19
Original file line numberDiff line numberDiff line change
@@ -1188,6 +1188,25 @@ def test_dti_isub_timedeltalike(self, tz_naive_fixture, two_hours):
11881188
rng -= two_hours
11891189
tm.assert_index_equal(rng, expected)
11901190

1191+
def test_dt64arr_add_sub_td64_nat(self, box, tz_naive_fixture):
1192+
# GH#23320 special handling for timedelta64("NaT")
1193+
tz = tz_naive_fixture
1194+
dti = pd.date_range("1994-04-01", periods=9, tz=tz, freq="QS")
1195+
other = np.timedelta64("NaT")
1196+
expected = pd.DatetimeIndex(["NaT"] * 9, tz=tz)
1197+
1198+
obj = tm.box_expected(dti, box)
1199+
expected = tm.box_expected(expected, box)
1200+
1201+
result = obj + other
1202+
tm.assert_equal(result, expected)
1203+
result = other + obj
1204+
tm.assert_equal(result, expected)
1205+
result = obj - other
1206+
tm.assert_equal(result, expected)
1207+
with pytest.raises(TypeError):
1208+
other - obj
1209+
11911210
# -------------------------------------------------------------
11921211
# Binary operations DatetimeIndex and TimedeltaIndex/array
11931212
def test_dti_add_tdi(self, tz_naive_fixture):

pandas/tests/arithmetic/test_period.py

+19-1
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ def test_pi_add_sub_td64_array_non_tick_raises(self):
419419

420420
with pytest.raises(period.IncompatibleFrequency):
421421
rng - tdarr
422-
with pytest.raises(period.IncompatibleFrequency):
422+
with pytest.raises(TypeError):
423423
tdarr - rng
424424

425425
def test_pi_add_sub_td64_array_tick(self):
@@ -801,6 +801,24 @@ def test_pi_add_sub_timedeltalike_freq_mismatch_monthly(self,
801801
with tm.assert_raises_regex(period.IncompatibleFrequency, msg):
802802
rng -= other
803803

804+
def test_parr_add_sub_td64_nat(self, box):
805+
# GH#23320 special handling for timedelta64("NaT")
806+
pi = pd.period_range("1994-04-01", periods=9, freq="19D")
807+
other = np.timedelta64("NaT")
808+
expected = pd.PeriodIndex(["NaT"] * 9, freq="19D")
809+
810+
obj = tm.box_expected(pi, box)
811+
expected = tm.box_expected(expected, box)
812+
813+
result = obj + other
814+
tm.assert_equal(result, expected)
815+
result = other + obj
816+
tm.assert_equal(result, expected)
817+
result = obj - other
818+
tm.assert_equal(result, expected)
819+
with pytest.raises(TypeError):
820+
other - obj
821+
804822

805823
class TestPeriodSeriesArithmetic(object):
806824
def test_ops_series_timedelta(self):

pandas/tests/arithmetic/test_timedelta64.py

+18
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,24 @@ def test_td64arr_add_sub_tdi(self, box_df_broadcast_failure, names):
735735
else:
736736
assert result.dtypes[0] == 'timedelta64[ns]'
737737

738+
def test_td64arr_add_sub_td64_nat(self, box):
739+
# GH#23320 special handling for timedelta64("NaT")
740+
tdi = pd.TimedeltaIndex([NaT, Timedelta('1s')])
741+
other = np.timedelta64("NaT")
742+
expected = pd.TimedeltaIndex(["NaT"] * 2)
743+
744+
obj = tm.box_expected(tdi, box)
745+
expected = tm.box_expected(expected, box)
746+
747+
result = obj + other
748+
tm.assert_equal(result, expected)
749+
result = other + obj
750+
tm.assert_equal(result, expected)
751+
result = obj - other
752+
tm.assert_equal(result, expected)
753+
result = other - obj
754+
tm.assert_equal(result, expected)
755+
738756
def test_td64arr_sub_NaT(self, box):
739757
# GH#18808
740758
ser = Series([NaT, Timedelta('1s')])

0 commit comments

Comments
 (0)