Skip to content

Commit

Permalink
datetimelike indexes add/sub zero-dim integer arrays (pandas-dev#19013)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbrockmendel authored and hexgnu committed Jan 1, 2018
1 parent 673612b commit 73a63a4
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 27 deletions.
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v0.23.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ Numeric
^^^^^^^

- Bug in :func:`Series.__sub__` subtracting a non-nanosecond ``np.datetime64`` object from a ``Series`` gave incorrect results (:issue:`7996`)
-
- Bug in :class:`DatetimeIndex`, :class:`TimedeltaIndex` addition and subtraction of zero-dimensional integer arrays gave incorrect results (:issue:`19012`)
-

Categorical
Expand Down
18 changes: 14 additions & 4 deletions pandas/core/indexes/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,8 @@ def __add__(self, other):
from pandas.core.index import Index
from pandas.core.indexes.timedeltas import TimedeltaIndex
from pandas.tseries.offsets import DateOffset

other = lib.item_from_zerodim(other)
if is_timedelta64_dtype(other):
return self._add_delta(other)
elif isinstance(self, TimedeltaIndex) and isinstance(other, Index):
Expand All @@ -689,6 +691,7 @@ def __add__(self, other):
return self._add_datelike(other)
else: # pragma: no cover
return NotImplemented

cls.__add__ = __add__
cls.__radd__ = __add__

Expand All @@ -697,6 +700,8 @@ def __sub__(self, other):
from pandas.core.indexes.datetimes import DatetimeIndex
from pandas.core.indexes.timedeltas import TimedeltaIndex
from pandas.tseries.offsets import DateOffset

other = lib.item_from_zerodim(other)
if is_timedelta64_dtype(other):
return self._add_delta(-other)
elif isinstance(self, TimedeltaIndex) and isinstance(other, Index):
Expand Down Expand Up @@ -724,6 +729,7 @@ def __sub__(self, other):

else: # pragma: no cover
return NotImplemented

cls.__sub__ = __sub__

def __rsub__(self, other):
Expand All @@ -737,8 +743,10 @@ def _add_delta(self, other):
return NotImplemented

def _add_delta_td(self, other):
# add a delta of a timedeltalike
# return the i8 result view
"""
Add a delta of a timedeltalike
return the i8 result view
"""

inc = delta_to_nanoseconds(other)
new_values = checked_add_with_arr(self.asi8, inc,
Expand All @@ -748,8 +756,10 @@ def _add_delta_td(self, other):
return new_values.view('i8')

def _add_delta_tdi(self, other):
# add a delta of a TimedeltaIndex
# return the i8 result view
"""
Add a delta of a TimedeltaIndex
return the i8 result view
"""

# delta operation
if not len(self) == len(other):
Expand Down
7 changes: 7 additions & 0 deletions pandas/tests/indexes/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
import numpy as np

import pandas.util.testing as tm
from pandas.core.indexes.api import Index, MultiIndex
Expand All @@ -22,3 +23,9 @@
ids=lambda x: type(x).__name__)
def indices(request):
return request.param


@pytest.fixture(params=[1, np.array(1, dtype=np.int64)])
def one(request):
# zero-dim integer array behaves like an integer
return request.param
17 changes: 9 additions & 8 deletions pandas/tests/indexes/datetimes/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,36 +58,37 @@ def test_dti_radd_timestamp_raises(self):
# -------------------------------------------------------------
# Binary operations DatetimeIndex and int

def test_dti_add_int(self, tz):
def test_dti_add_int(self, tz, one):
# Variants of `one` for #19012
rng = pd.date_range('2000-01-01 09:00', freq='H',
periods=10, tz=tz)
result = rng + 1
result = rng + one
expected = pd.date_range('2000-01-01 10:00', freq='H',
periods=10, tz=tz)
tm.assert_index_equal(result, expected)

def test_dti_iadd_int(self, tz):
def test_dti_iadd_int(self, tz, one):
rng = pd.date_range('2000-01-01 09:00', freq='H',
periods=10, tz=tz)
expected = pd.date_range('2000-01-01 10:00', freq='H',
periods=10, tz=tz)
rng += 1
rng += one
tm.assert_index_equal(rng, expected)

def test_dti_sub_int(self, tz):
def test_dti_sub_int(self, tz, one):
rng = pd.date_range('2000-01-01 09:00', freq='H',
periods=10, tz=tz)
result = rng - 1
result = rng - one
expected = pd.date_range('2000-01-01 08:00', freq='H',
periods=10, tz=tz)
tm.assert_index_equal(result, expected)

def test_dti_isub_int(self, tz):
def test_dti_isub_int(self, tz, one):
rng = pd.date_range('2000-01-01 09:00', freq='H',
periods=10, tz=tz)
expected = pd.date_range('2000-01-01 08:00', freq='H',
periods=10, tz=tz)
rng -= 1
rng -= one
tm.assert_index_equal(rng, expected)

# -------------------------------------------------------------
Expand Down
14 changes: 8 additions & 6 deletions pandas/tests/indexes/period/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,21 @@ def test_add_iadd(self):
period.IncompatibleFrequency, msg):
rng += delta

# int
def test_pi_add_int(self, one):
# Variants of `one` for #19012
rng = pd.period_range('2000-01-01 09:00', freq='H', periods=10)
result = rng + 1
result = rng + one
expected = pd.period_range('2000-01-01 10:00', freq='H', periods=10)
tm.assert_index_equal(result, expected)
rng += 1
rng += one
tm.assert_index_equal(rng, expected)

def test_sub(self):
@pytest.mark.parametrize('five', [5, np.array(5, dtype=np.int64)])
def test_sub(self, five):
rng = period_range('2007-01', periods=50)

result = rng - 5
exp = rng + (-5)
result = rng - five
exp = rng + (-five)
tm.assert_index_equal(result, exp)

def test_sub_isub(self):
Expand Down
17 changes: 9 additions & 8 deletions pandas/tests/indexes/timedeltas/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,28 +121,29 @@ def test_ufunc_coercions(self):
# -------------------------------------------------------------
# Binary operations TimedeltaIndex and integer

def test_tdi_add_int(self):
def test_tdi_add_int(self, one):
# Variants of `one` for #19012
rng = timedelta_range('1 days 09:00:00', freq='H', periods=10)
result = rng + 1
result = rng + one
expected = timedelta_range('1 days 10:00:00', freq='H', periods=10)
tm.assert_index_equal(result, expected)

def test_tdi_iadd_int(self):
def test_tdi_iadd_int(self, one):
rng = timedelta_range('1 days 09:00:00', freq='H', periods=10)
expected = timedelta_range('1 days 10:00:00', freq='H', periods=10)
rng += 1
rng += one
tm.assert_index_equal(rng, expected)

def test_tdi_sub_int(self):
def test_tdi_sub_int(self, one):
rng = timedelta_range('1 days 09:00:00', freq='H', periods=10)
result = rng - 1
result = rng - one
expected = timedelta_range('1 days 08:00:00', freq='H', periods=10)
tm.assert_index_equal(result, expected)

def test_tdi_isub_int(self):
def test_tdi_isub_int(self, one):
rng = timedelta_range('1 days 09:00:00', freq='H', periods=10)
expected = timedelta_range('1 days 08:00:00', freq='H', periods=10)
rng -= 1
rng -= one
tm.assert_index_equal(rng, expected)

# -------------------------------------------------------------
Expand Down

0 comments on commit 73a63a4

Please sign in to comment.