Skip to content

Commit 3797b7d

Browse files
DeaMariaLeonMarcoGorelli
authored andcommitted
DEPR: Series.first() and DataFrame.first() (pandas-dev#53419)
* Deprecating first() * Added requested changes * removed repeated ts * Update doc/source/whatsnew/v2.1.0.rst * Update pandas/core/generic.py * pre-commit * Separated test for first() on test_finalize.py * Avoiding docstring on CI --------- Co-authored-by: Marco Edward Gorelli <[email protected]> Co-authored-by: MarcoGorelli <[email protected]>
1 parent 1430e42 commit 3797b7d

File tree

5 files changed

+63
-29
lines changed

5 files changed

+63
-29
lines changed

doc/source/whatsnew/v2.1.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -269,12 +269,12 @@ Deprecations
269269
- Deprecated unused "closed" and "normalize" keywords in the :class:`DatetimeIndex` constructor (:issue:`52628`)
270270
- Deprecated unused "closed" keyword in the :class:`TimedeltaIndex` constructor (:issue:`52628`)
271271
- 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`)
272+
- Deprecated :meth:`Series.first` and :meth:`DataFrame.first` (please create a mask and filter using ``.loc`` instead) (:issue:`45908`)
272273
- 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`)
273274
- 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`)
274275
- 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`)
275276
- Deprecated constructing :class:`SparseArray` from scalar data, pass a sequence instead (:issue:`53039`)
276277
- 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`)
277-
-
278278

279279
.. ---------------------------------------------------------------------------
280280
.. _whatsnew_210.performance:

pandas/conftest.py

+5
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ def pytest_collection_modifyitems(items, config) -> None:
145145
"(Series|DataFrame).bool is now deprecated and will be removed "
146146
"in future version of pandas",
147147
),
148+
(
149+
"pandas.core.generic.NDFrame.first",
150+
"first is deprecated and will be removed in a future version. "
151+
"Please create a mask and filter using `.loc` instead",
152+
),
148153
]
149154

150155
for item in items:

pandas/core/generic.py

+6
Original file line numberDiff line numberDiff line change
@@ -9162,6 +9162,12 @@ def first(self, offset) -> Self:
91629162
3 days observed in the dataset, and therefore data for 2018-04-13 was
91639163
not returned.
91649164
"""
9165+
warnings.warn(
9166+
"first is deprecated and will be removed in a future version. "
9167+
"Please create a mask and filter using `.loc` instead",
9168+
FutureWarning,
9169+
stacklevel=find_stack_level(),
9170+
)
91659171
if not isinstance(self.index, DatetimeIndex):
91669172
raise TypeError("'first' only supports a DatetimeIndex index")
91679173

pandas/tests/frame/methods/test_first_and_last.py

+34-18
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,48 @@
1010
)
1111
import pandas._testing as tm
1212

13+
deprecated_msg = "first is deprecated"
14+
1315

1416
class TestFirst:
1517
def test_first_subset(self, frame_or_series):
1618
ts = tm.makeTimeDataFrame(freq="12h")
1719
ts = tm.get_obj(ts, frame_or_series)
18-
result = ts.first("10d")
19-
assert len(result) == 20
20+
with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
21+
result = ts.first("10d")
22+
assert len(result) == 20
2023

2124
ts = tm.makeTimeDataFrame(freq="D")
2225
ts = tm.get_obj(ts, frame_or_series)
23-
result = ts.first("10d")
24-
assert len(result) == 10
26+
with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
27+
result = ts.first("10d")
28+
assert len(result) == 10
2529

26-
result = ts.first("3M")
27-
expected = ts[:"3/31/2000"]
28-
tm.assert_equal(result, expected)
30+
with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
31+
result = ts.first("3M")
32+
expected = ts[:"3/31/2000"]
33+
tm.assert_equal(result, expected)
2934

30-
result = ts.first("21D")
31-
expected = ts[:21]
32-
tm.assert_equal(result, expected)
35+
with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
36+
result = ts.first("21D")
37+
expected = ts[:21]
38+
tm.assert_equal(result, expected)
3339

34-
result = ts[:0].first("3M")
35-
tm.assert_equal(result, ts[:0])
40+
with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
41+
result = ts[:0].first("3M")
42+
tm.assert_equal(result, ts[:0])
3643

3744
def test_first_last_raises(self, frame_or_series):
3845
# GH#20725
3946
obj = DataFrame([[1, 2, 3], [4, 5, 6]])
4047
obj = tm.get_obj(obj, frame_or_series)
4148

4249
msg = "'first' only supports a DatetimeIndex index"
43-
with pytest.raises(TypeError, match=msg): # index is not a DatetimeIndex
50+
with tm.assert_produces_warning(
51+
FutureWarning, match=deprecated_msg
52+
), pytest.raises(
53+
TypeError, match=msg
54+
): # index is not a DatetimeIndex
4455
obj.first("1D")
4556

4657
msg = "'last' only supports a DatetimeIndex index"
@@ -73,7 +84,8 @@ def test_last_subset(self, frame_or_series):
7384
def test_first_with_first_day_last_of_month(self, frame_or_series, start, periods):
7485
# GH#29623
7586
x = frame_or_series([1] * 100, index=bdate_range(start, periods=100))
76-
result = x.first("1M")
87+
with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
88+
result = x.first("1M")
7789
expected = frame_or_series(
7890
[1] * periods, index=bdate_range(start, periods=periods)
7991
)
@@ -82,16 +94,20 @@ def test_first_with_first_day_last_of_month(self, frame_or_series, start, period
8294
def test_first_with_first_day_end_of_frq_n_greater_one(self, frame_or_series):
8395
# GH#29623
8496
x = frame_or_series([1] * 100, index=bdate_range("2010-03-31", periods=100))
85-
result = x.first("2M")
97+
with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
98+
result = x.first("2M")
8699
expected = frame_or_series(
87100
[1] * 23, index=bdate_range("2010-03-31", "2010-04-30")
88101
)
89102
tm.assert_equal(result, expected)
90103

91-
@pytest.mark.parametrize("func", ["first", "last"])
92-
def test_empty_not_input(self, func):
104+
def test_empty_not_input(self):
93105
# GH#51032
94106
df = DataFrame(index=pd.DatetimeIndex([]))
95-
result = getattr(df, func)(offset=1)
107+
result = df.last(offset=1)
108+
109+
with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
110+
result = df.first(offset=1)
111+
96112
tm.assert_frame_equal(df, result)
97113
assert df is not result

pandas/tests/generic/test_finalize.py

+17-10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import pytest
99

1010
import pandas as pd
11+
import pandas._testing as tm
1112

1213
# TODO:
1314
# * Binary methods (mul, div, etc.)
@@ -333,16 +334,6 @@
333334
({"A": [1, 1, 1, 1]}, pd.date_range("2000", periods=4)),
334335
operator.methodcaller("between_time", "12:00", "13:00"),
335336
),
336-
(
337-
pd.Series,
338-
(1, pd.date_range("2000", periods=4)),
339-
operator.methodcaller("first", "3D"),
340-
),
341-
(
342-
pd.DataFrame,
343-
({"A": [1, 1, 1, 1]}, pd.date_range("2000", periods=4)),
344-
operator.methodcaller("first", "3D"),
345-
),
346337
(
347338
pd.Series,
348339
(1, pd.date_range("2000", periods=4)),
@@ -451,6 +442,22 @@ def test_finalize_called(ndframe_method):
451442
assert result.attrs == {"a": 1}
452443

453444

445+
@pytest.mark.parametrize(
446+
"data",
447+
[
448+
pd.Series(1, pd.date_range("2000", periods=4)),
449+
pd.DataFrame({"A": [1, 1, 1, 1]}, pd.date_range("2000", periods=4)),
450+
],
451+
)
452+
def test_finalize_first(data):
453+
deprecated_msg = "first is deprecated"
454+
455+
data.attrs = {"a": 1}
456+
with tm.assert_produces_warning(FutureWarning, match=deprecated_msg):
457+
result = data.first("3D")
458+
assert result.attrs == {"a": 1}
459+
460+
454461
@not_implemented_mark
455462
def test_finalize_called_eval_numexpr():
456463
pytest.importorskip("numexpr")

0 commit comments

Comments
 (0)