Repository: pandas-dev/pandas

Issue:
DEPR: Deprecate Series.ravel
The methods looks quite unnecessary, the EAs are a no-op and for NumPy users can simply call ravel on the NumPy array.


Discussion:
+1 (I suppose this is mostly a left-over from Series subclassing np.ndarray initially (long time ago ;))
+1

Patch:
diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst
index b30a11dca7c57..65271886c5769 100644
--- a/doc/source/whatsnew/v2.2.0.rst
+++ b/doc/source/whatsnew/v2.2.0.rst
@@ -362,6 +362,7 @@ Other Deprecations
 - Deprecated :func:`read_gbq` and :meth:`DataFrame.to_gbq`. Use ``pandas_gbq.read_gbq`` and ``pandas_gbq.to_gbq`` instead https://pandas-gbq.readthedocs.io/en/latest/api.html (:issue:`55525`)
 - Deprecated :meth:`.DataFrameGroupBy.fillna` and :meth:`.SeriesGroupBy.fillna`; use :meth:`.DataFrameGroupBy.ffill`, :meth:`.DataFrameGroupBy.bfill` for forward and backward filling or :meth:`.DataFrame.fillna` to fill with a single value (or the Series equivalents) (:issue:`55718`)
 - Deprecated :meth:`Index.format`, use ``index.astype(str)`` or ``index.map(formatter)`` instead (:issue:`55413`)
+- Deprecated :meth:`Series.ravel`, the underlying array is already 1D, so ravel is not necessary (:issue:`52511`)
 - Deprecated :meth:`Series.view`, use ``astype`` instead to change the dtype (:issue:`20251`)
 - Deprecated ``core.internals`` members ``Block``, ``ExtensionBlock``, and ``DatetimeTZBlock``, use public APIs instead (:issue:`55139`)
 - Deprecated ``year``, ``month``, ``quarter``, ``day``, ``hour``, ``minute``, and ``second`` keywords in the :class:`PeriodIndex` constructor, use :meth:`PeriodIndex.from_fields` instead (:issue:`55960`)
diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py
index 792fa4fdc24a5..320f028f4484c 100644
--- a/pandas/core/dtypes/cast.py
+++ b/pandas/core/dtypes/cast.py
@@ -261,6 +261,8 @@ def maybe_downcast_to_dtype(result: ArrayLike, dtype: str | np.dtype) -> ArrayLi
     try to cast to the specified dtype (e.g. convert back to bool/int
     or could be an astype of float64->float32
     """
+    if isinstance(result, ABCSeries):
+        result = result._values
     do_round = False
 
     if isinstance(dtype, str):
diff --git a/pandas/core/reshape/pivot.py b/pandas/core/reshape/pivot.py
index c39fbfe6b6d33..eba4f72b5eb8f 100644
--- a/pandas/core/reshape/pivot.py
+++ b/pandas/core/reshape/pivot.py
@@ -309,6 +309,7 @@ def _add_margins(
 
     row_names = result.index.names
     # check the result column and leave floats
+
     for dtype in set(result.dtypes):
         if isinstance(dtype, ExtensionDtype):
             # Can hold NA already
diff --git a/pandas/core/series.py b/pandas/core/series.py
index 6b06786549ade..758476f5e5fea 100644
--- a/pandas/core/series.py
+++ b/pandas/core/series.py
@@ -847,6 +847,11 @@ def ravel(self, order: str = "C") -> ArrayLike:
         """
         Return the flattened underlying data as an ndarray or ExtensionArray.
 
+        .. deprecated:: 2.2.0
+            Series.ravel is deprecated. The underlying array is already 1D, so
+            ravel is not necessary.  Use :meth:`to_numpy` for conversion to a numpy
+            array instead.
+
         Returns
         -------
         numpy.ndarray or ExtensionArray
@@ -859,9 +864,16 @@ def ravel(self, order: str = "C") -> ArrayLike:
         Examples
         --------
         >>> s = pd.Series([1, 2, 3])
-        >>> s.ravel()
+        >>> s.ravel()  # doctest: +SKIP
         array([1, 2, 3])
         """
+        warnings.warn(
+            "Series.ravel is deprecated. The underlying array is already 1D, so "
+            "ravel is not necessary.  Use `to_numpy()` for conversion to a numpy "
+            "array instead.",
+            FutureWarning,
+            stacklevel=2,
+        )
         arr = self._values.ravel(order=order)
         if isinstance(arr, np.ndarray) and using_copy_on_write():
             arr.flags.writeable = False


Test patch:
diff --git a/pandas/_testing/asserters.py b/pandas/_testing/asserters.py
index 0d8fb7bfce33a..55cf734c32eae 100644
--- a/pandas/_testing/asserters.py
+++ b/pandas/_testing/asserters.py
@@ -426,6 +426,8 @@ def assert_is_valid_plot_return_object(objs) -> None:
     from matplotlib.axes import Axes
 
     if isinstance(objs, (Series, np.ndarray)):
+        if isinstance(objs, Series):
+            objs = objs._values
         for el in objs.ravel():
             msg = (
                 "one of 'objs' is not a matplotlib Axes instance, "
diff --git a/pandas/tests/copy_view/test_array.py b/pandas/tests/copy_view/test_array.py
index 62a6a3374e612..e5d0aafb80b35 100644
--- a/pandas/tests/copy_view/test_array.py
+++ b/pandas/tests/copy_view/test_array.py
@@ -116,7 +116,8 @@ def test_series_to_numpy(using_copy_on_write):
 @pytest.mark.parametrize("order", ["F", "C"])
 def test_ravel_read_only(using_copy_on_write, order):
     ser = Series([1, 2, 3])
-    arr = ser.ravel(order=order)
+    with tm.assert_produces_warning(FutureWarning, match="is deprecated"):
+        arr = ser.ravel(order=order)
     if using_copy_on_write:
         assert arr.flags.writeable is False
     assert np.shares_memory(get_array(ser), arr)
diff --git a/pandas/tests/dtypes/cast/test_downcast.py b/pandas/tests/dtypes/cast/test_downcast.py
index c01eac746455c..9430ba2c478ae 100644
--- a/pandas/tests/dtypes/cast/test_downcast.py
+++ b/pandas/tests/dtypes/cast/test_downcast.py
@@ -56,8 +56,8 @@ def test_downcast_booleans():
     ser = Series([True, True, False])
     result = maybe_downcast_to_dtype(ser, np.dtype(np.float64))
 
-    expected = ser
-    tm.assert_series_equal(result, expected)
+    expected = ser.values
+    tm.assert_numpy_array_equal(result, expected)
 
 
 def test_downcast_conversion_no_nan(any_real_numpy_dtype):
diff --git a/pandas/tests/series/test_api.py b/pandas/tests/series/test_api.py
index c29fe6ba06ab4..9d69321ff7dbb 100644
--- a/pandas/tests/series/test_api.py
+++ b/pandas/tests/series/test_api.py
@@ -140,7 +140,9 @@ def test_ndarray_compat_like_func(self):
     def test_ndarray_compat_ravel(self):
         # ravel
         s = Series(np.random.default_rng(2).standard_normal(10))
-        tm.assert_almost_equal(s.ravel(order="F"), s.values.ravel(order="F"))
+        with tm.assert_produces_warning(FutureWarning, match="ravel is deprecated"):
+            result = s.ravel(order="F")
+        tm.assert_almost_equal(result, s.values.ravel(order="F"))
 
     def test_empty_method(self):
         s_empty = Series(dtype=object)