Skip to content

Commit 81f386c

Browse files
jbrockmendeljreback
authored andcommitted
De-duplicate dispatch code, remove unreachable branches (#22068)
1 parent ed35aef commit 81f386c

File tree

3 files changed

+55
-50
lines changed

3 files changed

+55
-50
lines changed

pandas/_libs/tslibs/timedeltas.pyx

+1-1
Original file line numberDiff line numberDiff line change
@@ -930,7 +930,7 @@ cdef class _Timedelta(timedelta):
930930
def nanoseconds(self):
931931
"""
932932
Return the number of nanoseconds (n), where 0 <= n < 1 microsecond.
933-
933+
934934
Returns
935935
-------
936936
int

pandas/core/frame.py

+9-33
Original file line numberDiff line numberDiff line change
@@ -4911,20 +4911,7 @@ def _arith_op(left, right):
49114911

49124912
if this._is_mixed_type or other._is_mixed_type:
49134913
# iterate over columns
4914-
if this.columns.is_unique:
4915-
# unique columns
4916-
result = {col: _arith_op(this[col], other[col])
4917-
for col in this}
4918-
result = self._constructor(result, index=new_index,
4919-
columns=new_columns, copy=False)
4920-
else:
4921-
# non-unique columns
4922-
result = {i: _arith_op(this.iloc[:, i], other.iloc[:, i])
4923-
for i, col in enumerate(this.columns)}
4924-
result = self._constructor(result, index=new_index, copy=False)
4925-
result.columns = new_columns
4926-
return result
4927-
4914+
return ops.dispatch_to_series(this, other, _arith_op)
49284915
else:
49294916
result = _arith_op(this.values, other.values)
49304917

@@ -4958,27 +4945,16 @@ def _compare_frame(self, other, func, str_rep):
49584945
# compare_frame assumes self._indexed_same(other)
49594946

49604947
import pandas.core.computation.expressions as expressions
4961-
# unique
4962-
if self.columns.is_unique:
4963-
4964-
def _compare(a, b):
4965-
return {col: func(a[col], b[col]) for col in a.columns}
49664948

4967-
new_data = expressions.evaluate(_compare, str_rep, self, other)
4968-
return self._constructor(data=new_data, index=self.index,
4969-
columns=self.columns, copy=False)
4970-
# non-unique
4971-
else:
4972-
4973-
def _compare(a, b):
4974-
return {i: func(a.iloc[:, i], b.iloc[:, i])
4975-
for i, col in enumerate(a.columns)}
4949+
def _compare(a, b):
4950+
return {i: func(a.iloc[:, i], b.iloc[:, i])
4951+
for i in range(len(a.columns))}
49764952

4977-
new_data = expressions.evaluate(_compare, str_rep, self, other)
4978-
result = self._constructor(data=new_data, index=self.index,
4979-
copy=False)
4980-
result.columns = self.columns
4981-
return result
4953+
new_data = expressions.evaluate(_compare, str_rep, self, other)
4954+
result = self._constructor(data=new_data, index=self.index,
4955+
copy=False)
4956+
result.columns = self.columns
4957+
return result
49824958

49834959
def combine(self, other, func, fill_value=None, overwrite=True):
49844960
"""

pandas/core/ops.py

+45-16
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,7 @@ def na_op(x, y):
11431143
result[mask] = op(x[mask], com.values_from_object(y[mask]))
11441144
else:
11451145
assert isinstance(x, np.ndarray)
1146+
assert is_scalar(y)
11461147
result = np.empty(len(x), dtype=x.dtype)
11471148
mask = notna(x)
11481149
result[mask] = op(x[mask], y)
@@ -1189,6 +1190,7 @@ def wrapper(left, right):
11891190

11901191
elif (is_extension_array_dtype(left) or
11911192
is_extension_array_dtype(right)):
1193+
# TODO: should this include `not is_scalar(right)`?
11921194
return dispatch_to_extension_op(op, left, right)
11931195

11941196
elif is_datetime64_dtype(left) or is_datetime64tz_dtype(left):
@@ -1278,13 +1280,11 @@ def na_op(x, y):
12781280
# should have guarantess on what x, y can be type-wise
12791281
# Extension Dtypes are not called here
12801282

1281-
# dispatch to the categorical if we have a categorical
1282-
# in either operand
1283-
if is_categorical_dtype(y) and not is_scalar(y):
1284-
# The `not is_scalar(y)` check excludes the string "category"
1285-
return op(y, x)
1283+
# Checking that cases that were once handled here are no longer
1284+
# reachable.
1285+
assert not (is_categorical_dtype(y) and not is_scalar(y))
12861286

1287-
elif is_object_dtype(x.dtype):
1287+
if is_object_dtype(x.dtype):
12881288
result = _comp_method_OBJECT_ARRAY(op, x, y)
12891289

12901290
elif is_datetimelike_v_numeric(x, y):
@@ -1342,7 +1342,7 @@ def wrapper(self, other, axis=None):
13421342
return self._constructor(res_values, index=self.index,
13431343
name=res_name)
13441344

1345-
if is_datetime64_dtype(self) or is_datetime64tz_dtype(self):
1345+
elif is_datetime64_dtype(self) or is_datetime64tz_dtype(self):
13461346
# Dispatch to DatetimeIndex to ensure identical
13471347
# Series/Index behavior
13481348
if (isinstance(other, datetime.date) and
@@ -1384,8 +1384,9 @@ def wrapper(self, other, axis=None):
13841384
name=res_name)
13851385

13861386
elif (is_extension_array_dtype(self) or
1387-
(is_extension_array_dtype(other) and
1388-
not is_scalar(other))):
1387+
(is_extension_array_dtype(other) and not is_scalar(other))):
1388+
# Note: the `not is_scalar(other)` condition rules out
1389+
# e.g. other == "category"
13891390
return dispatch_to_extension_op(op, self, other)
13901391

13911392
elif isinstance(other, ABCSeries):
@@ -1408,13 +1409,6 @@ def wrapper(self, other, axis=None):
14081409
# is not.
14091410
return result.__finalize__(self).rename(res_name)
14101411

1411-
elif isinstance(other, pd.Categorical):
1412-
# ordering of checks matters; by this point we know
1413-
# that not is_categorical_dtype(self)
1414-
res_values = op(self.values, other)
1415-
return self._constructor(res_values, index=self.index,
1416-
name=res_name)
1417-
14181412
elif is_scalar(other) and isna(other):
14191413
# numpy does not like comparisons vs None
14201414
if op is operator.ne:
@@ -1544,6 +1538,41 @@ def flex_wrapper(self, other, level=None, fill_value=None, axis=0):
15441538
# -----------------------------------------------------------------------------
15451539
# DataFrame
15461540

1541+
def dispatch_to_series(left, right, func):
1542+
"""
1543+
Evaluate the frame operation func(left, right) by evaluating
1544+
column-by-column, dispatching to the Series implementation.
1545+
1546+
Parameters
1547+
----------
1548+
left : DataFrame
1549+
right : scalar or DataFrame
1550+
func : arithmetic or comparison operator
1551+
1552+
Returns
1553+
-------
1554+
DataFrame
1555+
"""
1556+
# Note: we use iloc to access columns for compat with cases
1557+
# with non-unique columns.
1558+
if lib.is_scalar(right):
1559+
new_data = {i: func(left.iloc[:, i], right)
1560+
for i in range(len(left.columns))}
1561+
elif isinstance(right, ABCDataFrame):
1562+
assert right._indexed_same(left)
1563+
new_data = {i: func(left.iloc[:, i], right.iloc[:, i])
1564+
for i in range(len(left.columns))}
1565+
else:
1566+
# Remaining cases have less-obvious dispatch rules
1567+
raise NotImplementedError
1568+
1569+
result = left._constructor(new_data, index=left.index, copy=False)
1570+
# Pin columns instead of passing to constructor for compat with
1571+
# non-unique columns case
1572+
result.columns = left.columns
1573+
return result
1574+
1575+
15471576
def _combine_series_frame(self, other, func, fill_value=None, axis=None,
15481577
level=None, try_cast=True):
15491578
"""

0 commit comments

Comments
 (0)