Skip to content

Commit 7db4bea

Browse files
jbrockmendeljreback
authored andcommitted
Continue de-nesting core.ops (pandas-dev#19448)
1 parent f6b260b commit 7db4bea

File tree

2 files changed

+74
-70
lines changed

2 files changed

+74
-70
lines changed

pandas/core/ops.py

+73-69
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@
3939
ABCSeries,
4040
ABCDataFrame,
4141
ABCIndex,
42-
ABCPeriodIndex,
43-
ABCSparseSeries)
42+
ABCSparseSeries, ABCSparseArray)
4443

4544

4645
def _gen_eval_kwargs(name):
@@ -445,17 +444,22 @@ def names(x):
445444
return new_methods
446445

447446

448-
def add_methods(cls, new_methods, force):
447+
def add_methods(cls, new_methods):
449448
for name, method in new_methods.items():
449+
# For most methods, if we find that the class already has a method
450+
# of the same name, it is OK to over-write it. The exception is
451+
# inplace methods (__iadd__, __isub__, ...) for SparseArray, which
452+
# retain the np.ndarray versions.
453+
force = not (issubclass(cls, ABCSparseArray) and
454+
name.startswith('__i'))
450455
if force or name not in cls.__dict__:
451456
bind_method(cls, name, method)
452457

453458

454459
# ----------------------------------------------------------------------
455460
# Arithmetic
456461
def add_special_arithmetic_methods(cls, arith_method=None,
457-
comp_method=None, bool_method=None,
458-
force=False):
462+
comp_method=None, bool_method=None):
459463
"""
460464
Adds the full suite of special arithmetic methods (``__add__``,
461465
``__sub__``, etc.) to the class.
@@ -469,9 +473,6 @@ def add_special_arithmetic_methods(cls, arith_method=None,
469473
factory for rich comparison - signature: f(op, name, str_rep)
470474
bool_method : function (optional)
471475
factory for boolean methods - signature: f(op, name, str_rep)
472-
force : bool, default False
473-
if False, checks whether function is defined **on ``cls.__dict__``**
474-
before defining if True, always defines functions on class base
475476
"""
476477
new_methods = _create_methods(cls, arith_method, comp_method, bool_method,
477478
special=True)
@@ -512,12 +513,11 @@ def f(self, other):
512513
__ior__=_wrap_inplace_method(new_methods["__or__"]),
513514
__ixor__=_wrap_inplace_method(new_methods["__xor__"])))
514515

515-
add_methods(cls, new_methods=new_methods, force=force)
516+
add_methods(cls, new_methods=new_methods)
516517

517518

518519
def add_flex_arithmetic_methods(cls, flex_arith_method,
519-
flex_comp_method=None, flex_bool_method=None,
520-
force=False):
520+
flex_comp_method=None, flex_bool_method=None):
521521
"""
522522
Adds the full suite of flex arithmetic methods (``pow``, ``mul``, ``add``)
523523
to the class.
@@ -529,9 +529,6 @@ def add_flex_arithmetic_methods(cls, flex_arith_method,
529529
f(op, name, str_rep)
530530
flex_comp_method : function, optional,
531531
factory for rich comparison - signature: f(op, name, str_rep)
532-
force : bool, default False
533-
if False, checks whether function is defined **on ``cls.__dict__``**
534-
before defining if True, always defines functions on class base
535532
"""
536533
new_methods = _create_methods(cls, flex_arith_method,
537534
flex_comp_method, flex_bool_method,
@@ -544,7 +541,7 @@ def add_flex_arithmetic_methods(cls, flex_arith_method,
544541
if k in new_methods:
545542
new_methods.pop(k)
546543

547-
add_methods(cls, new_methods=new_methods, force=force)
544+
add_methods(cls, new_methods=new_methods)
548545

549546

550547
# -----------------------------------------------------------------------------
@@ -614,14 +611,11 @@ def na_op(x, y):
614611
result = np.empty(x.size, dtype=dtype)
615612
mask = notna(x) & notna(y)
616613
result[mask] = op(x[mask], com._values_from_object(y[mask]))
617-
elif isinstance(x, np.ndarray):
614+
else:
615+
assert isinstance(x, np.ndarray)
618616
result = np.empty(len(x), dtype=x.dtype)
619617
mask = notna(x)
620618
result[mask] = op(x[mask], y)
621-
else:
622-
raise TypeError("{typ} cannot perform the operation "
623-
"{op}".format(typ=type(x).__name__,
624-
op=str_rep))
625619

626620
result, changed = maybe_upcast_putmask(result, ~mask, np.nan)
627621

@@ -658,6 +652,10 @@ def wrapper(left, right, name=name, na_op=na_op):
658652
index=left.index, name=res_name,
659653
dtype=result.dtype)
660654

655+
elif is_categorical_dtype(left):
656+
raise TypeError("{typ} cannot perform the operation "
657+
"{op}".format(typ=type(left).__name__, op=str_rep))
658+
661659
lvalues = left.values
662660
rvalues = right
663661
if isinstance(rvalues, ABCSeries):
@@ -745,24 +743,19 @@ def na_op(x, y):
745743
elif is_categorical_dtype(y) and not is_scalar(y):
746744
return op(y, x)
747745

748-
if is_object_dtype(x.dtype):
746+
elif is_object_dtype(x.dtype):
749747
result = _comp_method_OBJECT_ARRAY(op, x, y)
748+
749+
elif is_datetimelike_v_numeric(x, y):
750+
raise TypeError("invalid type comparison")
751+
750752
else:
751753

752754
# we want to compare like types
753755
# we only want to convert to integer like if
754756
# we are not NotImplemented, otherwise
755757
# we would allow datetime64 (but viewed as i8) against
756758
# integer comparisons
757-
if is_datetimelike_v_numeric(x, y):
758-
raise TypeError("invalid type comparison")
759-
760-
# numpy does not like comparisons vs None
761-
if is_scalar(y) and isna(y):
762-
if name == '__ne__':
763-
return np.ones(len(x), dtype=bool)
764-
else:
765-
return np.zeros(len(x), dtype=bool)
766759

767760
# we have a datetime/timedelta and may need to convert
768761
mask = None
@@ -795,39 +788,44 @@ def wrapper(self, other, axis=None):
795788
if axis is not None:
796789
self._get_axis_number(axis)
797790

798-
if isinstance(other, ABCSeries):
791+
if isinstance(other, ABCDataFrame): # pragma: no cover
792+
# Defer to DataFrame implementation; fail early
793+
return NotImplemented
794+
795+
elif isinstance(other, ABCSeries):
799796
name = com._maybe_match_name(self, other)
800797
if not self._indexed_same(other):
801798
msg = 'Can only compare identically-labeled Series objects'
802799
raise ValueError(msg)
803-
return self._constructor(na_op(self.values, other.values),
804-
index=self.index, name=name)
805-
elif isinstance(other, ABCDataFrame): # pragma: no cover
806-
return NotImplemented
800+
res_values = na_op(self.values, other.values)
801+
return self._constructor(res_values, index=self.index, name=name)
802+
807803
elif isinstance(other, (np.ndarray, pd.Index)):
808804
# do not check length of zerodim array
809805
# as it will broadcast
810806
if (not is_scalar(lib.item_from_zerodim(other)) and
811807
len(self) != len(other)):
812808
raise ValueError('Lengths must match to compare')
813809

814-
if isinstance(other, ABCPeriodIndex):
815-
# temp workaround until fixing GH 13637
816-
# tested in test_nat_comparisons
817-
# (pandas.tests.series.test_operators.TestSeriesOperators)
818-
return self._constructor(na_op(self.values,
819-
other.astype(object).values),
820-
index=self.index)
821-
822-
return self._constructor(na_op(self.values, np.asarray(other)),
810+
res_values = na_op(self.values, np.asarray(other))
811+
return self._constructor(res_values,
823812
index=self.index).__finalize__(self)
824813

825-
elif isinstance(other, pd.Categorical):
826-
if not is_categorical_dtype(self):
827-
msg = ("Cannot compare a Categorical for op {op} with Series "
828-
"of dtype {typ}.\nIf you want to compare values, use "
829-
"'series <op> np.asarray(other)'.")
830-
raise TypeError(msg.format(op=op, typ=self.dtype))
814+
elif (isinstance(other, pd.Categorical) and
815+
not is_categorical_dtype(self)):
816+
raise TypeError("Cannot compare a Categorical for op {op} with "
817+
"Series of dtype {typ}.\nIf you want to compare "
818+
"values, use 'series <op> np.asarray(other)'."
819+
.format(op=op, typ=self.dtype))
820+
821+
elif is_scalar(other) and isna(other):
822+
# numpy does not like comparisons vs None
823+
if op is operator.ne:
824+
res_values = np.ones(len(self), dtype=bool)
825+
else:
826+
res_values = np.zeros(len(self), dtype=bool)
827+
return self._constructor(res_values, index=self.index,
828+
name=self.name, dtype='bool')
831829

832830
if is_categorical_dtype(self):
833831
# cats are a special case as get_values() would return an ndarray,
@@ -877,11 +875,10 @@ def na_op(x, y):
877875
y = _ensure_object(y)
878876
result = lib.vec_binop(x, y, op)
879877
else:
878+
# let null fall thru
879+
if not isna(y):
880+
y = bool(y)
880881
try:
881-
882-
# let null fall thru
883-
if not isna(y):
884-
y = bool(y)
885882
result = lib.scalar_binop(x, y, op)
886883
except:
887884
msg = ("cannot compare a dtyped [{dtype}] array "
@@ -899,26 +896,31 @@ def wrapper(self, other):
899896

900897
self, other = _align_method_SERIES(self, other, align_asobject=True)
901898

902-
if isinstance(other, ABCSeries):
899+
if isinstance(other, ABCDataFrame):
900+
# Defer to DataFrame implementation; fail early
901+
return NotImplemented
902+
903+
elif isinstance(other, ABCSeries):
903904
name = com._maybe_match_name(self, other)
904905
is_other_int_dtype = is_integer_dtype(other.dtype)
905906
other = fill_int(other) if is_other_int_dtype else fill_bool(other)
906907

907908
filler = (fill_int if is_self_int_dtype and is_other_int_dtype
908909
else fill_bool)
909-
return filler(self._constructor(na_op(self.values, other.values),
910-
index=self.index, name=name))
911910

912-
elif isinstance(other, ABCDataFrame):
913-
return NotImplemented
911+
res_values = na_op(self.values, other.values)
912+
unfilled = self._constructor(res_values,
913+
index=self.index, name=name)
914+
return filler(unfilled)
914915

915916
else:
916917
# scalars, list, tuple, np.array
917918
filler = (fill_int if is_self_int_dtype and
918919
is_integer_dtype(np.asarray(other)) else fill_bool)
919-
return filler(self._constructor(
920-
na_op(self.values, other),
921-
index=self.index)).__finalize__(self)
920+
921+
res_values = na_op(self.values, other)
922+
unfilled = self._constructor(res_values, index=self.index)
923+
return filler(unfilled).__finalize__(self)
922924

923925
return wrapper
924926

@@ -1023,21 +1025,23 @@ def na_op(x, y):
10231025
mask = notna(xrav) & notna(yrav)
10241026
xrav = xrav[mask]
10251027

1026-
# we may need to manually
1027-
# broadcast a 1 element array
10281028
if yrav.shape != mask.shape:
1029-
yrav = np.empty(mask.shape, dtype=yrav.dtype)
1030-
yrav.fill(yrav.item())
1029+
# FIXME: GH#5284, GH#5035, GH#19448
1030+
# Without specifically raising here we get mismatched
1031+
# errors in Py3 (TypeError) vs Py2 (ValueError)
1032+
raise ValueError('Cannot broadcast operands together.')
10311033

10321034
yrav = yrav[mask]
1033-
if np.prod(xrav.shape) and np.prod(yrav.shape):
1035+
if xrav.size:
10341036
with np.errstate(all='ignore'):
10351037
result[mask] = op(xrav, yrav)
1036-
elif hasattr(x, 'size'):
1038+
1039+
elif isinstance(x, np.ndarray):
1040+
# mask is only meaningful for x
10371041
result = np.empty(x.size, dtype=x.dtype)
10381042
mask = notna(xrav)
10391043
xrav = xrav[mask]
1040-
if np.prod(xrav.shape):
1044+
if xrav.size:
10411045
with np.errstate(all='ignore'):
10421046
result[mask] = op(xrav, y)
10431047
else:

pandas/core/sparse/series.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -819,4 +819,4 @@ def from_coo(cls, A, dense_index=False):
819819
ops.add_special_arithmetic_methods(SparseSeries,
820820
ops._arith_method_SPARSE_SERIES,
821821
comp_method=ops._arith_method_SPARSE_SERIES,
822-
bool_method=None, force=True)
822+
bool_method=None)

0 commit comments

Comments
 (0)