diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index da1b2e750392c..d29aa8d8a2d1d 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -259,6 +259,7 @@ Deprecations - Deprecated unused "closed" and "normalize" keywords in the :class:`DatetimeIndex` constructor (:issue:`52628`) - Deprecated unused "closed" keyword in the :class:`TimedeltaIndex` constructor (:issue:`52628`) - Deprecated logical operation between two non boolean :class:`Series` with different indexes always coercing the result to bool dtype. In a future version, this will maintain the return type of the inputs. (:issue:`52500`, :issue:`52538`) +- Deprecated allowing ``downcast`` keyword other than ``None``, ``False``, "infer", or a dict with these as values in :meth:`Series.fillna`, :meth:`DataFrame.fillna` (:issue:`40988`) - Deprecated constructing :class:`SparseArray` from scalar data, pass a sequence instead (:issue:`53039`) - @@ -360,7 +361,7 @@ Indexing Missing ^^^^^^^ -- +- Bug in :meth:`Series.interpolate` and :meth:`DataFrame.interpolate` failing to raise on invalid ``downcast`` keyword, which can be only ``None`` or "infer" (:issue:`53103`) - MultiIndex diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 4eb29428b7dd1..64f664c76927b 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -6972,6 +6972,25 @@ def fillna( inplace = validate_bool_kwarg(inplace, "inplace") value, method = validate_fillna_kwargs(value, method) + if isinstance(downcast, dict): + # GH#40988 + for dc in downcast.values(): + if dc is not None and dc is not False and dc != "infer": + warnings.warn( + "downcast entries other than None, False, and 'infer' " + "are deprecated and will raise in a future version", + FutureWarning, + stacklevel=find_stack_level(), + ) + elif downcast is not None and downcast is not False and downcast != "infer": + # GH#40988 + warnings.warn( + "downcast other than None, False, and 'infer' are deprecated " + "and will raise in a future version", + FutureWarning, + stacklevel=find_stack_level(), + ) + # set the default here, so functions examining the signaure # can detect if something was set (e.g. in groupby) (GH9221) if axis is None: @@ -7548,7 +7567,7 @@ def interpolate( inplace: bool_t = False, limit_direction: Literal["forward", "backward", "both"] | None = None, limit_area: Literal["inside", "outside"] | None = None, - downcast: str | None = None, + downcast: Literal["infer"] | None = None, **kwargs, ) -> Self | None: """ @@ -7746,6 +7765,9 @@ def interpolate( 3 16.0 Name: d, dtype: float64 """ + if downcast is not None and downcast != "infer": + raise ValueError("downcast must be either None or 'infer'") + inplace = validate_bool_kwarg(inplace, "inplace") axis = self._get_axis_number(axis) diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index 85463d803b9a7..8d5f4d542cf72 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -7,6 +7,7 @@ Any, Callable, Iterable, + Literal, Sequence, cast, final, @@ -1327,7 +1328,7 @@ def interpolate( limit_direction: str = "forward", limit_area: str | None = None, fill_value: Any | None = None, - downcast: str | None = None, + downcast: Literal["infer"] | None = None, using_cow: bool = False, **kwargs, ) -> list[Block]: diff --git a/pandas/tests/frame/methods/test_fillna.py b/pandas/tests/frame/methods/test_fillna.py index d80c3c0da9935..027e12392206b 100644 --- a/pandas/tests/frame/methods/test_fillna.py +++ b/pandas/tests/frame/methods/test_fillna.py @@ -297,7 +297,11 @@ def test_fillna_downcast_noop(self, frame_or_series): # 2) _can_hold_na + noop + not can_hold_element obj = frame_or_series([1, 2, 3], dtype=np.int64) - res = obj.fillna("foo", downcast=np.dtype(np.int32)) + + msg = "downcast other than None, False, and 'infer' are deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + # GH#40988 + res = obj.fillna("foo", downcast=np.dtype(np.int32)) expected = obj.astype(np.int32) tm.assert_equal(res, expected) @@ -306,7 +310,9 @@ def test_fillna_downcast_noop(self, frame_or_series): expected2 = obj # get back int64 tm.assert_equal(res2, expected2) - res3 = obj2.fillna("foo", downcast=np.dtype(np.int32)) + with tm.assert_produces_warning(FutureWarning, match=msg): + # GH#40988 + res3 = obj2.fillna("foo", downcast=np.dtype(np.int32)) tm.assert_equal(res3, expected) @pytest.mark.parametrize("columns", [["A", "A", "B"], ["A", "A"]]) @@ -605,7 +611,10 @@ def test_fill_corner(self, float_frame, float_string_frame): def test_fillna_downcast_dict(self): # GH#40809 df = DataFrame({"col1": [1, np.nan]}) - result = df.fillna({"col1": 2}, downcast={"col1": "int64"}) + + msg = "downcast entries other than None, False, and 'infer' are deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + result = df.fillna({"col1": 2}, downcast={"col1": "int64"}) expected = DataFrame({"col1": [1, 2]}) tm.assert_frame_equal(result, expected) diff --git a/pandas/tests/frame/methods/test_interpolate.py b/pandas/tests/frame/methods/test_interpolate.py index d6da967106fe6..2b54c34096152 100644 --- a/pandas/tests/frame/methods/test_interpolate.py +++ b/pandas/tests/frame/methods/test_interpolate.py @@ -151,6 +151,23 @@ def test_interp_combo(self): expected = Series([1, 2, 3, 4], name="A") tm.assert_series_equal(result, expected) + def test_inerpolate_invalid_downcast(self): + # GH#53103 + df = DataFrame( + { + "A": [1.0, 2.0, np.nan, 4.0], + "B": [1, 4, 9, np.nan], + "C": [1, 2, 3, 5], + "D": list("abcd"), + } + ) + + msg = "downcast must be either None or 'infer'" + with pytest.raises(ValueError, match=msg): + df.interpolate(downcast="int64") + with pytest.raises(ValueError, match=msg): + df["A"].interpolate(downcast="int64") + def test_interp_nan_idx(self): df = DataFrame({"A": [1, 2, np.nan, 4], "B": [np.nan, 2, 3, 4]}) df = df.set_index("A")