diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 7ec50137c3039..f929c63c6bf82 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -641,6 +641,7 @@ Other Deprecations - Deprecated strings ``w``, ``d``, ``MIN``, ``MS``, ``US`` and ``NS`` denoting units in :class:`Timedelta` in favour of ``W``, ``D``, ``min``, ``ms``, ``us`` and ``ns`` (:issue:`59051`) - Deprecated the ``arg`` parameter of ``Series.map``; pass the added ``func`` argument instead. (:issue:`61260`) - Deprecated using ``epoch`` date format in :meth:`DataFrame.to_json` and :meth:`Series.to_json`, use ``iso`` instead. (:issue:`57063`) +- Deprecated allowing ``fill_value`` that cannot be held in the original dtype (excepting NA values for integer and bool dtypes) in :meth:`Series.unstack` and :meth:`DataFrame.unstack` (:issue:`12189`, :issue:`53868`) .. --------------------------------------------------------------------------- .. _whatsnew_300.prior_deprecations: diff --git a/pandas/core/reshape/reshape.py b/pandas/core/reshape/reshape.py index dd22f900be926..e101944e72ef0 100644 --- a/pandas/core/reshape/reshape.py +++ b/pandas/core/reshape/reshape.py @@ -13,7 +13,10 @@ from pandas._config.config import get_option import pandas._libs.reshape as libreshape -from pandas.errors import PerformanceWarning +from pandas.errors import ( + Pandas4Warning, + PerformanceWarning, +) from pandas.util._decorators import cache_readonly from pandas.util._exceptions import find_stack_level @@ -28,7 +31,10 @@ needs_i8_conversion, ) from pandas.core.dtypes.dtypes import ExtensionDtype -from pandas.core.dtypes.missing import notna +from pandas.core.dtypes.missing import ( + isna, + notna, +) import pandas.core.algorithms as algos from pandas.core.algorithms import ( @@ -298,7 +304,25 @@ def get_new_values(self, values, fill_value=None): new_values[:] = fill_value else: if not mask_all: + old_dtype = dtype dtype, fill_value = maybe_promote(dtype, fill_value) + if old_dtype != dtype: + if old_dtype.kind not in "iub": + warnings.warn( + # GH#12189, GH#53868 + "Using a fill_value that cannot be held in the existing " + "dtype is deprecated and will raise in a future version.", + Pandas4Warning, + stacklevel=find_stack_level(), + ) + elif not isna(fill_value): + warnings.warn( + # GH#12189, GH#53868 + "Using a fill_value that cannot be held in the existing " + "dtype is deprecated and will raise in a future version.", + Pandas4Warning, + stacklevel=find_stack_level(), + ) new_values = np.empty(result_shape, dtype=dtype) if not mask_all: new_values.fill(fill_value) diff --git a/pandas/tests/frame/test_stack_unstack.py b/pandas/tests/frame/test_stack_unstack.py index 756d454ebd11f..9124443219e3c 100644 --- a/pandas/tests/frame/test_stack_unstack.py +++ b/pandas/tests/frame/test_stack_unstack.py @@ -105,8 +105,12 @@ def test_unstack_fill(self, future_stack): ) tm.assert_frame_equal(result, expected) - # From a series with incorrect data type for fill_value - result = data.unstack(fill_value=0.5) + msg = ( + "Using a fill_value that cannot be held in the existing dtype is deprecated" + ) + with tm.assert_produces_warning(Pandas4Warning, match=msg): + # From a series with incorrect data type for fill_value + result = data.unstack(fill_value=0.5) expected = DataFrame( {"a": [1, 0.5, 5], "b": [2, 4, 0.5]}, index=["x", "y", "z"], dtype=float ) @@ -161,8 +165,12 @@ def test_unstack_fill_frame(self): expected["B"] = expected["B"].astype(np.float64) tm.assert_frame_equal(result, expected) - # From a dataframe with incorrect data type for fill_value - result = df.unstack(fill_value=0.5) + msg = ( + "Using a fill_value that cannot be held in the existing dtype is deprecated" + ) + with tm.assert_produces_warning(Pandas4Warning, match=msg): + # From a dataframe with incorrect data type for fill_value + result = df.unstack(fill_value=0.5) rows = [[1, 3, 2, 4], [0.5, 5, 0.5, 6], [7, 0.5, 8, 0.5]] expected = DataFrame(rows, index=list("xyz"), dtype=float)