diff --git a/doc/source/user_guide/missing_data.rst b/doc/source/user_guide/missing_data.rst index ac7e383d6d7ff..27d7e63f72111 100644 --- a/doc/source/user_guide/missing_data.rst +++ b/doc/source/user_guide/missing_data.rst @@ -436,7 +436,7 @@ at the new values. # interpolate at new_index new_index = ser.index.union(pd.Index([49.25, 49.5, 49.75, 50.25, 50.5, 50.75])) interp_s = ser.reindex(new_index).interpolate(method="pchip") - interp_s[49:51] + interp_s.loc[49:51] .. _scipy: https://scipy.org/ .. _documentation: https://docs.scipy.org/doc/scipy/reference/interpolate.html#univariate-interpolation diff --git a/doc/source/whatsnew/v0.13.0.rst b/doc/source/whatsnew/v0.13.0.rst index 2e086f560bd53..bfabfb1a27e73 100644 --- a/doc/source/whatsnew/v0.13.0.rst +++ b/doc/source/whatsnew/v0.13.0.rst @@ -343,6 +343,7 @@ Float64Index API change Slicing is ALWAYS on the values of the index, for ``[],ix,loc`` and ALWAYS positional with ``iloc`` .. ipython:: python + :okwarning: s[2:4] s.loc[2:4] diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index 64948e78676d4..f8207a0c1afb3 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -552,6 +552,7 @@ Other Deprecations - Deprecated the "method" and "limit" keywords in ``ExtensionArray.fillna``, implement and use ``pad_or_backfill`` instead (:issue:`53621`) - Deprecated the "method" and "limit" keywords on :meth:`Series.fillna`, :meth:`DataFrame.fillna`, :meth:`SeriesGroupBy.fillna`, :meth:`DataFrameGroupBy.fillna`, and :meth:`Resampler.fillna`, use ``obj.bfill()`` or ``obj.ffill()`` instead (:issue:`53394`) - Deprecated the ``method`` and ``limit`` keywords in :meth:`DataFrame.replace` and :meth:`Series.replace` (:issue:`33302`) +- Deprecated the behavior of :meth:`Series.__getitem__`, :meth:`Series.__setitem__`, :meth:`DataFrame.__getitem__`, :meth:`DataFrame.__setitem__` with an integer slice on objects with a floating-dtype index, in a future version this will be treated as *positional* indexing (:issue:`49612`) - Deprecated the use of non-supported datetime64 and timedelta64 resolutions with :func:`pandas.array`. Supported resolutions are: "s", "ms", "us", "ns" resolutions (:issue:`53058`) - Deprecated values "pad", "ffill", "bfill", "backfill" for :meth:`Series.interpolate` and :meth:`DataFrame.interpolate`, use ``obj.ffill()`` or ``obj.bfill()`` instead (:issue:`53581`) - Deprecated the behavior of :meth:`Index.argmax`, :meth:`Index.argmin`, :meth:`Series.argmax`, :meth:`Series.argmin` with either all-NAs and skipna=True or any-NAs and skipna=False returning -1; in a future version this will raise ``ValueError`` (:issue:`33941`, :issue:`33942`) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 80b4bcc583fdb..e5c940df93a1c 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -4212,16 +4212,30 @@ def _convert_slice_indexer(self, key: slice, kind: Literal["loc", "getitem"]): # potentially cast the bounds to integers start, stop, step = key.start, key.stop, key.step + # figure out if this is a positional indexer + is_index_slice = is_valid_positional_slice(key) + # TODO(GH#50617): once Series.__[gs]etitem__ is removed we should be able # to simplify this. if lib.is_np_dtype(self.dtype, "f"): # We always treat __getitem__ slicing as label-based # translate to locations + if kind == "getitem" and is_index_slice and not start == stop and step != 0: + # exclude step=0 from the warning because it will raise anyway + # start/stop both None e.g. [:] or [::-1] won't change. + # exclude start==stop since it will be empty either way, or + # will be [:] or [::-1] which won't change + warnings.warn( + # GH#49612 + "The behavior of obj[i:j] with a float-dtype index is " + "deprecated. In a future version, this will be treated as " + "positional instead of label-based. For label-based slicing, " + "use obj.loc[i:j] instead", + FutureWarning, + stacklevel=find_stack_level(), + ) return self.slice_indexer(start, stop, step) - # figure out if this is a positional indexer - is_index_slice = is_valid_positional_slice(key) - if kind == "getitem": # called from the getitem slicers, validate that we are in fact integers if is_index_slice: diff --git a/pandas/tests/frame/indexing/test_indexing.py b/pandas/tests/frame/indexing/test_indexing.py index ea978fe68b1ad..288aa1af746b6 100644 --- a/pandas/tests/frame/indexing/test_indexing.py +++ b/pandas/tests/frame/indexing/test_indexing.py @@ -755,7 +755,9 @@ def test_getitem_setitem_float_labels(self, using_array_manager): tm.assert_frame_equal(result, expected) df.loc[1:2] = 0 - result = df[1:2] + msg = r"The behavior of obj\[i:j\] with a float-dtype index" + with tm.assert_produces_warning(FutureWarning, match=msg): + result = df[1:2] assert (result == 0).all().all() # #2727 diff --git a/pandas/tests/indexing/test_floats.py b/pandas/tests/indexing/test_floats.py index 1c91b69597d78..c9fbf95751dfe 100644 --- a/pandas/tests/indexing/test_floats.py +++ b/pandas/tests/indexing/test_floats.py @@ -488,8 +488,12 @@ def test_floating_misc(self, indexer_sl): for fancy_idx in [[5, 0], np.array([5, 0])]: tm.assert_series_equal(indexer_sl(s)[fancy_idx], expected) + warn = FutureWarning if indexer_sl is tm.setitem else None + msg = r"The behavior of obj\[i:j\] with a float-dtype index" + # all should return the same as we are slicing 'the same' - result1 = indexer_sl(s)[2:5] + with tm.assert_produces_warning(warn, match=msg): + result1 = indexer_sl(s)[2:5] result2 = indexer_sl(s)[2.0:5.0] result3 = indexer_sl(s)[2.0:5] result4 = indexer_sl(s)[2.1:5] @@ -498,7 +502,8 @@ def test_floating_misc(self, indexer_sl): tm.assert_series_equal(result1, result4) expected = Series([1, 2], index=[2.5, 5.0]) - result = indexer_sl(s)[2:5] + with tm.assert_produces_warning(warn, match=msg): + result = indexer_sl(s)[2:5] tm.assert_series_equal(result, expected) diff --git a/pandas/tests/series/methods/test_interpolate.py b/pandas/tests/series/methods/test_interpolate.py index 3b099720dcdf2..99e8b9076fcc3 100644 --- a/pandas/tests/series/methods/test_interpolate.py +++ b/pandas/tests/series/methods/test_interpolate.py @@ -129,7 +129,7 @@ def test_interpolate_cubicspline(self): new_index = ser.index.union(Index([1.25, 1.5, 1.75, 2.25, 2.5, 2.75])).astype( float ) - result = ser.reindex(new_index).interpolate(method="cubicspline")[1:3] + result = ser.reindex(new_index).interpolate(method="cubicspline").loc[1:3] tm.assert_series_equal(result, expected) @td.skip_if_no_scipy @@ -142,7 +142,7 @@ def test_interpolate_pchip(self): ).astype(float) interp_s = ser.reindex(new_index).interpolate(method="pchip") # does not blow up, GH5977 - interp_s[49:51] + interp_s.loc[49:51] @td.skip_if_no_scipy def test_interpolate_akima(self): @@ -157,7 +157,7 @@ def test_interpolate_akima(self): float ) interp_s = ser.reindex(new_index).interpolate(method="akima") - tm.assert_series_equal(interp_s[1:3], expected) + tm.assert_series_equal(interp_s.loc[1:3], expected) # interpolate at new_index where `der` is a non-zero int expected = Series( @@ -168,7 +168,7 @@ def test_interpolate_akima(self): float ) interp_s = ser.reindex(new_index).interpolate(method="akima", der=1) - tm.assert_series_equal(interp_s[1:3], expected) + tm.assert_series_equal(interp_s.loc[1:3], expected) @td.skip_if_no_scipy def test_interpolate_piecewise_polynomial(self): @@ -183,7 +183,7 @@ def test_interpolate_piecewise_polynomial(self): float ) interp_s = ser.reindex(new_index).interpolate(method="piecewise_polynomial") - tm.assert_series_equal(interp_s[1:3], expected) + tm.assert_series_equal(interp_s.loc[1:3], expected) @td.skip_if_no_scipy def test_interpolate_from_derivatives(self): @@ -198,7 +198,7 @@ def test_interpolate_from_derivatives(self): float ) interp_s = ser.reindex(new_index).interpolate(method="from_derivatives") - tm.assert_series_equal(interp_s[1:3], expected) + tm.assert_series_equal(interp_s.loc[1:3], expected) @pytest.mark.parametrize( "kwargs", @@ -218,7 +218,7 @@ def test_interpolate_corners(self, kwargs): def test_interpolate_index_values(self): s = Series(np.nan, index=np.sort(np.random.default_rng(2).random(30))) - s[::3] = np.random.default_rng(2).standard_normal(10) + s.loc[::3] = np.random.default_rng(2).standard_normal(10) vals = s.index.values.astype(float)