diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index 55f396c621a99..7c7b13ab4e89d 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -264,12 +264,12 @@ Deprecations - Deprecated unused "closed" and "normalize" keywords in the :class:`DatetimeIndex` constructor (:issue:`52628`) - Deprecated unused "closed" keyword in the :class:`TimedeltaIndex` constructor (:issue:`52628`) - Deprecated logical operation between two non boolean :class:`Series` with different indexes always coercing the result to bool dtype. In a future version, this will maintain the return type of the inputs. (:issue:`52500`, :issue:`52538`) +- Deprecated :meth:`Series.first` and :meth:`DataFrame.first` (please create a mask and filter using ``.loc`` instead) (:issue:`45908`) - Deprecated allowing ``downcast`` keyword other than ``None``, ``False``, "infer", or a dict with these as values in :meth:`Series.fillna`, :meth:`DataFrame.fillna` (:issue:`40988`) - Deprecated allowing arbitrary ``fill_value`` in :class:`SparseDtype`, in a future version the ``fill_value`` will need to be compatible with the ``dtype.subtype``, either a scalar that can be held by that subtype or ``NaN`` for integer or bool subtypes (:issue:`23124`) - Deprecated behavior of :func:`assert_series_equal` and :func:`assert_frame_equal` considering NA-like values (e.g. ``NaN`` vs ``None`` as equivalent) (:issue:`52081`) - Deprecated constructing :class:`SparseArray` from scalar data, pass a sequence instead (:issue:`53039`) - Deprecated positional indexing on :class:`Series` with :meth:`Series.__getitem__` and :meth:`Series.__setitem__`, in a future version ``ser[item]`` will *always* interpret ``item`` as a label, not a position (:issue:`50617`) -- .. --------------------------------------------------------------------------- .. _whatsnew_210.performance: diff --git a/pandas/conftest.py b/pandas/conftest.py index 077939d2d05ce..7dab1714e0aa3 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -145,6 +145,11 @@ def pytest_collection_modifyitems(items, config) -> None: "(Series|DataFrame).bool is now deprecated and will be removed " "in future version of pandas", ), + ( + "pandas.core.generic.NDFrame.first", + "first is deprecated and will be removed in a future version. " + "Please create a mask and filter using `.loc` instead", + ), ] for item in items: diff --git a/pandas/core/generic.py b/pandas/core/generic.py index bcfbfa1a2b713..1adc331e3cd50 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -9162,6 +9162,12 @@ def first(self, offset) -> Self: 3 days observed in the dataset, and therefore data for 2018-04-13 was not returned. """ + warnings.warn( + "first is deprecated and will be removed in a future version. " + "Please create a mask and filter using `.loc` instead", + FutureWarning, + stacklevel=find_stack_level(), + ) if not isinstance(self.index, DatetimeIndex): raise TypeError("'first' only supports a DatetimeIndex index") diff --git a/pandas/tests/frame/methods/test_first_and_last.py b/pandas/tests/frame/methods/test_first_and_last.py index 64f6665ecd709..18173f7c66198 100644 --- a/pandas/tests/frame/methods/test_first_and_last.py +++ b/pandas/tests/frame/methods/test_first_and_last.py @@ -10,29 +10,36 @@ ) import pandas._testing as tm +deprecated_msg = "first is deprecated" + class TestFirst: def test_first_subset(self, frame_or_series): ts = tm.makeTimeDataFrame(freq="12h") ts = tm.get_obj(ts, frame_or_series) - result = ts.first("10d") - assert len(result) == 20 + with tm.assert_produces_warning(FutureWarning, match=deprecated_msg): + result = ts.first("10d") + assert len(result) == 20 ts = tm.makeTimeDataFrame(freq="D") ts = tm.get_obj(ts, frame_or_series) - result = ts.first("10d") - assert len(result) == 10 + with tm.assert_produces_warning(FutureWarning, match=deprecated_msg): + result = ts.first("10d") + assert len(result) == 10 - result = ts.first("3M") - expected = ts[:"3/31/2000"] - tm.assert_equal(result, expected) + with tm.assert_produces_warning(FutureWarning, match=deprecated_msg): + result = ts.first("3M") + expected = ts[:"3/31/2000"] + tm.assert_equal(result, expected) - result = ts.first("21D") - expected = ts[:21] - tm.assert_equal(result, expected) + with tm.assert_produces_warning(FutureWarning, match=deprecated_msg): + result = ts.first("21D") + expected = ts[:21] + tm.assert_equal(result, expected) - result = ts[:0].first("3M") - tm.assert_equal(result, ts[:0]) + with tm.assert_produces_warning(FutureWarning, match=deprecated_msg): + result = ts[:0].first("3M") + tm.assert_equal(result, ts[:0]) def test_first_last_raises(self, frame_or_series): # GH#20725 @@ -40,7 +47,11 @@ def test_first_last_raises(self, frame_or_series): obj = tm.get_obj(obj, frame_or_series) msg = "'first' only supports a DatetimeIndex index" - with pytest.raises(TypeError, match=msg): # index is not a DatetimeIndex + with tm.assert_produces_warning( + FutureWarning, match=deprecated_msg + ), pytest.raises( + TypeError, match=msg + ): # index is not a DatetimeIndex obj.first("1D") msg = "'last' only supports a DatetimeIndex index" @@ -73,7 +84,8 @@ def test_last_subset(self, frame_or_series): def test_first_with_first_day_last_of_month(self, frame_or_series, start, periods): # GH#29623 x = frame_or_series([1] * 100, index=bdate_range(start, periods=100)) - result = x.first("1M") + with tm.assert_produces_warning(FutureWarning, match=deprecated_msg): + result = x.first("1M") expected = frame_or_series( [1] * periods, index=bdate_range(start, periods=periods) ) @@ -82,16 +94,20 @@ def test_first_with_first_day_last_of_month(self, frame_or_series, start, period def test_first_with_first_day_end_of_frq_n_greater_one(self, frame_or_series): # GH#29623 x = frame_or_series([1] * 100, index=bdate_range("2010-03-31", periods=100)) - result = x.first("2M") + with tm.assert_produces_warning(FutureWarning, match=deprecated_msg): + result = x.first("2M") expected = frame_or_series( [1] * 23, index=bdate_range("2010-03-31", "2010-04-30") ) tm.assert_equal(result, expected) - @pytest.mark.parametrize("func", ["first", "last"]) - def test_empty_not_input(self, func): + def test_empty_not_input(self): # GH#51032 df = DataFrame(index=pd.DatetimeIndex([])) - result = getattr(df, func)(offset=1) + result = df.last(offset=1) + + with tm.assert_produces_warning(FutureWarning, match=deprecated_msg): + result = df.first(offset=1) + tm.assert_frame_equal(df, result) assert df is not result diff --git a/pandas/tests/generic/test_finalize.py b/pandas/tests/generic/test_finalize.py index 8159024966b0f..9dfa2c8a5a90a 100644 --- a/pandas/tests/generic/test_finalize.py +++ b/pandas/tests/generic/test_finalize.py @@ -8,6 +8,7 @@ import pytest import pandas as pd +import pandas._testing as tm # TODO: # * Binary methods (mul, div, etc.) @@ -333,16 +334,6 @@ ({"A": [1, 1, 1, 1]}, pd.date_range("2000", periods=4)), operator.methodcaller("between_time", "12:00", "13:00"), ), - ( - pd.Series, - (1, pd.date_range("2000", periods=4)), - operator.methodcaller("first", "3D"), - ), - ( - pd.DataFrame, - ({"A": [1, 1, 1, 1]}, pd.date_range("2000", periods=4)), - operator.methodcaller("first", "3D"), - ), ( pd.Series, (1, pd.date_range("2000", periods=4)), @@ -451,6 +442,22 @@ def test_finalize_called(ndframe_method): assert result.attrs == {"a": 1} +@pytest.mark.parametrize( + "data", + [ + pd.Series(1, pd.date_range("2000", periods=4)), + pd.DataFrame({"A": [1, 1, 1, 1]}, pd.date_range("2000", periods=4)), + ], +) +def test_finalize_first(data): + deprecated_msg = "first is deprecated" + + data.attrs = {"a": 1} + with tm.assert_produces_warning(FutureWarning, match=deprecated_msg): + result = data.first("3D") + assert result.attrs == {"a": 1} + + @not_implemented_mark def test_finalize_called_eval_numexpr(): pytest.importorskip("numexpr")