Skip to content

Commit 11424f8

Browse files
Backport PR #54838 on branch 2.1.x (Don't expose EA.pad_or_backfill to users + switch to DeprecationWarning) (#54851)
Backport PR #54838: Don't expose EA.pad_or_backfill to users + switch to DeprecationWarning Co-authored-by: Joris Van den Bossche <[email protected]>
1 parent 772beaa commit 11424f8

File tree

17 files changed

+102
-105
lines changed

17 files changed

+102
-105
lines changed

doc/source/reference/extensions.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ objects.
3939
api.extensions.ExtensionArray._from_sequence
4040
api.extensions.ExtensionArray._from_sequence_of_strings
4141
api.extensions.ExtensionArray._hash_pandas_object
42+
api.extensions.ExtensionArray._pad_or_backfill
4243
api.extensions.ExtensionArray._reduce
4344
api.extensions.ExtensionArray._values_for_argsort
4445
api.extensions.ExtensionArray._values_for_factorize
@@ -54,7 +55,6 @@ objects.
5455
api.extensions.ExtensionArray.interpolate
5556
api.extensions.ExtensionArray.isin
5657
api.extensions.ExtensionArray.isna
57-
api.extensions.ExtensionArray.pad_or_backfill
5858
api.extensions.ExtensionArray.ravel
5959
api.extensions.ExtensionArray.repeat
6060
api.extensions.ExtensionArray.searchsorted

doc/source/whatsnew/v2.1.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ Other Deprecations
583583
- 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`)
584584
- Deprecated replacing builtin and NumPy functions in ``.agg``, ``.apply``, and ``.transform``; use the corresponding string alias (e.g. ``"sum"`` for ``sum`` or ``np.sum``) instead (:issue:`53425`)
585585
- Deprecated strings ``T``, ``t``, ``L`` and ``l`` denoting units in :func:`to_timedelta` (:issue:`52536`)
586-
- Deprecated the "method" and "limit" keywords in ``.ExtensionArray.fillna``, implement and use ``pad_or_backfill`` instead (:issue:`53621`)
586+
- Deprecated the "method" and "limit" keywords in ``.ExtensionArray.fillna``, implement ``_pad_or_backfill`` instead (:issue:`53621`)
587587
- Deprecated the ``method`` and ``limit`` keywords in :meth:`DataFrame.replace` and :meth:`Series.replace` (:issue:`33302`)
588588
- 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`)
589589
- 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`)

pandas/core/arrays/_mixins.py

+2-7
Original file line numberDiff line numberDiff line change
@@ -296,13 +296,8 @@ def _fill_mask_inplace(
296296
func = missing.get_fill_func(method, ndim=self.ndim)
297297
func(self._ndarray.T, limit=limit, mask=mask.T)
298298

299-
def pad_or_backfill(
300-
self,
301-
*,
302-
method: FillnaOptions,
303-
limit: int | None = None,
304-
limit_area: Literal["inside", "outside"] | None = None,
305-
copy: bool = True,
299+
def _pad_or_backfill(
300+
self, *, method: FillnaOptions, limit: int | None = None, copy: bool = True
306301
) -> Self:
307302
mask = self.isna()
308303
if mask.any():

pandas/core/arrays/arrow/array.py

+5-12
Original file line numberDiff line numberDiff line change
@@ -924,19 +924,14 @@ def dropna(self) -> Self:
924924
"""
925925
return type(self)(pc.drop_null(self._pa_array))
926926

927-
def pad_or_backfill(
928-
self,
929-
*,
930-
method: FillnaOptions,
931-
limit: int | None = None,
932-
limit_area: Literal["inside", "outside"] | None = None,
933-
copy: bool = True,
927+
def _pad_or_backfill(
928+
self, *, method: FillnaOptions, limit: int | None = None, copy: bool = True
934929
) -> Self:
935930
if not self._hasna:
936931
# TODO(CoW): Not necessary anymore when CoW is the default
937932
return self.copy()
938933

939-
if limit is not None and limit_area is not None:
934+
if limit is None:
940935
method = missing.clean_fill_method(method)
941936
try:
942937
if method == "pad":
@@ -952,9 +947,7 @@ def pad_or_backfill(
952947

953948
# TODO(3.0): after EA.fillna 'method' deprecation is enforced, we can remove
954949
# this method entirely.
955-
return super().pad_or_backfill(
956-
method=method, limit=limit, limit_area=limit_area, copy=copy
957-
)
950+
return super()._pad_or_backfill(method=method, limit=limit, copy=copy)
958951

959952
@doc(ExtensionArray.fillna)
960953
def fillna(
@@ -974,7 +967,7 @@ def fillna(
974967
return super().fillna(value=value, method=method, limit=limit, copy=copy)
975968

976969
if method is not None:
977-
return super().pad_or_backfill(method=method, limit=limit, copy=copy)
970+
return super().fillna(method=method, limit=limit, copy=copy)
978971

979972
if isinstance(value, (np.ndarray, ExtensionArray)):
980973
# Similar to check_value_size, but we do not mask here since we may

pandas/core/arrays/base.py

+13-19
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ class ExtensionArray:
132132
interpolate
133133
isin
134134
isna
135-
pad_or_backfill
136135
ravel
137136
repeat
138137
searchsorted
@@ -148,6 +147,7 @@ class ExtensionArray:
148147
_from_sequence
149148
_from_sequence_of_strings
150149
_hash_pandas_object
150+
_pad_or_backfill
151151
_reduce
152152
_values_for_argsort
153153
_values_for_factorize
@@ -183,7 +183,7 @@ class ExtensionArray:
183183
methods:
184184
185185
* fillna
186-
* pad_or_backfill
186+
* _pad_or_backfill
187187
* dropna
188188
* unique
189189
* factorize / _values_for_factorize
@@ -917,13 +917,8 @@ def interpolate(
917917
f"{type(self).__name__} does not implement interpolate"
918918
)
919919

920-
def pad_or_backfill(
921-
self,
922-
*,
923-
method: FillnaOptions,
924-
limit: int | None = None,
925-
limit_area: Literal["inside", "outside"] | None = None,
926-
copy: bool = True,
920+
def _pad_or_backfill(
921+
self, *, method: FillnaOptions, limit: int | None = None, copy: bool = True
927922
) -> Self:
928923
"""
929924
Pad or backfill values, used by Series/DataFrame ffill and bfill.
@@ -959,26 +954,26 @@ def pad_or_backfill(
959954
Examples
960955
--------
961956
>>> arr = pd.array([np.nan, np.nan, 2, 3, np.nan, np.nan])
962-
>>> arr.pad_or_backfill(method="backfill", limit=1)
957+
>>> arr._pad_or_backfill(method="backfill", limit=1)
963958
<IntegerArray>
964959
[<NA>, 2, 2, 3, <NA>, <NA>]
965960
Length: 6, dtype: Int64
966961
"""
967962

968963
# If a 3rd-party EA has implemented this functionality in fillna,
969-
# we warn that they need to implement pad_or_backfill instead.
964+
# we warn that they need to implement _pad_or_backfill instead.
970965
if (
971966
type(self).fillna is not ExtensionArray.fillna
972-
and type(self).pad_or_backfill is ExtensionArray.pad_or_backfill
967+
and type(self)._pad_or_backfill is ExtensionArray._pad_or_backfill
973968
):
974-
# Check for pad_or_backfill here allows us to call
975-
# super().pad_or_backfill without getting this warning
969+
# Check for _pad_or_backfill here allows us to call
970+
# super()._pad_or_backfill without getting this warning
976971
warnings.warn(
977972
"ExtensionArray.fillna 'method' keyword is deprecated. "
978-
"In a future version. arr.pad_or_backfill will be called "
973+
"In a future version. arr._pad_or_backfill will be called "
979974
"instead. 3rd-party ExtensionArray authors need to implement "
980-
"pad_or_backfill.",
981-
FutureWarning,
975+
"_pad_or_backfill.",
976+
DeprecationWarning,
982977
stacklevel=find_stack_level(),
983978
)
984979
return self.fillna(method=method, limit=limit)
@@ -1062,8 +1057,7 @@ def fillna(
10621057
if method is not None:
10631058
warnings.warn(
10641059
f"The 'method' keyword in {type(self).__name__}.fillna is "
1065-
"deprecated and will be removed in a future version. "
1066-
"Use pad_or_backfill instead.",
1060+
"deprecated and will be removed in a future version.",
10671061
FutureWarning,
10681062
stacklevel=find_stack_level(),
10691063
)

pandas/core/arrays/interval.py

+3-10
Original file line numberDiff line numberDiff line change
@@ -890,19 +890,12 @@ def max(self, *, axis: AxisInt | None = None, skipna: bool = True) -> IntervalOr
890890
indexer = obj.argsort()[-1]
891891
return obj[indexer]
892892

893-
def pad_or_backfill( # pylint: disable=useless-parent-delegation
894-
self,
895-
*,
896-
method: FillnaOptions,
897-
limit: int | None = None,
898-
limit_area: Literal["inside", "outside"] | None = None,
899-
copy: bool = True,
893+
def _pad_or_backfill( # pylint: disable=useless-parent-delegation
894+
self, *, method: FillnaOptions, limit: int | None = None, copy: bool = True
900895
) -> Self:
901896
# TODO(3.0): after EA.fillna 'method' deprecation is enforced, we can remove
902897
# this method entirely.
903-
return super().pad_or_backfill(
904-
method=method, limit=limit, limit_area=limit_area, copy=copy
905-
)
898+
return super()._pad_or_backfill(method=method, limit=limit, copy=copy)
906899

907900
def fillna(
908901
self, value=None, method=None, limit: int | None = None, copy: bool = True

pandas/core/arrays/masked.py

+2-7
Original file line numberDiff line numberDiff line change
@@ -190,13 +190,8 @@ def __getitem__(self, item: PositionalIndexer) -> Self | Any:
190190

191191
return self._simple_new(self._data[item], newmask)
192192

193-
def pad_or_backfill(
194-
self,
195-
*,
196-
method: FillnaOptions,
197-
limit: int | None = None,
198-
limit_area: Literal["inside", "outside"] | None = None,
199-
copy: bool = True,
193+
def _pad_or_backfill(
194+
self, *, method: FillnaOptions, limit: int | None = None, copy: bool = True
200195
) -> Self:
201196
mask = self._mask
202197

pandas/core/arrays/numpy_.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,10 @@ def _values_for_factorize(self) -> tuple[np.ndarray, float | None]:
240240
fv = np.nan
241241
return self._ndarray, fv
242242

243-
def pad_or_backfill(
243+
# Base EA class (and all other EA classes) don't have limit_area keyword
244+
# This can be removed here as well when the interpolate ffill/bfill method
245+
# deprecation is enforced
246+
def _pad_or_backfill(
244247
self,
245248
*,
246249
method: FillnaOptions,

pandas/core/arrays/period.py

+3-10
Original file line numberDiff line numberDiff line change
@@ -791,20 +791,13 @@ def searchsorted(
791791
m8arr = self._ndarray.view("M8[ns]")
792792
return m8arr.searchsorted(npvalue, side=side, sorter=sorter)
793793

794-
def pad_or_backfill(
795-
self,
796-
*,
797-
method: FillnaOptions,
798-
limit: int | None = None,
799-
limit_area: Literal["inside", "outside"] | None = None,
800-
copy: bool = True,
794+
def _pad_or_backfill(
795+
self, *, method: FillnaOptions, limit: int | None = None, copy: bool = True
801796
) -> Self:
802797
# view as dt64 so we get treated as timelike in core.missing,
803798
# similar to dtl._period_dispatch
804799
dta = self.view("M8[ns]")
805-
result = dta.pad_or_backfill(
806-
method=method, limit=limit, limit_area=limit_area, copy=copy
807-
)
800+
result = dta._pad_or_backfill(method=method, limit=limit, copy=copy)
808801
if copy:
809802
return cast("Self", result.view(self.dtype))
810803
else:

pandas/core/arrays/sparse/array.py

+3-10
Original file line numberDiff line numberDiff line change
@@ -712,19 +712,12 @@ def isna(self):
712712
mask[self.sp_index.indices] = isna(self.sp_values)
713713
return type(self)(mask, fill_value=False, dtype=dtype)
714714

715-
def pad_or_backfill( # pylint: disable=useless-parent-delegation
716-
self,
717-
*,
718-
method: FillnaOptions,
719-
limit: int | None = None,
720-
limit_area: Literal["inside", "outside"] | None = None,
721-
copy: bool = True,
715+
def _pad_or_backfill( # pylint: disable=useless-parent-delegation
716+
self, *, method: FillnaOptions, limit: int | None = None, copy: bool = True
722717
) -> Self:
723718
# TODO(3.0): We can remove this method once deprecation for fillna method
724719
# keyword is enforced.
725-
return super().pad_or_backfill(
726-
method=method, limit=limit, limit_area=limit_area, copy=copy
727-
)
720+
return super()._pad_or_backfill(method=method, limit=limit, copy=copy)
728721

729722
def fillna(
730723
self,

pandas/core/internals/blocks.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1452,7 +1452,7 @@ def pad_or_backfill(
14521452
vals = cast(NumpyExtensionArray, self.array_values)
14531453
if axis == 1:
14541454
vals = vals.T
1455-
new_values = vals.pad_or_backfill(
1455+
new_values = vals._pad_or_backfill(
14561456
method=method,
14571457
limit=limit,
14581458
limit_area=limit_area,
@@ -1955,9 +1955,9 @@ def pad_or_backfill(
19551955

19561956
if values.ndim == 2 and axis == 1:
19571957
# NDArrayBackedExtensionArray.fillna assumes axis=0
1958-
new_values = values.T.pad_or_backfill(method=method, limit=limit).T
1958+
new_values = values.T._pad_or_backfill(method=method, limit=limit).T
19591959
else:
1960-
new_values = values.pad_or_backfill(method=method, limit=limit)
1960+
new_values = values._pad_or_backfill(method=method, limit=limit)
19611961
return [self.make_block_same_class(new_values)]
19621962

19631963

pandas/tests/arrays/test_datetimelike.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ def test_fillna_method_doesnt_change_orig(self, method):
258258

259259
fill_value = arr[3] if method == "pad" else arr[5]
260260

261-
result = arr.pad_or_backfill(method=method)
261+
result = arr._pad_or_backfill(method=method)
262262
assert result[4] == fill_value
263263

264264
# check that the original was not changed

pandas/tests/arrays/test_datetimes.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ def test_fillna_preserves_tz(self, method):
497497
dtype=DatetimeTZDtype(tz="US/Central"),
498498
)
499499

500-
result = arr.pad_or_backfill(method=method)
500+
result = arr._pad_or_backfill(method=method)
501501
tm.assert_extension_array_equal(result, expected)
502502

503503
# assert that arr and dti were not modified in-place
@@ -510,12 +510,12 @@ def test_fillna_2d(self):
510510
dta[0, 1] = pd.NaT
511511
dta[1, 0] = pd.NaT
512512

513-
res1 = dta.pad_or_backfill(method="pad")
513+
res1 = dta._pad_or_backfill(method="pad")
514514
expected1 = dta.copy()
515515
expected1[1, 0] = dta[0, 0]
516516
tm.assert_extension_array_equal(res1, expected1)
517517

518-
res2 = dta.pad_or_backfill(method="backfill")
518+
res2 = dta._pad_or_backfill(method="backfill")
519519
expected2 = dta.copy()
520520
expected2 = dta.copy()
521521
expected2[1, 0] = dta[2, 0]
@@ -529,10 +529,10 @@ def test_fillna_2d(self):
529529
assert not dta2._ndarray.flags["C_CONTIGUOUS"]
530530
tm.assert_extension_array_equal(dta, dta2)
531531

532-
res3 = dta2.pad_or_backfill(method="pad")
532+
res3 = dta2._pad_or_backfill(method="pad")
533533
tm.assert_extension_array_equal(res3, expected1)
534534

535-
res4 = dta2.pad_or_backfill(method="backfill")
535+
res4 = dta2._pad_or_backfill(method="backfill")
536536
tm.assert_extension_array_equal(res4, expected2)
537537

538538
# test the DataFrame method while we're here

pandas/tests/extension/base/dim2.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -160,20 +160,20 @@ def test_fillna_2d_method(self, data_missing, method):
160160
assert arr[0].isna().all()
161161
assert not arr[1].isna().any()
162162

163-
result = arr.pad_or_backfill(method=method, limit=None)
163+
result = arr._pad_or_backfill(method=method, limit=None)
164164

165-
expected = data_missing.pad_or_backfill(method=method).repeat(2).reshape(2, 2)
165+
expected = data_missing._pad_or_backfill(method=method).repeat(2).reshape(2, 2)
166166
tm.assert_extension_array_equal(result, expected)
167167

168168
# Reverse so that backfill is not a no-op.
169169
arr2 = arr[::-1]
170170
assert not arr2[0].isna().any()
171171
assert arr2[1].isna().all()
172172

173-
result2 = arr2.pad_or_backfill(method=method, limit=None)
173+
result2 = arr2._pad_or_backfill(method=method, limit=None)
174174

175175
expected2 = (
176-
data_missing[::-1].pad_or_backfill(method=method).repeat(2).reshape(2, 2)
176+
data_missing[::-1]._pad_or_backfill(method=method).repeat(2).reshape(2, 2)
177177
)
178178
tm.assert_extension_array_equal(result2, expected2)
179179

pandas/tests/extension/base/missing.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def test_fillna_no_op_returns_copy(self, data):
9494
assert result is not data
9595
tm.assert_extension_array_equal(result, data)
9696

97-
result = data.pad_or_backfill(method="backfill")
97+
result = data._pad_or_backfill(method="backfill")
9898
assert result is not data
9999
tm.assert_extension_array_equal(result, data)
100100

pandas/tests/extension/decimal/array.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ def value_counts(self, dropna: bool = True):
285285
return value_counts(self.to_numpy(), dropna=dropna)
286286

287287
# We override fillna here to simulate a 3rd party EA that has done so. This
288-
# lets us test the deprecation telling authors to implement pad_or_backfill
288+
# lets us test the deprecation telling authors to implement _pad_or_backfill
289289
# Simulate a 3rd-party EA that has not yet updated to include a "copy"
290290
# keyword in its fillna method.
291291
# error: Signature of "fillna" incompatible with supertype "ExtensionArray"

0 commit comments

Comments
 (0)