Skip to content

BUG: Series.mode with dt64tz or PeriodDtype #44582

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Nov 26, 2021
Merged
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.4.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 8 additions & 5 deletions pandas/core/algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
22 changes: 22 additions & 0 deletions pandas/tests/series/test_reductions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down