Skip to content

Dispatch Index ops to Series #27352

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 6 commits into from
Jul 12, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
30 changes: 12 additions & 18 deletions pandas/core/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@
from pandas.core.indexes.frozen import FrozenList
import pandas.core.missing as missing
from pandas.core.ops import get_op_result_name, make_invalid_op
from pandas.core.ops.missing import dispatch_missing
import pandas.core.sorting as sorting
from pandas.core.strings import StringMethods

Expand Down Expand Up @@ -144,27 +143,18 @@ def index_arithmetic_method(self, other):
out = op(self, other)
return Index(out, name=self.name)

other = self._validate_for_numeric_binop(other, op)

# handle time-based others
if isinstance(other, (ABCDateOffset, np.timedelta64, timedelta)):
return self._evaluate_with_timedelta_like(other, op)
elif isinstance(other, (datetime, np.datetime64)):
return self._evaluate_with_datetime_like(other, op)

values = self.values
with np.errstate(all="ignore"):
result = op(values, other)
other = self._validate_for_numeric_binop(other, op)

result = dispatch_missing(op, values, other, result)
from pandas import Series

attrs = self._get_attributes_dict()
attrs = self._maybe_update_attributes(attrs)
if op is divmod:
result = (Index(result[0], **attrs), Index(result[1], **attrs))
else:
result = Index(result, **attrs)
return result
result = op(Series(self), other)
if isinstance(result, tuple):
return (Index(result[0]), Index(result[1]))
return Index(result)

name = "__{name}__".format(name=op.__name__)
# TODO: docstring?
Expand Down Expand Up @@ -2361,10 +2351,14 @@ def _get_unique_index(self, dropna=False):
def __add__(self, other):
if isinstance(other, (ABCSeries, ABCDataFrame)):
return NotImplemented
return Index(np.array(self) + other)
from pandas import Series

return Index(Series(self) + other)

def __radd__(self, other):
return Index(other + np.array(self))
from pandas import Series

return Index(other + Series(self))

def __iadd__(self, other):
# alias for __add__
Expand Down
9 changes: 8 additions & 1 deletion pandas/core/ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
)
from pandas.core.dtypes.generic import (
ABCDataFrame,
ABCDatetimeArray,
ABCIndex,
ABCIndexClass,
ABCSeries,
Expand Down Expand Up @@ -1702,10 +1703,16 @@ def wrapper(left, right):
# does inference in the case where `result` has object-dtype.
return construct_result(left, result, index=left.index, name=res_name)

elif isinstance(right, (ABCDatetimeArray, pd.DatetimeIndex)):
result = op(left._values, right)
return construct_result(left, result, index=left.index, name=res_name)

lvalues = left.values
rvalues = right
if isinstance(rvalues, ABCSeries):
Copy link
Contributor

Choose a reason for hiding this comment

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

these cases are the same, yes?

isinstance(rvalues, (ABCSeries, ABCIndexClass))

rvalues = rvalues.values
rvalues = rvalues._values
elif isinstance(rvalues, ABCIndexClass):
rvalues = rvalues._values

with np.errstate(all="ignore"):
result = na_op(lvalues, rvalues)
Expand Down
34 changes: 10 additions & 24 deletions pandas/core/ops/missing.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,40 +148,26 @@ def mask_zero_div_zero(x, y, result):
return result


def dispatch_missing(op, left, right, result):
def dispatch_fill_zeros(op, left, right, result):
"""
Fill nulls caused by division by zero, casting to a different dtype
if necessary.
Call fill_zeros with the appropriate fill value depending on the operation,
with special logic for divmod and rdivmod.

Parameters
----------
op : function (operator.add, operator.div, ...)
left : object (Index for non-reversed ops)
right : object (Index fof reversed ops)
left : object (np.ndarray for non-reversed ops)
right : object (np.ndarray fof reversed ops)
Copy link
Contributor

Choose a reason for hiding this comment

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

fof -> for

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed+green

result : ndarray

Returns
-------
result : ndarray
"""
if op is operator.floordiv:
# Note: no need to do this for truediv; in py3 numpy behaves the way
# we want.
result = mask_zero_div_zero(left, right, result)
elif op is operator.mod:
result = fill_zeros(result, left, right, "__mod__", np.nan)
elif op is divmod:
res0 = mask_zero_div_zero(left, right, result[0])
res1 = fill_zeros(result[1], left, right, "__divmod__", np.nan)
result = (res0, res1)
return result

result : np.ndarray

# FIXME: de-duplicate with dispatch_missing
def dispatch_fill_zeros(op, left, right, result):
"""
Call fill_zeros with the appropriate fill value depending on the operation,
with special logic for divmod and rdivmod.
Notes
-----
For divmod and rdivmod, the `result` parameter and returned `result`
is a 2-tuple of ndarray objects.
"""
if op is divmod:
result = (
Expand Down
24 changes: 0 additions & 24 deletions pandas/tests/arithmetic/test_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,6 @@ def test_add_extension_scalar(self, other, box, op):
result = op(arr, other)
tm.assert_equal(result, expected)

@pytest.mark.parametrize(
"box",
[
pytest.param(
pd.Index,
marks=pytest.mark.xfail(reason="Does not mask nulls", raises=TypeError),
),
pd.Series,
pd.DataFrame,
],
ids=lambda x: x.__name__,
)
def test_objarr_add_str(self, box):
ser = pd.Series(["x", np.nan, "x"])
expected = pd.Series(["xa", np.nan, "xa"])
Expand All @@ -125,18 +113,6 @@ def test_objarr_add_str(self, box):
result = ser + "a"
tm.assert_equal(result, expected)

@pytest.mark.parametrize(
"box",
[
pytest.param(
pd.Index,
marks=pytest.mark.xfail(reason="Does not mask nulls", raises=TypeError),
),
pd.Series,
pd.DataFrame,
],
ids=lambda x: x.__name__,
)
def test_objarr_radd_str(self, box):
ser = pd.Series(["x", np.nan, "x"])
expected = pd.Series(["ax", np.nan, "ax"])
Expand Down
1 change: 1 addition & 0 deletions pandas/tests/indexes/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ def test_set_name_methods(self, indices):
assert res is None
assert indices.name == new_name
assert indices.names == [new_name]
# FIXME: dont leave commented-out
# with pytest.raises(TypeError, match="list-like"):
# # should still fail even if it would be the right length
# ind.set_names("a")
Expand Down