Skip to content

Commit b9e2278

Browse files
jbrockmendeljreback
authored andcommitted
REF: collect ops dispatch functions in one place, try to de-duplicate SparseDataFrame methods (#23060)
1 parent 398d017 commit b9e2278

File tree

9 files changed

+655
-624
lines changed

9 files changed

+655
-624
lines changed

Diff for: pandas/core/ops.py

+128-127
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,134 @@ def should_series_dispatch(left, right, op):
943943
return False
944944

945945

946+
def dispatch_to_series(left, right, func, str_rep=None, axis=None):
947+
"""
948+
Evaluate the frame operation func(left, right) by evaluating
949+
column-by-column, dispatching to the Series implementation.
950+
951+
Parameters
952+
----------
953+
left : DataFrame
954+
right : scalar or DataFrame
955+
func : arithmetic or comparison operator
956+
str_rep : str or None, default None
957+
axis : {None, 0, 1, "index", "columns"}
958+
959+
Returns
960+
-------
961+
DataFrame
962+
"""
963+
# Note: we use iloc to access columns for compat with cases
964+
# with non-unique columns.
965+
import pandas.core.computation.expressions as expressions
966+
967+
right = lib.item_from_zerodim(right)
968+
if lib.is_scalar(right):
969+
970+
def column_op(a, b):
971+
return {i: func(a.iloc[:, i], b)
972+
for i in range(len(a.columns))}
973+
974+
elif isinstance(right, ABCDataFrame):
975+
assert right._indexed_same(left)
976+
977+
def column_op(a, b):
978+
return {i: func(a.iloc[:, i], b.iloc[:, i])
979+
for i in range(len(a.columns))}
980+
981+
elif isinstance(right, ABCSeries) and axis == "columns":
982+
# We only get here if called via left._combine_match_columns,
983+
# in which case we specifically want to operate row-by-row
984+
assert right.index.equals(left.columns)
985+
986+
def column_op(a, b):
987+
return {i: func(a.iloc[:, i], b.iloc[i])
988+
for i in range(len(a.columns))}
989+
990+
elif isinstance(right, ABCSeries):
991+
assert right.index.equals(left.index) # Handle other cases later
992+
993+
def column_op(a, b):
994+
return {i: func(a.iloc[:, i], b)
995+
for i in range(len(a.columns))}
996+
997+
else:
998+
# Remaining cases have less-obvious dispatch rules
999+
raise NotImplementedError(right)
1000+
1001+
new_data = expressions.evaluate(column_op, str_rep, left, right)
1002+
1003+
result = left._constructor(new_data, index=left.index, copy=False)
1004+
# Pin columns instead of passing to constructor for compat with
1005+
# non-unique columns case
1006+
result.columns = left.columns
1007+
return result
1008+
1009+
1010+
def dispatch_to_index_op(op, left, right, index_class):
1011+
"""
1012+
Wrap Series left in the given index_class to delegate the operation op
1013+
to the index implementation. DatetimeIndex and TimedeltaIndex perform
1014+
type checking, timezone handling, overflow checks, etc.
1015+
1016+
Parameters
1017+
----------
1018+
op : binary operator (operator.add, operator.sub, ...)
1019+
left : Series
1020+
right : object
1021+
index_class : DatetimeIndex or TimedeltaIndex
1022+
1023+
Returns
1024+
-------
1025+
result : object, usually DatetimeIndex, TimedeltaIndex, or Series
1026+
"""
1027+
left_idx = index_class(left)
1028+
1029+
# avoid accidentally allowing integer add/sub. For datetime64[tz] dtypes,
1030+
# left_idx may inherit a freq from a cached DatetimeIndex.
1031+
# See discussion in GH#19147.
1032+
if getattr(left_idx, 'freq', None) is not None:
1033+
left_idx = left_idx._shallow_copy(freq=None)
1034+
try:
1035+
result = op(left_idx, right)
1036+
except NullFrequencyError:
1037+
# DatetimeIndex and TimedeltaIndex with freq == None raise ValueError
1038+
# on add/sub of integers (or int-like). We re-raise as a TypeError.
1039+
raise TypeError('incompatible type for a datetime/timedelta '
1040+
'operation [{name}]'.format(name=op.__name__))
1041+
return result
1042+
1043+
1044+
def dispatch_to_extension_op(op, left, right):
1045+
"""
1046+
Assume that left or right is a Series backed by an ExtensionArray,
1047+
apply the operator defined by op.
1048+
"""
1049+
1050+
# The op calls will raise TypeError if the op is not defined
1051+
# on the ExtensionArray
1052+
1053+
# unbox Series and Index to arrays
1054+
if isinstance(left, (ABCSeries, ABCIndexClass)):
1055+
new_left = left._values
1056+
else:
1057+
new_left = left
1058+
1059+
if isinstance(right, (ABCSeries, ABCIndexClass)):
1060+
new_right = right._values
1061+
else:
1062+
new_right = right
1063+
1064+
res_values = op(new_left, new_right)
1065+
res_name = get_op_result_name(left, right)
1066+
1067+
if op.__name__ in ['divmod', 'rdivmod']:
1068+
return _construct_divmod_result(
1069+
left, res_values, left.index, res_name)
1070+
1071+
return _construct_result(left, res_values, left.index, res_name)
1072+
1073+
9461074
# -----------------------------------------------------------------------------
9471075
# Functions that add arithmetic methods to objects, given arithmetic factory
9481076
# methods
@@ -1202,36 +1330,6 @@ def _construct_divmod_result(left, result, index, name, dtype=None):
12021330
)
12031331

12041332

1205-
def dispatch_to_extension_op(op, left, right):
1206-
"""
1207-
Assume that left or right is a Series backed by an ExtensionArray,
1208-
apply the operator defined by op.
1209-
"""
1210-
1211-
# The op calls will raise TypeError if the op is not defined
1212-
# on the ExtensionArray
1213-
1214-
# unbox Series and Index to arrays
1215-
if isinstance(left, (ABCSeries, ABCIndexClass)):
1216-
new_left = left._values
1217-
else:
1218-
new_left = left
1219-
1220-
if isinstance(right, (ABCSeries, ABCIndexClass)):
1221-
new_right = right._values
1222-
else:
1223-
new_right = right
1224-
1225-
res_values = op(new_left, new_right)
1226-
res_name = get_op_result_name(left, right)
1227-
1228-
if op.__name__ in ['divmod', 'rdivmod']:
1229-
return _construct_divmod_result(
1230-
left, res_values, left.index, res_name)
1231-
1232-
return _construct_result(left, res_values, left.index, res_name)
1233-
1234-
12351333
def _arith_method_SERIES(cls, op, special):
12361334
"""
12371335
Wrapper function for Series arithmetic operations, to avoid
@@ -1329,40 +1427,6 @@ def wrapper(left, right):
13291427
return wrapper
13301428

13311429

1332-
def dispatch_to_index_op(op, left, right, index_class):
1333-
"""
1334-
Wrap Series left in the given index_class to delegate the operation op
1335-
to the index implementation. DatetimeIndex and TimedeltaIndex perform
1336-
type checking, timezone handling, overflow checks, etc.
1337-
1338-
Parameters
1339-
----------
1340-
op : binary operator (operator.add, operator.sub, ...)
1341-
left : Series
1342-
right : object
1343-
index_class : DatetimeIndex or TimedeltaIndex
1344-
1345-
Returns
1346-
-------
1347-
result : object, usually DatetimeIndex, TimedeltaIndex, or Series
1348-
"""
1349-
left_idx = index_class(left)
1350-
1351-
# avoid accidentally allowing integer add/sub. For datetime64[tz] dtypes,
1352-
# left_idx may inherit a freq from a cached DatetimeIndex.
1353-
# See discussion in GH#19147.
1354-
if getattr(left_idx, 'freq', None) is not None:
1355-
left_idx = left_idx._shallow_copy(freq=None)
1356-
try:
1357-
result = op(left_idx, right)
1358-
except NullFrequencyError:
1359-
# DatetimeIndex and TimedeltaIndex with freq == None raise ValueError
1360-
# on add/sub of integers (or int-like). We re-raise as a TypeError.
1361-
raise TypeError('incompatible type for a datetime/timedelta '
1362-
'operation [{name}]'.format(name=op.__name__))
1363-
return result
1364-
1365-
13661430
def _comp_method_OBJECT_ARRAY(op, x, y):
13671431
if isinstance(y, list):
13681432
y = construct_1d_object_array_from_listlike(y)
@@ -1661,69 +1725,6 @@ def flex_wrapper(self, other, level=None, fill_value=None, axis=0):
16611725
# -----------------------------------------------------------------------------
16621726
# DataFrame
16631727

1664-
def dispatch_to_series(left, right, func, str_rep=None, axis=None):
1665-
"""
1666-
Evaluate the frame operation func(left, right) by evaluating
1667-
column-by-column, dispatching to the Series implementation.
1668-
1669-
Parameters
1670-
----------
1671-
left : DataFrame
1672-
right : scalar or DataFrame
1673-
func : arithmetic or comparison operator
1674-
str_rep : str or None, default None
1675-
axis : {None, 0, 1, "index", "columns"}
1676-
1677-
Returns
1678-
-------
1679-
DataFrame
1680-
"""
1681-
# Note: we use iloc to access columns for compat with cases
1682-
# with non-unique columns.
1683-
import pandas.core.computation.expressions as expressions
1684-
1685-
right = lib.item_from_zerodim(right)
1686-
if lib.is_scalar(right):
1687-
1688-
def column_op(a, b):
1689-
return {i: func(a.iloc[:, i], b)
1690-
for i in range(len(a.columns))}
1691-
1692-
elif isinstance(right, ABCDataFrame):
1693-
assert right._indexed_same(left)
1694-
1695-
def column_op(a, b):
1696-
return {i: func(a.iloc[:, i], b.iloc[:, i])
1697-
for i in range(len(a.columns))}
1698-
1699-
elif isinstance(right, ABCSeries) and axis == "columns":
1700-
# We only get here if called via left._combine_match_columns,
1701-
# in which case we specifically want to operate row-by-row
1702-
assert right.index.equals(left.columns)
1703-
1704-
def column_op(a, b):
1705-
return {i: func(a.iloc[:, i], b.iloc[i])
1706-
for i in range(len(a.columns))}
1707-
1708-
elif isinstance(right, ABCSeries):
1709-
assert right.index.equals(left.index) # Handle other cases later
1710-
1711-
def column_op(a, b):
1712-
return {i: func(a.iloc[:, i], b)
1713-
for i in range(len(a.columns))}
1714-
1715-
else:
1716-
# Remaining cases have less-obvious dispatch rules
1717-
raise NotImplementedError(right)
1718-
1719-
new_data = expressions.evaluate(column_op, str_rep, left, right)
1720-
1721-
result = left._constructor(new_data, index=left.index, copy=False)
1722-
# Pin columns instead of passing to constructor for compat with
1723-
# non-unique columns case
1724-
result.columns = left.columns
1725-
return result
1726-
17271728

17281729
def _combine_series_frame(self, other, func, fill_value=None, axis=None,
17291730
level=None):

0 commit comments

Comments
 (0)