Skip to content

Commit 4dba140

Browse files
jbrockmendeljreback
authored andcommitted
CLN/REF: Unify Arithmetic Methods (#27413)
1 parent 28317f5 commit 4dba140

File tree

5 files changed

+44
-78
lines changed

5 files changed

+44
-78
lines changed

pandas/core/arrays/datetimelike.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from pandas.core.dtypes.missing import is_valid_nat_for_dtype, isna
4040

4141
from pandas._typing import DatetimeLikeScalar
42-
from pandas.core import missing, nanops
42+
from pandas.core import missing, nanops, ops
4343
from pandas.core.algorithms import checked_add_with_arr, take, unique1d, value_counts
4444
import pandas.core.common as com
4545

@@ -926,6 +926,21 @@ def _is_unique(self):
926926
# ------------------------------------------------------------------
927927
# Arithmetic Methods
928928

929+
# pow is invalid for all three subclasses; TimedeltaArray will override
930+
# the multiplication and division ops
931+
__pow__ = ops.make_invalid_op("__pow__")
932+
__rpow__ = ops.make_invalid_op("__rpow__")
933+
__mul__ = ops.make_invalid_op("__mul__")
934+
__rmul__ = ops.make_invalid_op("__rmul__")
935+
__truediv__ = ops.make_invalid_op("__truediv__")
936+
__rtruediv__ = ops.make_invalid_op("__rtruediv__")
937+
__floordiv__ = ops.make_invalid_op("__floordiv__")
938+
__rfloordiv__ = ops.make_invalid_op("__rfloordiv__")
939+
__mod__ = ops.make_invalid_op("__mod__")
940+
__rmod__ = ops.make_invalid_op("__rmod__")
941+
__divmod__ = ops.make_invalid_op("__divmod__")
942+
__rdivmod__ = ops.make_invalid_op("__rdivmod__")
943+
929944
def _add_datetimelike_scalar(self, other):
930945
# Overriden by TimedeltaArray
931946
raise TypeError(

pandas/core/indexes/base.py

+2-49
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from pandas._libs import algos as libalgos, index as libindex, lib
1010
import pandas._libs.join as libjoin
1111
from pandas._libs.lib import is_datetime_array
12-
from pandas._libs.tslibs import OutOfBoundsDatetime, Timedelta, Timestamp
12+
from pandas._libs.tslibs import OutOfBoundsDatetime, Timestamp
1313
from pandas._libs.tslibs.timezones import tz_compare
1414
from pandas.compat import set_function_name
1515
from pandas.compat.numpy import function as nv
@@ -55,7 +55,6 @@
5555
ABCPandasArray,
5656
ABCPeriodIndex,
5757
ABCSeries,
58-
ABCTimedeltaArray,
5958
ABCTimedeltaIndex,
6059
)
6160
from pandas.core.dtypes.missing import array_equivalent, isna
@@ -126,28 +125,8 @@ def cmp_method(self, other):
126125

127126
def _make_arithmetic_op(op, cls):
128127
def index_arithmetic_method(self, other):
129-
if isinstance(other, (ABCSeries, ABCDataFrame)):
130-
return NotImplemented
131-
elif isinstance(other, ABCTimedeltaIndex):
132-
# Defer to subclass implementation
128+
if isinstance(other, (ABCSeries, ABCDataFrame, ABCTimedeltaIndex)):
133129
return NotImplemented
134-
elif isinstance(
135-
other, (np.ndarray, ABCTimedeltaArray)
136-
) and is_timedelta64_dtype(other):
137-
# GH#22390; wrap in Series for op, this will in turn wrap in
138-
# TimedeltaIndex, but will correctly raise TypeError instead of
139-
# NullFrequencyError for add/sub ops
140-
from pandas import Series
141-
142-
other = Series(other)
143-
out = op(self, other)
144-
return Index(out, name=self.name)
145-
146-
# handle time-based others
147-
if isinstance(other, (ABCDateOffset, np.timedelta64, timedelta)):
148-
return self._evaluate_with_timedelta_like(other, op)
149-
150-
other = self._validate_for_numeric_binop(other, op)
151130

152131
from pandas import Series
153132

@@ -5332,32 +5311,6 @@ def drop(self, labels, errors="raise"):
53325311
# --------------------------------------------------------------------
53335312
# Generated Arithmetic, Comparison, and Unary Methods
53345313

5335-
def _evaluate_with_timedelta_like(self, other, op):
5336-
# Timedelta knows how to operate with np.array, so dispatch to that
5337-
# operation and then wrap the results
5338-
if self._is_numeric_dtype and op.__name__ in ["add", "sub", "radd", "rsub"]:
5339-
raise TypeError(
5340-
"Operation {opname} between {cls} and {other} "
5341-
"is invalid".format(
5342-
opname=op.__name__, cls=self.dtype, other=type(other).__name__
5343-
)
5344-
)
5345-
5346-
other = Timedelta(other)
5347-
values = self.values
5348-
5349-
with np.errstate(all="ignore"):
5350-
result = op(values, other)
5351-
5352-
attrs = self._get_attributes_dict()
5353-
attrs = self._maybe_update_attributes(attrs)
5354-
if op == divmod:
5355-
return Index(result[0], **attrs), Index(result[1], **attrs)
5356-
return Index(result, **attrs)
5357-
5358-
def _evaluate_with_datetime_like(self, other, op):
5359-
raise TypeError("can only perform ops with datetime like values")
5360-
53615314
@classmethod
53625315
def _add_comparison_methods(cls):
53635316
"""

pandas/core/indexes/datetimelike.py

+23
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ def method(self, *args, **kwargs):
6262
return method
6363

6464

65+
def _make_wrapped_arith_op(opname):
66+
def method(self, other):
67+
meth = getattr(self._data, opname)
68+
result = meth(maybe_unwrap_index(other))
69+
return wrap_arithmetic_op(self, other, result)
70+
71+
method.__name__ = opname
72+
return method
73+
74+
6575
class DatetimeIndexOpsMixin(ExtensionOpsMixin):
6676
"""
6777
common ops mixin to support a unified interface datetimelike Index
@@ -531,6 +541,19 @@ def __rsub__(self, other):
531541

532542
cls.__rsub__ = __rsub__
533543

544+
__pow__ = _make_wrapped_arith_op("__pow__")
545+
__rpow__ = _make_wrapped_arith_op("__rpow__")
546+
__mul__ = _make_wrapped_arith_op("__mul__")
547+
__rmul__ = _make_wrapped_arith_op("__rmul__")
548+
__floordiv__ = _make_wrapped_arith_op("__floordiv__")
549+
__rfloordiv__ = _make_wrapped_arith_op("__rfloordiv__")
550+
__mod__ = _make_wrapped_arith_op("__mod__")
551+
__rmod__ = _make_wrapped_arith_op("__rmod__")
552+
__divmod__ = _make_wrapped_arith_op("__divmod__")
553+
__rdivmod__ = _make_wrapped_arith_op("__rdivmod__")
554+
__truediv__ = _make_wrapped_arith_op("__truediv__")
555+
__rtruediv__ = _make_wrapped_arith_op("__rtruediv__")
556+
534557
def isin(self, values, level=None):
535558
"""
536559
Compute boolean array of whether each index value is found in the

pandas/core/indexes/timedeltas.py

-25
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,13 @@
3030
from pandas.core.indexes.datetimelike import (
3131
DatetimeIndexOpsMixin,
3232
DatetimelikeDelegateMixin,
33-
maybe_unwrap_index,
34-
wrap_arithmetic_op,
3533
)
3634
from pandas.core.indexes.numeric import Int64Index
3735
from pandas.core.ops import get_op_result_name
3836

3937
from pandas.tseries.frequencies import to_offset
4038

4139

42-
def _make_wrapped_arith_op(opname):
43-
44-
meth = getattr(TimedeltaArray, opname)
45-
46-
def method(self, other):
47-
result = meth(self._data, maybe_unwrap_index(other))
48-
return wrap_arithmetic_op(self, other, result)
49-
50-
method.__name__ = opname
51-
return method
52-
53-
5440
class TimedeltaDelegateMixin(DatetimelikeDelegateMixin):
5541
# Most attrs are dispatched via datetimelike_{ops,methods}
5642
# Some are "raw" methods, the result is not not re-boxed in an Index
@@ -320,17 +306,6 @@ def _format_native_types(self, na_rep="NaT", date_format=None, **kwargs):
320306
# -------------------------------------------------------------------
321307
# Wrapping TimedeltaArray
322308

323-
__mul__ = _make_wrapped_arith_op("__mul__")
324-
__rmul__ = _make_wrapped_arith_op("__rmul__")
325-
__floordiv__ = _make_wrapped_arith_op("__floordiv__")
326-
__rfloordiv__ = _make_wrapped_arith_op("__rfloordiv__")
327-
__mod__ = _make_wrapped_arith_op("__mod__")
328-
__rmod__ = _make_wrapped_arith_op("__rmod__")
329-
__divmod__ = _make_wrapped_arith_op("__divmod__")
330-
__rdivmod__ = _make_wrapped_arith_op("__rdivmod__")
331-
__truediv__ = _make_wrapped_arith_op("__truediv__")
332-
__rtruediv__ = _make_wrapped_arith_op("__rtruediv__")
333-
334309
# Compat for frequency inference, see GH#23789
335310
_is_monotonic_increasing = Index.is_monotonic_increasing
336311
_is_monotonic_decreasing = Index.is_monotonic_decreasing

pandas/core/ops/__init__.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -425,8 +425,8 @@ def masked_arith_op(x, y, op):
425425
# For Series `x` is 1D so ravel() is a no-op; calling it anyway makes
426426
# the logic valid for both Series and DataFrame ops.
427427
xrav = x.ravel()
428-
assert isinstance(x, (np.ndarray, ABCSeries)), type(x)
429-
if isinstance(y, (np.ndarray, ABCSeries, ABCIndexClass)):
428+
assert isinstance(x, np.ndarray), type(x)
429+
if isinstance(y, np.ndarray):
430430
dtype = find_common_type([x.dtype, y.dtype])
431431
result = np.empty(x.size, dtype=dtype)
432432

@@ -444,7 +444,7 @@ def masked_arith_op(x, y, op):
444444

445445
if mask.any():
446446
with np.errstate(all="ignore"):
447-
result[mask] = op(xrav[mask], com.values_from_object(yrav[mask]))
447+
result[mask] = op(xrav[mask], yrav[mask])
448448

449449
else:
450450
assert is_scalar(y), type(y)

0 commit comments

Comments
 (0)