diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 85d9acff353be..1a11fffbf6b4e 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -749,6 +749,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:`DataFrame.fillna` not accepting dictionary for ``downcast`` keyword (:issue:`40809`) +- Bug in :func:`isna` not returning a copy of the mask for nullable types, causing any subsequent mask modification to change the original array (:issue:`40935`) MultiIndex ^^^^^^^^^^ diff --git a/pandas/core/arrays/masked.py b/pandas/core/arrays/masked.py index 93de1cd91d625..11f9f645920ec 100644 --- a/pandas/core/arrays/masked.py +++ b/pandas/core/arrays/masked.py @@ -352,7 +352,7 @@ def _hasna(self) -> bool: return self._mask.any() # type: ignore[return-value] def isna(self) -> np.ndarray: - return self._mask + return self._mask.copy() @property def _na_value(self): diff --git a/pandas/tests/extension/base/missing.py b/pandas/tests/extension/base/missing.py index c501694a7c2d5..3d43dc47b5280 100644 --- a/pandas/tests/extension/base/missing.py +++ b/pandas/tests/extension/base/missing.py @@ -1,7 +1,9 @@ import numpy as np +import pytest import pandas as pd import pandas._testing as tm +from pandas.api.types import is_sparse from pandas.tests.extension.base.base import BaseExtensionTests @@ -21,6 +23,17 @@ def test_isna(self, data_missing): expected = pd.Series([], dtype=bool) self.assert_series_equal(result, expected) + @pytest.mark.parametrize("na_func", ["isna", "notna"]) + def test_isna_returns_copy(self, data_missing, na_func): + result = pd.Series(data_missing) + expected = result.copy() + mask = getattr(result, na_func)() + if is_sparse(mask): + mask = np.array(mask) + + mask[:] = True + self.assert_series_equal(result, expected) + def test_dropna_array(self, data_missing): result = data_missing.dropna() expected = data_missing[[1]]