Skip to content

Commit cf70d11

Browse files
authored
BUG: Interval arithmetic operations reset closed (#22331)
1 parent f7f266c commit cf70d11

File tree

3 files changed

+72
-47
lines changed

3 files changed

+72
-47
lines changed

doc/source/whatsnew/v0.24.0.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ Interval
639639

640640
- Bug in the :class:`IntervalIndex` constructor where the ``closed`` parameter did not always override the inferred ``closed`` (:issue:`19370`)
641641
- Bug in the ``IntervalIndex`` repr where a trailing comma was missing after the list of intervals (:issue:`20611`)
642-
-
642+
- Bug in :class:`Interval` where scalar arithmetic operations did not retain the ``closed`` value (:issue:`22313`)
643643
-
644644

645645
Indexing

pandas/_libs/interval.pyx

+9-8
Original file line numberDiff line numberDiff line change
@@ -326,36 +326,37 @@ cdef class Interval(IntervalMixin):
326326

327327
def __add__(self, y):
328328
if isinstance(y, numbers.Number):
329-
return Interval(self.left + y, self.right + y)
329+
return Interval(self.left + y, self.right + y, closed=self.closed)
330330
elif isinstance(y, Interval) and isinstance(self, numbers.Number):
331-
return Interval(y.left + self, y.right + self)
331+
return Interval(y.left + self, y.right + self, closed=y.closed)
332332
return NotImplemented
333333

334334
def __sub__(self, y):
335335
if isinstance(y, numbers.Number):
336-
return Interval(self.left - y, self.right - y)
336+
return Interval(self.left - y, self.right - y, closed=self.closed)
337337
return NotImplemented
338338

339339
def __mul__(self, y):
340340
if isinstance(y, numbers.Number):
341-
return Interval(self.left * y, self.right * y)
341+
return Interval(self.left * y, self.right * y, closed=self.closed)
342342
elif isinstance(y, Interval) and isinstance(self, numbers.Number):
343-
return Interval(y.left * self, y.right * self)
343+
return Interval(y.left * self, y.right * self, closed=y.closed)
344344
return NotImplemented
345345

346346
def __div__(self, y):
347347
if isinstance(y, numbers.Number):
348-
return Interval(self.left / y, self.right / y)
348+
return Interval(self.left / y, self.right / y, closed=self.closed)
349349
return NotImplemented
350350

351351
def __truediv__(self, y):
352352
if isinstance(y, numbers.Number):
353-
return Interval(self.left / y, self.right / y)
353+
return Interval(self.left / y, self.right / y, closed=self.closed)
354354
return NotImplemented
355355

356356
def __floordiv__(self, y):
357357
if isinstance(y, numbers.Number):
358-
return Interval(self.left // y, self.right // y)
358+
return Interval(
359+
self.left // y, self.right // y, closed=self.closed)
359360
return NotImplemented
360361

361362

pandas/tests/scalar/interval/test_interval.py

+62-38
Original file line numberDiff line numberDiff line change
@@ -109,79 +109,103 @@ def test_length_errors(self, left, right):
109109
with tm.assert_raises_regex(TypeError, msg):
110110
iv.length
111111

112-
def test_math_add(self, interval):
113-
expected = Interval(1, 2)
114-
actual = interval + 1
115-
assert expected == actual
112+
def test_math_add(self, closed):
113+
interval = Interval(0, 1, closed=closed)
114+
expected = Interval(1, 2, closed=closed)
116115

117-
expected = Interval(1, 2)
118-
actual = 1 + interval
119-
assert expected == actual
116+
result = interval + 1
117+
assert result == expected
118+
119+
result = 1 + interval
120+
assert result == expected
120121

121-
actual = interval
122-
actual += 1
123-
assert expected == actual
122+
result = interval
123+
result += 1
124+
assert result == expected
124125

125126
msg = r"unsupported operand type\(s\) for \+"
126127
with tm.assert_raises_regex(TypeError, msg):
127-
interval + Interval(1, 2)
128+
interval + interval
128129

129130
with tm.assert_raises_regex(TypeError, msg):
130131
interval + 'foo'
131132

132-
def test_math_sub(self, interval):
133-
expected = Interval(-1, 0)
134-
actual = interval - 1
135-
assert expected == actual
133+
def test_math_sub(self, closed):
134+
interval = Interval(0, 1, closed=closed)
135+
expected = Interval(-1, 0, closed=closed)
136+
137+
result = interval - 1
138+
assert result == expected
136139

137-
actual = interval
138-
actual -= 1
139-
assert expected == actual
140+
result = interval
141+
result -= 1
142+
assert result == expected
140143

141144
msg = r"unsupported operand type\(s\) for -"
142145
with tm.assert_raises_regex(TypeError, msg):
143-
interval - Interval(1, 2)
146+
interval - interval
144147

145148
with tm.assert_raises_regex(TypeError, msg):
146149
interval - 'foo'
147150

148-
def test_math_mult(self, interval):
149-
expected = Interval(0, 2)
150-
actual = interval * 2
151-
assert expected == actual
151+
def test_math_mult(self, closed):
152+
interval = Interval(0, 1, closed=closed)
153+
expected = Interval(0, 2, closed=closed)
154+
155+
result = interval * 2
156+
assert result == expected
152157

153-
expected = Interval(0, 2)
154-
actual = 2 * interval
155-
assert expected == actual
158+
result = 2 * interval
159+
assert result == expected
156160

157-
actual = interval
158-
actual *= 2
159-
assert expected == actual
161+
result = interval
162+
result *= 2
163+
assert result == expected
160164

161165
msg = r"unsupported operand type\(s\) for \*"
162166
with tm.assert_raises_regex(TypeError, msg):
163-
interval * Interval(1, 2)
167+
interval * interval
164168

165169
msg = r"can\'t multiply sequence by non-int"
166170
with tm.assert_raises_regex(TypeError, msg):
167171
interval * 'foo'
168172

169-
def test_math_div(self, interval):
170-
expected = Interval(0, 0.5)
171-
actual = interval / 2.0
172-
assert expected == actual
173+
def test_math_div(self, closed):
174+
interval = Interval(0, 1, closed=closed)
175+
expected = Interval(0, 0.5, closed=closed)
173176

174-
actual = interval
175-
actual /= 2.0
176-
assert expected == actual
177+
result = interval / 2.0
178+
assert result == expected
179+
180+
result = interval
181+
result /= 2.0
182+
assert result == expected
177183

178184
msg = r"unsupported operand type\(s\) for /"
179185
with tm.assert_raises_regex(TypeError, msg):
180-
interval / Interval(1, 2)
186+
interval / interval
181187

182188
with tm.assert_raises_regex(TypeError, msg):
183189
interval / 'foo'
184190

191+
def test_math_floordiv(self, closed):
192+
interval = Interval(1, 2, closed=closed)
193+
expected = Interval(0, 1, closed=closed)
194+
195+
result = interval // 2
196+
assert result == expected
197+
198+
result = interval
199+
result //= 2
200+
assert result == expected
201+
202+
msg = r"unsupported operand type\(s\) for //"
203+
with tm.assert_raises_regex(TypeError, msg):
204+
interval // interval
205+
206+
with tm.assert_raises_regex(TypeError, msg):
207+
interval // 'foo'
208+
185209
def test_constructor_errors(self):
186210
msg = "invalid option for 'closed': foo"
187211
with tm.assert_raises_regex(ValueError, msg):

0 commit comments

Comments
 (0)