From dd81d94da86807260269de6b846e1e2da4425b0a Mon Sep 17 00:00:00 2001
From: Jack Wright <jack.louie.wright@gmail.com>
Date: Wed, 5 Jun 2024 14:25:44 +0100
Subject: [PATCH 1/2] BUG : fix Series.mode throwing exception with
 dropna=False and no nulls present (#58926)

---
 doc/source/whatsnew/v3.0.0.rst         | 1 +
 pandas/core/arrays/masked.py           | 8 +++++++-
 pandas/tests/series/test_reductions.py | 8 ++++++++
 3 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst
index 7f5c879c0d9f5..b7e3e55fd2c46 100644
--- a/doc/source/whatsnew/v3.0.0.rst
+++ b/doc/source/whatsnew/v3.0.0.rst
@@ -572,6 +572,7 @@ Other
 - Bug in :meth:`DataFrame.where` where using a non-bool type array in the function would return a ``ValueError`` instead of a ``TypeError`` (:issue:`56330`)
 - Bug in :meth:`Index.sort_values` when passing a key function that turns values into tuples, e.g. ``key=natsort.natsort_key``, would raise ``TypeError`` (:issue:`56081`)
 - Bug in :meth:`Series.diff` allowing non-integer values for the ``periods`` argument. (:issue:`56607`)
+- Bug in :meth:`Series.mode` where an exception was raised when taking the mode with nullable types with no null values in the series. (:issue:`58926`)
 - Bug in :meth:`Series.rank` that doesn't preserve missing values for nullable integers when ``na_option='keep'``. (:issue:`56976`)
 - Bug in :meth:`Series.replace` and :meth:`DataFrame.replace` inconsistently replacing matching instances when ``regex=True`` and missing values are present. (:issue:`56599`)
 - Bug in Dataframe Interchange Protocol implementation was returning incorrect results for data buffers' associated dtype, for string and datetime columns (:issue:`54781`)
diff --git a/pandas/core/arrays/masked.py b/pandas/core/arrays/masked.py
index 04cffcaaa5f04..ac7c542f1e643 100644
--- a/pandas/core/arrays/masked.py
+++ b/pandas/core/arrays/masked.py
@@ -1093,7 +1093,13 @@ def _mode(self, dropna: bool = True) -> Self:
             result = mode(self._data, dropna=dropna, mask=self._mask)
             res_mask = np.zeros(result.shape, dtype=np.bool_)
         else:
-            result, res_mask = mode(self._data, dropna=dropna, mask=self._mask)
+            res_tuple = mode(self._data, dropna=dropna, mask=self._mask)
+            if len(res_tuple) == 2:
+                result = res_tuple[0]
+                res_mask = res_tuple[1]
+            else:
+                result = res_tuple
+                res_mask = np.zeros(result.shape, dtype=np.bool_)
         result = type(self)(result, res_mask)  # type: ignore[arg-type]
         return result[result.argsort()]
 
diff --git a/pandas/tests/series/test_reductions.py b/pandas/tests/series/test_reductions.py
index 0bc3092d30b43..535a3c9171b40 100644
--- a/pandas/tests/series/test_reductions.py
+++ b/pandas/tests/series/test_reductions.py
@@ -51,6 +51,14 @@ def test_mode_nullable_dtype(any_numeric_ea_dtype):
     tm.assert_series_equal(result, expected)
 
 
+def test_mode_nullable_dtype_edge_case(any_numeric_ea_dtype):
+    # GH##58926
+    ser = Series([1, 1, 2, 3], dtype=any_numeric_ea_dtype)
+    result = ser.mode(dropna=False)
+    expected = Series([1], dtype=any_numeric_ea_dtype)
+    tm.assert_series_equal(result, expected)
+
+
 def test_mode_infer_string():
     # GH#56183
     pytest.importorskip("pyarrow")

From 8a515bb66ea9b066a125ed090facb830c4000864 Mon Sep 17 00:00:00 2001
From: Jack Wright <jack.louie.wright@gmail.com>
Date: Thu, 6 Jun 2024 09:27:40 +0100
Subject: [PATCH 2/2] PR 1 : hashtable.mode masking fix

---
 pandas/_libs/hashtable_func_helper.pxi.in |  2 +-
 pandas/tests/series/test_reductions.py    | 10 ++++++++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/pandas/_libs/hashtable_func_helper.pxi.in b/pandas/_libs/hashtable_func_helper.pxi.in
index 5500fadb73b6d..837d083d483fe 100644
--- a/pandas/_libs/hashtable_func_helper.pxi.in
+++ b/pandas/_libs/hashtable_func_helper.pxi.in
@@ -443,7 +443,7 @@ def mode(ndarray[htfunc_t] values, bint dropna, const uint8_t[:] mask=None):
 
     if na_counter > 0:
         res_mask = np.zeros(j+1, dtype=np.bool_)
-        res_mask[j] = True
+        res_mask[j] = (na_counter == max_count)
     return modes[:j + 1], res_mask
 
 
diff --git a/pandas/tests/series/test_reductions.py b/pandas/tests/series/test_reductions.py
index 535a3c9171b40..ad265f491d50e 100644
--- a/pandas/tests/series/test_reductions.py
+++ b/pandas/tests/series/test_reductions.py
@@ -58,6 +58,16 @@ def test_mode_nullable_dtype_edge_case(any_numeric_ea_dtype):
     expected = Series([1], dtype=any_numeric_ea_dtype)
     tm.assert_series_equal(result, expected)
 
+    ser2 = Series([1, 1, 2, 3, pd.NA], dtype=any_numeric_ea_dtype)
+    result = ser2.mode(dropna=False)
+    expected = Series([1], dtype=any_numeric_ea_dtype)
+    tm.assert_series_equal(result, expected)
+
+    ser3 = Series([1, pd.NA, pd.NA], dtype=any_numeric_ea_dtype)
+    result = ser3.mode(dropna=False)
+    expected = Series([pd.NA], dtype=any_numeric_ea_dtype)
+    tm.assert_series_equal(result, expected)
+
 
 def test_mode_infer_string():
     # GH#56183