diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index 219398f4071f5..c9675e0e1961a 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -553,6 +553,7 @@ Datetimelike - Bug in addition with a :class:`Tick` object and a ``np.timedelta64`` object incorrectly raising instead of returning :class:`Timedelta` (:issue:`44474`) - Bug in adding a ``np.timedelta64`` object to a :class:`BusinessDay` or :class:`CustomBusinessDay` object incorrectly raising (:issue:`44532`) - Bug in :meth:`Index.insert` for inserting ``np.datetime64``, ``np.timedelta64`` or ``tuple`` into :class:`Index` with ``dtype='object'`` with negative loc adding ``None`` and replacing existing value (:issue:`44509`) +- Bug in :meth:`Series.mode` with ``DatetimeTZDtype`` incorrectly returning timezone-naive and ``PeriodDtype`` incorrectly raising (:issue:`41927`) - Timedelta diff --git a/pandas/core/algorithms.py b/pandas/core/algorithms.py index 763e76f8497fa..538d9b0348d5f 100644 --- a/pandas/core/algorithms.py +++ b/pandas/core/algorithms.py @@ -215,7 +215,7 @@ def _reconstruct_data( if isinstance(values, cls) and values.dtype == dtype: return values - values = cls._from_sequence(values) + values = cls._from_sequence(values, dtype=dtype) elif is_bool_dtype(dtype): values = values.astype(dtype, copy=False) @@ -960,15 +960,18 @@ def mode(values, dropna: bool = True) -> Series: original = values # categorical is a fast-path - if is_categorical_dtype(values): + if is_categorical_dtype(values.dtype): if isinstance(values, Series): # TODO: should we be passing `name` below? return Series(values._values.mode(dropna=dropna), name=values.name) return values.mode(dropna=dropna) - if dropna and needs_i8_conversion(values.dtype): - mask = values.isnull() - values = values[~mask] + if needs_i8_conversion(values.dtype): + if dropna: + mask = values.isna() + values = values[~mask] + modes = mode(values.view("i8")) + return modes.view(original.dtype) values = _ensure_data(values) diff --git a/pandas/core/series.py b/pandas/core/series.py index 6c6caac955efc..ffa31b4f66211 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -1972,7 +1972,7 @@ def count(self, level=None): self, method="count" ) - def mode(self, dropna=True) -> Series: + def mode(self, dropna: bool = True) -> Series: """ Return the mode(s) of the Series. diff --git a/pandas/tests/series/test_reductions.py b/pandas/tests/series/test_reductions.py index ca30e8f1ee6fd..c5b0428131973 100644 --- a/pandas/tests/series/test_reductions.py +++ b/pandas/tests/series/test_reductions.py @@ -7,6 +7,28 @@ Series, ) import pandas._testing as tm +from pandas.core.algorithms import mode + + +@pytest.mark.parametrize("as_period", [True, False]) +def test_mode_extension_dtype(as_period): + # GH#41927 preserve dt64tz dtype + ser = Series([pd.Timestamp(1979, 4, n) for n in range(1, 5)]) + + if as_period: + ser = ser.dt.to_period("D") + else: + ser = ser.dt.tz_localize("US/Central") + + res = ser.mode() + assert res.dtype == ser.dtype + tm.assert_series_equal(res, ser) + + res = mode(ser._values) + tm.assert_series_equal(res, ser) + + res = mode(pd.Index(ser)) + tm.assert_series_equal(res, ser) def test_reductions_td64_with_nat():