Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into bugfix-slice-selection
Browse files Browse the repository at this point in the history
  • Loading branch information
tehunter committed Mar 28, 2024
2 parents 236d3f3 + da80247 commit b48abb2
Show file tree
Hide file tree
Showing 16 changed files with 133 additions and 232 deletions.
3 changes: 3 additions & 0 deletions doc/source/whatsnew/v3.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ Removal of prior version deprecations/changes
- All arguments except ``name`` in :meth:`Index.rename` are now keyword only (:issue:`56493`)
- All arguments except the first ``path``-like argument in IO writers are now keyword only (:issue:`54229`)
- Removed "freq" keyword from :class:`PeriodArray` constructor, use "dtype" instead (:issue:`52462`)
- Removed deprecated "method" and "limit" keywords from :meth:`Series.replace` and :meth:`DataFrame.replace` (:issue:`53492`)
- Removed the "closed" and "normalize" keywords in :meth:`DatetimeIndex.__new__` (:issue:`52628`)
- Removed the "closed" and "unit" keywords in :meth:`TimedeltaIndex.__new__` (:issue:`52628`, :issue:`55499`)
- All arguments in :meth:`Index.sort_values` are now keyword only (:issue:`56493`)
Expand Down Expand Up @@ -298,6 +299,7 @@ Performance improvements
- Performance improvement in :meth:`DataFrameGroupBy.ffill`, :meth:`DataFrameGroupBy.bfill`, :meth:`SeriesGroupBy.ffill`, and :meth:`SeriesGroupBy.bfill` (:issue:`56902`)
- Performance improvement in :meth:`Index.join` by propagating cached attributes in cases where the result matches one of the inputs (:issue:`57023`)
- Performance improvement in :meth:`Index.take` when ``indices`` is a full range indexer from zero to length of index (:issue:`56806`)
- Performance improvement in :meth:`Index.to_frame` returning a :class:`RangeIndex` columns of a :class:`Index` when possible. (:issue:`58018`)
- Performance improvement in :meth:`MultiIndex.equals` for equal length indexes (:issue:`56990`)
- 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`)
- Performance improvement in :meth:`RangeIndex.append` when appending the same index (:issue:`57252`)
Expand All @@ -319,6 +321,7 @@ Bug fixes
~~~~~~~~~
- Fixed bug in :class:`SparseDtype` for equal comparison with na fill value. (:issue:`54770`)
- Fixed bug in :meth:`.DataFrameGroupBy.median` where nat values gave an incorrect result. (:issue:`57926`)
- Fixed bug in :meth:`DataFrame.cumsum` which was raising ``IndexError`` if dtype is ``timedelta64[ns]`` (:issue:`57956`)
- Fixed bug in :meth:`DataFrame.join` inconsistently setting result index name (:issue:`55815`)
- Fixed bug in :meth:`DataFrame.to_string` that raised ``StopIteration`` with nested DataFrames. (:issue:`16098`)
- Fixed bug in :meth:`DataFrame.update` bool dtype being converted to object (:issue:`55509`)
Expand Down
1 change: 0 additions & 1 deletion pandas/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ def pytest_collection_modifyitems(items, config) -> None:
("is_categorical_dtype", "is_categorical_dtype is deprecated"),
("is_sparse", "is_sparse is deprecated"),
("DataFrameGroupBy.fillna", "DataFrameGroupBy.fillna is deprecated"),
("NDFrame.replace", "The 'method' keyword"),
("NDFrame.replace", "Series.replace without 'value'"),
("NDFrame.clip", "Downcasting behavior in Series and DataFrame methods"),
("Series.idxmin", "The behavior of Series.idxmin"),
Expand Down
3 changes: 2 additions & 1 deletion pandas/core/array_algos/datetimelike_accumulations.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def _cum_func(
if not skipna:
mask = np.maximum.accumulate(mask)

result = func(y)
# GH 57956
result = func(y, axis=0)
result[mask] = iNaT

if values.dtype.kind in "mM":
Expand Down
57 changes: 9 additions & 48 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -6727,12 +6727,10 @@ def _pad_or_backfill(
axis = self._get_axis_number(axis)
method = clean_fill_method(method)

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

new_mgr = self._mgr.pad_or_backfill(
method=method,
axis=self._get_block_manager_axis(axis),
limit=limit,
limit_area=limit_area,
inplace=inplace,
Expand Down Expand Up @@ -7285,9 +7282,7 @@ def replace(
value=...,
*,
inplace: Literal[False] = ...,
limit: int | None = ...,
regex: bool = ...,
method: Literal["pad", "ffill", "bfill"] | lib.NoDefault = ...,
) -> Self: ...

@overload
Expand All @@ -7297,9 +7292,7 @@ def replace(
value=...,
*,
inplace: Literal[True],
limit: int | None = ...,
regex: bool = ...,
method: Literal["pad", "ffill", "bfill"] | lib.NoDefault = ...,
) -> None: ...

@overload
Expand All @@ -7309,9 +7302,7 @@ def replace(
value=...,
*,
inplace: bool = ...,
limit: int | None = ...,
regex: bool = ...,
method: Literal["pad", "ffill", "bfill"] | lib.NoDefault = ...,
) -> Self | None: ...

@final
Expand All @@ -7326,32 +7317,9 @@ def replace(
value=lib.no_default,
*,
inplace: bool = False,
limit: int | None = None,
regex: bool = False,
method: Literal["pad", "ffill", "bfill"] | lib.NoDefault = lib.no_default,
) -> Self | None:
if method is not lib.no_default:
warnings.warn(
# GH#33302
f"The 'method' keyword in {type(self).__name__}.replace is "
"deprecated and will be removed in a future version.",
FutureWarning,
stacklevel=find_stack_level(),
)
elif limit is not None:
warnings.warn(
# GH#33302
f"The 'limit' keyword in {type(self).__name__}.replace is "
"deprecated and will be removed in a future version.",
FutureWarning,
stacklevel=find_stack_level(),
)
if (
value is lib.no_default
and method is lib.no_default
and not is_dict_like(to_replace)
and regex is False
):
if value is lib.no_default and not is_dict_like(to_replace) and regex is False:
# case that goes through _replace_single and defaults to method="pad"
warnings.warn(
# GH#33302
Expand Down Expand Up @@ -7387,14 +7355,11 @@ def replace(
if not is_bool(regex) and to_replace is not None:
raise ValueError("'to_replace' must be 'None' if 'regex' is not a bool")

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

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

result = self.apply(
Series._replace_single,
args=(to_replace, method, inplace, limit),
args=(to_replace, inplace),
)
if inplace:
return None
return result
return self._replace_single(to_replace, method, inplace, limit)
return self._replace_single(to_replace, inplace)

if not is_dict_like(to_replace):
if not is_dict_like(regex):
Expand Down Expand Up @@ -7458,9 +7423,7 @@ def replace(
else:
to_replace, value = keys, values

return self.replace(
to_replace, value, inplace=inplace, limit=limit, regex=regex
)
return self.replace(to_replace, value, inplace=inplace, regex=regex)
else:
# need a non-zero len on all axes
if not self.size:
Expand Down Expand Up @@ -7524,9 +7487,7 @@ def replace(
f"or a list or dict of strings or regular expressions, "
f"you passed a {type(regex).__name__!r}"
)
return self.replace(
regex, value, inplace=inplace, limit=limit, regex=True
)
return self.replace(regex, value, inplace=inplace, regex=True)
else:
# dest iterable dict-like
if is_dict_like(value): # NA -> {'A' : 0, 'B' : -1}
Expand Down
20 changes: 13 additions & 7 deletions pandas/core/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1374,16 +1374,19 @@ def _format_attrs(self) -> list[tuple[str_t, str_t | int | bool | None]]:
return attrs

@final
def _get_level_names(self) -> Hashable | Sequence[Hashable]:
def _get_level_names(self) -> range | Sequence[Hashable]:
"""
Return a name or list of names with None replaced by the level number.
"""
if self._is_multi:
return [
level if name is None else name for level, name in enumerate(self.names)
]
return maybe_sequence_to_range(
[
level if name is None else name
for level, name in enumerate(self.names)
]
)
else:
return 0 if self.name is None else self.name
return range(1) if self.name is None else [self.name]

@final
def _mpl_repr(self) -> np.ndarray:
Expand Down Expand Up @@ -1630,8 +1633,11 @@ def to_frame(
from pandas import DataFrame

if name is lib.no_default:
name = self._get_level_names()
result = DataFrame({name: self}, copy=False)
result_name = self._get_level_names()
else:
result_name = Index([name]) # type: ignore[assignment]
result = DataFrame(self, copy=False)
result.columns = result_name

if index:
result.index = self
Expand Down
14 changes: 4 additions & 10 deletions pandas/core/internals/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1343,7 +1343,6 @@ def pad_or_backfill(
self,
*,
method: FillnaOptions,
axis: AxisInt = 0,
inplace: bool = False,
limit: int | None = None,
limit_area: Literal["inside", "outside"] | None = None,
Expand All @@ -1357,16 +1356,12 @@ def pad_or_backfill(
# Dispatch to the NumpyExtensionArray method.
# We know self.array_values is a NumpyExtensionArray bc EABlock overrides
vals = cast(NumpyExtensionArray, self.array_values)
if axis == 1:
vals = vals.T
new_values = vals._pad_or_backfill(
new_values = vals.T._pad_or_backfill(
method=method,
limit=limit,
limit_area=limit_area,
copy=copy,
)
if axis == 1:
new_values = new_values.T
).T

data = extract_array(new_values, extract_numpy=True)
return [self.make_block_same_class(data, refs=refs)]
Expand Down Expand Up @@ -1814,7 +1809,6 @@ def pad_or_backfill(
self,
*,
method: FillnaOptions,
axis: AxisInt = 0,
inplace: bool = False,
limit: int | None = None,
limit_area: Literal["inside", "outside"] | None = None,
Expand All @@ -1827,11 +1821,11 @@ def pad_or_backfill(
elif limit_area is not None:
raise NotImplementedError(
f"{type(values).__name__} does not implement limit_area "
"(added in pandas 2.2). 3rd-party ExtnsionArray authors "
"(added in pandas 2.2). 3rd-party ExtensionArray authors "
"need to add this argument to _pad_or_backfill."
)

if values.ndim == 2 and axis == 1:
if values.ndim == 2:
# NDArrayBackedExtensionArray.fillna assumes axis=0
new_values = values.T._pad_or_backfill(**kwargs).T
else:
Expand Down
18 changes: 6 additions & 12 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -5113,28 +5113,22 @@ def info(
)

@overload
def _replace_single(
self, to_replace, method: str, inplace: Literal[False], limit
) -> Self: ...
def _replace_single(self, to_replace, inplace: Literal[False]) -> Self: ...

@overload
def _replace_single(
self, to_replace, method: str, inplace: Literal[True], limit
) -> None: ...
def _replace_single(self, to_replace, inplace: Literal[True]) -> None: ...

@overload
def _replace_single(
self, to_replace, method: str, inplace: bool, limit
) -> Self | None: ...
def _replace_single(self, to_replace, inplace: bool) -> Self | None: ...

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

result = self if inplace else self.copy()

Expand Down
19 changes: 1 addition & 18 deletions pandas/core/shared_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,20 +429,11 @@
filled). Regular expressions, strings and lists or dicts of such
objects are also allowed.
{inplace}
limit : int, default None
Maximum size gap to forward or backward fill.
.. deprecated:: 2.1.0
regex : bool or same types as `to_replace`, default False
Whether to interpret `to_replace` and/or `value` as regular
expressions. Alternatively, this could be a regular expression or a
list, dict, or array of regular expressions in which case
`to_replace` must be ``None``.
method : {{'pad', 'ffill', 'bfill'}}
The method to use when for replacement, when `to_replace` is a
scalar, list or tuple and `value` is ``None``.
.. deprecated:: 2.1.0
Returns
-------
Expand Down Expand Up @@ -538,14 +529,6 @@
3 1 8 d
4 4 9 e
>>> s.replace([1, 2], method='bfill')
0 3
1 3
2 3
3 4
4 5
dtype: int64
**dict-like `to_replace`**
>>> df.replace({{0: 10, 1: 100}})
Expand Down Expand Up @@ -615,7 +598,7 @@
When one uses a dict as the `to_replace` value, it is like the
value(s) in the dict are equal to the `value` parameter.
``s.replace({{'a': None}})`` is equivalent to
``s.replace(to_replace={{'a': None}}, value=None, method=None)``:
``s.replace(to_replace={{'a': None}}, value=None)``:
>>> s.replace({{'a': None}})
0 10
Expand Down
42 changes: 0 additions & 42 deletions pandas/tests/frame/methods/test_replace.py
Original file line number Diff line number Diff line change
Expand Up @@ -1171,48 +1171,6 @@ def test_replace_with_empty_dictlike(self, mix_abc):
tm.assert_frame_equal(df, df.replace({"b": {}}))
tm.assert_frame_equal(df, df.replace(Series({"b": {}})))

@pytest.mark.parametrize(
"to_replace, method, expected",
[
(0, "bfill", {"A": [1, 1, 2], "B": [5, np.nan, 7], "C": ["a", "b", "c"]}),
(
np.nan,
"bfill",
{"A": [0, 1, 2], "B": [5.0, 7.0, 7.0], "C": ["a", "b", "c"]},
),
("d", "ffill", {"A": [0, 1, 2], "B": [5, np.nan, 7], "C": ["a", "b", "c"]}),
(
[0, 2],
"bfill",
{"A": [1, 1, 2], "B": [5, np.nan, 7], "C": ["a", "b", "c"]},
),
(
[1, 2],
"pad",
{"A": [0, 0, 0], "B": [5, np.nan, 7], "C": ["a", "b", "c"]},
),
(
(1, 2),
"bfill",
{"A": [0, 2, 2], "B": [5, np.nan, 7], "C": ["a", "b", "c"]},
),
(
["b", "c"],
"ffill",
{"A": [0, 1, 2], "B": [5, np.nan, 7], "C": ["a", "a", "a"]},
),
],
)
def test_replace_method(self, to_replace, method, expected):
# GH 19632
df = DataFrame({"A": [0, 1, 2], "B": [5, np.nan, 7], "C": ["a", "b", "c"]})

msg = "The 'method' keyword in DataFrame.replace is deprecated"
with tm.assert_produces_warning(FutureWarning, match=msg):
result = df.replace(to_replace=to_replace, value=None, method=method)
expected = DataFrame(expected)
tm.assert_frame_equal(result, expected)

@pytest.mark.parametrize(
"replace_dict, final_data",
[({"a": 1, "b": 1}, [[3, 3], [2, 2]]), ({"a": 1, "b": 2}, [[3, 1], [2, 3]])],
Expand Down
Loading

0 comments on commit b48abb2

Please sign in to comment.