From 6e1accbff5e9983cf7e9ae9324e328a4aa447d3e Mon Sep 17 00:00:00 2001 From: Simon Hawkins Date: Sun, 20 Mar 2022 13:33:12 +0000 Subject: [PATCH] REGR: RecursionError when attempting to replace np.nan values --- pandas/core/dtypes/cast.py | 5 +++-- pandas/core/internals/blocks.py | 6 +++--- pandas/tests/frame/methods/test_replace.py | 7 +++++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index cba055d5b4345..a375a26fe64ad 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -1454,7 +1454,7 @@ def _ensure_nanosecond_dtype(dtype: DtypeObj) -> DtypeObj: # TODO: other value-dependent functions to standardize here include # dtypes.concat.cast_to_common_type and Index._find_common_type_compat -def find_result_type(left: ArrayLike, right: Any) -> DtypeObj: +def find_result_type(left: ArrayLike, right: Any, strict_na: bool = False) -> DtypeObj: """ Find the type/dtype for a the result of an operation between these objects. @@ -1466,6 +1466,7 @@ def find_result_type(left: ArrayLike, right: Any) -> DtypeObj: ---------- left : np.ndarray or ExtensionArray right : Any + strict_na: bool Returns ------- @@ -1491,7 +1492,7 @@ def find_result_type(left: ArrayLike, right: Any) -> DtypeObj: new_dtype = np.result_type(left, right) - elif is_valid_na_for_dtype(right, left.dtype): + elif not strict_na and is_valid_na_for_dtype(right, left.dtype): # e.g. IntervalDtype[int] and None/np.nan new_dtype = ensure_dtype_can_hold_na(left.dtype) diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index 2ff195fa8e5d2..1f44b92b2ec01 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -436,7 +436,7 @@ def split_and_operate(self, func, *args, **kwargs) -> list[Block]: # Up/Down-casting @final - def coerce_to_target_dtype(self, other) -> Block: + def coerce_to_target_dtype(self, other, strict_na: bool = False) -> Block: """ coerce the current block to a dtype compat for other we will return a block, possibly object, and not raise @@ -444,7 +444,7 @@ def coerce_to_target_dtype(self, other) -> Block: we can also safely try to coerce to the same dtype and will receive the same block """ - new_dtype = find_result_type(self.values, other) + new_dtype = find_result_type(self.values, other, strict_na=strict_na) return self.astype(new_dtype, copy=False) @@ -601,7 +601,7 @@ def replace( return blocks elif self.ndim == 1 or self.shape[0] == 1: - blk = self.coerce_to_target_dtype(value) + blk = self.coerce_to_target_dtype(value, strict_na=True) return blk.replace( to_replace=to_replace, value=value, diff --git a/pandas/tests/frame/methods/test_replace.py b/pandas/tests/frame/methods/test_replace.py index 870a64cfa59c9..8879225436586 100644 --- a/pandas/tests/frame/methods/test_replace.py +++ b/pandas/tests/frame/methods/test_replace.py @@ -675,6 +675,13 @@ def test_replace_NAT_with_None(self): expected = DataFrame([None, None]) tm.assert_frame_equal(result, expected) + def test_replace_float_nan_with_pd_NA(self): + # gh-45725 + df = DataFrame([np.nan, np.nan], dtype=float) + result = df.replace({np.nan: pd.NA}) + expected = DataFrame([pd.NA, pd.NA], dtype=object) + tm.assert_frame_equal(result, expected) + def test_replace_value_is_none(self, datetime_frame): orig_value = datetime_frame.iloc[0, 0] orig2 = datetime_frame.iloc[1, 0]