Repository: pandas-dev/pandas

Issue:
DEPR: NDFrame.bool
We recently deprecated `Series.__int__` and `Series.__float__`, let's do `NFrame.bool` too.

xref #32923



Discussion:

Patch:
diff --git a/doc/source/user_guide/basics.rst b/doc/source/user_guide/basics.rst
index 5c2f2410e688c..16a85ccc74b43 100644
--- a/doc/source/user_guide/basics.rst
+++ b/doc/source/user_guide/basics.rst
@@ -329,16 +329,6 @@ You can test if a pandas object is empty, via the :attr:`~DataFrame.empty` prope
    df.empty
    pd.DataFrame(columns=list("ABC")).empty
 
-To evaluate single-element pandas objects in a boolean context, use the method
-:meth:`~DataFrame.bool`:
-
-.. ipython:: python
-
-   pd.Series([True]).bool()
-   pd.Series([False]).bool()
-   pd.DataFrame([[True]]).bool()
-   pd.DataFrame([[False]]).bool()
-
 .. warning::
 
    You might be tempted to do the following:
diff --git a/doc/source/user_guide/gotchas.rst b/doc/source/user_guide/gotchas.rst
index adb40e166eab4..47f1b74c0b894 100644
--- a/doc/source/user_guide/gotchas.rst
+++ b/doc/source/user_guide/gotchas.rst
@@ -121,16 +121,6 @@ Below is how to check if any of the values are ``True``:
     if pd.Series([False, True, False]).any():
         print("I am any")
 
-To evaluate single-element pandas objects in a boolean context, use the method
-:meth:`~DataFrame.bool`:
-
-.. ipython:: python
-
-   pd.Series([True]).bool()
-   pd.Series([False]).bool()
-   pd.DataFrame([[True]]).bool()
-   pd.DataFrame([[False]]).bool()
-
 Bitwise boolean
 ~~~~~~~~~~~~~~~
 
diff --git a/doc/source/whatsnew/v0.13.0.rst b/doc/source/whatsnew/v0.13.0.rst
index 8ce038200acc4..2e086f560bd53 100644
--- a/doc/source/whatsnew/v0.13.0.rst
+++ b/doc/source/whatsnew/v0.13.0.rst
@@ -153,12 +153,16 @@ API changes
 
   Added the ``.bool()`` method to ``NDFrame`` objects to facilitate evaluating of single-element boolean Series:
 
-  .. ipython:: python
+  .. code-block:: python
 
-     pd.Series([True]).bool()
-     pd.Series([False]).bool()
-     pd.DataFrame([[True]]).bool()
-     pd.DataFrame([[False]]).bool()
+     >>> pd.Series([True]).bool()
+      True
+     >>> pd.Series([False]).bool()
+      False
+     >>> pd.DataFrame([[True]]).bool()
+      True
+     >>> pd.DataFrame([[False]]).bool()
+      False
 
 - All non-Index NDFrames (``Series``, ``DataFrame``, ``Panel``, ``Panel4D``,
   ``SparsePanel``, etc.), now support the entire set of arithmetic operators
diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst
index 69a955dc3cd9f..44188c03fcf17 100644
--- a/doc/source/whatsnew/v2.1.0.rst
+++ b/doc/source/whatsnew/v2.1.0.rst
@@ -125,6 +125,7 @@ Deprecations
 - Deprecated the 'axis' keyword in :meth:`.GroupBy.idxmax`, :meth:`.GroupBy.idxmin`, :meth:`.GroupBy.fillna`, :meth:`.GroupBy.take`, :meth:`.GroupBy.skew`, :meth:`.GroupBy.rank`, :meth:`.GroupBy.cumprod`, :meth:`.GroupBy.cumsum`, :meth:`.GroupBy.cummax`, :meth:`.GroupBy.cummin`, :meth:`.GroupBy.pct_change`, :meth:`GroupBy.diff`, :meth:`.GroupBy.shift`, and :meth:`DataFrameGroupBy.corrwith`; for ``axis=1`` operate on the underlying :class:`DataFrame` instead (:issue:`50405`, :issue:`51046`)
 - Deprecated passing a dictionary to :meth:`.SeriesGroupBy.agg`; pass a list of aggregations instead (:issue:`50684`)
 - Deprecated logical operations (``|``, ``&``, ``^``) between pandas objects and dtype-less sequences (e.g. ``list``, ``tuple``), wrap a sequence in a :class:`Series` or numpy array before operating instead (:issue:`51521`)
+- Deprecated the methods :meth:`Series.bool` and :meth:`DataFrame.bool` (:issue:`51749`)
 - Deprecated :meth:`DataFrame.swapaxes` and :meth:`Series.swapaxes`, use :meth:`DataFrame.transpose` or :meth:`Series.transpose` instead (:issue:`51946`)
 - Deprecated parameter ``convert_type`` in :meth:`Series.apply` (:issue:`52140`)
 -
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
index bb3573d148e98..52a4353dcbfac 100644
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -1513,6 +1513,13 @@ def bool(self) -> bool_t:
         >>> pd.DataFrame({'col': [False]}).bool()
         False
         """
+
+        warnings.warn(
+            f"{type(self).__name__}.bool is now deprecated and will be removed "
+            "in future version of pandas",
+            FutureWarning,
+            stacklevel=find_stack_level(),
+        )
         v = self.squeeze()
         if isinstance(v, (bool, np.bool_)):
             return bool(v)


Test patch:
diff --git a/pandas/conftest.py b/pandas/conftest.py
index 7ede781db9018..70e1c317c2043 100644
--- a/pandas/conftest.py
+++ b/pandas/conftest.py
@@ -137,6 +137,11 @@ def pytest_collection_modifyitems(items, config) -> None:
     ignored_doctest_warnings = [
         # Docstring divides by zero to show behavior difference
         ("missing.mask_zero_div_zero", "divide by zero encountered"),
+        (
+            "pandas.core.generic.NDFrame.bool",
+            "(Series|DataFrame).bool is now deprecated and will be removed "
+            "in future version of pandas",
+        ),
     ]
 
     for item in items:
diff --git a/pandas/tests/generic/test_frame.py b/pandas/tests/generic/test_frame.py
index 8a1e0f0923531..79f055909fdea 100644
--- a/pandas/tests/generic/test_frame.py
+++ b/pandas/tests/generic/test_frame.py
@@ -47,19 +47,27 @@ def test_set_axis_name_mi(self, func):
 
     def test_nonzero_single_element(self):
         # allow single item via bool method
+        msg_warn = (
+            "DataFrame.bool is now deprecated and will be removed "
+            "in future version of pandas"
+        )
         df = DataFrame([[True]])
-        assert df.bool()
+        df1 = DataFrame([[False]])
+        with tm.assert_produces_warning(FutureWarning, match=msg_warn):
+            assert df.bool()
 
-        df = DataFrame([[False]])
-        assert not df.bool()
+        with tm.assert_produces_warning(FutureWarning, match=msg_warn):
+            assert not df1.bool()
 
         df = DataFrame([[False, False]])
-        msg = "The truth value of a DataFrame is ambiguous"
-        with pytest.raises(ValueError, match=msg):
-            df.bool()
-        with pytest.raises(ValueError, match=msg):
+        msg_err = "The truth value of a DataFrame is ambiguous"
+        with pytest.raises(ValueError, match=msg_err):
             bool(df)
 
+        with tm.assert_produces_warning(FutureWarning, match=msg_warn):
+            with pytest.raises(ValueError, match=msg_err):
+                df.bool()
+
     def test_metadata_propagation_indiv_groupby(self):
         # groupby
         df = DataFrame(
diff --git a/pandas/tests/generic/test_generic.py b/pandas/tests/generic/test_generic.py
index d23d97f43de03..acc1a8c2e1d05 100644
--- a/pandas/tests/generic/test_generic.py
+++ b/pandas/tests/generic/test_generic.py
@@ -451,3 +451,12 @@ def test_flags_identity(self, frame_or_series):
         assert obj.flags is obj.flags
         obj2 = obj.copy()
         assert obj2.flags is not obj.flags
+
+    def test_bool_dep(self) -> None:
+        # GH-51749
+        msg_warn = (
+            "DataFrame.bool is now deprecated and will be removed "
+            "in future version of pandas"
+        )
+        with tm.assert_produces_warning(FutureWarning, match=msg_warn):
+            DataFrame({"col": [False]}).bool()
diff --git a/pandas/tests/generic/test_series.py b/pandas/tests/generic/test_series.py
index 5098897f057a5..ee0a7fb77f336 100644
--- a/pandas/tests/generic/test_series.py
+++ b/pandas/tests/generic/test_series.py
@@ -41,11 +41,16 @@ def test_get_bool_data_preserve_dtype(self):
 
     def test_nonzero_single_element(self):
         # allow single item via bool method
+        msg_warn = (
+            "Series.bool is now deprecated and will be removed "
+            "in future version of pandas"
+        )
         ser = Series([True])
-        assert ser.bool()
-
-        ser = Series([False])
-        assert not ser.bool()
+        ser1 = Series([False])
+        with tm.assert_produces_warning(FutureWarning, match=msg_warn):
+            assert ser.bool()
+        with tm.assert_produces_warning(FutureWarning, match=msg_warn):
+            assert not ser1.bool()
 
     @pytest.mark.parametrize("data", [np.nan, pd.NaT, True, False])
     def test_nonzero_single_element_raise_1(self, data):
@@ -58,35 +63,46 @@ def test_nonzero_single_element_raise_1(self, data):
 
     @pytest.mark.parametrize("data", [np.nan, pd.NaT])
     def test_nonzero_single_element_raise_2(self, data):
+        msg_warn = (
+            "Series.bool is now deprecated and will be removed "
+            "in future version of pandas"
+        )
+        msg_err = "bool cannot act on a non-boolean single element Series"
         series = Series([data])
-
-        msg = "bool cannot act on a non-boolean single element Series"
-        with pytest.raises(ValueError, match=msg):
-            series.bool()
+        with tm.assert_produces_warning(FutureWarning, match=msg_warn):
+            with pytest.raises(ValueError, match=msg_err):
+                series.bool()
 
     @pytest.mark.parametrize("data", [(True, True), (False, False)])
     def test_nonzero_multiple_element_raise(self, data):
         # multiple bool are still an error
+        msg_warn = (
+            "Series.bool is now deprecated and will be removed "
+            "in future version of pandas"
+        )
+        msg_err = "The truth value of a Series is ambiguous"
         series = Series([data])
-
-        msg = "The truth value of a Series is ambiguous"
-        with pytest.raises(ValueError, match=msg):
+        with pytest.raises(ValueError, match=msg_err):
             bool(series)
-        with pytest.raises(ValueError, match=msg):
-            series.bool()
+        with tm.assert_produces_warning(FutureWarning, match=msg_warn):
+            with pytest.raises(ValueError, match=msg_err):
+                series.bool()
 
     @pytest.mark.parametrize("data", [1, 0, "a", 0.0])
     def test_nonbool_single_element_raise(self, data):
         # single non-bool are an error
+        msg_warn = (
+            "Series.bool is now deprecated and will be removed "
+            "in future version of pandas"
+        )
+        msg_err1 = "The truth value of a Series is ambiguous"
+        msg_err2 = "bool cannot act on a non-boolean single element Series"
         series = Series([data])
-
-        msg = "The truth value of a Series is ambiguous"
-        with pytest.raises(ValueError, match=msg):
+        with pytest.raises(ValueError, match=msg_err1):
             bool(series)
-
-        msg = "bool cannot act on a non-boolean single element Series"
-        with pytest.raises(ValueError, match=msg):
-            series.bool()
+        with tm.assert_produces_warning(FutureWarning, match=msg_warn):
+            with pytest.raises(ValueError, match=msg_err2):
+                series.bool()
 
     def test_metadata_propagation_indiv_resample(self):
         # resample