diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 63902b53ea36d..8319aa957d232 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -620,6 +620,7 @@ Missing - Bug in :class:`Grouper` now correctly propagates ``dropna`` argument and :meth:`DataFrameGroupBy.transform` now correctly handles missing values for ``dropna=True`` (:issue:`35612`) - Bug in :func:`isna`, and :meth:`Series.isna`, :meth:`Index.isna`, :meth:`DataFrame.isna` (and the corresponding ``notna`` functions) not recognizing ``Decimal("NaN")`` objects (:issue:`39409`) +- Bug in :meth:`Series.fillna` and :meth:`DataFrame.fillna` overwriting missing values in indices skipped by the ``value`` argument (:issue:`40498`) - MultiIndex diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 8524907a84099..affcc821457e8 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -6524,6 +6524,11 @@ def fillna( value, dtype_if_empty=object ) value = value.reindex(self.index, copy=False) + + # GH-40498: Indices to not apply fillna to are marked with NaN, + # but that will still cause other missing values to be replaced + # with NaN (which is problematic if those aren't NaN) + value.loc[self.isna() & value.isna()] = self value = value._values elif not is_list_like(value): pass diff --git a/pandas/tests/frame/methods/test_fillna.py b/pandas/tests/frame/methods/test_fillna.py index 564481d01abc8..253b34639faf2 100644 --- a/pandas/tests/frame/methods/test_fillna.py +++ b/pandas/tests/frame/methods/test_fillna.py @@ -153,6 +153,19 @@ def test_fillna_tzaware_different_column(self): ) tm.assert_frame_equal(result, expected) + def test_other_missing_vals_not_modified( + self, unique_nulls_fixture, unique_nulls_fixture2 + ): + # GH-40498 + missing_val1, missing_val2 = unique_nulls_fixture, unique_nulls_fixture2 + df = DataFrame( + {"A": [1, missing_val1, missing_val2], "B": [2, missing_val1, missing_val2]} + ) + filler = {"A": {1: 0}, "B": {2: 0}} + result = df.fillna(filler) + expected = DataFrame({"A": [1, 0, missing_val2], "B": [2, missing_val1, 0]}) + tm.assert_frame_equal(result, expected) + def test_na_actions_categorical(self): cat = Categorical([1, 2, 3, np.nan], categories=[1, 2, 3]) diff --git a/pandas/tests/series/methods/test_fillna.py b/pandas/tests/series/methods/test_fillna.py index cf6b357d0a418..d7b873ff14357 100644 --- a/pandas/tests/series/methods/test_fillna.py +++ b/pandas/tests/series/methods/test_fillna.py @@ -620,6 +620,16 @@ def test_fillna_numeric_inplace(self): expected = x.fillna(value=0) tm.assert_series_equal(y, expected) + def test_fillna_does_not_modify_other_missing_vals( + self, unique_nulls_fixture, unique_nulls_fixture2 + ): + # GH-40498 + missing_val1, missing_val2 = unique_nulls_fixture, unique_nulls_fixture2 + ser = Series([1, missing_val1, missing_val2, ""]) + result = ser.fillna({2: 0}) + expected = Series([1, missing_val1, 0, ""]) + tm.assert_series_equal(result, expected) + # --------------------------------------------------------------- # CategoricalDtype