Skip to content

Commit 7a45aaa

Browse files
authored
BUG: Series[Interval[int64]] setitem Interval[float] (#44201)
1 parent de57600 commit 7a45aaa

File tree

3 files changed

+62
-7
lines changed

3 files changed

+62
-7
lines changed

doc/source/whatsnew/v1.4.0.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,8 +530,10 @@ Indexing
530530
- Bug in :meth:`Series.__setitem__` with object dtype when setting an array with matching size and dtype='datetime64[ns]' or dtype='timedelta64[ns]' incorrectly converting the datetime/timedeltas to integers (:issue:`43868`)
531531
- Bug in :meth:`DataFrame.sort_index` where ``ignore_index=True`` was not being respected when the index was already sorted (:issue:`43591`)
532532
- Bug in :meth:`Index.get_indexer_non_unique` when index contains multiple ``np.datetime64("NaT")`` and ``np.timedelta64("NaT")`` (:issue:`43869`)
533+
- Bug in setting a scalar :class:`Interval` value into a :class:`Series` with ``IntervalDtype`` when the scalar's sides are floats and the values' sides are integers (:issue:`44201`)
533534
-
534535

536+
535537
Missing
536538
^^^^^^^
537539
- Bug in :meth:`DataFrame.fillna` with limit and no method ignores axis='columns' or ``axis = 1`` (:issue:`40989`)

pandas/core/internals/blocks.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
is_1d_only_ea_obj,
4949
is_dtype_equal,
5050
is_extension_array_dtype,
51+
is_interval_dtype,
5152
is_list_like,
5253
is_sparse,
5354
is_string_dtype,
@@ -1441,7 +1442,21 @@ def putmask(self, mask, new) -> list[Block]:
14411442
# TODO(EA2D): unnecessary with 2D EAs
14421443
mask = mask.reshape(new_values.shape)
14431444

1444-
new_values[mask] = new
1445+
try:
1446+
new_values[mask] = new
1447+
except TypeError:
1448+
if not is_interval_dtype(self.dtype):
1449+
# Discussion about what we want to support in the general
1450+
# case GH#39584
1451+
raise
1452+
1453+
blk = self.coerce_to_target_dtype(new)
1454+
if blk.dtype == _dtype_obj:
1455+
# For now at least, only support casting e.g.
1456+
# Interval[int64]->Interval[float64],
1457+
raise
1458+
return blk.putmask(mask, new)
1459+
14451460
nb = type(self)(new_values, placement=self._mgr_locs, ndim=self.ndim)
14461461
return [nb]
14471462

@@ -1478,12 +1493,8 @@ def setitem(self, indexer, value):
14781493
be a compatible shape.
14791494
"""
14801495
if not self._can_hold_element(value):
1481-
# This is only relevant for DatetimeTZBlock, PeriodDtype, IntervalDtype,
1482-
# which has a non-trivial `_can_hold_element`.
1483-
# https://github.com/pandas-dev/pandas/issues/24020
1484-
# Need a dedicated setitem until GH#24020 (type promotion in setitem
1485-
# for extension arrays) is designed and implemented.
1486-
return self.astype(_dtype_obj).setitem(indexer, value)
1496+
# see TestSetitemFloatIntervalWithIntIntervalValues
1497+
return self.coerce_to_target_dtype(value).setitem(indexer, value)
14871498

14881499
if isinstance(indexer, tuple):
14891500
# TODO(EA2D): not needed with 2D EAs
@@ -1643,6 +1654,15 @@ def where(self, other, cond, errors="raise") -> list[Block]:
16431654
# TODO: don't special-case
16441655
raise
16451656

1657+
if is_interval_dtype(self.dtype):
1658+
# TestSetitemFloatIntervalWithIntIntervalValues
1659+
blk = self.coerce_to_target_dtype(other)
1660+
if blk.dtype == _dtype_obj:
1661+
# For now at least only support casting e.g.
1662+
# Interval[int64]->Interval[float64]
1663+
raise
1664+
return blk.where(other, cond, errors)
1665+
16461666
result = type(self.values)._from_sequence(
16471667
np.where(cond, self.values, other), dtype=dtype
16481668
)

pandas/tests/series/indexing/test_setitem.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
DataFrame,
1212
DatetimeIndex,
1313
Index,
14+
Interval,
1415
IntervalIndex,
1516
MultiIndex,
1617
NaT,
@@ -928,6 +929,38 @@ def is_inplace(self, obj):
928929
return obj.dtype.kind != "i"
929930

930931

932+
class TestSetitemFloatIntervalWithIntIntervalValues(SetitemCastingEquivalents):
933+
# GH#44201 Cast to shared IntervalDtype rather than object
934+
935+
def test_setitem_example(self):
936+
# Just a case here to make obvious what this test class is aimed at
937+
idx = IntervalIndex.from_breaks(range(4))
938+
obj = Series(idx)
939+
val = Interval(0.5, 1.5)
940+
941+
obj[0] = val
942+
assert obj.dtype == "Interval[float64, right]"
943+
944+
@pytest.fixture
945+
def obj(self):
946+
idx = IntervalIndex.from_breaks(range(4))
947+
return Series(idx)
948+
949+
@pytest.fixture
950+
def val(self):
951+
return Interval(0.5, 1.5)
952+
953+
@pytest.fixture
954+
def key(self):
955+
return 0
956+
957+
@pytest.fixture
958+
def expected(self, obj, val):
959+
data = [val] + list(obj[1:])
960+
idx = IntervalIndex(data, dtype="Interval[float64]")
961+
return Series(idx)
962+
963+
931964
def test_setitem_int_as_positional_fallback_deprecation():
932965
# GH#42215 deprecated falling back to positional on __setitem__ with an
933966
# int not contained in the index

0 commit comments

Comments
 (0)