Skip to content

Commit 34a4c51

Browse files
authored
REF: simplify Block.replace (pandas-dev#37781)
1 parent 25f9b4e commit 34a4c51

File tree

4 files changed

+23
-35
lines changed

4 files changed

+23
-35
lines changed

doc/source/whatsnew/v1.2.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,7 @@ Other
586586

587587
- Bug in :meth:`DataFrame.replace` and :meth:`Series.replace` incorrectly raising ``AssertionError`` instead of ``ValueError`` when invalid parameter combinations are passed (:issue:`36045`)
588588
- Bug in :meth:`DataFrame.replace` and :meth:`Series.replace` with numeric values and string ``to_replace`` (:issue:`34789`)
589+
- Bug in :meth:`DataFrame.replace` and :meth:`Series.replace` incorrectly casting from ``PeriodDtype`` to object dtype (:issue:`34871`)
589590
- Fixed bug in metadata propagation incorrectly copying DataFrame columns as metadata when the column name overlaps with the metadata name (:issue:`37037`)
590591
- Fixed metadata propagation in the :class:`Series.dt`, :class:`Series.str` accessors, :class:`DataFrame.duplicated`, :class:`DataFrame.stack`, :class:`DataFrame.unstack`, :class:`DataFrame.pivot`, :class:`DataFrame.append`, :class:`DataFrame.diff`, :class:`DataFrame.applymap` and :class:`DataFrame.update` methods (:issue:`28283`) (:issue:`37381`)
591592
- Bug in :meth:`Index.union` behaving differently depending on whether operand is a :class:`Index` or other list-like (:issue:`36384`)

pandas/core/internals/blocks.py

+9-24
Original file line numberDiff line numberDiff line change
@@ -752,36 +752,21 @@ def replace(
752752
to_replace = convert_scalar_for_putitemlike(to_replace, values.dtype)
753753

754754
mask = missing.mask_missing(values, to_replace)
755+
if not mask.any():
756+
# Note: we get here with test_replace_extension_other incorrectly
757+
# bc _can_hold_element is incorrect.
758+
return [self] if inplace else [self.copy()]
755759

756-
try:
757-
blocks = self.putmask(mask, value, inplace=inplace)
758-
# Note: it is _not_ the case that self._can_hold_element(value)
759-
# is always true at this point. In particular, that can fail
760-
# for:
761-
# "2u" with bool-dtype, float-dtype
762-
# 0.5 with int64-dtype
763-
# np.nan with int64-dtype
764-
except (TypeError, ValueError):
765-
# GH 22083, TypeError or ValueError occurred within error handling
766-
# causes infinite loop. Cast and retry only if not objectblock.
767-
if is_object_dtype(self):
768-
raise
769-
770-
if not self.is_extension:
771-
# TODO: https://github.com/pandas-dev/pandas/issues/32586
772-
# Need an ExtensionArray._can_hold_element to indicate whether
773-
# a scalar value can be placed in the array.
774-
assert not self._can_hold_element(value), value
775-
776-
# try again with a compatible block
777-
block = self.astype(object)
778-
return block.replace(
760+
if not self._can_hold_element(value):
761+
blk = self.astype(object)
762+
return blk.replace(
779763
to_replace=original_to_replace,
780764
value=value,
781-
inplace=inplace,
765+
inplace=True,
782766
regex=regex,
783767
)
784768

769+
blocks = self.putmask(mask, value, inplace=inplace)
785770
blocks = extend_blocks(
786771
[b.convert(numeric=False, copy=not inplace) for b in blocks]
787772
)

pandas/tests/frame/methods/test_replace.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -1523,18 +1523,18 @@ def test_replace_with_duplicate_columns(self, replacement):
15231523

15241524
tm.assert_frame_equal(result, expected)
15251525

1526-
@pytest.mark.xfail(
1527-
reason="replace() changes dtype from period to object, see GH34871", strict=True
1528-
)
1529-
def test_replace_period_ignore_float(self):
1526+
def test_replace_period_ignore_float(self, frame_or_series):
15301527
"""
15311528
Regression test for GH#34871: if df.replace(1.0, 0.0) is called on a df
15321529
with a Period column the old, faulty behavior is to raise TypeError.
15331530
"""
1534-
df = DataFrame({"Per": [pd.Period("2020-01")] * 3})
1535-
result = df.replace(1.0, 0.0)
1536-
expected = DataFrame({"Per": [pd.Period("2020-01")] * 3})
1537-
tm.assert_frame_equal(expected, result)
1531+
obj = DataFrame({"Per": [pd.Period("2020-01")] * 3})
1532+
if frame_or_series is not DataFrame:
1533+
obj = obj["Per"]
1534+
1535+
expected = obj.copy()
1536+
result = obj.replace(1.0, 0.0)
1537+
tm.assert_equal(expected, result)
15381538

15391539
def test_replace_value_category_type(self):
15401540
"""

pandas/tests/series/methods/test_replace.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -424,10 +424,12 @@ def test_replace_only_one_dictlike_arg(self):
424424
with pytest.raises(ValueError, match=msg):
425425
ser.replace(to_replace, value)
426426

427-
def test_replace_extension_other(self):
427+
def test_replace_extension_other(self, frame_or_series):
428428
# https://github.com/pandas-dev/pandas/issues/34530
429-
ser = pd.Series(pd.array([1, 2, 3], dtype="Int64"))
430-
ser.replace("", "") # no exception
429+
obj = frame_or_series(pd.array([1, 2, 3], dtype="Int64"))
430+
result = obj.replace("", "") # no exception
431+
# should not have changed dtype
432+
tm.assert_equal(obj, result)
431433

432434
def test_replace_with_compiled_regex(self):
433435
# https://github.com/pandas-dev/pandas/issues/35680

0 commit comments

Comments
 (0)