Skip to content

Commit d03beab

Browse files
jbrockmendeljreback
authored andcommitted
implement array_ops (#27936)
1 parent e66ad6c commit d03beab

File tree

4 files changed

+135
-121
lines changed

4 files changed

+135
-121
lines changed

pandas/core/arrays/datetimes.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,11 @@ def wrapper(self, other):
195195
return invalid_comparison(self, other, op)
196196

197197
if is_object_dtype(other):
198-
# We have to use _comp_method_OBJECT_ARRAY instead of numpy
198+
# We have to use comp_method_OBJECT_ARRAY instead of numpy
199199
# comparison otherwise it would fail to raise when
200200
# comparing tz-aware and tz-naive
201201
with np.errstate(all="ignore"):
202-
result = ops._comp_method_OBJECT_ARRAY(
202+
result = ops.comp_method_OBJECT_ARRAY(
203203
op, self.astype(object), other
204204
)
205205
o_mask = isna(other)

pandas/core/indexes/base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def cmp_method(self, other):
109109
elif is_object_dtype(self) and not isinstance(self, ABCMultiIndex):
110110
# don't pass MultiIndex
111111
with np.errstate(all="ignore"):
112-
result = ops._comp_method_OBJECT_ARRAY(op, self.values, other)
112+
result = ops.comp_method_OBJECT_ARRAY(op, self.values, other)
113113

114114
else:
115115
with np.errstate(all="ignore"):

pandas/core/ops/__init__.py

+5-118
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@
1313
from pandas.errors import NullFrequencyError
1414
from pandas.util._decorators import Appender
1515

16-
from pandas.core.dtypes.cast import (
17-
construct_1d_object_array_from_listlike,
18-
find_common_type,
19-
maybe_upcast_putmask,
20-
)
16+
from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike
2117
from pandas.core.dtypes.common import (
2218
ensure_object,
2319
is_bool_dtype,
@@ -29,15 +25,13 @@
2925
is_integer_dtype,
3026
is_list_like,
3127
is_object_dtype,
32-
is_period_dtype,
3328
is_scalar,
3429
is_timedelta64_dtype,
3530
)
3631
from pandas.core.dtypes.generic import (
3732
ABCDataFrame,
3833
ABCDatetimeArray,
3934
ABCDatetimeIndex,
40-
ABCIndex,
4135
ABCIndexClass,
4236
ABCSeries,
4337
ABCSparseSeries,
@@ -47,7 +41,7 @@
4741
import pandas as pd
4842
from pandas._typing import ArrayLike
4943
from pandas.core.construction import array, extract_array
50-
from pandas.core.ops import missing
44+
from pandas.core.ops.array_ops import comp_method_OBJECT_ARRAY, define_na_arithmetic_op
5145
from pandas.core.ops.docstrings import (
5246
_arith_doc_FRAME,
5347
_flex_comp_doc_FRAME,
@@ -398,63 +392,6 @@ def mask_cmp_op(x, y, op):
398392
return result
399393

400394

401-
def masked_arith_op(x, y, op):
402-
"""
403-
If the given arithmetic operation fails, attempt it again on
404-
only the non-null elements of the input array(s).
405-
406-
Parameters
407-
----------
408-
x : np.ndarray
409-
y : np.ndarray, Series, Index
410-
op : binary operator
411-
"""
412-
# For Series `x` is 1D so ravel() is a no-op; calling it anyway makes
413-
# the logic valid for both Series and DataFrame ops.
414-
xrav = x.ravel()
415-
assert isinstance(x, np.ndarray), type(x)
416-
if isinstance(y, np.ndarray):
417-
dtype = find_common_type([x.dtype, y.dtype])
418-
result = np.empty(x.size, dtype=dtype)
419-
420-
# PeriodIndex.ravel() returns int64 dtype, so we have
421-
# to work around that case. See GH#19956
422-
yrav = y if is_period_dtype(y) else y.ravel()
423-
mask = notna(xrav) & notna(yrav)
424-
425-
if yrav.shape != mask.shape:
426-
# FIXME: GH#5284, GH#5035, GH#19448
427-
# Without specifically raising here we get mismatched
428-
# errors in Py3 (TypeError) vs Py2 (ValueError)
429-
# Note: Only = an issue in DataFrame case
430-
raise ValueError("Cannot broadcast operands together.")
431-
432-
if mask.any():
433-
with np.errstate(all="ignore"):
434-
result[mask] = op(xrav[mask], yrav[mask])
435-
436-
else:
437-
assert is_scalar(y), type(y)
438-
assert isinstance(x, np.ndarray), type(x)
439-
# mask is only meaningful for x
440-
result = np.empty(x.size, dtype=x.dtype)
441-
mask = notna(xrav)
442-
443-
# 1 ** np.nan is 1. So we have to unmask those.
444-
if op == pow:
445-
mask = np.where(x == 1, False, mask)
446-
elif op == rpow:
447-
mask = np.where(y == 1, False, mask)
448-
449-
if mask.any():
450-
with np.errstate(all="ignore"):
451-
result[mask] = op(xrav[mask], y)
452-
453-
result, changed = maybe_upcast_putmask(result, ~mask, np.nan)
454-
result = result.reshape(x.shape) # 2D compat
455-
return result
456-
457-
458395
# -----------------------------------------------------------------------------
459396
# Dispatch logic
460397

@@ -673,33 +610,7 @@ def _arith_method_SERIES(cls, op, special):
673610
_construct_divmod_result if op in [divmod, rdivmod] else _construct_result
674611
)
675612

676-
def na_op(x, y):
677-
"""
678-
Return the result of evaluating op on the passed in values.
679-
680-
If native types are not compatible, try coersion to object dtype.
681-
682-
Parameters
683-
----------
684-
x : array-like
685-
y : array-like or scalar
686-
687-
Returns
688-
-------
689-
array-like
690-
691-
Raises
692-
------
693-
TypeError : invalid operation
694-
"""
695-
import pandas.core.computation.expressions as expressions
696-
697-
try:
698-
result = expressions.evaluate(op, str_rep, x, y, **eval_kwargs)
699-
except TypeError:
700-
result = masked_arith_op(x, y, op)
701-
702-
return missing.dispatch_fill_zeros(op, x, y, result)
613+
na_op = define_na_arithmetic_op(op, str_rep, eval_kwargs)
703614

704615
def wrapper(left, right):
705616
if isinstance(right, ABCDataFrame):
@@ -735,22 +646,6 @@ def wrapper(left, right):
735646
return wrapper
736647

737648

738-
def _comp_method_OBJECT_ARRAY(op, x, y):
739-
if isinstance(y, list):
740-
y = construct_1d_object_array_from_listlike(y)
741-
if isinstance(y, (np.ndarray, ABCSeries, ABCIndex)):
742-
if not is_object_dtype(y.dtype):
743-
y = y.astype(np.object_)
744-
745-
if isinstance(y, (ABCSeries, ABCIndex)):
746-
y = y.values
747-
748-
result = libops.vec_compare(x, y, op)
749-
else:
750-
result = libops.scalar_compare(x, y, op)
751-
return result
752-
753-
754649
def _comp_method_SERIES(cls, op, special):
755650
"""
756651
Wrapper function for Series arithmetic operations, to avoid
@@ -764,7 +659,7 @@ def na_op(x, y):
764659
# Extension Dtypes are not called here
765660

766661
if is_object_dtype(x.dtype):
767-
result = _comp_method_OBJECT_ARRAY(op, x, y)
662+
result = comp_method_OBJECT_ARRAY(op, x, y)
768663

769664
elif is_datetimelike_v_numeric(x, y):
770665
return invalid_comparison(x, y, op)
@@ -1091,15 +986,7 @@ def _arith_method_FRAME(cls, op, special):
1091986
eval_kwargs = _gen_eval_kwargs(op_name)
1092987
default_axis = _get_frame_op_default_axis(op_name)
1093988

1094-
def na_op(x, y):
1095-
import pandas.core.computation.expressions as expressions
1096-
1097-
try:
1098-
result = expressions.evaluate(op, str_rep, x, y, **eval_kwargs)
1099-
except TypeError:
1100-
result = masked_arith_op(x, y, op)
1101-
1102-
return missing.dispatch_fill_zeros(op, x, y, result)
989+
na_op = define_na_arithmetic_op(op, str_rep, eval_kwargs)
1103990

1104991
if op_name in _op_descriptions:
1105992
# i.e. include "add" but not "__add__"

pandas/core/ops/array_ops.py

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""
2+
Functions for arithmetic and comparison operations on NumPy arrays and
3+
ExtensionArrays.
4+
"""
5+
import numpy as np
6+
7+
from pandas._libs import ops as libops
8+
9+
from pandas.core.dtypes.cast import (
10+
construct_1d_object_array_from_listlike,
11+
find_common_type,
12+
maybe_upcast_putmask,
13+
)
14+
from pandas.core.dtypes.common import is_object_dtype, is_period_dtype, is_scalar
15+
from pandas.core.dtypes.generic import ABCIndex, ABCSeries
16+
from pandas.core.dtypes.missing import notna
17+
18+
from pandas.core.ops import missing
19+
from pandas.core.ops.roperator import rpow
20+
21+
22+
def comp_method_OBJECT_ARRAY(op, x, y):
23+
if isinstance(y, list):
24+
y = construct_1d_object_array_from_listlike(y)
25+
26+
# TODO: Should the checks below be ABCIndexClass?
27+
if isinstance(y, (np.ndarray, ABCSeries, ABCIndex)):
28+
# TODO: should this be ABCIndexClass??
29+
if not is_object_dtype(y.dtype):
30+
y = y.astype(np.object_)
31+
32+
if isinstance(y, (ABCSeries, ABCIndex)):
33+
y = y.values
34+
35+
result = libops.vec_compare(x, y, op)
36+
else:
37+
result = libops.scalar_compare(x, y, op)
38+
return result
39+
40+
41+
def masked_arith_op(x, y, op):
42+
"""
43+
If the given arithmetic operation fails, attempt it again on
44+
only the non-null elements of the input array(s).
45+
46+
Parameters
47+
----------
48+
x : np.ndarray
49+
y : np.ndarray, Series, Index
50+
op : binary operator
51+
"""
52+
# For Series `x` is 1D so ravel() is a no-op; calling it anyway makes
53+
# the logic valid for both Series and DataFrame ops.
54+
xrav = x.ravel()
55+
assert isinstance(x, np.ndarray), type(x)
56+
if isinstance(y, np.ndarray):
57+
dtype = find_common_type([x.dtype, y.dtype])
58+
result = np.empty(x.size, dtype=dtype)
59+
60+
# PeriodIndex.ravel() returns int64 dtype, so we have
61+
# to work around that case. See GH#19956
62+
yrav = y if is_period_dtype(y) else y.ravel()
63+
mask = notna(xrav) & notna(yrav)
64+
65+
if yrav.shape != mask.shape:
66+
# FIXME: GH#5284, GH#5035, GH#19448
67+
# Without specifically raising here we get mismatched
68+
# errors in Py3 (TypeError) vs Py2 (ValueError)
69+
# Note: Only = an issue in DataFrame case
70+
raise ValueError("Cannot broadcast operands together.")
71+
72+
if mask.any():
73+
with np.errstate(all="ignore"):
74+
result[mask] = op(xrav[mask], yrav[mask])
75+
76+
else:
77+
assert is_scalar(y), type(y)
78+
assert isinstance(x, np.ndarray), type(x)
79+
# mask is only meaningful for x
80+
result = np.empty(x.size, dtype=x.dtype)
81+
mask = notna(xrav)
82+
83+
# 1 ** np.nan is 1. So we have to unmask those.
84+
if op == pow:
85+
mask = np.where(x == 1, False, mask)
86+
elif op == rpow:
87+
mask = np.where(y == 1, False, mask)
88+
89+
if mask.any():
90+
with np.errstate(all="ignore"):
91+
result[mask] = op(xrav[mask], y)
92+
93+
result, changed = maybe_upcast_putmask(result, ~mask, np.nan)
94+
result = result.reshape(x.shape) # 2D compat
95+
return result
96+
97+
98+
def define_na_arithmetic_op(op, str_rep, eval_kwargs):
99+
def na_op(x, y):
100+
"""
101+
Return the result of evaluating op on the passed in values.
102+
103+
If native types are not compatible, try coersion to object dtype.
104+
105+
Parameters
106+
----------
107+
x : array-like
108+
y : array-like or scalar
109+
110+
Returns
111+
-------
112+
array-like
113+
114+
Raises
115+
------
116+
TypeError : invalid operation
117+
"""
118+
import pandas.core.computation.expressions as expressions
119+
120+
try:
121+
result = expressions.evaluate(op, str_rep, x, y, **eval_kwargs)
122+
except TypeError:
123+
result = masked_arith_op(x, y, op)
124+
125+
return missing.dispatch_fill_zeros(op, x, y, result)
126+
127+
return na_op

0 commit comments

Comments
 (0)