@@ -1139,35 +1139,43 @@ def dispatch_to_extension_op(op, left, right):
1139
1139
# we need to listify to avoid ndarray, or non-same-type extension array
1140
1140
# dispatching
1141
1141
1142
+ new_type , left_type , right_type = None , None , None
1142
1143
if is_extension_array_dtype (left ):
1143
-
1144
+ left_type = left . dtype . type
1144
1145
new_left = left .values
1145
1146
if isinstance (right , np .ndarray ):
1146
1147
1147
1148
# handle numpy scalars, this is a PITA
1148
1149
# TODO(jreback)
1149
1150
new_right = lib .item_from_zerodim (right )
1151
+ right_type = new_right .dtype
1150
1152
if is_scalar (new_right ):
1151
1153
new_right = [new_right ]
1152
1154
new_right = list (new_right )
1153
1155
elif is_extension_array_dtype (right ) and type (left ) != type (right ):
1156
+ right_type = new_right .dtype .type
1154
1157
new_right = list (new_right )
1155
1158
else :
1156
1159
new_right = right
1157
-
1160
+ right_type = type ( right )
1158
1161
else :
1159
1162
1160
1163
new_left = list (left .values )
1161
1164
new_right = right
1162
1165
1163
1166
res_values = op (new_left , new_right )
1164
1167
res_name = get_op_result_name (left , right )
1165
-
1168
+ if right_type and left_type :
1169
+ new_type = find_common_type ([right_type , left_type ])
1166
1170
if op .__name__ == 'divmod' :
1167
1171
return _construct_divmod_result (
1168
1172
left , res_values , left .index , res_name )
1169
1173
1170
- return _construct_result (left , res_values , left .index , res_name )
1174
+ result = _construct_result (left , res_values , left .index , res_name )
1175
+ if result .dtype == "object" :
1176
+ result = _construct_result (left , res_values , left .index , res_name ,
1177
+ new_type )
1178
+ return result
1171
1179
1172
1180
1173
1181
def _arith_method_SERIES (cls , op , special ):
@@ -1319,11 +1327,13 @@ def na_op(x, y):
1319
1327
# should have guarantess on what x, y can be type-wise
1320
1328
# Extension Dtypes are not called here
1321
1329
1322
- # Checking that cases that were once handled here are no longer
1323
- # reachable.
1324
- assert not (is_categorical_dtype (y ) and not is_scalar (y ))
1330
+ # dispatch to the categorical if we have a categorical
1331
+ # in either operand
1332
+ if is_categorical_dtype (y ) and not is_scalar (y ):
1333
+ # The `not is_scalar(y)` check excludes the string "category"
1334
+ return op (y , x )
1325
1335
1326
- if is_object_dtype (x .dtype ):
1336
+ elif is_object_dtype (x .dtype ):
1327
1337
result = _comp_method_OBJECT_ARRAY (op , x , y )
1328
1338
1329
1339
elif is_datetimelike_v_numeric (x , y ):
@@ -1385,7 +1395,7 @@ def wrapper(self, other, axis=None):
1385
1395
return self ._constructor (res_values , index = self .index ,
1386
1396
name = res_name )
1387
1397
1388
- elif is_datetime64_dtype (self ) or is_datetime64tz_dtype (self ):
1398
+ if is_datetime64_dtype (self ) or is_datetime64tz_dtype (self ):
1389
1399
# Dispatch to DatetimeIndex to ensure identical
1390
1400
# Series/Index behavior
1391
1401
if (isinstance (other , datetime .date ) and
@@ -1427,9 +1437,8 @@ def wrapper(self, other, axis=None):
1427
1437
name = res_name )
1428
1438
1429
1439
elif (is_extension_array_dtype (self ) or
1430
- (is_extension_array_dtype (other ) and not is_scalar (other ))):
1431
- # Note: the `not is_scalar(other)` condition rules out
1432
- # e.g. other == "category"
1440
+ (is_extension_array_dtype (other ) and
1441
+ not is_scalar (other ))):
1433
1442
return dispatch_to_extension_op (op , self , other )
1434
1443
1435
1444
elif isinstance (other , ABCSeries ):
@@ -1452,6 +1461,13 @@ def wrapper(self, other, axis=None):
1452
1461
# is not.
1453
1462
return result .__finalize__ (self ).rename (res_name )
1454
1463
1464
+ elif isinstance (other , pd .Categorical ):
1465
+ # ordering of checks matters; by this point we know
1466
+ # that not is_categorical_dtype(self)
1467
+ res_values = op (self .values , other )
1468
+ return self ._constructor (res_values , index = self .index ,
1469
+ name = res_name )
1470
+
1455
1471
elif is_scalar (other ) and isna (other ):
1456
1472
# numpy does not like comparisons vs None
1457
1473
if op is operator .ne :
@@ -1579,41 +1595,6 @@ def flex_wrapper(self, other, level=None, fill_value=None, axis=0):
1579
1595
# -----------------------------------------------------------------------------
1580
1596
# DataFrame
1581
1597
1582
- def dispatch_to_series (left , right , func ):
1583
- """
1584
- Evaluate the frame operation func(left, right) by evaluating
1585
- column-by-column, dispatching to the Series implementation.
1586
-
1587
- Parameters
1588
- ----------
1589
- left : DataFrame
1590
- right : scalar or DataFrame
1591
- func : arithmetic or comparison operator
1592
-
1593
- Returns
1594
- -------
1595
- DataFrame
1596
- """
1597
- # Note: we use iloc to access columns for compat with cases
1598
- # with non-unique columns.
1599
- if lib .is_scalar (right ):
1600
- new_data = {i : func (left .iloc [:, i ], right )
1601
- for i in range (len (left .columns ))}
1602
- elif isinstance (right , ABCDataFrame ):
1603
- assert right ._indexed_same (left )
1604
- new_data = {i : func (left .iloc [:, i ], right .iloc [:, i ])
1605
- for i in range (len (left .columns ))}
1606
- else :
1607
- # Remaining cases have less-obvious dispatch rules
1608
- raise NotImplementedError
1609
-
1610
- result = left ._constructor (new_data , index = left .index , copy = False )
1611
- # Pin columns instead of passing to constructor for compat with
1612
- # non-unique columns case
1613
- result .columns = left .columns
1614
- return result
1615
-
1616
-
1617
1598
def _combine_series_frame (self , other , func , fill_value = None , axis = None ,
1618
1599
level = None , try_cast = True ):
1619
1600
"""
0 commit comments