Skip to content

Commit fe92f93

Browse files
REGR: fillna on datetime64[ns, UTC] column hits RecursionError (#39194)
* Revert "CLN: remove unnecessary DatetimeTZBlock.fillna (#37040)" This reverts commit 58f7468. * add test and release note
1 parent 01c7d77 commit fe92f93

File tree

4 files changed

+42
-11
lines changed

4 files changed

+42
-11
lines changed

doc/source/whatsnew/v1.2.1.rst

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Fixed regressions
2727
- Fixed regression in :meth:`read_csv` and other read functions were the encoding error policy (``errors``) did not default to ``"replace"`` when no encoding was specified (:issue:`38989`)
2828
- Fixed regression in :meth:`DataFrame.replace` raising ``ValueError`` when :class:`DataFrame` has dtype ``bytes`` (:issue:`38900`)
2929
- Fixed regression in :meth:`DataFrameGroupBy.diff` raising for ``int8`` and ``int16`` columns (:issue:`39050`)
30+
- Fixed regression in :meth:`Series.fillna` that raised ``RecursionError`` with ``datetime64[ns, UTC]`` dtype (:issue:`38851`)
3031
- Fixed regression that raised ``AttributeError`` with PyArrow versions [0.16.0, 1.0.0) (:issue:`38801`)
3132
- Fixed regression in :meth:`DataFrame.groupby` when aggregating an :class:`ExtensionDType` that could fail for non-numeric values (:issue:`38980`)
3233
- Fixed regression in :meth:`DataFrame.loc.__setitem__` raising ``KeyError`` with :class:`MultiIndex` and list-like columns indexer enlarging :class:`DataFrame` (:issue:`39147`)

pandas/core/internals/blocks.py

+30-9
Original file line numberDiff line numberDiff line change
@@ -1968,7 +1968,13 @@ class IntBlock(NumericBlock):
19681968
class DatetimeLikeBlockMixin(HybridMixin, Block):
19691969
"""Mixin class for DatetimeBlock, DatetimeTZBlock, and TimedeltaBlock."""
19701970

1971-
_can_hold_na = True
1971+
@property
1972+
def _holder(self):
1973+
return DatetimeArray
1974+
1975+
@property
1976+
def fill_value(self):
1977+
return np.datetime64("NaT", "ns")
19721978

19731979
def get_values(self, dtype: Optional[Dtype] = None):
19741980
"""
@@ -2052,8 +2058,10 @@ def where(self, other, cond, errors="raise", axis: int = 0) -> List["Block"]:
20522058
class DatetimeBlock(DatetimeLikeBlockMixin):
20532059
__slots__ = ()
20542060
is_datetime = True
2055-
_holder = DatetimeArray
2056-
fill_value = np.datetime64("NaT", "ns")
2061+
2062+
@property
2063+
def _can_hold_na(self):
2064+
return True
20572065

20582066
def _maybe_coerce_values(self, values):
20592067
"""
@@ -2099,18 +2107,18 @@ class DatetimeTZBlock(ExtensionBlock, DatetimeBlock):
20992107
is_extension = True
21002108

21012109
internal_values = Block.internal_values
2102-
2103-
_holder = DatetimeBlock._holder
21042110
_can_hold_element = DatetimeBlock._can_hold_element
21052111
to_native_types = DatetimeBlock.to_native_types
21062112
diff = DatetimeBlock.diff
2107-
fillna = DatetimeBlock.fillna # i.e. Block.fillna
2108-
fill_value = DatetimeBlock.fill_value
2109-
_can_hold_na = DatetimeBlock._can_hold_na
2113+
fill_value = np.datetime64("NaT", "ns")
21102114
where = DatetimeBlock.where
21112115

21122116
array_values = ExtensionBlock.array_values
21132117

2118+
@property
2119+
def _holder(self):
2120+
return DatetimeArray
2121+
21142122
def _maybe_coerce_values(self, values):
21152123
"""
21162124
Input validation for values passed to __init__. Ensure that
@@ -2175,6 +2183,17 @@ def external_values(self):
21752183
# return an object-dtype ndarray of Timestamps.
21762184
return np.asarray(self.values.astype("datetime64[ns]", copy=False))
21772185

2186+
def fillna(self, value, limit=None, inplace=False, downcast=None):
2187+
# We support filling a DatetimeTZ with a `value` whose timezone
2188+
# is different by coercing to object.
2189+
if self._can_hold_element(value):
2190+
return super().fillna(value, limit, inplace, downcast)
2191+
2192+
# different timezones, or a non-tz
2193+
return self.astype(object).fillna(
2194+
value, limit=limit, inplace=inplace, downcast=downcast
2195+
)
2196+
21782197
def quantile(self, qs, interpolation="linear", axis=0):
21792198
naive = self.values.view("M8[ns]")
21802199

@@ -2211,9 +2230,11 @@ def _check_ndim(self, values, ndim):
22112230
return ndim
22122231

22132232

2214-
class TimeDeltaBlock(DatetimeLikeBlockMixin):
2233+
class TimeDeltaBlock(DatetimeLikeBlockMixin, IntBlock):
22152234
__slots__ = ()
22162235
is_timedelta = True
2236+
_can_hold_na = True
2237+
is_numeric = False
22172238
fill_value = np.timedelta64("NaT", "ns")
22182239

22192240
def _maybe_coerce_values(self, values):

pandas/core/internals/managers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1891,7 +1891,7 @@ def _consolidate(blocks):
18911891
merged_blocks = _merge_blocks(
18921892
list(group_blocks), dtype=dtype, can_consolidate=_can_consolidate
18931893
)
1894-
new_blocks.extend(merged_blocks)
1894+
new_blocks = extend_blocks(merged_blocks, new_blocks)
18951895
return new_blocks
18961896

18971897

pandas/tests/series/methods/test_fillna.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime, timedelta
1+
from datetime import datetime, timedelta, timezone
22

33
import numpy as np
44
import pytest
@@ -13,6 +13,7 @@
1313
Series,
1414
Timedelta,
1515
Timestamp,
16+
date_range,
1617
isna,
1718
)
1819
import pandas._testing as tm
@@ -724,6 +725,14 @@ def test_fillna_method_and_limit_invalid(self):
724725
with pytest.raises(ValueError, match=msg):
725726
ser.fillna(1, limit=limit, method=method)
726727

728+
def test_fillna_datetime64_with_timezone_tzinfo(self):
729+
# https://github.com/pandas-dev/pandas/issues/38851
730+
s = Series(date_range("2020", periods=3, tz="UTC"))
731+
expected = s.astype(object)
732+
s[1] = NaT
733+
result = s.fillna(datetime(2020, 1, 2, tzinfo=timezone.utc))
734+
tm.assert_series_equal(result, expected)
735+
727736

728737
class TestFillnaPad:
729738
def test_fillna_bug(self):

0 commit comments

Comments
 (0)