Skip to content

Commit 92e2379

Browse files
authored
BUG: Series.interpolate with dt64/td64 raises (#51005)
* BUG: Series.interpolate with dt64/td64 raises * mypy fixup * fix+test DataFrame case
1 parent 50d288e commit 92e2379

File tree

5 files changed

+79
-1
lines changed

5 files changed

+79
-1
lines changed

doc/source/whatsnew/v2.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,7 @@ Datetimelike
11061106
- Bug in :func:`to_datetime` was raising ``ValueError`` when parsing mixed-offset :class:`Timestamp` with ``errors='ignore'`` (:issue:`50585`)
11071107
- Bug in :func:`to_datetime` was incorrectly handling floating-point inputs within 1 ``unit`` of the overflow boundaries (:issue:`50183`)
11081108
- Bug in :func:`to_datetime` with unit of "Y" or "M" giving incorrect results, not matching pointwise :class:`Timestamp` results (:issue:`50870`)
1109+
- Bug in :meth:`Series.interpolate` and :meth:`DataFrame.interpolate` with datetime or timedelta dtypes incorrectly raising ``ValueError`` (:issue:`11312`)
11091110
- Bug in :func:`to_datetime` was not returning input with ``errors='ignore'`` when input was out-of-bounds (:issue:`50587`)
11101111
-
11111112

pandas/core/internals/blocks.py

+33
Original file line numberDiff line numberDiff line change
@@ -1599,6 +1599,7 @@ def values_for_json(self) -> np.ndarray:
15991599

16001600
def interpolate(
16011601
self,
1602+
*,
16021603
method: FillnaOptions = "pad",
16031604
axis: int = 0,
16041605
inplace: bool = False,
@@ -1974,6 +1975,38 @@ class DatetimeLikeBlock(NDArrayBackedExtensionBlock):
19741975
def values_for_json(self) -> np.ndarray:
19751976
return self.values._ndarray
19761977

1978+
def interpolate(
1979+
self,
1980+
*,
1981+
method: FillnaOptions = "pad",
1982+
index: Index | None = None,
1983+
axis: int = 0,
1984+
inplace: bool = False,
1985+
limit: int | None = None,
1986+
fill_value=None,
1987+
**kwargs,
1988+
):
1989+
values = self.values
1990+
1991+
# error: Non-overlapping equality check (left operand type:
1992+
# "Literal['backfill', 'bfill', 'ffill', 'pad']", right operand type:
1993+
# "Literal['linear']") [comparison-overlap]
1994+
if method == "linear": # type: ignore[comparison-overlap]
1995+
# TODO: GH#50950 implement for arbitrary EAs
1996+
data_out = values._ndarray if inplace else values._ndarray.copy()
1997+
missing.interpolate_array_2d(
1998+
data_out, method=method, limit=limit, index=index, axis=axis
1999+
)
2000+
new_values = type(values)._simple_new(data_out, dtype=values.dtype)
2001+
return self.make_block_same_class(new_values)
2002+
2003+
elif values.ndim == 2 and axis == 0:
2004+
# NDArrayBackedExtensionArray.fillna assumes axis=1
2005+
new_values = values.T.fillna(value=fill_value, method=method, limit=limit).T
2006+
else:
2007+
new_values = values.fillna(value=fill_value, method=method, limit=limit)
2008+
return self.make_block_same_class(new_values)
2009+
19772010

19782011
class DatetimeTZBlock(DatetimeLikeBlock):
19792012
"""implement a datetime64 block with a tz attribute"""

pandas/core/missing.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import numpy as np
1717

1818
from pandas._libs import (
19+
NaT,
1920
algos,
2021
lib,
2122
)
@@ -457,6 +458,11 @@ def _interpolate_1d(
457458
# sort preserve_nans and convert to list
458459
preserve_nans = sorted(preserve_nans)
459460

461+
is_datetimelike = needs_i8_conversion(yvalues.dtype)
462+
463+
if is_datetimelike:
464+
yvalues = yvalues.view("i8")
465+
460466
if method in NP_METHODS:
461467
# np.interp requires sorted X values, #21037
462468

@@ -476,7 +482,10 @@ def _interpolate_1d(
476482
**kwargs,
477483
)
478484

479-
yvalues[preserve_nans] = np.nan
485+
if is_datetimelike:
486+
yvalues[preserve_nans] = NaT.value
487+
else:
488+
yvalues[preserve_nans] = np.nan
480489
return
481490

482491

pandas/tests/frame/methods/test_interpolate.py

+23
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,36 @@
55

66
from pandas import (
77
DataFrame,
8+
NaT,
89
Series,
910
date_range,
1011
)
1112
import pandas._testing as tm
1213

1314

1415
class TestDataFrameInterpolate:
16+
def test_interpolate_datetimelike_values(self, frame_or_series):
17+
# GH#11312, GH#51005
18+
orig = Series(date_range("2012-01-01", periods=5))
19+
ser = orig.copy()
20+
ser[2] = NaT
21+
22+
res = frame_or_series(ser).interpolate()
23+
expected = frame_or_series(orig)
24+
tm.assert_equal(res, expected)
25+
26+
# datetime64tz cast
27+
ser_tz = ser.dt.tz_localize("US/Pacific")
28+
res_tz = frame_or_series(ser_tz).interpolate()
29+
expected_tz = frame_or_series(orig.dt.tz_localize("US/Pacific"))
30+
tm.assert_equal(res_tz, expected_tz)
31+
32+
# timedelta64 cast
33+
ser_td = ser - ser[0]
34+
res_td = frame_or_series(ser_td).interpolate()
35+
expected_td = frame_or_series(orig - orig[0])
36+
tm.assert_equal(res_td, expected_td)
37+
1538
def test_interpolate_inplace(self, frame_or_series, using_array_manager, request):
1639
# GH#44749
1740
if using_array_manager and frame_or_series is DataFrame:

pandas/tests/series/methods/test_interpolate.py

+12
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ def interp_methods_ind(request):
7878

7979

8080
class TestSeriesInterpolateData:
81+
@pytest.mark.xfail(reason="EA.fillna does not handle 'linear' method")
82+
def test_interpolate_period_values(self):
83+
orig = Series(date_range("2012-01-01", periods=5))
84+
ser = orig.copy()
85+
ser[2] = pd.NaT
86+
87+
# period cast
88+
ser_per = ser.dt.to_period("D")
89+
res_per = ser_per.interpolate()
90+
expected_per = orig.dt.to_period("D")
91+
tm.assert_series_equal(res_per, expected_per)
92+
8193
def test_interpolate(self, datetime_series):
8294
ts = Series(np.arange(len(datetime_series), dtype=float), datetime_series.index)
8395

0 commit comments

Comments
 (0)