Skip to content

Commit 51b12e8

Browse files
authored
BUG: Avoid casting to float for datetimelike in min/max reductions (#60850)
* BUG: Avoid casting to float for datetimelike in min/max reductions * Fix a line and add whatsnew
1 parent 1cd4c63 commit 51b12e8

File tree

3 files changed

+48
-2
lines changed

3 files changed

+48
-2
lines changed

doc/source/whatsnew/v3.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,7 @@ Datetimelike
631631
- Bug in :func:`date_range` where using a negative frequency value would not include all points between the start and end values (:issue:`56147`)
632632
- Bug in :func:`tseries.api.guess_datetime_format` would fail to infer time format when "%Y" == "%H%M" (:issue:`57452`)
633633
- Bug in :func:`tseries.frequencies.to_offset` would fail to parse frequency strings starting with "LWOM" (:issue:`59218`)
634+
- Bug in :meth:`DataFrame.min` and :meth:`DataFrame.max` casting ``datetime64`` and ``timedelta64`` columns to ``float64`` and losing precision (:issue:`60850`)
634635
- Bug in :meth:`Dataframe.agg` with df with missing values resulting in IndexError (:issue:`58810`)
635636
- Bug in :meth:`DatetimeIndex.is_year_start` and :meth:`DatetimeIndex.is_quarter_start` does not raise on Custom business days frequencies bigger then "1C" (:issue:`58664`)
636637
- Bug in :meth:`DatetimeIndex.is_year_start` and :meth:`DatetimeIndex.is_quarter_start` returning ``False`` on double-digit frequencies (:issue:`58523`)

pandas/core/nanops.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -1093,11 +1093,14 @@ def reduction(
10931093
if values.size == 0:
10941094
return _na_for_min_count(values, axis)
10951095

1096+
dtype = values.dtype
10961097
values, mask = _get_values(
10971098
values, skipna, fill_value_typ=fill_value_typ, mask=mask
10981099
)
10991100
result = getattr(values, meth)(axis)
1100-
result = _maybe_null_out(result, axis, mask, values.shape)
1101+
result = _maybe_null_out(
1102+
result, axis, mask, values.shape, datetimelike=dtype.kind in "mM"
1103+
)
11011104
return result
11021105

11031106
return reduction
@@ -1499,6 +1502,7 @@ def _maybe_null_out(
14991502
mask: npt.NDArray[np.bool_] | None,
15001503
shape: tuple[int, ...],
15011504
min_count: int = 1,
1505+
datetimelike: bool = False,
15021506
) -> np.ndarray | float | NaTType:
15031507
"""
15041508
Returns
@@ -1520,7 +1524,10 @@ def _maybe_null_out(
15201524
null_mask = np.broadcast_to(below_count, new_shape)
15211525

15221526
if np.any(null_mask):
1523-
if is_numeric_dtype(result):
1527+
if datetimelike:
1528+
# GH#60646 For datetimelike, no need to cast to float
1529+
result[null_mask] = iNaT
1530+
elif is_numeric_dtype(result):
15241531
if np.iscomplexobj(result):
15251532
result = result.astype("c16")
15261533
elif not is_float_dtype(result):

pandas/tests/frame/test_reductions.py

+38
Original file line numberDiff line numberDiff line change
@@ -1544,6 +1544,44 @@ def test_min_max_dt64_with_NaT(self):
15441544
exp = Series([pd.NaT], index=["foo"])
15451545
tm.assert_series_equal(res, exp)
15461546

1547+
def test_min_max_dt64_with_NaT_precision(self):
1548+
# GH#60646 Make sure the reduction doesn't cast input timestamps to
1549+
# float and lose precision.
1550+
df = DataFrame(
1551+
{"foo": [pd.NaT, pd.NaT, Timestamp("2012-05-01 09:20:00.123456789")]},
1552+
dtype="datetime64[ns]",
1553+
)
1554+
1555+
res = df.min(axis=1)
1556+
exp = df.foo.rename(None)
1557+
tm.assert_series_equal(res, exp)
1558+
1559+
res = df.max(axis=1)
1560+
exp = df.foo.rename(None)
1561+
tm.assert_series_equal(res, exp)
1562+
1563+
def test_min_max_td64_with_NaT_precision(self):
1564+
# GH#60646 Make sure the reduction doesn't cast input timedeltas to
1565+
# float and lose precision.
1566+
df = DataFrame(
1567+
{
1568+
"foo": [
1569+
pd.NaT,
1570+
pd.NaT,
1571+
to_timedelta("10000 days 06:05:01.123456789"),
1572+
],
1573+
},
1574+
dtype="timedelta64[ns]",
1575+
)
1576+
1577+
res = df.min(axis=1)
1578+
exp = df.foo.rename(None)
1579+
tm.assert_series_equal(res, exp)
1580+
1581+
res = df.max(axis=1)
1582+
exp = df.foo.rename(None)
1583+
tm.assert_series_equal(res, exp)
1584+
15471585
def test_min_max_dt64_with_NaT_skipna_false(self, request, tz_naive_fixture):
15481586
# GH#36907
15491587
tz = tz_naive_fixture

0 commit comments

Comments
 (0)