Skip to content

Commit b48abb2

Browse files
committed
Merge remote-tracking branch 'upstream/main' into bugfix-slice-selection
2 parents 236d3f3 + da80247 commit b48abb2

File tree

16 files changed

+133
-232
lines changed

16 files changed

+133
-232
lines changed

doc/source/whatsnew/v3.0.0.rst

+3
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ Removal of prior version deprecations/changes
207207
- All arguments except ``name`` in :meth:`Index.rename` are now keyword only (:issue:`56493`)
208208
- All arguments except the first ``path``-like argument in IO writers are now keyword only (:issue:`54229`)
209209
- Removed "freq" keyword from :class:`PeriodArray` constructor, use "dtype" instead (:issue:`52462`)
210+
- Removed deprecated "method" and "limit" keywords from :meth:`Series.replace` and :meth:`DataFrame.replace` (:issue:`53492`)
210211
- Removed the "closed" and "normalize" keywords in :meth:`DatetimeIndex.__new__` (:issue:`52628`)
211212
- Removed the "closed" and "unit" keywords in :meth:`TimedeltaIndex.__new__` (:issue:`52628`, :issue:`55499`)
212213
- All arguments in :meth:`Index.sort_values` are now keyword only (:issue:`56493`)
@@ -298,6 +299,7 @@ Performance improvements
298299
- Performance improvement in :meth:`DataFrameGroupBy.ffill`, :meth:`DataFrameGroupBy.bfill`, :meth:`SeriesGroupBy.ffill`, and :meth:`SeriesGroupBy.bfill` (:issue:`56902`)
299300
- Performance improvement in :meth:`Index.join` by propagating cached attributes in cases where the result matches one of the inputs (:issue:`57023`)
300301
- Performance improvement in :meth:`Index.take` when ``indices`` is a full range indexer from zero to length of index (:issue:`56806`)
302+
- Performance improvement in :meth:`Index.to_frame` returning a :class:`RangeIndex` columns of a :class:`Index` when possible. (:issue:`58018`)
301303
- Performance improvement in :meth:`MultiIndex.equals` for equal length indexes (:issue:`56990`)
302304
- Performance improvement in :meth:`RangeIndex.__getitem__` with a boolean mask or integers returning a :class:`RangeIndex` instead of a :class:`Index` when possible. (:issue:`57588`)
303305
- Performance improvement in :meth:`RangeIndex.append` when appending the same index (:issue:`57252`)
@@ -319,6 +321,7 @@ Bug fixes
319321
~~~~~~~~~
320322
- Fixed bug in :class:`SparseDtype` for equal comparison with na fill value. (:issue:`54770`)
321323
- Fixed bug in :meth:`.DataFrameGroupBy.median` where nat values gave an incorrect result. (:issue:`57926`)
324+
- Fixed bug in :meth:`DataFrame.cumsum` which was raising ``IndexError`` if dtype is ``timedelta64[ns]`` (:issue:`57956`)
322325
- Fixed bug in :meth:`DataFrame.join` inconsistently setting result index name (:issue:`55815`)
323326
- Fixed bug in :meth:`DataFrame.to_string` that raised ``StopIteration`` with nested DataFrames. (:issue:`16098`)
324327
- Fixed bug in :meth:`DataFrame.update` bool dtype being converted to object (:issue:`55509`)

pandas/conftest.py

-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@ def pytest_collection_modifyitems(items, config) -> None:
150150
("is_categorical_dtype", "is_categorical_dtype is deprecated"),
151151
("is_sparse", "is_sparse is deprecated"),
152152
("DataFrameGroupBy.fillna", "DataFrameGroupBy.fillna is deprecated"),
153-
("NDFrame.replace", "The 'method' keyword"),
154153
("NDFrame.replace", "Series.replace without 'value'"),
155154
("NDFrame.clip", "Downcasting behavior in Series and DataFrame methods"),
156155
("Series.idxmin", "The behavior of Series.idxmin"),

pandas/core/array_algos/datetimelike_accumulations.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ def _cum_func(
4949
if not skipna:
5050
mask = np.maximum.accumulate(mask)
5151

52-
result = func(y)
52+
# GH 57956
53+
result = func(y, axis=0)
5354
result[mask] = iNaT
5455

5556
if values.dtype.kind in "mM":

pandas/core/generic.py

+9-48
Original file line numberDiff line numberDiff line change
@@ -6727,12 +6727,10 @@ def _pad_or_backfill(
67276727
axis = self._get_axis_number(axis)
67286728
method = clean_fill_method(method)
67296729

6730-
if not self._mgr.is_single_block and axis == 1:
6731-
# e.g. test_align_fill_method
6732-
# TODO(3.0): once downcast is removed, we can do the .T
6733-
# in all axis=1 cases, and remove axis kward from mgr.pad_or_backfill.
6734-
if inplace:
6730+
if axis == 1:
6731+
if not self._mgr.is_single_block and inplace:
67356732
raise NotImplementedError()
6733+
# e.g. test_align_fill_method
67366734
result = self.T._pad_or_backfill(
67376735
method=method, limit=limit, limit_area=limit_area
67386736
).T
@@ -6741,7 +6739,6 @@ def _pad_or_backfill(
67416739

67426740
new_mgr = self._mgr.pad_or_backfill(
67436741
method=method,
6744-
axis=self._get_block_manager_axis(axis),
67456742
limit=limit,
67466743
limit_area=limit_area,
67476744
inplace=inplace,
@@ -7285,9 +7282,7 @@ def replace(
72857282
value=...,
72867283
*,
72877284
inplace: Literal[False] = ...,
7288-
limit: int | None = ...,
72897285
regex: bool = ...,
7290-
method: Literal["pad", "ffill", "bfill"] | lib.NoDefault = ...,
72917286
) -> Self: ...
72927287

72937288
@overload
@@ -7297,9 +7292,7 @@ def replace(
72977292
value=...,
72987293
*,
72997294
inplace: Literal[True],
7300-
limit: int | None = ...,
73017295
regex: bool = ...,
7302-
method: Literal["pad", "ffill", "bfill"] | lib.NoDefault = ...,
73037296
) -> None: ...
73047297

73057298
@overload
@@ -7309,9 +7302,7 @@ def replace(
73097302
value=...,
73107303
*,
73117304
inplace: bool = ...,
7312-
limit: int | None = ...,
73137305
regex: bool = ...,
7314-
method: Literal["pad", "ffill", "bfill"] | lib.NoDefault = ...,
73157306
) -> Self | None: ...
73167307

73177308
@final
@@ -7326,32 +7317,9 @@ def replace(
73267317
value=lib.no_default,
73277318
*,
73287319
inplace: bool = False,
7329-
limit: int | None = None,
73307320
regex: bool = False,
7331-
method: Literal["pad", "ffill", "bfill"] | lib.NoDefault = lib.no_default,
73327321
) -> Self | None:
7333-
if method is not lib.no_default:
7334-
warnings.warn(
7335-
# GH#33302
7336-
f"The 'method' keyword in {type(self).__name__}.replace is "
7337-
"deprecated and will be removed in a future version.",
7338-
FutureWarning,
7339-
stacklevel=find_stack_level(),
7340-
)
7341-
elif limit is not None:
7342-
warnings.warn(
7343-
# GH#33302
7344-
f"The 'limit' keyword in {type(self).__name__}.replace is "
7345-
"deprecated and will be removed in a future version.",
7346-
FutureWarning,
7347-
stacklevel=find_stack_level(),
7348-
)
7349-
if (
7350-
value is lib.no_default
7351-
and method is lib.no_default
7352-
and not is_dict_like(to_replace)
7353-
and regex is False
7354-
):
7322+
if value is lib.no_default and not is_dict_like(to_replace) and regex is False:
73557323
# case that goes through _replace_single and defaults to method="pad"
73567324
warnings.warn(
73577325
# GH#33302
@@ -7387,14 +7355,11 @@ def replace(
73877355
if not is_bool(regex) and to_replace is not None:
73887356
raise ValueError("'to_replace' must be 'None' if 'regex' is not a bool")
73897357

7390-
if value is lib.no_default or method is not lib.no_default:
7358+
if value is lib.no_default:
73917359
# GH#36984 if the user explicitly passes value=None we want to
73927360
# respect that. We have the corner case where the user explicitly
73937361
# passes value=None *and* a method, which we interpret as meaning
73947362
# they want the (documented) default behavior.
7395-
if method is lib.no_default:
7396-
# TODO: get this to show up as the default in the docs?
7397-
method = "pad"
73987363

73997364
# passing a single value that is scalar like
74007365
# when value is None (GH5319), for compat
@@ -7408,12 +7373,12 @@ def replace(
74087373

74097374
result = self.apply(
74107375
Series._replace_single,
7411-
args=(to_replace, method, inplace, limit),
7376+
args=(to_replace, inplace),
74127377
)
74137378
if inplace:
74147379
return None
74157380
return result
7416-
return self._replace_single(to_replace, method, inplace, limit)
7381+
return self._replace_single(to_replace, inplace)
74177382

74187383
if not is_dict_like(to_replace):
74197384
if not is_dict_like(regex):
@@ -7458,9 +7423,7 @@ def replace(
74587423
else:
74597424
to_replace, value = keys, values
74607425

7461-
return self.replace(
7462-
to_replace, value, inplace=inplace, limit=limit, regex=regex
7463-
)
7426+
return self.replace(to_replace, value, inplace=inplace, regex=regex)
74647427
else:
74657428
# need a non-zero len on all axes
74667429
if not self.size:
@@ -7524,9 +7487,7 @@ def replace(
75247487
f"or a list or dict of strings or regular expressions, "
75257488
f"you passed a {type(regex).__name__!r}"
75267489
)
7527-
return self.replace(
7528-
regex, value, inplace=inplace, limit=limit, regex=True
7529-
)
7490+
return self.replace(regex, value, inplace=inplace, regex=True)
75307491
else:
75317492
# dest iterable dict-like
75327493
if is_dict_like(value): # NA -> {'A' : 0, 'B' : -1}

pandas/core/indexes/base.py

+13-7
Original file line numberDiff line numberDiff line change
@@ -1374,16 +1374,19 @@ def _format_attrs(self) -> list[tuple[str_t, str_t | int | bool | None]]:
13741374
return attrs
13751375

13761376
@final
1377-
def _get_level_names(self) -> Hashable | Sequence[Hashable]:
1377+
def _get_level_names(self) -> range | Sequence[Hashable]:
13781378
"""
13791379
Return a name or list of names with None replaced by the level number.
13801380
"""
13811381
if self._is_multi:
1382-
return [
1383-
level if name is None else name for level, name in enumerate(self.names)
1384-
]
1382+
return maybe_sequence_to_range(
1383+
[
1384+
level if name is None else name
1385+
for level, name in enumerate(self.names)
1386+
]
1387+
)
13851388
else:
1386-
return 0 if self.name is None else self.name
1389+
return range(1) if self.name is None else [self.name]
13871390

13881391
@final
13891392
def _mpl_repr(self) -> np.ndarray:
@@ -1630,8 +1633,11 @@ def to_frame(
16301633
from pandas import DataFrame
16311634

16321635
if name is lib.no_default:
1633-
name = self._get_level_names()
1634-
result = DataFrame({name: self}, copy=False)
1636+
result_name = self._get_level_names()
1637+
else:
1638+
result_name = Index([name]) # type: ignore[assignment]
1639+
result = DataFrame(self, copy=False)
1640+
result.columns = result_name
16351641

16361642
if index:
16371643
result.index = self

pandas/core/internals/blocks.py

+4-10
Original file line numberDiff line numberDiff line change
@@ -1343,7 +1343,6 @@ def pad_or_backfill(
13431343
self,
13441344
*,
13451345
method: FillnaOptions,
1346-
axis: AxisInt = 0,
13471346
inplace: bool = False,
13481347
limit: int | None = None,
13491348
limit_area: Literal["inside", "outside"] | None = None,
@@ -1357,16 +1356,12 @@ def pad_or_backfill(
13571356
# Dispatch to the NumpyExtensionArray method.
13581357
# We know self.array_values is a NumpyExtensionArray bc EABlock overrides
13591358
vals = cast(NumpyExtensionArray, self.array_values)
1360-
if axis == 1:
1361-
vals = vals.T
1362-
new_values = vals._pad_or_backfill(
1359+
new_values = vals.T._pad_or_backfill(
13631360
method=method,
13641361
limit=limit,
13651362
limit_area=limit_area,
13661363
copy=copy,
1367-
)
1368-
if axis == 1:
1369-
new_values = new_values.T
1364+
).T
13701365

13711366
data = extract_array(new_values, extract_numpy=True)
13721367
return [self.make_block_same_class(data, refs=refs)]
@@ -1814,7 +1809,6 @@ def pad_or_backfill(
18141809
self,
18151810
*,
18161811
method: FillnaOptions,
1817-
axis: AxisInt = 0,
18181812
inplace: bool = False,
18191813
limit: int | None = None,
18201814
limit_area: Literal["inside", "outside"] | None = None,
@@ -1827,11 +1821,11 @@ def pad_or_backfill(
18271821
elif limit_area is not None:
18281822
raise NotImplementedError(
18291823
f"{type(values).__name__} does not implement limit_area "
1830-
"(added in pandas 2.2). 3rd-party ExtnsionArray authors "
1824+
"(added in pandas 2.2). 3rd-party ExtensionArray authors "
18311825
"need to add this argument to _pad_or_backfill."
18321826
)
18331827

1834-
if values.ndim == 2 and axis == 1:
1828+
if values.ndim == 2:
18351829
# NDArrayBackedExtensionArray.fillna assumes axis=0
18361830
new_values = values.T._pad_or_backfill(**kwargs).T
18371831
else:

pandas/core/series.py

+6-12
Original file line numberDiff line numberDiff line change
@@ -5113,28 +5113,22 @@ def info(
51135113
)
51145114

51155115
@overload
5116-
def _replace_single(
5117-
self, to_replace, method: str, inplace: Literal[False], limit
5118-
) -> Self: ...
5116+
def _replace_single(self, to_replace, inplace: Literal[False]) -> Self: ...
51195117

51205118
@overload
5121-
def _replace_single(
5122-
self, to_replace, method: str, inplace: Literal[True], limit
5123-
) -> None: ...
5119+
def _replace_single(self, to_replace, inplace: Literal[True]) -> None: ...
51245120

51255121
@overload
5126-
def _replace_single(
5127-
self, to_replace, method: str, inplace: bool, limit
5128-
) -> Self | None: ...
5122+
def _replace_single(self, to_replace, inplace: bool) -> Self | None: ...
51295123

51305124
# TODO(3.0): this can be removed once GH#33302 deprecation is enforced
5131-
def _replace_single(
5132-
self, to_replace, method: str, inplace: bool, limit
5133-
) -> Self | None:
5125+
def _replace_single(self, to_replace, inplace: bool) -> Self | None:
51345126
"""
51355127
Replaces values in a Series using the fill method specified when no
51365128
replacement value is given in the replace method
51375129
"""
5130+
limit = None
5131+
method = "pad"
51385132

51395133
result = self if inplace else self.copy()
51405134

pandas/core/shared_docs.py

+1-18
Original file line numberDiff line numberDiff line change
@@ -429,20 +429,11 @@
429429
filled). Regular expressions, strings and lists or dicts of such
430430
objects are also allowed.
431431
{inplace}
432-
limit : int, default None
433-
Maximum size gap to forward or backward fill.
434-
435-
.. deprecated:: 2.1.0
436432
regex : bool or same types as `to_replace`, default False
437433
Whether to interpret `to_replace` and/or `value` as regular
438434
expressions. Alternatively, this could be a regular expression or a
439435
list, dict, or array of regular expressions in which case
440436
`to_replace` must be ``None``.
441-
method : {{'pad', 'ffill', 'bfill'}}
442-
The method to use when for replacement, when `to_replace` is a
443-
scalar, list or tuple and `value` is ``None``.
444-
445-
.. deprecated:: 2.1.0
446437
447438
Returns
448439
-------
@@ -538,14 +529,6 @@
538529
3 1 8 d
539530
4 4 9 e
540531
541-
>>> s.replace([1, 2], method='bfill')
542-
0 3
543-
1 3
544-
2 3
545-
3 4
546-
4 5
547-
dtype: int64
548-
549532
**dict-like `to_replace`**
550533
551534
>>> df.replace({{0: 10, 1: 100}})
@@ -615,7 +598,7 @@
615598
When one uses a dict as the `to_replace` value, it is like the
616599
value(s) in the dict are equal to the `value` parameter.
617600
``s.replace({{'a': None}})`` is equivalent to
618-
``s.replace(to_replace={{'a': None}}, value=None, method=None)``:
601+
``s.replace(to_replace={{'a': None}}, value=None)``:
619602
620603
>>> s.replace({{'a': None}})
621604
0 10

pandas/tests/frame/methods/test_replace.py

-42
Original file line numberDiff line numberDiff line change
@@ -1171,48 +1171,6 @@ def test_replace_with_empty_dictlike(self, mix_abc):
11711171
tm.assert_frame_equal(df, df.replace({"b": {}}))
11721172
tm.assert_frame_equal(df, df.replace(Series({"b": {}})))
11731173

1174-
@pytest.mark.parametrize(
1175-
"to_replace, method, expected",
1176-
[
1177-
(0, "bfill", {"A": [1, 1, 2], "B": [5, np.nan, 7], "C": ["a", "b", "c"]}),
1178-
(
1179-
np.nan,
1180-
"bfill",
1181-
{"A": [0, 1, 2], "B": [5.0, 7.0, 7.0], "C": ["a", "b", "c"]},
1182-
),
1183-
("d", "ffill", {"A": [0, 1, 2], "B": [5, np.nan, 7], "C": ["a", "b", "c"]}),
1184-
(
1185-
[0, 2],
1186-
"bfill",
1187-
{"A": [1, 1, 2], "B": [5, np.nan, 7], "C": ["a", "b", "c"]},
1188-
),
1189-
(
1190-
[1, 2],
1191-
"pad",
1192-
{"A": [0, 0, 0], "B": [5, np.nan, 7], "C": ["a", "b", "c"]},
1193-
),
1194-
(
1195-
(1, 2),
1196-
"bfill",
1197-
{"A": [0, 2, 2], "B": [5, np.nan, 7], "C": ["a", "b", "c"]},
1198-
),
1199-
(
1200-
["b", "c"],
1201-
"ffill",
1202-
{"A": [0, 1, 2], "B": [5, np.nan, 7], "C": ["a", "a", "a"]},
1203-
),
1204-
],
1205-
)
1206-
def test_replace_method(self, to_replace, method, expected):
1207-
# GH 19632
1208-
df = DataFrame({"A": [0, 1, 2], "B": [5, np.nan, 7], "C": ["a", "b", "c"]})
1209-
1210-
msg = "The 'method' keyword in DataFrame.replace is deprecated"
1211-
with tm.assert_produces_warning(FutureWarning, match=msg):
1212-
result = df.replace(to_replace=to_replace, value=None, method=method)
1213-
expected = DataFrame(expected)
1214-
tm.assert_frame_equal(result, expected)
1215-
12161174
@pytest.mark.parametrize(
12171175
"replace_dict, final_data",
12181176
[({"a": 1, "b": 1}, [[3, 3], [2, 2]]), ({"a": 1, "b": 2}, [[3, 1], [2, 3]])],

0 commit comments

Comments
 (0)