Repository: pandas-dev/pandas
Pull request #58167 (2024-04-05 20:26:39)

DEPR: enforce Sparse deprecations
- [ ] closes #xxxx (Replace xxxx with the GitHub issue number)
- [ ] [Tests added and passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [ ] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [ ] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [ ] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.



Patch:
diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst
index bb1e0b33c2..c0a2dc7e39 100644
--- a/doc/source/whatsnew/v3.0.0.rst
+++ b/doc/source/whatsnew/v3.0.0.rst
@@ -208,6 +208,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`)
 - Disallow calling :meth:`Series.replace` or :meth:`DataFrame.replace` without a ``value`` and with non-dict-like ``to_replace`` (:issue:`33302`)
+- Disallow constructing a :class:`arrays.SparseArray` with scalar data (:issue:`53039`)
 - Disallow non-standard (``np.ndarray``, :class:`Index`, :class:`ExtensionArray`, or :class:`Series`) to :func:`isin`, :func:`unique`, :func:`factorize` (:issue:`52986`)
 - Disallow passing a pandas type to :meth:`Index.view` (:issue:`55709`)
 - Disallow units other than "s", "ms", "us", "ns" for datetime64 and timedelta64 dtypes in :func:`array` (:issue:`53817`)
@@ -217,6 +218,7 @@ Removal of prior version deprecations/changes
 - Removed deprecated "method" and "limit" keywords from :meth:`Series.replace` and :meth:`DataFrame.replace` (:issue:`53492`)
 - Removed extension test classes ``BaseNoReduceTests``, ``BaseNumericReduceTests``, ``BaseBooleanReduceTests`` (:issue:`54663`)
 - Removed the "closed" and "normalize" keywords in :meth:`DatetimeIndex.__new__` (:issue:`52628`)
+- Require :meth:`SparseDtype.fill_value` to be a valid value for the :meth:`SparseDtype.subtype` (:issue:`53043`)
 - Stopped performing dtype inference with in :meth:`Index.insert` with object-dtype index; this often affects the index/columns that result when setting new entries into an empty :class:`Series` or :class:`DataFrame` (:issue:`51363`)
 - 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`)
diff --git a/pandas/core/arrays/sparse/array.py b/pandas/core/arrays/sparse/array.py
index 2a96423017..1347020993 100644
--- a/pandas/core/arrays/sparse/array.py
+++ b/pandas/core/arrays/sparse/array.py
@@ -40,7 +40,6 @@ from pandas.util._validators import (
 
 from pandas.core.dtypes.astype import astype_array
 from pandas.core.dtypes.cast import (
-    construct_1d_arraylike_from_scalar,
     find_common_type,
     maybe_box_datetimelike,
 )
@@ -399,19 +398,10 @@ class SparseArray(OpsMixin, PandasObject, ExtensionArray):
             dtype = dtype.subtype
 
         if is_scalar(data):
-            warnings.warn(
-                f"Constructing {type(self).__name__} with scalar data is deprecated "
-                "and will raise in a future version. Pass a sequence instead.",
-                FutureWarning,
-                stacklevel=find_stack_level(),
+            raise TypeError(
+                f"Cannot construct {type(self).__name__} from scalar data. "
+                "Pass a sequence instead."
             )
-            if sparse_index is None:
-                npoints = 1
-            else:
-                npoints = sparse_index.length
-
-            data = construct_1d_arraylike_from_scalar(data, npoints, dtype=None)
-            dtype = data.dtype
 
         if dtype is not None:
             dtype = pandas_dtype(dtype)
diff --git a/pandas/core/dtypes/dtypes.py b/pandas/core/dtypes/dtypes.py
index f94d32a3b8..2d8e490f02 100644
--- a/pandas/core/dtypes/dtypes.py
+++ b/pandas/core/dtypes/dtypes.py
@@ -1762,24 +1762,18 @@ class SparseDtype(ExtensionDtype):
         val = self._fill_value
         if isna(val):
             if not is_valid_na_for_dtype(val, self.subtype):
-                warnings.warn(
-                    "Allowing arbitrary scalar fill_value in SparseDtype is "
-                    "deprecated. In a future version, the fill_value must be "
-                    "a valid value for the SparseDtype.subtype.",
-                    FutureWarning,
-                    stacklevel=find_stack_level(),
+                raise ValueError(
+                    # GH#53043
+                    "fill_value must be a valid value for the SparseDtype.subtype"
                 )
         else:
             dummy = np.empty(0, dtype=self.subtype)
             dummy = ensure_wrapped_if_datetimelike(dummy)
 
             if not can_hold_element(dummy, val):
-                warnings.warn(
-                    "Allowing arbitrary scalar fill_value in SparseDtype is "
-                    "deprecated. In a future version, the fill_value must be "
-                    "a valid value for the SparseDtype.subtype.",
-                    FutureWarning,
-                    stacklevel=find_stack_level(),
+                raise ValueError(
+                    # GH#53043
+                    "fill_value must be a valid value for the SparseDtype.subtype"
                 )
 
     @property


Test patch:
diff --git a/pandas/tests/arrays/sparse/test_array.py b/pandas/tests/arrays/sparse/test_array.py
index 883d6ea395..c35e8204f3 100644
--- a/pandas/tests/arrays/sparse/test_array.py
+++ b/pandas/tests/arrays/sparse/test_array.py
@@ -52,10 +52,11 @@ class TestSparseArray:
         arr.fill_value = 2
         assert arr.fill_value == 2
 
-        msg = "Allowing arbitrary scalar fill_value in SparseDtype is deprecated"
-        with tm.assert_produces_warning(FutureWarning, match=msg):
+        msg = "fill_value must be a valid value for the SparseDtype.subtype"
+        with pytest.raises(ValueError, match=msg):
+            # GH#53043
             arr.fill_value = 3.1
-        assert arr.fill_value == 3.1
+        assert arr.fill_value == 2
 
         arr.fill_value = np.nan
         assert np.isnan(arr.fill_value)
@@ -64,8 +65,9 @@ class TestSparseArray:
         arr.fill_value = True
         assert arr.fill_value is True
 
-        with tm.assert_produces_warning(FutureWarning, match=msg):
+        with pytest.raises(ValueError, match=msg):
             arr.fill_value = 0
+        assert arr.fill_value is True
 
         arr.fill_value = np.nan
         assert np.isnan(arr.fill_value)
diff --git a/pandas/tests/arrays/sparse/test_constructors.py b/pandas/tests/arrays/sparse/test_constructors.py
index 2831c8abda..012ff1da0d 100644
--- a/pandas/tests/arrays/sparse/test_constructors.py
+++ b/pandas/tests/arrays/sparse/test_constructors.py
@@ -144,20 +144,12 @@ class TestConstructors:
     @pytest.mark.parametrize("sparse_index", [None, IntIndex(1, [0])])
     def test_constructor_spindex_dtype_scalar(self, sparse_index):
         # scalar input
-        msg = "Constructing SparseArray with scalar data is deprecated"
-        with tm.assert_produces_warning(FutureWarning, match=msg):
-            arr = SparseArray(data=1, sparse_index=sparse_index, dtype=None)
-        exp = SparseArray([1], dtype=None)
-        tm.assert_sp_array_equal(arr, exp)
-        assert arr.dtype == SparseDtype(np.int64)
-        assert arr.fill_value == 0
+        msg = "Cannot construct SparseArray from scalar data. Pass a sequence instead"
+        with pytest.raises(TypeError, match=msg):
+            SparseArray(data=1, sparse_index=sparse_index, dtype=None)
 
-        with tm.assert_produces_warning(FutureWarning, match=msg):
-            arr = SparseArray(data=1, sparse_index=IntIndex(1, [0]), dtype=None)
-        exp = SparseArray([1], dtype=None)
-        tm.assert_sp_array_equal(arr, exp)
-        assert arr.dtype == SparseDtype(np.int64)
-        assert arr.fill_value == 0
+        with pytest.raises(TypeError, match=msg):
+            SparseArray(data=1, sparse_index=IntIndex(1, [0]), dtype=None)
 
     def test_constructor_spindex_dtype_scalar_broadcasts(self):
         arr = SparseArray(
diff --git a/pandas/tests/arrays/sparse/test_dtype.py b/pandas/tests/arrays/sparse/test_dtype.py
index 6f0d41333f..1819744d9a 100644
--- a/pandas/tests/arrays/sparse/test_dtype.py
+++ b/pandas/tests/arrays/sparse/test_dtype.py
@@ -84,7 +84,6 @@ with warnings.catch_warnings():
         (SparseDtype("float64"), SparseDtype("float32")),
         (SparseDtype("float64"), SparseDtype("float64", 0)),
         (SparseDtype("float64"), SparseDtype("datetime64[ns]", np.nan)),
-        (SparseDtype(int, pd.NaT), SparseDtype(float, pd.NaT)),
         (SparseDtype("float64"), np.dtype("float64")),
     ]