Skip to content

Commit 3ff71e4

Browse files
authored
BUG: replace raising RecursionError (#50760)
* BUG: replace raising RecursionError * Fix other replace bug
1 parent 35586f3 commit 3ff71e4

File tree

4 files changed

+42
-5
lines changed

4 files changed

+42
-5
lines changed

doc/source/whatsnew/v2.0.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,8 @@ Missing
981981
- Bug in :meth:`Series.map` caused incorrect result when data has NaNs and defaultdict mapping was used (:issue:`48813`)
982982
- Bug in :class:`NA` raising a ``TypeError`` instead of return :class:`NA` when performing a binary operation with a ``bytes`` object (:issue:`49108`)
983983
- Bug in :meth:`DataFrame.update` with ``overwrite=False`` raising ``TypeError`` when ``self`` has column with ``NaT`` values and column not present in ``other`` (:issue:`16713`)
984+
- Bug in :meth:`Series.replace` raising ``RecursionError`` when replacing value in object-dtype :class:`Series` containing ``NA`` (:issue:`47480`)
985+
- Bug in :meth:`Series.replace` raising ``RecursionError`` when replacing value in numeric :class:`Series` with ``NA`` (:issue:`50758`)
984986

985987
MultiIndex
986988
^^^^^^^^^^

pandas/core/internals/blocks.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
writers,
2222
)
2323
from pandas._libs.internals import BlockPlacement
24+
from pandas._libs.missing import NA
2425
from pandas._libs.tslibs import IncompatibleFrequency
2526
from pandas._typing import (
2627
ArrayLike,
@@ -569,7 +570,7 @@ def replace(
569570
return blocks
570571

571572
elif self.ndim == 1 or self.shape[0] == 1:
572-
if value is None:
573+
if value is None or value is NA:
573574
blk = self.astype(np.dtype(object))
574575
else:
575576
blk = self.coerce_to_target_dtype(value)

pandas/core/missing.py

+16-4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from pandas.core.dtypes.common import (
3333
is_array_like,
3434
is_numeric_v_string_like,
35+
is_object_dtype,
3536
needs_i8_conversion,
3637
)
3738
from pandas.core.dtypes.missing import (
@@ -83,6 +84,12 @@ def mask_missing(arr: ArrayLike, values_to_mask) -> npt.NDArray[np.bool_]:
8384
# _DTypeDict, Tuple[Any, Any]]]"
8485
values_to_mask = np.array(values_to_mask, dtype=dtype) # type: ignore[arg-type]
8586

87+
potential_na = False
88+
if is_object_dtype(arr):
89+
# pre-compute mask to avoid comparison to NA
90+
potential_na = True
91+
arr_mask = ~isna(arr)
92+
8693
na_mask = isna(values_to_mask)
8794
nonna = values_to_mask[~na_mask]
8895

@@ -93,10 +100,15 @@ def mask_missing(arr: ArrayLike, values_to_mask) -> npt.NDArray[np.bool_]:
93100
# GH#29553 prevent numpy deprecation warnings
94101
pass
95102
else:
96-
new_mask = arr == x
97-
if not isinstance(new_mask, np.ndarray):
98-
# usually BooleanArray
99-
new_mask = new_mask.to_numpy(dtype=bool, na_value=False)
103+
if potential_na:
104+
new_mask = np.zeros(arr.shape, dtype=np.bool_)
105+
new_mask[arr_mask] = arr[arr_mask] == x
106+
else:
107+
new_mask = arr == x
108+
109+
if not isinstance(new_mask, np.ndarray):
110+
# usually BooleanArray
111+
new_mask = new_mask.to_numpy(dtype=bool, na_value=False)
100112
mask |= new_mask
101113

102114
if na_mask.any():

pandas/tests/series/methods/test_replace.py

+22
Original file line numberDiff line numberDiff line change
@@ -690,3 +690,25 @@ def test_replace_change_dtype_series(self):
690690
df = pd.DataFrame.from_dict({"Test": ["0.5", None, "0.6"]})
691691
df["Test"] = df["Test"].fillna(np.nan)
692692
tm.assert_frame_equal(df, expected)
693+
694+
@pytest.mark.parametrize("dtype", ["object", "Int64"])
695+
def test_replace_na_in_obj_column(self, dtype):
696+
# GH#47480
697+
ser = pd.Series([0, 1, pd.NA], dtype=dtype)
698+
expected = pd.Series([0, 2, pd.NA], dtype=dtype)
699+
result = ser.replace(to_replace=1, value=2)
700+
tm.assert_series_equal(result, expected)
701+
702+
ser.replace(to_replace=1, value=2, inplace=True)
703+
tm.assert_series_equal(ser, expected)
704+
705+
@pytest.mark.parametrize("val", [0, 0.5])
706+
def test_replace_numeric_column_with_na(self, val):
707+
# GH#50758
708+
ser = pd.Series([val, 1])
709+
expected = pd.Series([val, pd.NA])
710+
result = ser.replace(to_replace=1, value=pd.NA)
711+
tm.assert_series_equal(result, expected)
712+
713+
ser.replace(to_replace=1, value=pd.NA, inplace=True)
714+
tm.assert_series_equal(ser, expected)

0 commit comments

Comments
 (0)