Skip to content

REF: implement arithmetic, comparison, logical ops on arrays #28431

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Sep 23, 2019
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9a617e3
REF: implement logical and comparison array ops
jbrockmendel Sep 12, 2019
3f414b1
Merge branch 'master' of https://github.com/pandas-dev/pandas into ar…
jbrockmendel Sep 13, 2019
396b4a8
Merge branch 'master' of https://github.com/pandas-dev/pandas into ar…
jbrockmendel Sep 13, 2019
56dff20
implement arithmetic_op
jbrockmendel Sep 13, 2019
77e3241
Merge branch 'master' of https://github.com/pandas-dev/pandas into ar…
jbrockmendel Sep 13, 2019
148a8e8
add comments, types
jbrockmendel Sep 13, 2019
fcf9735
typo fixup
jbrockmendel Sep 13, 2019
fec86de
revert types
jbrockmendel Sep 14, 2019
2abdccb
add types
jbrockmendel Sep 17, 2019
121d783
docstrings
jbrockmendel Sep 17, 2019
5faa820
Merge branch 'master' of https://github.com/pandas-dev/pandas into ar…
jbrockmendel Sep 17, 2019
267c7ca
ignore type
jbrockmendel Sep 17, 2019
0b5aa34
revert technically-incorrect type
jbrockmendel Sep 17, 2019
ad6da57
Merge branch 'master' of https://github.com/pandas-dev/pandas into ar…
jbrockmendel Sep 18, 2019
8ced97b
REF: move na_op out
jbrockmendel Sep 18, 2019
6b9bce0
Merge branch 'master' of https://github.com/pandas-dev/pandas into ar…
jbrockmendel Sep 18, 2019
b0d6263
Merge branch 'master' of https://github.com/pandas-dev/pandas into ar…
jbrockmendel Sep 19, 2019
3226401
Merge branch 'master' of https://github.com/pandas-dev/pandas into ar…
jbrockmendel Sep 20, 2019
dc003e4
Merge branch 'master' of https://github.com/pandas-dev/pandas into ar…
jbrockmendel Sep 21, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 9 additions & 146 deletions pandas/core/ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,11 @@

import numpy as np

from pandas._libs import Timedelta, Timestamp, lib, ops as libops
from pandas._libs import Timedelta, Timestamp, lib
from pandas.errors import NullFrequencyError
from pandas.util._decorators import Appender

from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike
from pandas.core.dtypes.common import (
ensure_object,
is_bool_dtype,
is_datetime64_dtype,
is_extension_array_dtype,
is_integer_dtype,
Expand All @@ -27,30 +24,28 @@
)
from pandas.core.dtypes.generic import (
ABCDataFrame,
ABCDatetimeArray,
ABCDatetimeIndex,
ABCExtensionArray,
ABCIndexClass,
ABCSeries,
ABCTimedeltaArray,
ABCTimedeltaIndex,
)
from pandas.core.dtypes.missing import isna, notna

from pandas._typing import ArrayLike
from pandas.core.construction import array, extract_array
from pandas.core.ops.array_ops import (
comp_method_OBJECT_ARRAY,
arithmetic_op,
comparison_op,
define_na_arithmetic_op,
na_arithmetic_op,
logical_op,
)
from pandas.core.ops.array_ops import comp_method_OBJECT_ARRAY # noqa:F401
from pandas.core.ops.docstrings import (
_arith_doc_FRAME,
_flex_comp_doc_FRAME,
_make_flex_doc,
_op_descriptions,
)
from pandas.core.ops.invalid import invalid_comparison
from pandas.core.ops.invalid import invalid_comparison # noqa:F401
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this still needed? (also on comp_method_OBJECT_ARRAY)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. They are imported elsewhere. This will get cleaned up as we get more stuff out of __init__

from pandas.core.ops.methods import ( # noqa:F401
add_flex_arithmetic_methods,
add_special_arithmetic_methods,
Expand Down Expand Up @@ -632,30 +627,8 @@ def wrapper(left, right):
left, right = _align_method_SERIES(left, right)
res_name = get_op_result_name(left, right)

keep_null_freq = isinstance(
right,
(
ABCDatetimeIndex,
ABCDatetimeArray,
ABCTimedeltaIndex,
ABCTimedeltaArray,
Timestamp,
),
)

lvalues = extract_array(left, extract_numpy=True)
rvalues = extract_array(right, extract_numpy=True)

rvalues = maybe_upcast_for_op(rvalues, lvalues.shape)

if should_extension_dispatch(left, rvalues) or isinstance(
rvalues, (ABCTimedeltaArray, ABCDatetimeArray, Timestamp)
):
result = dispatch_to_extension_op(op, lvalues, rvalues, keep_null_freq)

else:
with np.errstate(all="ignore"):
result = na_arithmetic_op(lvalues, rvalues, op, str_rep, eval_kwargs)
result = arithmetic_op(lvalues, right, op, str_rep, eval_kwargs)

# We do not pass dtype to ensure that the Series constructor
# does inference in the case where `result` has object-dtype.
Expand Down Expand Up @@ -691,46 +664,10 @@ def wrapper(self, other):
if isinstance(other, ABCSeries) and not self._indexed_same(other):
raise ValueError("Can only compare identically-labeled Series objects")

other = lib.item_from_zerodim(other)
if isinstance(other, list):
# TODO: same for tuples?
other = np.asarray(other)

if isinstance(other, (np.ndarray, ABCExtensionArray, ABCIndexClass)):
# TODO: make this treatment consistent across ops and classes.
# We are not catching all listlikes here (e.g. frozenset, tuple)
# The ambiguous case is object-dtype. See GH#27803
if len(self) != len(other):
raise ValueError("Lengths must match to compare")

lvalues = extract_array(self, extract_numpy=True)
rvalues = extract_array(other, extract_numpy=True)

if should_extension_dispatch(lvalues, rvalues):
res_values = dispatch_to_extension_op(op, lvalues, rvalues)

elif is_scalar(rvalues) and isna(rvalues):
# numpy does not like comparisons vs None
if op is operator.ne:
res_values = np.ones(len(lvalues), dtype=bool)
else:
res_values = np.zeros(len(lvalues), dtype=bool)

elif is_object_dtype(lvalues.dtype):
res_values = comp_method_OBJECT_ARRAY(op, lvalues, rvalues)

else:
op_name = "__{op}__".format(op=op.__name__)
method = getattr(lvalues, op_name)
with np.errstate(all="ignore"):
res_values = method(rvalues)

if res_values is NotImplemented:
res_values = invalid_comparison(lvalues, rvalues, op)
if is_scalar(res_values):
raise TypeError(
"Could not compare {typ} type with Series".format(typ=type(rvalues))
)
res_values = comparison_op(lvalues, rvalues, op)

result = self._constructor(res_values, index=self.index)
result = finalizer(result)
Expand All @@ -751,58 +688,7 @@ def _bool_method_SERIES(cls, op, special):
"""
op_name = _get_op_name(op, special)

def na_op(x, y):
try:
result = op(x, y)
except TypeError:
assert not isinstance(y, (list, ABCSeries, ABCIndexClass))
if isinstance(y, np.ndarray):
# bool-bool dtype operations should be OK, should not get here
assert not (is_bool_dtype(x.dtype) and is_bool_dtype(y.dtype))
x = ensure_object(x)
y = ensure_object(y)
result = libops.vec_binop(x, y, op)
else:
# let null fall thru
assert lib.is_scalar(y)
if not isna(y):
y = bool(y)
try:
result = libops.scalar_binop(x, y, op)
except (
TypeError,
ValueError,
AttributeError,
OverflowError,
NotImplementedError,
):
raise TypeError(
"cannot compare a dtyped [{dtype}] array "
"with a scalar of type [{typ}]".format(
dtype=x.dtype, typ=type(y).__name__
)
)

return result

fill_int = lambda x: x

def fill_bool(x, left=None):
# if `left` is specifically not-boolean, we do not cast to bool
if x.dtype.kind in ["c", "f", "O"]:
# dtypes that can hold NA
mask = isna(x)
if mask.any():
x = x.astype(object)
x[mask] = False

if left is None or is_bool_dtype(left.dtype):
x = x.astype(bool)
return x

def wrapper(self, other):
is_self_int_dtype = is_integer_dtype(self.dtype)

self, other = _align_method_SERIES(self, other, align_asobject=True)
res_name = get_op_result_name(self, other)

Expand All @@ -818,33 +704,10 @@ def wrapper(self, other):
# Defer to DataFrame implementation; fail early
return NotImplemented

other = lib.item_from_zerodim(other)
if is_list_like(other) and not hasattr(other, "dtype"):
# e.g. list, tuple
other = construct_1d_object_array_from_listlike(other)

lvalues = extract_array(self, extract_numpy=True)
rvalues = extract_array(other, extract_numpy=True)

if should_extension_dispatch(self, rvalues):
res_values = dispatch_to_extension_op(op, lvalues, rvalues)

else:
if isinstance(rvalues, (ABCSeries, ABCIndexClass, np.ndarray)):
is_other_int_dtype = is_integer_dtype(rvalues.dtype)
rvalues = rvalues if is_other_int_dtype else fill_bool(rvalues, lvalues)

else:
# i.e. scalar
is_other_int_dtype = lib.is_integer(rvalues)

# For int vs int `^`, `|`, `&` are bitwise operators and return
# integer dtypes. Otherwise these are boolean ops
filler = fill_int if is_self_int_dtype and is_other_int_dtype else fill_bool

res_values = na_op(lvalues, rvalues)
res_values = filler(res_values)

res_values = logical_op(lvalues, rvalues, op)
result = self._constructor(res_values, index=self.index, name=res_name)
return finalizer(result)

Expand Down
Loading