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/_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/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..ad265f491d50e 100644 --- a/pandas/tests/series/test_reductions.py +++ b/pandas/tests/series/test_reductions.py @@ -51,6 +51,24 @@ 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) + + 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 pytest.importorskip("pyarrow")