Skip to content

Commit 6c14c22

Browse files
committed
fix: pd.to_numeric handling of datetime
1 parent 1863adb commit 6c14c22

File tree

4 files changed

+62
-3
lines changed

4 files changed

+62
-3
lines changed

doc/source/whatsnew/v3.0.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,7 @@ Other
11911191
- Bug in :func:`eval` where method calls on binary operations like ``(x + y).dropna()`` would raise ``AttributeError: 'BinOp' object has no attribute 'value'`` (:issue:`61175`)
11921192
- Bug in :func:`eval` where the names of the :class:`Series` were not preserved when using ``engine="numexpr"``. (:issue:`10239`)
11931193
- Bug in :func:`eval` with ``engine="numexpr"`` returning unexpected result for float division. (:issue:`59736`)
1194+
- Bug in :func:`to_numeric` for ``datetime``, :class:`Series` and ``NaT`` conversions. (:issue:`43280`)
11941195
- Bug in :func:`to_numeric` raising ``TypeError`` when ``arg`` is a :class:`Timedelta` or :class:`Timestamp` scalar. (:issue:`59944`)
11951196
- Bug in :func:`unique` on :class:`Index` not always returning :class:`Index` (:issue:`57043`)
11961197
- Bug in :meth:`DataFrame.apply` raising ``RecursionError`` when passing ``func=list[int]``. (:issue:`61565`)

pandas/_libs/lib.pyx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2449,6 +2449,16 @@ def maybe_convert_numeric(
24492449
elif is_decimal(val):
24502450
floats[i] = complexes[i] = val
24512451
seen.float_ = True
2452+
elif PyDateTime_Check(val) or cnp.is_datetime64_object(val):
2453+
seen.datetime_ = True
2454+
if val in na_values or checknull(val):
2455+
seen.saw_null()
2456+
mask[i] = 1
2457+
floats[i] = NaN
2458+
else:
2459+
ints[i] = np.datetime64(val).astype(int)
2460+
# because of pd.NaT, we may need to return in floats
2461+
floats[i] = float(ints[i])
24522462
else:
24532463
try:
24542464
floatify(val, &fval, &maybe_int)
@@ -2517,7 +2527,7 @@ def maybe_convert_numeric(
25172527
if seen.null_ and convert_to_masked_nullable:
25182528
return (floats, mask.view(np.bool_))
25192529
return (floats, None)
2520-
elif seen.int_:
2530+
elif seen.int_ or seen.datetime_:
25212531
if seen.null_ and convert_to_masked_nullable:
25222532
if seen.uint_:
25232533
return (uints, mask.view(np.bool_))

pandas/core/tools/numeric.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,6 @@ def to_numeric(
227227
new_mask: np.ndarray | None = None
228228
if is_numeric_dtype(values_dtype):
229229
pass
230-
elif lib.is_np_dtype(values_dtype, "mM"):
231-
values = values.view(np.int64)
232230
else:
233231
values = ensure_object(values)
234232
coerce_numeric = errors != "raise"

pandas/tests/tools/test_to_numeric.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
from datetime import datetime
12
import decimal
3+
from functools import partial
24

35
import numpy as np
46
from numpy import iinfo
@@ -902,6 +904,54 @@ def test_to_numeric_dtype_backend_error(dtype_backend):
902904
tm.assert_series_equal(result, expected)
903905

904906

907+
@pytest.mark.parametrize(
908+
"input_value, expected, pd_type",
909+
[
910+
(datetime(2021, 8, 22), 1629590400000000, "scalar"),
911+
(datetime(2025, 2, 21), 1740096000000000, "scalar"),
912+
(pd.NaT, np.nan, "scalar"),
913+
([datetime(2021, 8, 22)], [1629590400000000], "series"),
914+
([datetime(2025, 2, 21)], [1740096000000000], "series"),
915+
([pd.NaT], [np.nan], "series"),
916+
([datetime(2021, 8, 22), pd.NaT], [float(1629590400000000), np.nan], "series"),
917+
([pd.NaT, datetime(2021, 8, 22)], [np.nan, float(1629590400000000)], "series"),
918+
(
919+
["apple", 1, datetime(2021, 8, 22)],
920+
[np.nan, float(1.0), float(1629590400000000)],
921+
"series_coerce",
922+
),
923+
([pd.NaT], [np.nan], "series_partial"),
924+
([datetime(2025, 2, 21)], [1740096000000000], "series_partial"),
925+
(
926+
[pd.NaT, datetime(2025, 2, 21)],
927+
[np.nan, float(1740096000000000)],
928+
"series_partial",
929+
),
930+
],
931+
)
932+
def test_to_numeric_datetime(input_value, expected, pd_type):
933+
"""Test converting a scalar datetime to numeric."""
934+
if pd_type == "scalar":
935+
val = to_numeric(input_value)
936+
# special handling because Nan!=Nan
937+
if pd.isna(expected):
938+
assert pd.isna(val)
939+
else:
940+
assert val == expected
941+
942+
elif pd_type == "series":
943+
val = to_numeric(Series(input_value))
944+
tm.assert_series_equal(val, Series(expected))
945+
946+
elif pd_type == "series_coerce":
947+
val = to_numeric(Series(input_value), errors="coerce")
948+
tm.assert_series_equal(val, Series(expected))
949+
950+
elif pd_type == "series_partial":
951+
val = Series(input_value).apply(partial(to_numeric))
952+
tm.assert_series_equal(val, Series(expected))
953+
954+
905955
def test_invalid_dtype_backend():
906956
ser = Series([1, 2, 3])
907957
msg = (

0 commit comments

Comments
 (0)