78
78
}
79
79
80
80
81
- COMPARISON_BINOPS : Set [str ] = {
82
- "eq" ,
83
- "ne" ,
84
- "lt" ,
85
- "gt" ,
86
- "le" ,
87
- "ge" ,
88
- }
81
+ COMPARISON_BINOPS : Set [str ] = {"eq" , "ne" , "lt" , "gt" , "le" , "ge" }
89
82
90
83
# -----------------------------------------------------------------------------
91
84
# Ops Wrapping Utilities
@@ -246,17 +239,17 @@ def fill_binop(left, right, fill_value):
246
239
# Dispatch logic
247
240
248
241
249
- def dispatch_to_series (left , right , func , axis = None ):
242
+ def dispatch_to_series (left , right , func , axis : Optional [ int ] = None ):
250
243
"""
251
244
Evaluate the frame operation func(left, right) by evaluating
252
245
column-by-column, dispatching to the Series implementation.
253
246
254
247
Parameters
255
248
----------
256
249
left : DataFrame
257
- right : scalar or DataFrame
250
+ right : scalar, Series, or DataFrame
258
251
func : arithmetic or comparison operator
259
- axis : {None, 0, 1, "index", "columns" }
252
+ axis : {None, 0, 1}
260
253
261
254
Returns
262
255
-------
@@ -266,7 +259,8 @@ def dispatch_to_series(left, right, func, axis=None):
266
259
array_op = get_array_op (func )
267
260
268
261
right = lib .item_from_zerodim (right )
269
- if lib .is_scalar (right ) or np .ndim (right ) == 0 :
262
+ if not is_list_like (right ):
263
+ # i.e. scalar, faster than checking np.ndim(right) == 0
270
264
bm = left ._mgr .apply (array_op , right = right )
271
265
return type (left )(bm )
272
266
@@ -284,15 +278,9 @@ def dispatch_to_series(left, right, func, axis=None):
284
278
# axis=1 means we want to operate row-by-row
285
279
assert right .index .equals (left .columns )
286
280
287
- if right .dtype == "timedelta64[ns]" :
288
- # ensure we treat NaT values as the correct dtype
289
- # Note: we do not do this unconditionally as it may be lossy or
290
- # expensive for EA dtypes.
291
- right = np .asarray (right )
292
- else :
293
- right = right ._values
294
- # maybe_align_as_frame ensures we do not have an ndarray here
295
- assert not isinstance (right , np .ndarray )
281
+ right = right ._values
282
+ # maybe_align_as_frame ensures we do not have an ndarray here
283
+ assert not isinstance (right , np .ndarray )
296
284
297
285
arrays = [array_op (l , r ) for l , r in zip (left ._iter_column_arrays (), right )]
298
286
@@ -315,7 +303,7 @@ def dispatch_to_series(left, right, func, axis=None):
315
303
# Series
316
304
317
305
318
- def _align_method_SERIES (left , right , align_asobject = False ):
306
+ def _align_method_SERIES (left : "Series" , right , align_asobject : bool = False ):
319
307
""" align lhs and rhs Series """
320
308
# ToDo: Different from _align_method_FRAME, list, tuple and ndarray
321
309
# are not coerced here
@@ -596,7 +584,11 @@ def _maybe_align_series_as_frame(frame: "DataFrame", series: "Series", axis: int
596
584
rvalues = series ._values
597
585
if not isinstance (rvalues , np .ndarray ):
598
586
# TODO(EA2D): no need to special-case with 2D EAs
599
- return series
587
+ if rvalues .dtype == "datetime64[ns]" or rvalues .dtype == "timedelta64[ns]" :
588
+ # We can losslessly+cheaply cast to ndarray
589
+ rvalues = np .asarray (rvalues )
590
+ else :
591
+ return series
600
592
601
593
if axis == 0 :
602
594
rvalues = rvalues .reshape (- 1 , 1 )
@@ -633,6 +625,8 @@ def f(self, other, axis=default_axis, level=None, fill_value=None):
633
625
# through the DataFrame path
634
626
raise NotImplementedError (f"fill_value { fill_value } not supported." )
635
627
628
+ axis = self ._get_axis_number (axis ) if axis is not None else 1
629
+
636
630
# TODO: why are we passing flex=True instead of flex=not special?
637
631
# 15 tests fail if we pass flex=not special instead
638
632
self , other = _align_method_FRAME (self , other , axis , flex = True , level = level )
@@ -642,7 +636,6 @@ def f(self, other, axis=default_axis, level=None, fill_value=None):
642
636
new_data = self ._combine_frame (other , na_op , fill_value )
643
637
644
638
elif isinstance (other , ABCSeries ):
645
- axis = self ._get_axis_number (axis ) if axis is not None else 1
646
639
new_data = dispatch_to_series (self , other , op , axis = axis )
647
640
else :
648
641
# in this case we always have `np.ndim(other) == 0`
@@ -670,20 +663,11 @@ def _flex_comp_method_FRAME(cls: Type["DataFrame"], op, special: bool):
670
663
671
664
@Appender (doc )
672
665
def f (self , other , axis = default_axis , level = None ):
666
+ axis = self ._get_axis_number (axis ) if axis is not None else 1
673
667
674
668
self , other = _align_method_FRAME (self , other , axis , flex = True , level = level )
675
669
676
- if isinstance (other , ABCDataFrame ):
677
- # Another DataFrame
678
- new_data = dispatch_to_series (self , other , op )
679
-
680
- elif isinstance (other , ABCSeries ):
681
- axis = self ._get_axis_number (axis ) if axis is not None else 1
682
- new_data = dispatch_to_series (self , other , op , axis = axis )
683
- else :
684
- # in this case we always have `np.ndim(other) == 0`
685
- new_data = dispatch_to_series (self , other , op )
686
-
670
+ new_data = dispatch_to_series (self , other , op , axis = axis )
687
671
return self ._construct_result (new_data )
688
672
689
673
f .__name__ = op_name
@@ -697,12 +681,10 @@ def _comp_method_FRAME(cls: Type["DataFrame"], op, special: bool):
697
681
698
682
@Appender (f"Wrapper for comparison method { op_name } " )
699
683
def f (self , other ):
684
+ axis = 1 # only relevant for Series other case
700
685
701
- self , other = _align_method_FRAME (
702
- self , other , axis = None , level = None , flex = False
703
- )
686
+ self , other = _align_method_FRAME (self , other , axis , level = None , flex = False )
704
687
705
- axis = 1 # only relevant for Series other case
706
688
# See GH#4537 for discussion of scalar op behavior
707
689
new_data = dispatch_to_series (self , other , op , axis = axis )
708
690
return self ._construct_result (new_data )
0 commit comments