From d991d9e811745461ea75486125ffafb62cc9203c Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Mon, 24 Nov 2025 23:35:50 +0100 Subject: [PATCH 1/5] PDEP-8 inplace: return self for inplace=True in methods that will keep the keyword --- pandas/core/frame.py | 18 +- pandas/core/generic.py | 367 ++++------------------------------------- 2 files changed, 39 insertions(+), 346 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index c8c246434f6d8..2e1f89932ccc4 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -6054,19 +6054,9 @@ def pop(self, item: Hashable) -> Series: """ return super().pop(item=item) - @overload - def _replace_columnwise( - self, mapping: dict[Hashable, tuple[Any, Any]], inplace: Literal[True], regex - ) -> None: ... - - @overload - def _replace_columnwise( - self, mapping: dict[Hashable, tuple[Any, Any]], inplace: Literal[False], regex - ) -> Self: ... - def _replace_columnwise( self, mapping: dict[Hashable, tuple[Any, Any]], inplace: bool, regex - ) -> Self | None: + ) -> Self: """ Dispatch to Series.replace column-wise. @@ -6079,7 +6069,7 @@ def _replace_columnwise( Returns ------- - DataFrame or None + DataFrame """ # Operate column-wise res = self if inplace else self.copy(deep=False) @@ -6094,9 +6084,7 @@ def _replace_columnwise( res._iset_item(i, newobj, inplace=inplace) - if inplace: - return None - return res.__finalize__(self) + return res if inplace else res.__finalize__(self) @doc(NDFrame.shift, klass=_shared_doc_kwargs["klass"]) def shift( diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 940231233e308..3a7320bf886b6 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -219,7 +219,7 @@ "axes_single_arg": "{0 or 'index'} for Series, {0 or 'index', 1 or 'columns'} for DataFrame", # noqa: E501 "inplace": """ inplace : bool, default False - If True, performs operation inplace and returns None.""", + If True, performs operation inplace.""", "optional_by": """ by : str or list of str Name or list of names to sort by""", @@ -6931,40 +6931,11 @@ def _pad_or_backfill( ) result = self._constructor_from_mgr(new_mgr, axes=new_mgr.axes) if inplace: - return self._update_inplace(result) + self._update_inplace(result) + return self else: return result.__finalize__(self, method="fillna") - @overload - def fillna( - self, - value: Hashable | Mapping | Series | DataFrame, - *, - axis: Axis | None = ..., - inplace: Literal[False] = ..., - limit: int | None = ..., - ) -> Self: ... - - @overload - def fillna( - self, - value: Hashable | Mapping | Series | DataFrame, - *, - axis: Axis | None = ..., - inplace: Literal[True], - limit: int | None = ..., - ) -> None: ... - - @overload - def fillna( - self, - value: Hashable | Mapping | Series | DataFrame, - *, - axis: Axis | None = ..., - inplace: bool = ..., - limit: int | None = ..., - ) -> Self | None: ... - @final @doc( klass=_shared_doc_kwargs["klass"], @@ -7002,8 +6973,8 @@ def fillna( Returns ------- - {klass} or None - Object with missing values filled or None if ``inplace=True``. + {klass} + Object with missing values filled. See Also -------- @@ -7105,9 +7076,7 @@ def fillna( if isinstance(value, (dict, ABCSeries)): if not len(value): # test_fillna_nonscalar - if inplace: - return None - return self.copy(deep=False) + return self if inplace else self.copy(deep=False) from pandas import Series value = Series(value) @@ -7188,10 +7157,7 @@ def fillna( result.iloc[:, loc] = res_loc else: result.isetitem(loc, res_loc) - if inplace: - return self._update_inplace(result) - else: - return result + return result elif not is_list_like(value): if axis == 1: @@ -7206,40 +7172,11 @@ def fillna( result = self._constructor_from_mgr(new_data, axes=new_data.axes) if inplace: - return self._update_inplace(result) + self._update_inplace(result) + return self else: return result.__finalize__(self, method="fillna") - @overload - def ffill( - self, - *, - axis: None | Axis = ..., - inplace: Literal[False] = ..., - limit: None | int = ..., - limit_area: Literal["inside", "outside"] | None = ..., - ) -> Self: ... - - @overload - def ffill( - self, - *, - axis: None | Axis = ..., - inplace: Literal[True], - limit: None | int = ..., - limit_area: Literal["inside", "outside"] | None = ..., - ) -> None: ... - - @overload - def ffill( - self, - *, - axis: None | Axis = ..., - inplace: bool = ..., - limit: None | int = ..., - limit_area: Literal["inside", "outside"] | None = ..., - ) -> Self | None: ... - @final @doc( klass=_shared_doc_kwargs["klass"], @@ -7252,7 +7189,7 @@ def ffill( inplace: bool = False, limit: None | int = None, limit_area: Literal["inside", "outside"] | None = None, - ) -> Self | None: + ) -> Self: """ Fill NA/NaN values by propagating the last valid observation to next valid. @@ -7285,8 +7222,8 @@ def ffill( Returns ------- - {klass} or None - Object with missing values filled or None if ``inplace=True``. + {klass} + Object with missing values filled. See Also -------- @@ -7344,35 +7281,6 @@ def ffill( limit_area=limit_area, ) - @overload - def bfill( - self, - *, - axis: None | Axis = ..., - inplace: Literal[False] = ..., - limit: None | int = ..., - limit_area: Literal["inside", "outside"] | None = ..., - ) -> Self: ... - - @overload - def bfill( - self, - *, - axis: None | Axis = ..., - inplace: Literal[True], - limit: None | int = ..., - ) -> None: ... - - @overload - def bfill( - self, - *, - axis: None | Axis = ..., - inplace: bool = ..., - limit: None | int = ..., - limit_area: Literal["inside", "outside"] | None = ..., - ) -> Self | None: ... - @final @doc( klass=_shared_doc_kwargs["klass"], @@ -7385,7 +7293,7 @@ def bfill( inplace: bool = False, limit: None | int = None, limit_area: Literal["inside", "outside"] | None = None, - ) -> Self | None: + ) -> Self: """ Fill NA/NaN values by using the next valid observation to fill the gap. @@ -7418,8 +7326,8 @@ def bfill( Returns ------- - {klass} or None - Object with missing values filled or None if ``inplace=True``. + {klass} + Object with missing values filled. See Also -------- @@ -7484,36 +7392,6 @@ def bfill( limit_area=limit_area, ) - @overload - def replace( - self, - to_replace=..., - value=..., - *, - inplace: Literal[False] = ..., - regex: bool = ..., - ) -> Self: ... - - @overload - def replace( - self, - to_replace=..., - value=..., - *, - inplace: Literal[True], - regex: bool = ..., - ) -> None: ... - - @overload - def replace( - self, - to_replace=..., - value=..., - *, - inplace: bool = ..., - regex: bool = ..., - ) -> Self | None: ... - @final @doc( _shared_docs["replace"], @@ -7527,7 +7405,7 @@ def replace( *, inplace: bool = False, regex: bool = False, - ) -> Self | None: + ) -> Self: if not is_bool(regex) and to_replace is not None: raise ValueError("'to_replace' must be 'None' if 'regex' is not a bool") @@ -7605,9 +7483,7 @@ def replace( else: # need a non-zero len on all axes if not self.size: - if inplace: - return None - return self.copy(deep=False) + return self if inplace else self.copy(deep=False) if is_dict_like(to_replace): if is_dict_like(value): # {'A' : NA} -> {'A' : 0} if isinstance(self, ABCSeries): @@ -7701,49 +7577,11 @@ def replace( result = self._constructor_from_mgr(new_data, axes=new_data.axes) if inplace: - return self._update_inplace(result) + self._update_inplace(result) + return self else: return result.__finalize__(self, method="replace") - @overload - def interpolate( - self, - method: InterpolateOptions = ..., - *, - axis: Axis = ..., - limit: int | None = ..., - inplace: Literal[False] = ..., - limit_direction: Literal["forward", "backward", "both"] | None = ..., - limit_area: Literal["inside", "outside"] | None = ..., - **kwargs, - ) -> Self: ... - - @overload - def interpolate( - self, - method: InterpolateOptions = ..., - *, - axis: Axis = ..., - limit: int | None = ..., - inplace: Literal[True], - limit_direction: Literal["forward", "backward", "both"] | None = ..., - limit_area: Literal["inside", "outside"] | None = ..., - **kwargs, - ) -> None: ... - - @overload - def interpolate( - self, - method: InterpolateOptions = ..., - *, - axis: Axis = ..., - limit: int | None = ..., - inplace: bool = ..., - limit_direction: Literal["forward", "backward", "both"] | None = ..., - limit_area: Literal["inside", "outside"] | None = ..., - **kwargs, - ) -> Self | None: ... - @final def interpolate( self, @@ -7755,7 +7593,7 @@ def interpolate( limit_direction: Literal["forward", "backward", "both"] | None = None, limit_area: Literal["inside", "outside"] | None = None, **kwargs, - ) -> Self | None: + ) -> Self: """ Fill NaN values using an interpolation method. @@ -7816,9 +7654,9 @@ def interpolate( Returns ------- - Series or DataFrame or None + Series or DataFrame Returns the same object type as the caller, interpolated at - some or all ``NaN`` values or None if ``inplace=True``. + some or all ``NaN`` values. See Also -------- @@ -7927,9 +7765,7 @@ def interpolate( axis = self._get_axis_number(axis) if self.empty: - if inplace: - return None - return self.copy() + return self if inplace else self.copy() if not isinstance(method, str): raise ValueError("'method' should be a string, not None.") @@ -7958,7 +7794,8 @@ def interpolate( if should_transpose: result = result.T if inplace: - return self._update_inplace(result) + self._update_inplace(result) + return self else: return result.__finalize__(self, method="interpolate") @@ -8379,7 +8216,6 @@ def _clip_with_scalar(self, lower, upper, inplace: bool = False): result = result.where(cond, lower, inplace=inplace) # type: ignore[assignment] if upper is not None: cond = mask | (self <= upper) - result = self if inplace else result result = result.where(cond, upper, inplace=inplace) # type: ignore[assignment] return result @@ -8417,39 +8253,6 @@ def _clip_with_one_bound(self, threshold, method, axis, inplace): # GH 40420 return self.where(subset, threshold, axis=axis, inplace=inplace) - @overload - def clip( - self, - lower=..., - upper=..., - *, - axis: Axis | None = ..., - inplace: Literal[False] = ..., - **kwargs, - ) -> Self: ... - - @overload - def clip( - self, - lower=..., - upper=..., - *, - axis: Axis | None = ..., - inplace: Literal[True], - **kwargs, - ) -> None: ... - - @overload - def clip( - self, - lower=..., - upper=..., - *, - axis: Axis | None = ..., - inplace: bool = ..., - **kwargs, - ) -> Self | None: ... - @final def clip( self, @@ -8459,7 +8262,7 @@ def clip( axis: Axis | None = None, inplace: bool = False, **kwargs, - ) -> Self | None: + ) -> Self: """ Trim values at input threshold(s). @@ -8488,9 +8291,9 @@ def clip( Returns ------- - Series or DataFrame or None + Series or DataFrame Same type as calling object with the values outside the - clip boundaries replaced or None if ``inplace=True``. + clip boundaries replaced. See Also -------- @@ -9808,39 +9611,6 @@ def _align_series( return left, right, join_index - @overload - def _where( - self, - cond, - other=..., - *, - inplace: Literal[False] = ..., - axis: Axis | None = ..., - level=..., - ) -> Self: ... - - @overload - def _where( - self, - cond, - other=..., - *, - inplace: Literal[True], - axis: Axis | None = ..., - level=..., - ) -> None: ... - - @overload - def _where( - self, - cond, - other=..., - *, - inplace: bool, - axis: Axis | None = ..., - level=..., - ) -> Self | None: ... - @final def _where( self, @@ -9850,7 +9620,7 @@ def _where( inplace: bool = False, axis: Axis | None = None, level=None, - ) -> Self | None: + ) -> Self: """ Equivalent to public method `where`, except that `other` is not applied as a function even if callable. Used in __setitem__. @@ -9957,7 +9727,8 @@ def _where( res.index = self.index res.columns = self.columns if inplace: - return self._update_inplace(res) + self._update_inplace(res) + return self return res.__finalize__(self) # slice me out of the other @@ -10001,7 +9772,8 @@ def _where( new_data = self._mgr.putmask(mask=cond, new=other, align=align) result = self._constructor_from_mgr(new_data, axes=new_data.axes) - return self._update_inplace(result) + self._update_inplace(result) + return self else: new_data = self._mgr.where( @@ -10012,39 +9784,6 @@ def _where( result = self._constructor_from_mgr(new_data, axes=new_data.axes) return result.__finalize__(self) - @overload - def where( - self, - cond, - other=..., - *, - inplace: Literal[False] = ..., - axis: Axis | None = ..., - level: Level = ..., - ) -> Self: ... - - @overload - def where( - self, - cond, - other=..., - *, - inplace: Literal[True], - axis: Axis | None = ..., - level: Level = ..., - ) -> None: ... - - @overload - def where( - self, - cond, - other=..., - *, - inplace: bool = ..., - axis: Axis | None = ..., - level: Level = ..., - ) -> Self | None: ... - @final @doc( klass=_shared_doc_kwargs["klass"], @@ -10061,7 +9800,7 @@ def where( inplace: bool = False, axis: Axis | None = None, level: Level | None = None, - ) -> Self | None: + ) -> Self: """ Replace values where the condition is {cond_rev}. @@ -10092,10 +9831,9 @@ def where( Returns ------- - Series or DataFrame or None + Series or DataFrame When applied to a Series, the function will return a Series, and when applied to a DataFrame, it will return a DataFrame; - if ``inplace=True``, it will return None. See Also -------- @@ -10217,39 +9955,6 @@ def where( other = common.apply_if_callable(other, self) return self._where(cond, other, inplace=inplace, axis=axis, level=level) - @overload - def mask( - self, - cond, - other=..., - *, - inplace: Literal[False] = ..., - axis: Axis | None = ..., - level: Level = ..., - ) -> Self: ... - - @overload - def mask( - self, - cond, - other=..., - *, - inplace: Literal[True], - axis: Axis | None = ..., - level: Level = ..., - ) -> None: ... - - @overload - def mask( - self, - cond, - other=..., - *, - inplace: bool = ..., - axis: Axis | None = ..., - level: Level = ..., - ) -> Self | None: ... - @final @doc( where, @@ -10267,7 +9972,7 @@ def mask( inplace: bool = False, axis: Axis | None = None, level: Level | None = None, - ) -> Self | None: + ) -> Self: inplace = validate_bool_kwarg(inplace, "inplace") if inplace: if not CHAINED_WARNING_DISABLED_INPLACE_METHOD: From 0fe1d1a7e1fecd18789aa79adc46f5e1ae383987 Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Tue, 25 Nov 2025 09:47:19 +0100 Subject: [PATCH 2/5] update tests --- pandas/core/generic.py | 3 + pandas/tests/frame/indexing/test_mask.py | 8 +- pandas/tests/frame/indexing/test_where.py | 115 ++++++++++-------- pandas/tests/frame/methods/test_clip.py | 8 +- pandas/tests/frame/methods/test_fillna.py | 20 +-- .../tests/frame/methods/test_interpolate.py | 33 ++--- pandas/tests/frame/methods/test_replace.py | 111 ++++++++--------- pandas/tests/frame/test_api.py | 30 +++-- .../series/accessors/test_dt_accessor.py | 4 +- pandas/tests/series/indexing/test_where.py | 6 +- pandas/tests/series/methods/test_clip.py | 2 +- pandas/tests/series/methods/test_fillna.py | 12 +- pandas/tests/series/methods/test_replace.py | 34 +++--- 13 files changed, 205 insertions(+), 181 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 3a7320bf886b6..45ead977ec3c9 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -7112,6 +7112,9 @@ def fillna( f"{value} not a suitable type to fill into {frame_dtype}" ) result = result.T.fillna(value=value).T + if inplace: + self._update_inplace(result) + result = self else: for k, v in value.items(): if k not in result: diff --git a/pandas/tests/frame/indexing/test_mask.py b/pandas/tests/frame/indexing/test_mask.py index e4036efeab7ff..f52bdd0a430ac 100644 --- a/pandas/tests/frame/indexing/test_mask.py +++ b/pandas/tests/frame/indexing/test_mask.py @@ -45,14 +45,14 @@ def test_mask_inplace(self): rdf = df.copy() - return_value = rdf.where(cond, inplace=True) - assert return_value is None + result = rdf.where(cond, inplace=True) + assert result is rdf tm.assert_frame_equal(rdf, df.where(cond)) tm.assert_frame_equal(rdf, df.mask(~cond)) rdf = df.copy() - return_value = rdf.where(cond, -df, inplace=True) - assert return_value is None + result = rdf.where(cond, -df, inplace=True) + assert result is rdf tm.assert_frame_equal(rdf, df.where(cond, -df)) tm.assert_frame_equal(rdf, df.mask(~cond, -df)) diff --git a/pandas/tests/frame/indexing/test_where.py b/pandas/tests/frame/indexing/test_where.py index 01dcce34e9fd2..bd57166ac10da 100644 --- a/pandas/tests/frame/indexing/test_where.py +++ b/pandas/tests/frame/indexing/test_where.py @@ -184,8 +184,8 @@ def _check_set(df, cond, check_dtypes=True): econd = cond.reindex_like(df).fillna(True).infer_objects() expected = dfi.mask(~econd) - return_value = dfi.where(cond, np.nan, inplace=True) - assert return_value is None + result = dfi.where(cond, np.nan, inplace=True) + assert result is dfi tm.assert_frame_equal(dfi, expected) # dtypes (and confirm upcasts)x @@ -320,24 +320,27 @@ def test_where_ndframe_align(self): def test_where_bug(self): # see gh-2793 - df = DataFrame( + df_orig = DataFrame( {"a": [1.0, 2.0, 3.0, 4.0], "b": [4.0, 3.0, 2.0, 1.0]}, dtype="float64" ) expected = DataFrame( {"a": [np.nan, np.nan, 3.0, 4.0], "b": [4.0, 3.0, np.nan, np.nan]}, dtype="float64", ) + + df = df_orig.copy() result = df.where(df > 2, np.nan) tm.assert_frame_equal(result, expected) + tm.assert_frame_equal(df, df_orig) - result = df.copy() - return_value = result.where(result > 2, np.nan, inplace=True) - assert return_value is None - tm.assert_frame_equal(result, expected) + df = df_orig.copy() + result = df.where(df > 2, np.nan, inplace=True) + assert result is df + tm.assert_frame_equal(df, expected) def test_where_bug_mixed(self, any_signed_int_numpy_dtype): # see gh-2793 - df = DataFrame( + df_orig = DataFrame( { "a": np.array([1, 2, 3, 4], dtype=any_signed_int_numpy_dtype), "b": np.array([4.0, 3.0, 2.0, 1.0], dtype="float64"), @@ -348,13 +351,15 @@ def test_where_bug_mixed(self, any_signed_int_numpy_dtype): {"a": [-1, -1, 3, 4], "b": [4.0, 3.0, -1, -1]}, ).astype({"a": any_signed_int_numpy_dtype, "b": "float64"}) + df = df_orig.copy() result = df.where(df > 2, -1) tm.assert_frame_equal(result, expected) + tm.assert_frame_equal(df, df_orig) - result = df.copy() - return_value = result.where(result > 2, -1, inplace=True) - assert return_value is None - tm.assert_frame_equal(result, expected) + df = df_orig.copy() + result = df.where(df > 2, -1, inplace=True) + assert result is df + tm.assert_frame_equal(df, expected) def test_where_bug_transposition(self): # see gh-7506 @@ -461,8 +466,8 @@ def create(): result = df.where(pd.notna(df), df.mean(), axis="columns") tm.assert_frame_equal(result, expected) - return_value = df.where(pd.notna(df), df.mean(), inplace=True, axis="columns") - assert return_value is None + result = df.where(pd.notna(df), df.mean(), inplace=True, axis="columns") + assert result is df tm.assert_frame_equal(df, expected) df = create().fillna(0) @@ -489,27 +494,30 @@ def test_where_complex(self): def test_where_axis(self): # GH 9736 - df = DataFrame(np.random.default_rng(2).standard_normal((2, 2))) + df_orig = DataFrame(np.random.default_rng(2).standard_normal((2, 2))) mask = DataFrame([[False, False], [False, False]]) ser = Series([0, 1]) + df = df_orig.copy() expected = DataFrame([[0, 0], [1, 1]], dtype="float64") result = df.where(mask, ser, axis="index") tm.assert_frame_equal(result, expected) - result = df.copy() - return_value = result.where(mask, ser, axis="index", inplace=True) - assert return_value is None + df = df_orig.copy() + result = df.where(mask, ser, axis="index", inplace=True) + assert result is df tm.assert_frame_equal(result, expected) + df = df_orig.copy() expected = DataFrame([[0, 1], [0, 1]], dtype="float64") result = df.where(mask, ser, axis="columns") tm.assert_frame_equal(result, expected) + df = df_orig.copy() result = df.copy() - return_value = result.where(mask, ser, axis="columns", inplace=True) - assert return_value is None - tm.assert_frame_equal(result, expected) + result = df.where(mask, ser, axis="columns", inplace=True) + assert result is df + tm.assert_frame_equal(df, expected) def test_where_axis_with_upcast(self): # Upcast needed @@ -534,7 +542,7 @@ def test_where_axis_with_upcast(self): def test_where_axis_multiple_dtypes(self): # Multiple dtypes (=> multiple Blocks) - df = pd.concat( + df_orig = pd.concat( [ DataFrame(np.random.default_rng(2).standard_normal((10, 2))), DataFrame( @@ -545,64 +553,68 @@ def test_where_axis_multiple_dtypes(self): ignore_index=True, axis=1, ) - mask = DataFrame(False, columns=df.columns, index=df.index) - s1 = Series(1, index=df.columns) - s2 = Series(2, index=df.index) + mask = DataFrame(False, columns=df_orig.columns, index=df_orig.index) + s1 = Series(1, index=df_orig.columns) + s2 = Series(2, index=df_orig.index) + df = df_orig.copy() result = df.where(mask, s1, axis="columns") - expected = DataFrame(1.0, columns=df.columns, index=df.index) + expected = DataFrame(1.0, columns=df_orig.columns, index=df_orig.index) expected[2] = expected[2].astype("int64") expected[3] = expected[3].astype("int64") tm.assert_frame_equal(result, expected) - result = df.copy() - return_value = result.where(mask, s1, axis="columns", inplace=True) - assert return_value is None - tm.assert_frame_equal(result, expected) + df = df_orig.copy() + result = df.where(mask, s1, axis="columns", inplace=True) + assert result is df + tm.assert_frame_equal(df, expected) + df = df_orig.copy() result = df.where(mask, s2, axis="index") - expected = DataFrame(2.0, columns=df.columns, index=df.index) + expected = DataFrame(2.0, columns=df_orig.columns, index=df_orig.index) expected[2] = expected[2].astype("int64") expected[3] = expected[3].astype("int64") tm.assert_frame_equal(result, expected) - result = df.copy() - return_value = result.where(mask, s2, axis="index", inplace=True) - assert return_value is None - tm.assert_frame_equal(result, expected) + df = df_orig.copy() + result = df.where(mask, s2, axis="index", inplace=True) + assert result is df + tm.assert_frame_equal(df, expected) # DataFrame vs DataFrame - d1 = df.copy().drop(1, axis=0) + d1 = df_orig.copy().drop(1, axis=0) # Explicit cast to avoid implicit cast when setting value to np.nan - expected = df.copy().astype("float") + expected = df_orig.copy().astype("float") expected.loc[1, :] = np.nan + df = df_orig.copy() result = df.where(mask, d1) tm.assert_frame_equal(result, expected) result = df.where(mask, d1, axis="index") tm.assert_frame_equal(result, expected) - result = df.copy() + df = df_orig.copy() with pytest.raises(TypeError, match="Invalid value"): - result.where(mask, d1, inplace=True) + df.where(mask, d1, inplace=True) with pytest.raises(TypeError, match="Invalid value"): - return_value = result.where(mask, d1, inplace=True, axis="index") + df.where(mask, d1, inplace=True, axis="index") - d2 = df.copy().drop(1, axis=1) - expected = df.copy() + d2 = df_orig.copy().drop(1, axis=1) + expected = df_orig.copy() expected.loc[:, 1] = np.nan + df = df_orig.copy() result = df.where(mask, d2) tm.assert_frame_equal(result, expected) result = df.where(mask, d2, axis="columns") tm.assert_frame_equal(result, expected) - result = df.copy() - return_value = result.where(mask, d2, inplace=True) - assert return_value is None - tm.assert_frame_equal(result, expected) - result = df.copy() - return_value = result.where(mask, d2, inplace=True, axis="columns") - assert return_value is None - tm.assert_frame_equal(result, expected) + df = df_orig.copy() + result = df.where(mask, d2, inplace=True) + assert result is df + tm.assert_frame_equal(df, expected) + df = df_orig.copy() + result = df.where(mask, d2, inplace=True, axis="columns") + assert result is df + tm.assert_frame_equal(df, expected) def test_where_callable(self): # GH 12533 @@ -1047,6 +1059,7 @@ def test_where_inplace_no_other(): # GH#51685 df = DataFrame({"a": [1.0, 2.0], "b": ["x", "y"]}) cond = DataFrame({"a": [True, False], "b": [False, True]}) - df.where(cond, inplace=True) + result = df.where(cond, inplace=True) + assert result is df expected = DataFrame({"a": [1, np.nan], "b": [np.nan, "y"]}) tm.assert_frame_equal(df, expected) diff --git a/pandas/tests/frame/methods/test_clip.py b/pandas/tests/frame/methods/test_clip.py index 62a97271d208b..1fe8d4bf8dae9 100644 --- a/pandas/tests/frame/methods/test_clip.py +++ b/pandas/tests/frame/methods/test_clip.py @@ -24,8 +24,8 @@ def test_inplace_clip(self, float_frame): median = float_frame.median().median() frame_copy = float_frame.copy() - return_value = frame_copy.clip(upper=median, lower=median, inplace=True) - assert return_value is None + result = frame_copy.clip(upper=median, lower=median, inplace=True) + assert result is frame_copy assert not (frame_copy.values != median).any() def test_dataframe_clip(self): @@ -68,7 +68,7 @@ def test_clip_against_series(self, inplace): clipped_df = df.clip(lb, ub, axis=0, inplace=inplace) if inplace: - clipped_df = df + assert clipped_df is df for i in range(2): lb_mask = original.iloc[:, i] <= lb @@ -106,7 +106,7 @@ def test_clip_against_list_like(self, inplace, lower, axis, res): expected = DataFrame(res, columns=original.columns, index=original.index) if inplace: - result = original + assert result is original tm.assert_frame_equal(result, expected, check_exact=True) @pytest.mark.parametrize("axis", [0, 1, None]) diff --git a/pandas/tests/frame/methods/test_fillna.py b/pandas/tests/frame/methods/test_fillna.py index d229fe5aaaa84..3abd9572bebcd 100644 --- a/pandas/tests/frame/methods/test_fillna.py +++ b/pandas/tests/frame/methods/test_fillna.py @@ -103,9 +103,9 @@ def test_fillna_different_dtype(self): expected[2] = expected[2].astype("object") tm.assert_frame_equal(result, expected) - return_value = df.fillna({2: "foo"}, inplace=True) + result = df.fillna({2: "foo"}, inplace=True) + assert result is df tm.assert_frame_equal(df, expected) - assert return_value is None def test_fillna_limit_and_value(self): # limit and value @@ -423,11 +423,12 @@ def test_fillna_inplace(self): expected = df.fillna(value=0) assert expected is not df - df.fillna(value=0, inplace=True) + result = df.fillna(value=0, inplace=True) + assert result is df tm.assert_frame_equal(df, expected) - expected = df.fillna(value={0: 0}, inplace=True) - assert expected is None + result = df.fillna(value={0: 0}, inplace=True) + assert result is df df.loc[:4, 1] = np.nan df.loc[-4:, 3] = np.nan @@ -470,7 +471,8 @@ def test_fillna_dict_series_axis_1(self): } ) result = df.fillna(df.max(axis=1), axis=1) - df.fillna(df.max(axis=1), axis=1, inplace=True) + result = df.fillna(df.max(axis=1), axis=1, inplace=True) + assert result is df expected = DataFrame( { "a": [1.0, 1.0, 2.0, 3.0, 4.0], @@ -643,7 +645,8 @@ def test_fillna_inplace_with_columns_limit_and_value(self): expected = df.fillna(axis=1, value=100, limit=1) assert expected is not df - df.fillna(axis=1, value=100, limit=1, inplace=True) + result = df.fillna(axis=1, value=100, limit=1, inplace=True) + assert result is df tm.assert_frame_equal(df, expected) @pytest.mark.parametrize("val", [-1, {"x": -1, "y": -1}]) @@ -730,7 +733,8 @@ def test_fillna_nones_inplace(): [[None, None], [None, None]], columns=["A", "B"], ) - df.fillna(value={"A": 1, "B": 2}, inplace=True) + result = df.fillna(value={"A": 1, "B": 2}, inplace=True) + assert result is df expected = DataFrame([[1, 2], [1, 2]], columns=["A", "B"], dtype=object) tm.assert_frame_equal(df, expected) diff --git a/pandas/tests/frame/methods/test_interpolate.py b/pandas/tests/frame/methods/test_interpolate.py index f512ed3e4a0af..0d73de98abbf5 100644 --- a/pandas/tests/frame/methods/test_interpolate.py +++ b/pandas/tests/frame/methods/test_interpolate.py @@ -56,7 +56,8 @@ def test_interpolate_inplace(self, frame_or_series, request): obj = frame_or_series([1, np.nan, 2]) orig = obj.values - obj.interpolate(inplace=True) + result = obj.interpolate(inplace=True) + assert result is obj expected = frame_or_series([1, 1.5, 2]) tm.assert_equal(obj, expected) @@ -307,22 +308,22 @@ def test_interp_raise_on_all_object_dtype(self): def test_interp_inplace(self): df = DataFrame({"a": [1.0, 2.0, np.nan, 4.0]}) - expected = df.copy() - result = df.copy() + df_orig = df.copy() + expected = df.copy().interpolate() with tm.raises_chained_assignment_error(inplace_method=True): - return_value = result["a"].interpolate(inplace=True) - assert return_value is None - tm.assert_frame_equal(result, expected) + result = df["a"].interpolate(inplace=True) + tm.assert_series_equal(result, expected["a"]) + tm.assert_frame_equal(df, df_orig) def test_interp_inplace_row(self): # GH 10395 - result = DataFrame( + df = DataFrame( {"a": [1.0, 2.0, 3.0, 4.0], "b": [np.nan, 2.0, 3.0, 4.0], "c": [3, 2, 2, 2]} ) - expected = result.interpolate(method="linear", axis=1, inplace=False) - return_value = result.interpolate(method="linear", axis=1, inplace=True) - assert return_value is None + expected = df.interpolate(method="linear", axis=1, inplace=False) + result = df.interpolate(method="linear", axis=1, inplace=True) + assert result is df tm.assert_frame_equal(result, expected) def test_interp_ignore_all_good(self): @@ -356,11 +357,11 @@ def test_interp_time_inplace_axis(self): idx = date_range(start="2014-01-01", periods=periods) data = np.random.default_rng(2).random((periods, periods)) data[data < 0.5] = np.nan - expected = DataFrame(index=idx, columns=idx, data=data) + df = DataFrame(index=idx, columns=idx, data=data) - result = expected.interpolate(axis=0, method="time") - return_value = expected.interpolate(axis=0, method="time", inplace=True) - assert return_value is None + expected = df.interpolate(axis=0, method="time") + result = df.interpolate(axis=0, method="time", inplace=True) + assert result is df tm.assert_frame_equal(result, expected) @pytest.mark.parametrize("axis_name, axis_number", [("index", 0), ("columns", 1)]) @@ -399,8 +400,8 @@ def test_interpolate_empty_df(self): df = DataFrame() expected = df.copy() result = df.interpolate(inplace=True) - assert result is None - tm.assert_frame_equal(df, expected) + assert result is df + tm.assert_frame_equal(result, expected) def test_interpolate_ea(self, any_int_ea_dtype): # GH#55347 diff --git a/pandas/tests/frame/methods/test_replace.py b/pandas/tests/frame/methods/test_replace.py index ff1113e4d5f95..17f2d9809cc88 100644 --- a/pandas/tests/frame/methods/test_replace.py +++ b/pandas/tests/frame/methods/test_replace.py @@ -35,8 +35,8 @@ def test_replace_inplace(self, datetime_frame, float_string_frame): datetime_frame.loc[datetime_frame.index[-5:], "A"] = np.nan tsframe = datetime_frame.copy() - return_value = tsframe.replace(np.nan, 0, inplace=True) - assert return_value is None + result = tsframe.replace(np.nan, 0, inplace=True) + assert result is tsframe tm.assert_frame_equal(tsframe, datetime_frame.fillna(0)) # mixed type @@ -51,8 +51,8 @@ def test_replace_inplace(self, datetime_frame, float_string_frame): tm.assert_frame_equal(result, expected) tsframe = datetime_frame.copy() - return_value = tsframe.replace([np.nan], [0], inplace=True) - assert return_value is None + result = tsframe.replace([np.nan], [0], inplace=True) + assert result is tsframe tm.assert_frame_equal(tsframe, datetime_frame.fillna(0)) @pytest.mark.parametrize( @@ -105,8 +105,7 @@ def test_regex_replace_list_obj( result = df.replace(to_replace, values, regex=True, inplace=inplace) if inplace: - assert result is None - result = df + assert result is df expected = DataFrame(expected) tm.assert_frame_equal(result, expected) @@ -160,8 +159,8 @@ def test_regex_replace_list_mixed_inplace(self, mix_ab): to_replace_res = [r"\s*\.\s*", r"a"] values = [np.nan, "crap"] res = dfmix.copy() - return_value = res.replace(to_replace_res, values, inplace=True, regex=True) - assert return_value is None + result = res.replace(to_replace_res, values, inplace=True, regex=True) + assert result is res expec = DataFrame({"a": mix_ab["a"], "b": ["crap", "b", np.nan, np.nan]}) tm.assert_frame_equal(res, expec) @@ -169,8 +168,8 @@ def test_regex_replace_list_mixed_inplace(self, mix_ab): to_replace_res = [r"\s*(\.)\s*", r"(a|b)"] values = [r"\1\1", r"\1_crap"] res = dfmix.copy() - return_value = res.replace(to_replace_res, values, inplace=True, regex=True) - assert return_value is None + result = res.replace(to_replace_res, values, inplace=True, regex=True) + assert result is res expec = DataFrame({"a": mix_ab["a"], "b": ["a_crap", "b_crap", "..", ".."]}) tm.assert_frame_equal(res, expec) @@ -179,16 +178,16 @@ def test_regex_replace_list_mixed_inplace(self, mix_ab): to_replace_res = [r"\s*(\.)\s*", r"a", r"(b)"] values = [r"\1\1", r"crap", r"\1_crap"] res = dfmix.copy() - return_value = res.replace(to_replace_res, values, inplace=True, regex=True) - assert return_value is None + result = res.replace(to_replace_res, values, inplace=True, regex=True) + assert result is res expec = DataFrame({"a": mix_ab["a"], "b": ["crap", "b_crap", "..", ".."]}) tm.assert_frame_equal(res, expec) to_replace_res = [r"\s*(\.)\s*", r"a", r"(b)"] values = [r"\1\1", r"crap", r"\1_crap"] res = dfmix.copy() - return_value = res.replace(regex=to_replace_res, value=values, inplace=True) - assert return_value is None + result = res.replace(regex=to_replace_res, value=values, inplace=True) + assert result is res expec = DataFrame({"a": mix_ab["a"], "b": ["crap", "b_crap", "..", ".."]}) tm.assert_frame_equal(res, expec) @@ -203,10 +202,10 @@ def test_regex_replace_dict_mixed(self, mix_abc): # frame res = dfmix.replace({"b": r"\s*\.\s*"}, {"b": np.nan}, regex=True) res2 = dfmix.copy() - return_value = res2.replace( + result = res2.replace( {"b": r"\s*\.\s*"}, {"b": np.nan}, inplace=True, regex=True ) - assert return_value is None + assert result is res2 expec = DataFrame( {"a": mix_abc["a"], "b": ["a", "b", np.nan, np.nan], "c": mix_abc["c"]} ) @@ -217,10 +216,10 @@ def test_regex_replace_dict_mixed(self, mix_abc): # whole frame res = dfmix.replace({"b": r"\s*(\.)\s*"}, {"b": r"\1ty"}, regex=True) res2 = dfmix.copy() - return_value = res2.replace( + result = res2.replace( {"b": r"\s*(\.)\s*"}, {"b": r"\1ty"}, inplace=True, regex=True ) - assert return_value is None + assert result is res2 expec = DataFrame( {"a": mix_abc["a"], "b": ["a", "b", ".ty", ".ty"], "c": mix_abc["c"]} ) @@ -229,10 +228,10 @@ def test_regex_replace_dict_mixed(self, mix_abc): res = dfmix.replace(regex={"b": r"\s*(\.)\s*"}, value={"b": r"\1ty"}) res2 = dfmix.copy() - return_value = res2.replace( + result = res2.replace( regex={"b": r"\s*(\.)\s*"}, value={"b": r"\1ty"}, inplace=True ) - assert return_value is None + assert result is res2 expec = DataFrame( {"a": mix_abc["a"], "b": ["a", "b", ".ty", ".ty"], "c": mix_abc["c"]} ) @@ -246,15 +245,15 @@ def test_regex_replace_dict_mixed(self, mix_abc): ) res = dfmix.replace("a", {"b": np.nan}, regex=True) res2 = dfmix.copy() - return_value = res2.replace("a", {"b": np.nan}, regex=True, inplace=True) - assert return_value is None + result = res2.replace("a", {"b": np.nan}, regex=True, inplace=True) + assert result is res2 tm.assert_frame_equal(res, expec) tm.assert_frame_equal(res2, expec) res = dfmix.replace("a", {"b": np.nan}, regex=True) res2 = dfmix.copy() - return_value = res2.replace(regex="a", value={"b": np.nan}, inplace=True) - assert return_value is None + result = res2.replace(regex="a", value={"b": np.nan}, inplace=True) + assert result is res2 expec = DataFrame( {"a": mix_abc["a"], "b": [np.nan, "b", ".", "."], "c": mix_abc["c"]} ) @@ -267,13 +266,11 @@ def test_regex_replace_dict_nested(self, mix_abc): res = dfmix.replace({"b": {r"\s*\.\s*": np.nan}}, regex=True) res2 = dfmix.copy() res4 = dfmix.copy() - return_value = res2.replace( - {"b": {r"\s*\.\s*": np.nan}}, inplace=True, regex=True - ) - assert return_value is None + result = res2.replace({"b": {r"\s*\.\s*": np.nan}}, inplace=True, regex=True) + assert result is res2 res3 = dfmix.replace(regex={"b": {r"\s*\.\s*": np.nan}}) - return_value = res4.replace(regex={"b": {r"\s*\.\s*": np.nan}}, inplace=True) - assert return_value is None + result = res4.replace(regex={"b": {r"\s*\.\s*": np.nan}}, inplace=True) + assert result is res4 expec = DataFrame( {"a": mix_abc["a"], "b": ["a", "b", np.nan, np.nan], "c": mix_abc["c"]} ) @@ -313,14 +310,10 @@ def test_regex_replace_list_to_scalar(self, mix_abc): res = df.replace([r"\s*\.\s*", "a|b"], np.nan, regex=True) res2 = df.copy() res3 = df.copy() - return_value = res2.replace( - [r"\s*\.\s*", "a|b"], np.nan, regex=True, inplace=True - ) - assert return_value is None - return_value = res3.replace( - regex=[r"\s*\.\s*", "a|b"], value=np.nan, inplace=True - ) - assert return_value is None + result = res2.replace([r"\s*\.\s*", "a|b"], np.nan, regex=True, inplace=True) + assert result is res2 + result = res3.replace(regex=[r"\s*\.\s*", "a|b"], value=np.nan, inplace=True) + assert result is res3 tm.assert_frame_equal(res, expec) tm.assert_frame_equal(res2, expec) tm.assert_frame_equal(res3, expec) @@ -330,11 +323,11 @@ def test_regex_replace_str_to_numeric(self, mix_abc): df = DataFrame(mix_abc) res = df.replace(r"\s*\.\s*", 0, regex=True) res2 = df.copy() - return_value = res2.replace(r"\s*\.\s*", 0, inplace=True, regex=True) - assert return_value is None + result = res2.replace(r"\s*\.\s*", 0, inplace=True, regex=True) + assert result is res2 res3 = df.copy() - return_value = res3.replace(regex=r"\s*\.\s*", value=0, inplace=True) - assert return_value is None + result = res3.replace(regex=r"\s*\.\s*", value=0, inplace=True) + assert result is res3 expec = DataFrame({"a": mix_abc["a"], "b": ["a", "b", 0, 0], "c": mix_abc["c"]}) expec["c"] = expec["c"].astype(object) tm.assert_frame_equal(res, expec) @@ -345,11 +338,11 @@ def test_regex_replace_regex_list_to_numeric(self, mix_abc): df = DataFrame(mix_abc) res = df.replace([r"\s*\.\s*", "b"], 0, regex=True) res2 = df.copy() - return_value = res2.replace([r"\s*\.\s*", "b"], 0, regex=True, inplace=True) - assert return_value is None + result = res2.replace([r"\s*\.\s*", "b"], 0, regex=True, inplace=True) + assert result is res2 res3 = df.copy() - return_value = res3.replace(regex=[r"\s*\.\s*", "b"], value=0, inplace=True) - assert return_value is None + result = res3.replace(regex=[r"\s*\.\s*", "b"], value=0, inplace=True) + assert result is res3 expec = DataFrame( {"a": mix_abc["a"], "b": ["a", 0, 0, 0], "c": ["a", 0, np.nan, "d"]} ) @@ -363,11 +356,11 @@ def test_regex_replace_series_of_regexes(self, mix_abc): s2 = Series({"b": np.nan}) res = df.replace(s1, s2, regex=True) res2 = df.copy() - return_value = res2.replace(s1, s2, inplace=True, regex=True) - assert return_value is None + result = res2.replace(s1, s2, inplace=True, regex=True) + assert result is res2 res3 = df.copy() - return_value = res3.replace(regex=s1, value=s2, inplace=True) - assert return_value is None + result = res3.replace(regex=s1, value=s2, inplace=True) + assert result is res3 expec = DataFrame( {"a": mix_abc["a"], "b": ["a", "b", np.nan, np.nan], "c": mix_abc["c"]} ) @@ -582,8 +575,8 @@ def test_replace_mixed_int_block_upcasting(self): result = df.replace(0, 0.5) tm.assert_frame_equal(result, expected) - return_value = df.replace(0, 0.5, inplace=True) - assert return_value is None + result = df.replace(0, 0.5, inplace=True) + assert result is df tm.assert_frame_equal(df, expected) def test_replace_mixed_int_block_splitting(self): @@ -891,8 +884,8 @@ def test_replace_input_formats_listlike(self): result = df.replace(to_rep, values) expected = df.copy() for rep, value in zip(to_rep, values): - return_value = expected.replace(rep, value, inplace=True) - assert return_value is None + result = expected.replace(rep, value, inplace=True) + assert result is expected tm.assert_frame_equal(result, expected) msg = r"Replacement lists must match in length\. Expecting 3 got 2" @@ -919,8 +912,8 @@ def test_replace_input_formats_scalar(self): result = df.replace(to_rep, -1) expected = df.copy() for rep in to_rep: - return_value = expected.replace(rep, -1, inplace=True) - assert return_value is None + result = expected.replace(rep, -1, inplace=True) + assert result is expected tm.assert_frame_equal(result, expected) def test_replace_limit(self): @@ -1272,8 +1265,8 @@ def test_categorical_replace_with_dict(self, replace_dict, final_data): with pytest.raises(AssertionError, match=msg): # ensure non-inplace call does not affect original tm.assert_frame_equal(df, expected) - return_value = df.replace(replace_dict, 2, inplace=True) - assert return_value is None + result = df.replace(replace_dict, 2, inplace=True) + assert result is df tm.assert_frame_equal(df, expected) def test_replace_value_category_type(self): @@ -1508,7 +1501,7 @@ def test_regex_replace_scalar( result = df.replace(to_replace, value, inplace=inplace, regex=regex) if inplace: - assert result is None + assert result is df result = df if value is np.nan: diff --git a/pandas/tests/frame/test_api.py b/pandas/tests/frame/test_api.py index af61cd75c4540..f54e760552825 100644 --- a/pandas/tests/frame/test_api.py +++ b/pandas/tests/frame/test_api.py @@ -233,62 +233,66 @@ def test_inplace_return_self(self): {"a": ["foo", "bar", "baz", "qux"], "b": [0, 0, 1, 1], "c": [1, 2, 3, 4]} ) - def _check_f(base, f): + def _check_none(base, f): result = f(base) assert result is None + def _check_return(base, f): + result = f(base) + assert result is base + # -----DataFrame----- # set_index f = lambda x: x.set_index("a", inplace=True) - _check_f(data.copy(), f) + _check_none(data.copy(), f) # reset_index f = lambda x: x.reset_index(inplace=True) - _check_f(data.set_index("a"), f) + _check_none(data.set_index("a"), f) # drop_duplicates f = lambda x: x.drop_duplicates(inplace=True) - _check_f(data.copy(), f) + _check_none(data.copy(), f) # sort f = lambda x: x.sort_values("b", inplace=True) - _check_f(data.copy(), f) + _check_none(data.copy(), f) # sort_index f = lambda x: x.sort_index(inplace=True) - _check_f(data.copy(), f) + _check_none(data.copy(), f) # fillna f = lambda x: x.fillna(0, inplace=True) - _check_f(data.copy(), f) + _check_return(data.copy(), f) # replace f = lambda x: x.replace(1, 0, inplace=True) - _check_f(data.copy(), f) + _check_return(data.copy(), f) # rename f = lambda x: x.rename({1: "foo"}, inplace=True) - _check_f(data.copy(), f) + _check_none(data.copy(), f) # -----Series----- d = data.copy()["c"] # reset_index f = lambda x: x.reset_index(inplace=True, drop=True) - _check_f(data.set_index("a")["c"], f) + _check_none(data.set_index("a")["c"], f) # fillna f = lambda x: x.fillna(0, inplace=True) - _check_f(d.copy(), f) + _check_return(d.copy(), f) # replace f = lambda x: x.replace(1, 0, inplace=True) - _check_f(d.copy(), f) + _check_return(d.copy(), f) # rename f = lambda x: x.rename({1: "foo"}, inplace=True) - _check_f(d.copy(), f) + _check_none(d.copy(), f) def test_tab_complete_warning(self, ip, frame_or_series): # GH 16409 diff --git a/pandas/tests/series/accessors/test_dt_accessor.py b/pandas/tests/series/accessors/test_dt_accessor.py index 86d49dc519b6a..46353f0a48ab7 100644 --- a/pandas/tests/series/accessors/test_dt_accessor.py +++ b/pandas/tests/series/accessors/test_dt_accessor.py @@ -701,8 +701,8 @@ def test_dt_accessor_invalid(self, data): def test_dt_accessor_updates_on_inplace(self): ser = Series(date_range("2018-01-01", periods=10)) ser[2] = None - return_value = ser.fillna(pd.Timestamp("2018-01-01"), inplace=True) - assert return_value is None + result = ser.fillna(pd.Timestamp("2018-01-01"), inplace=True) + assert result is ser result = ser.dt.date assert result[0] == result[2] diff --git a/pandas/tests/series/indexing/test_where.py b/pandas/tests/series/indexing/test_where.py index 663ee8ad0ee38..ec8c15714bf82 100644 --- a/pandas/tests/series/indexing/test_where.py +++ b/pandas/tests/series/indexing/test_where.py @@ -326,12 +326,14 @@ def test_where_inplace(): rs = s.copy() - rs.where(cond, inplace=True) + result = rs.where(cond, inplace=True) + assert result is rs tm.assert_series_equal(rs.dropna(), s[cond]) tm.assert_series_equal(rs, s.where(cond)) rs = s.copy() - rs.where(cond, -s, inplace=True) + result = rs.where(cond, -s, inplace=True) + assert result is rs tm.assert_series_equal(rs, s.where(cond, -s)) diff --git a/pandas/tests/series/methods/test_clip.py b/pandas/tests/series/methods/test_clip.py index c1ee7f8c9e008..293f034ea322c 100644 --- a/pandas/tests/series/methods/test_clip.py +++ b/pandas/tests/series/methods/test_clip.py @@ -97,7 +97,7 @@ def test_clip_against_list_like(self, inplace, upper): expected = Series([1, 2, 3]) if inplace: - result = original + assert result is original tm.assert_series_equal(result, expected, check_exact=True) def test_clip_with_datetimes(self): diff --git a/pandas/tests/series/methods/test_fillna.py b/pandas/tests/series/methods/test_fillna.py index e3d091132dca3..b345840d61823 100644 --- a/pandas/tests/series/methods/test_fillna.py +++ b/pandas/tests/series/methods/test_fillna.py @@ -667,8 +667,8 @@ def test_fillna_numeric_inplace(self): x = Series([np.nan, 1.0, np.nan, 3.0, np.nan], ["z", "a", "b", "c", "d"]) y = x.copy() - return_value = y.fillna(value=0, inplace=True) - assert return_value is None + result = y.fillna(value=0, inplace=True) + assert result is y expected = x.fillna(value=0) tm.assert_series_equal(y, expected) @@ -872,8 +872,8 @@ def test_pad_nan(self): [np.nan, 1.0, np.nan, 3.0, np.nan], ["z", "a", "b", "c", "d"], dtype=float ) - return_value = x.ffill(inplace=True) - assert return_value is None + result = x.ffill(inplace=True) + assert result is x expected = Series( [np.nan, 1.0, 1.0, 3.0, 3.0], ["z", "a", "b", "c", "d"], dtype=float @@ -917,8 +917,8 @@ def test_series_pad_backfill_limit(self): def test_fillna_int(self): ser = Series(np.random.default_rng(2).integers(-100, 100, 50)) - return_value = ser.ffill(inplace=True) - assert return_value is None + result = ser.ffill(inplace=True) + assert result is ser tm.assert_series_equal(ser.ffill(inplace=False), ser) def test_datetime64tz_fillna_round_issue(self): diff --git a/pandas/tests/series/methods/test_replace.py b/pandas/tests/series/methods/test_replace.py index 5134ba445352b..a30acb7ca0f26 100644 --- a/pandas/tests/series/methods/test_replace.py +++ b/pandas/tests/series/methods/test_replace.py @@ -58,8 +58,8 @@ def test_replace(self): ser[6:10] = 0 # replace list with a single value - return_value = ser.replace([np.nan], -1, inplace=True) - assert return_value is None + result = ser.replace([np.nan], -1, inplace=True) + assert result is ser exp = ser.fillna(-1) tm.assert_series_equal(ser, exp) @@ -99,8 +99,8 @@ def test_replace(self): tm.assert_series_equal(rs, rs2) # replace inplace - return_value = ser.replace([np.nan, "foo", "bar"], -1, inplace=True) - assert return_value is None + result = ser.replace([np.nan, "foo", "bar"], -1, inplace=True) + assert result is ser assert (ser[:5] == -1).all() assert (ser[6:10] == -1).all() @@ -194,8 +194,8 @@ def test_replace_mixed_types(self): def check_replace(to_rep, val, expected): sc = ser.copy() result = ser.replace(to_rep, val) - return_value = sc.replace(to_rep, val, inplace=True) - assert return_value is None + result = sc.replace(to_rep, val, inplace=True) + assert result is sc tm.assert_series_equal(expected, result) tm.assert_series_equal(expected, sc) @@ -261,7 +261,8 @@ def test_replace_Int_with_na(self, any_int_ea_dtype): expected = pd.Series([pd.NA, pd.NA], dtype=any_int_ea_dtype) tm.assert_series_equal(result, expected) result = pd.Series([0, 1], dtype=any_int_ea_dtype).replace(0, pd.NA) - result.replace(1, pd.NA, inplace=True) + result2 = result.replace(1, pd.NA, inplace=True) + assert result2 is result tm.assert_series_equal(result, expected) def test_replace2(self): @@ -296,8 +297,8 @@ def test_replace2(self): tm.assert_series_equal(rs, rs2) # replace inplace - return_value = ser.replace([np.nan, "foo", "bar"], -1, inplace=True) - assert return_value is None + result = ser.replace([np.nan, "foo", "bar"], -1, inplace=True) + assert result is ser assert (ser[:5] == -1).all() assert (ser[6:10] == -1).all() assert (ser[20:30] == -1).all() @@ -381,10 +382,11 @@ def test_replace_categorical_inplace(self): # GH 53358 data = ["a", "b", "c"] data_exp = ["b", "b", "c"] - result = pd.Series(data, dtype="category") - result.replace(to_replace="a", value="b", inplace=True) + ser = pd.Series(data, dtype="category") + result = ser.replace(to_replace="a", value="b", inplace=True) + assert result is ser expected = pd.Series(pd.Categorical(data_exp, categories=data)) - tm.assert_series_equal(result, expected) + tm.assert_series_equal(ser, expected) def test_replace_categorical_single(self): # GH 26988 @@ -474,7 +476,7 @@ def test_replace_empty_copy(self, frame): obj = obj.to_frame() res = obj.replace(4, 5, inplace=True) - assert res is None + assert res is obj res = obj.replace(4, 5, inplace=False) tm.assert_equal(res, obj) @@ -687,7 +689,8 @@ def test_replace_na_in_obj_column(self, dtype): result = ser.replace(to_replace=1, value=2) tm.assert_series_equal(result, expected) - ser.replace(to_replace=1, value=2, inplace=True) + result = ser.replace(to_replace=1, value=2, inplace=True) + assert result is ser tm.assert_series_equal(ser, expected) @pytest.mark.parametrize("val", [0, 0.5]) @@ -698,7 +701,8 @@ def test_replace_numeric_column_with_na(self, val): result = ser.replace(to_replace=1, value=pd.NA) tm.assert_series_equal(result, expected) - ser.replace(to_replace=1, value=pd.NA, inplace=True) + result = ser.replace(to_replace=1, value=pd.NA, inplace=True) + assert result is ser tm.assert_series_equal(ser, expected) def test_replace_ea_float_with_bool(self): From 4d9ec5bc6a498f10d2bf47785904da06729ee0f3 Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Tue, 25 Nov 2025 09:51:53 +0100 Subject: [PATCH 3/5] fix return type of fillna --- pandas/core/generic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 45ead977ec3c9..b3448652026f4 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -6948,7 +6948,7 @@ def fillna( axis: Axis | None = None, inplace: bool = False, limit: int | None = None, - ) -> Self | None: + ) -> Self: """ Fill NA/NaN values with `value`. @@ -8216,10 +8216,10 @@ def _clip_with_scalar(self, lower, upper, inplace: bool = False): if lower is not None: cond = mask | (self >= lower) - result = result.where(cond, lower, inplace=inplace) # type: ignore[assignment] + result = result.where(cond, lower, inplace=inplace) if upper is not None: cond = mask | (self <= upper) - result = result.where(cond, upper, inplace=inplace) # type: ignore[assignment] + result = result.where(cond, upper, inplace=inplace) return result From 8eca1b5c63411f7aa0eed9289b55bc5a125ff06c Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Tue, 25 Nov 2025 10:24:03 +0100 Subject: [PATCH 4/5] fixup docstring --- pandas/core/generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index b3448652026f4..3e806f6dc9091 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -9836,7 +9836,7 @@ def where( ------- Series or DataFrame When applied to a Series, the function will return a Series, - and when applied to a DataFrame, it will return a DataFrame; + and when applied to a DataFrame, it will return a DataFrame. See Also -------- From be9c177cf6ccdc1c81b4a30fd9ad3c6eb182b80e Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Wed, 26 Nov 2025 10:55:00 +0100 Subject: [PATCH 5/5] add brief whatsnew --- doc/source/whatsnew/v3.0.0.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index c440b52a07da1..6e0abf47231eb 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -749,6 +749,10 @@ Other API changes - :meth:`ExtensionDtype.construct_array_type` is now a regular method instead of a ``classmethod`` (:issue:`58663`) - Comparison operations between :class:`Index` and :class:`Series` now consistently return :class:`Series` regardless of which object is on the left or right (:issue:`36759`) - Numpy functions like ``np.isinf`` that return a bool dtype when called on a :class:`Index` object now return a bool-dtype :class:`Index` instead of ``np.ndarray`` (:issue:`52676`) +- Methods that can operate in-place (:meth:`~DataFrame.replace`, :meth:`~DataFrame.fillna`, + :meth:`~DataFrame.ffill`, :meth:`~DataFrame.bfill`, :meth:`~DataFrame.interpolate`, + :meth:`~DataFrame.where`, :meth:`~DataFrame.mask`, :meth:`~DataFrame.clip`) now return + the modified DataFrame or Series (``self``) instead of ``None`` when ``inplace=True`` (:issue:`63207`) .. --------------------------------------------------------------------------- .. _whatsnew_300.deprecations: