Skip to content

Commit d9b72b7

Browse files
committed
BUG: Reverse operators on integer-NA series and numpy scalars resulting in object dtype (pandas-dev#22024)
1 parent 140c7bb commit d9b72b7

File tree

1 file changed

+28
-47
lines changed

1 file changed

+28
-47
lines changed

pandas/core/ops.py

+28-47
Original file line numberDiff line numberDiff line change
@@ -1139,35 +1139,43 @@ def dispatch_to_extension_op(op, left, right):
11391139
# we need to listify to avoid ndarray, or non-same-type extension array
11401140
# dispatching
11411141

1142+
new_type, left_type, right_type = None, None, None
11421143
if is_extension_array_dtype(left):
1143-
1144+
left_type = left.dtype.type
11441145
new_left = left.values
11451146
if isinstance(right, np.ndarray):
11461147

11471148
# handle numpy scalars, this is a PITA
11481149
# TODO(jreback)
11491150
new_right = lib.item_from_zerodim(right)
1151+
right_type = new_right.dtype
11501152
if is_scalar(new_right):
11511153
new_right = [new_right]
11521154
new_right = list(new_right)
11531155
elif is_extension_array_dtype(right) and type(left) != type(right):
1156+
right_type = new_right.dtype.type
11541157
new_right = list(new_right)
11551158
else:
11561159
new_right = right
1157-
1160+
right_type = type(right)
11581161
else:
11591162

11601163
new_left = list(left.values)
11611164
new_right = right
11621165

11631166
res_values = op(new_left, new_right)
11641167
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])
11661170
if op.__name__ == 'divmod':
11671171
return _construct_divmod_result(
11681172
left, res_values, left.index, res_name)
11691173

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
11711179

11721180

11731181
def _arith_method_SERIES(cls, op, special):
@@ -1319,11 +1327,13 @@ def na_op(x, y):
13191327
# should have guarantess on what x, y can be type-wise
13201328
# Extension Dtypes are not called here
13211329

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)
13251335

1326-
if is_object_dtype(x.dtype):
1336+
elif is_object_dtype(x.dtype):
13271337
result = _comp_method_OBJECT_ARRAY(op, x, y)
13281338

13291339
elif is_datetimelike_v_numeric(x, y):
@@ -1385,7 +1395,7 @@ def wrapper(self, other, axis=None):
13851395
return self._constructor(res_values, index=self.index,
13861396
name=res_name)
13871397

1388-
elif is_datetime64_dtype(self) or is_datetime64tz_dtype(self):
1398+
if is_datetime64_dtype(self) or is_datetime64tz_dtype(self):
13891399
# Dispatch to DatetimeIndex to ensure identical
13901400
# Series/Index behavior
13911401
if (isinstance(other, datetime.date) and
@@ -1427,9 +1437,8 @@ def wrapper(self, other, axis=None):
14271437
name=res_name)
14281438

14291439
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))):
14331442
return dispatch_to_extension_op(op, self, other)
14341443

14351444
elif isinstance(other, ABCSeries):
@@ -1452,6 +1461,13 @@ def wrapper(self, other, axis=None):
14521461
# is not.
14531462
return result.__finalize__(self).rename(res_name)
14541463

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+
14551471
elif is_scalar(other) and isna(other):
14561472
# numpy does not like comparisons vs None
14571473
if op is operator.ne:
@@ -1579,41 +1595,6 @@ def flex_wrapper(self, other, level=None, fill_value=None, axis=0):
15791595
# -----------------------------------------------------------------------------
15801596
# DataFrame
15811597

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-
16171598
def _combine_series_frame(self, other, func, fill_value=None, axis=None,
16181599
level=None, try_cast=True):
16191600
"""

0 commit comments

Comments
 (0)