From 826413957a61ed3a0e00dd32ac20f6cc2a9389d4 Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 26 Oct 2021 21:26:46 -0700 Subject: [PATCH 1/2] BUG: Series[Interval[int64]] setitem Interval[float] --- doc/source/whatsnew/v1.4.0.rst | 2 ++ pandas/core/internals/blocks.py | 34 ++++++++++++++++---- pandas/tests/series/indexing/test_setitem.py | 33 +++++++++++++++++++ 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index 254a004a37c40..db5fffd55ffe3 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -530,8 +530,10 @@ Indexing - 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`) - Bug in :meth:`DataFrame.sort_index` where ``ignore_index=True`` was not being respected when the index was already sorted (:issue:`43591`) - Bug in :meth:`Index.get_indexer_non_unique` when index contains multiple ``np.datetime64("NaT")`` and ``np.timedelta64("NaT")`` (:issue:`43869`) +- 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:`??`) - + Missing ^^^^^^^ - Bug in :meth:`DataFrame.fillna` with limit and no method ignores axis='columns' or ``axis = 1`` (:issue:`40989`) diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index de612b367f78f..06ec02794e578 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -48,6 +48,7 @@ is_1d_only_ea_obj, is_dtype_equal, is_extension_array_dtype, + is_interval_dtype, is_list_like, is_sparse, is_string_dtype, @@ -1440,7 +1441,21 @@ def putmask(self, mask, new) -> list[Block]: # TODO(EA2D): unnecessary with 2D EAs mask = mask.reshape(new_values.shape) - new_values[mask] = new + try: + new_values[mask] = new + except TypeError: + if not is_interval_dtype(self.dtype): + # Discussion about what we want to support in the general + # case GH#39584 + raise + + blk = self.coerce_to_target_dtype(new) + if blk.dtype == _dtype_obj: + # For now at least, only support casting e.g. + # Interval[int64]->Interval[float64], + raise + return blk.putmask(mask, new) + nb = type(self)(new_values, placement=self._mgr_locs, ndim=self.ndim) return [nb] @@ -1477,12 +1492,8 @@ def setitem(self, indexer, value): be a compatible shape. """ if not self._can_hold_element(value): - # This is only relevant for DatetimeTZBlock, PeriodDtype, IntervalDtype, - # which has a non-trivial `_can_hold_element`. - # https://github.com/pandas-dev/pandas/issues/24020 - # Need a dedicated setitem until GH#24020 (type promotion in setitem - # for extension arrays) is designed and implemented. - return self.astype(_dtype_obj).setitem(indexer, value) + # see TestSetitemFloatIntervalWithIntIntervalValues + return self.coerce_to_target_dtype(value).setitem(indexer, value) if isinstance(indexer, tuple): # TODO(EA2D): not needed with 2D EAs @@ -1642,6 +1653,15 @@ def where(self, other, cond, errors="raise") -> list[Block]: # TODO: don't special-case raise + if is_interval_dtype(self.dtype): + # TestSetitemFloatIntervalWithIntIntervalValues + blk = self.coerce_to_target_dtype(other) + if blk.dtype == _dtype_obj: + # For now at least only support casting e.g. + # Interval[int64]->Interval[float64] + raise + return blk.where(other, cond, errors) + result = type(self.values)._from_sequence( np.where(cond, self.values, other), dtype=dtype ) diff --git a/pandas/tests/series/indexing/test_setitem.py b/pandas/tests/series/indexing/test_setitem.py index fe3495abd2fb0..57a22a826138c 100644 --- a/pandas/tests/series/indexing/test_setitem.py +++ b/pandas/tests/series/indexing/test_setitem.py @@ -11,6 +11,7 @@ DataFrame, DatetimeIndex, Index, + Interval, IntervalIndex, MultiIndex, NaT, @@ -928,6 +929,38 @@ def is_inplace(self, obj): return obj.dtype.kind != "i" +class TestSetitemFloatIntervalWithIntIntervalValues(SetitemCastingEquivalents): + # Cast to shared IntervalDtype rather than object + + def test_setitem_example(self): + # Just a case here to make obvious what this test class is aimed at + idx = IntervalIndex.from_breaks(range(4)) + obj = Series(idx) + val = Interval(0.5, 1.5) + + obj[0] = val + assert obj.dtype == "Interval[float64, right]" + + @pytest.fixture + def obj(self): + idx = IntervalIndex.from_breaks(range(4)) + return Series(idx) + + @pytest.fixture + def val(self): + return Interval(0.5, 1.5) + + @pytest.fixture + def key(self): + return 0 + + @pytest.fixture + def expected(self, obj, val): + data = [val] + list(obj[1:]) + idx = IntervalIndex(data, dtype="Interval[float64]") + return Series(idx) + + def test_setitem_int_as_positional_fallback_deprecation(): # GH#42215 deprecated falling back to positional on __setitem__ with an # int not contained in the index From a8e2b8c258ab7108c02f3b91b44e41126598461f Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 26 Oct 2021 21:28:37 -0700 Subject: [PATCH 2/2] GH ref --- doc/source/whatsnew/v1.4.0.rst | 2 +- pandas/tests/series/indexing/test_setitem.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index db5fffd55ffe3..7b10ca64a9b3d 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -530,7 +530,7 @@ Indexing - 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`) - Bug in :meth:`DataFrame.sort_index` where ``ignore_index=True`` was not being respected when the index was already sorted (:issue:`43591`) - Bug in :meth:`Index.get_indexer_non_unique` when index contains multiple ``np.datetime64("NaT")`` and ``np.timedelta64("NaT")`` (:issue:`43869`) -- 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:`??`) +- 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`) - diff --git a/pandas/tests/series/indexing/test_setitem.py b/pandas/tests/series/indexing/test_setitem.py index 57a22a826138c..a922a937ce9d3 100644 --- a/pandas/tests/series/indexing/test_setitem.py +++ b/pandas/tests/series/indexing/test_setitem.py @@ -930,7 +930,7 @@ def is_inplace(self, obj): class TestSetitemFloatIntervalWithIntIntervalValues(SetitemCastingEquivalents): - # Cast to shared IntervalDtype rather than object + # GH#44201 Cast to shared IntervalDtype rather than object def test_setitem_example(self): # Just a case here to make obvious what this test class is aimed at