diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 9b70bda82e247..75c2a8acdc859 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -600,7 +600,7 @@ Interval - Bug in the :class:`IntervalIndex` constructor where the ``closed`` parameter did not always override the inferred ``closed`` (:issue:`19370`) - Bug in the ``IntervalIndex`` repr where a trailing comma was missing after the list of intervals (:issue:`20611`) -- +- Bug in :class:`Interval` where scalar arithmetic operations did not retain the ``closed`` value (:issue:`22313`) - Indexing diff --git a/pandas/_libs/interval.pyx b/pandas/_libs/interval.pyx index 22153b58cc49b..5ae20a27c2381 100644 --- a/pandas/_libs/interval.pyx +++ b/pandas/_libs/interval.pyx @@ -326,36 +326,37 @@ cdef class Interval(IntervalMixin): def __add__(self, y): if isinstance(y, numbers.Number): - return Interval(self.left + y, self.right + y) + return Interval(self.left + y, self.right + y, closed=self.closed) elif isinstance(y, Interval) and isinstance(self, numbers.Number): - return Interval(y.left + self, y.right + self) + return Interval(y.left + self, y.right + self, closed=y.closed) return NotImplemented def __sub__(self, y): if isinstance(y, numbers.Number): - return Interval(self.left - y, self.right - y) + return Interval(self.left - y, self.right - y, closed=self.closed) return NotImplemented def __mul__(self, y): if isinstance(y, numbers.Number): - return Interval(self.left * y, self.right * y) + return Interval(self.left * y, self.right * y, closed=self.closed) elif isinstance(y, Interval) and isinstance(self, numbers.Number): - return Interval(y.left * self, y.right * self) + return Interval(y.left * self, y.right * self, closed=y.closed) return NotImplemented def __div__(self, y): if isinstance(y, numbers.Number): - return Interval(self.left / y, self.right / y) + return Interval(self.left / y, self.right / y, closed=self.closed) return NotImplemented def __truediv__(self, y): if isinstance(y, numbers.Number): - return Interval(self.left / y, self.right / y) + return Interval(self.left / y, self.right / y, closed=self.closed) return NotImplemented def __floordiv__(self, y): if isinstance(y, numbers.Number): - return Interval(self.left // y, self.right // y) + return Interval( + self.left // y, self.right // y, closed=self.closed) return NotImplemented diff --git a/pandas/tests/scalar/interval/test_interval.py b/pandas/tests/scalar/interval/test_interval.py index c9e6e84d226a8..8d17989ebc7b1 100644 --- a/pandas/tests/scalar/interval/test_interval.py +++ b/pandas/tests/scalar/interval/test_interval.py @@ -109,79 +109,103 @@ def test_length_errors(self, left, right): with tm.assert_raises_regex(TypeError, msg): iv.length - def test_math_add(self, interval): - expected = Interval(1, 2) - actual = interval + 1 - assert expected == actual + def test_math_add(self, closed): + interval = Interval(0, 1, closed=closed) + expected = Interval(1, 2, closed=closed) - expected = Interval(1, 2) - actual = 1 + interval - assert expected == actual + result = interval + 1 + assert result == expected + + result = 1 + interval + assert result == expected - actual = interval - actual += 1 - assert expected == actual + result = interval + result += 1 + assert result == expected msg = r"unsupported operand type\(s\) for \+" with tm.assert_raises_regex(TypeError, msg): - interval + Interval(1, 2) + interval + interval with tm.assert_raises_regex(TypeError, msg): interval + 'foo' - def test_math_sub(self, interval): - expected = Interval(-1, 0) - actual = interval - 1 - assert expected == actual + def test_math_sub(self, closed): + interval = Interval(0, 1, closed=closed) + expected = Interval(-1, 0, closed=closed) + + result = interval - 1 + assert result == expected - actual = interval - actual -= 1 - assert expected == actual + result = interval + result -= 1 + assert result == expected msg = r"unsupported operand type\(s\) for -" with tm.assert_raises_regex(TypeError, msg): - interval - Interval(1, 2) + interval - interval with tm.assert_raises_regex(TypeError, msg): interval - 'foo' - def test_math_mult(self, interval): - expected = Interval(0, 2) - actual = interval * 2 - assert expected == actual + def test_math_mult(self, closed): + interval = Interval(0, 1, closed=closed) + expected = Interval(0, 2, closed=closed) + + result = interval * 2 + assert result == expected - expected = Interval(0, 2) - actual = 2 * interval - assert expected == actual + result = 2 * interval + assert result == expected - actual = interval - actual *= 2 - assert expected == actual + result = interval + result *= 2 + assert result == expected msg = r"unsupported operand type\(s\) for \*" with tm.assert_raises_regex(TypeError, msg): - interval * Interval(1, 2) + interval * interval msg = r"can\'t multiply sequence by non-int" with tm.assert_raises_regex(TypeError, msg): interval * 'foo' - def test_math_div(self, interval): - expected = Interval(0, 0.5) - actual = interval / 2.0 - assert expected == actual + def test_math_div(self, closed): + interval = Interval(0, 1, closed=closed) + expected = Interval(0, 0.5, closed=closed) - actual = interval - actual /= 2.0 - assert expected == actual + result = interval / 2.0 + assert result == expected + + result = interval + result /= 2.0 + assert result == expected msg = r"unsupported operand type\(s\) for /" with tm.assert_raises_regex(TypeError, msg): - interval / Interval(1, 2) + interval / interval with tm.assert_raises_regex(TypeError, msg): interval / 'foo' + def test_math_floordiv(self, closed): + interval = Interval(1, 2, closed=closed) + expected = Interval(0, 1, closed=closed) + + result = interval // 2 + assert result == expected + + result = interval + result //= 2 + assert result == expected + + msg = r"unsupported operand type\(s\) for //" + with tm.assert_raises_regex(TypeError, msg): + interval // interval + + with tm.assert_raises_regex(TypeError, msg): + interval // 'foo' + def test_constructor_errors(self): msg = "invalid option for 'closed': foo" with tm.assert_raises_regex(ValueError, msg):