2
2
Functions for arithmetic and comparison operations on NumPy arrays and
3
3
ExtensionArrays.
4
4
"""
5
- from datetime import timedelta
5
+ import datetime
6
6
from functools import partial
7
7
import operator
8
8
from typing import Any
9
9
10
10
import numpy as np
11
11
12
12
from pandas ._libs import (
13
+ NaT ,
13
14
Timedelta ,
14
15
Timestamp ,
15
16
lib ,
16
17
ops as libops ,
17
18
)
19
+ from pandas ._libs .tslibs import BaseOffset
18
20
from pandas ._typing import (
19
21
ArrayLike ,
20
22
Shape ,
@@ -154,8 +156,14 @@ def _na_arithmetic_op(left, right, op, is_cmp: bool = False):
154
156
------
155
157
TypeError : invalid operation
156
158
"""
159
+ if isinstance (right , str ):
160
+ # can never use numexpr
161
+ func = op
162
+ else :
163
+ func = partial (expressions .evaluate , op )
164
+
157
165
try :
158
- result = expressions . evaluate ( op , left , right )
166
+ result = func ( left , right )
159
167
except TypeError :
160
168
if is_object_dtype (left ) or is_object_dtype (right ) and not is_cmp :
161
169
# For object dtype, fallback to a masked operation (only operating
@@ -201,8 +209,13 @@ def arithmetic_op(left: ArrayLike, right: Any, op):
201
209
# casts integer dtypes to timedelta64 when operating with timedelta64 - GH#22390)
202
210
right = _maybe_upcast_for_op (right , left .shape )
203
211
204
- if should_extension_dispatch (left , right ) or isinstance (right , Timedelta ):
205
- # Timedelta is included because numexpr will fail on it, see GH#31457
212
+ if (
213
+ should_extension_dispatch (left , right )
214
+ or isinstance (right , (Timedelta , BaseOffset , Timestamp ))
215
+ or right is NaT
216
+ ):
217
+ # Timedelta/Timestamp and other custom scalars are included in the check
218
+ # because numexpr will fail on it, see GH#31457
206
219
res_values = op (left , right )
207
220
else :
208
221
res_values = _na_arithmetic_op (left , right , op )
@@ -246,7 +259,10 @@ def comparison_op(left: ArrayLike, right: Any, op) -> ArrayLike:
246
259
"Lengths must match to compare" , lvalues .shape , rvalues .shape
247
260
)
248
261
249
- if should_extension_dispatch (lvalues , rvalues ):
262
+ if should_extension_dispatch (lvalues , rvalues ) or (
263
+ (isinstance (rvalues , (Timedelta , BaseOffset , Timestamp )) or right is NaT )
264
+ and not is_object_dtype (lvalues .dtype )
265
+ ):
250
266
# Call the method on lvalues
251
267
res_values = op (lvalues , rvalues )
252
268
@@ -261,7 +277,7 @@ def comparison_op(left: ArrayLike, right: Any, op) -> ArrayLike:
261
277
# GH#36377 going through the numexpr path would incorrectly raise
262
278
return invalid_comparison (lvalues , rvalues , op )
263
279
264
- elif is_object_dtype (lvalues .dtype ):
280
+ elif is_object_dtype (lvalues .dtype ) or isinstance ( rvalues , str ) :
265
281
res_values = comp_method_OBJECT_ARRAY (op , lvalues , rvalues )
266
282
267
283
else :
@@ -438,11 +454,14 @@ def _maybe_upcast_for_op(obj, shape: Shape):
438
454
Be careful to call this *after* determining the `name` attribute to be
439
455
attached to the result of the arithmetic operation.
440
456
"""
441
- if type (obj ) is timedelta :
457
+ if type (obj ) is datetime . timedelta :
442
458
# GH#22390 cast up to Timedelta to rely on Timedelta
443
459
# implementation; otherwise operation against numeric-dtype
444
460
# raises TypeError
445
461
return Timedelta (obj )
462
+ elif type (obj ) is datetime .datetime :
463
+ # cast up to Timestamp to rely on Timestamp implementation, see Timedelta above
464
+ return Timestamp (obj )
446
465
elif isinstance (obj , np .datetime64 ):
447
466
# GH#28080 numpy casts integer-dtype to datetime64 when doing
448
467
# array[int] + datetime64, which we do not allow
0 commit comments