Skip to content

Commit c8d510e

Browse files
authored
Backport PR #42473: REGR: isin with nullable types with missing values raising (#42543)
1 parent aec6293 commit c8d510e

File tree

4 files changed

+38
-17
lines changed

4 files changed

+38
-17
lines changed

asv_bench/benchmarks/algos/isin.py

+3-12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import numpy as np
22

3-
from pandas.compat.numpy import np_version_under1p20
4-
53
from pandas import (
64
Categorical,
75
NaT,
@@ -280,10 +278,6 @@ class IsInLongSeriesLookUpDominates:
280278
def setup(self, dtype, MaxNumber, series_type):
281279
N = 10 ** 7
282280

283-
# https://github.com/pandas-dev/pandas/issues/39844
284-
if not np_version_under1p20 and dtype in ("Int64", "Float64"):
285-
raise NotImplementedError
286-
287281
if series_type == "random_hits":
288282
array = np.random.randint(0, MaxNumber, N)
289283
if series_type == "random_misses":
@@ -294,7 +288,8 @@ def setup(self, dtype, MaxNumber, series_type):
294288
array = np.arange(N) + MaxNumber
295289

296290
self.series = Series(array).astype(dtype)
297-
self.values = np.arange(MaxNumber).astype(dtype)
291+
292+
self.values = np.arange(MaxNumber).astype(dtype.lower())
298293

299294
def time_isin(self, dtypes, MaxNumber, series_type):
300295
self.series.isin(self.values)
@@ -310,16 +305,12 @@ class IsInLongSeriesValuesDominate:
310305
def setup(self, dtype, series_type):
311306
N = 10 ** 7
312307

313-
# https://github.com/pandas-dev/pandas/issues/39844
314-
if not np_version_under1p20 and dtype in ("Int64", "Float64"):
315-
raise NotImplementedError
316-
317308
if series_type == "random":
318309
vals = np.random.randint(0, 10 * N, N)
319310
if series_type == "monotone":
320311
vals = np.arange(N)
321312

322-
self.values = vals.astype(dtype)
313+
self.values = vals.astype(dtype.lower())
323314
M = 10 ** 6 + 1
324315
self.series = Series(np.arange(M)).astype(dtype)
325316

doc/source/whatsnew/v1.3.1.rst

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Fixed regressions
2222
- Performance regression in :class:`DataFrame` in reduction operations requiring casting such as :meth:`DataFrame.mean` on integer data (:issue:`38592`)
2323
- Performance regression in :meth:`DataFrame.to_dict` and :meth:`Series.to_dict` when ``orient`` argument one of "records", "dict", or "split" (:issue:`42352`)
2424
- Fixed regression in indexing with a ``list`` subclass incorrectly raising ``TypeError`` (:issue:`42433`, :issue:`42461`)
25+
- Fixed regression in :meth:`DataFrame.isin` and :meth:`Series.isin` raising ``TypeError`` with nullable data containing at least one missing value (:issue:`42405`)
2526
-
2627

2728
.. ---------------------------------------------------------------------------

pandas/core/arrays/masked.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -403,12 +403,20 @@ def isin(self, values) -> BooleanArray: # type: ignore[override]
403403

404404
from pandas.core.arrays import BooleanArray
405405

406-
result = isin(self._data, values)
406+
# algorithms.isin will eventually convert values to an ndarray, so no extra
407+
# cost to doing it here first
408+
values_arr = np.asarray(values)
409+
result = isin(self._data, values_arr)
410+
407411
if self._hasna:
408-
if libmissing.NA in values:
409-
result += self._mask
410-
else:
411-
result *= np.invert(self._mask)
412+
values_have_NA = is_object_dtype(values_arr.dtype) and any(
413+
val is self.dtype.na_value for val in values_arr
414+
)
415+
416+
# For now, NA does not propagate so set result according to presence of NA,
417+
# see https://github.com/pandas-dev/pandas/pull/38379 for some discussion
418+
result[self._mask] = values_have_NA
419+
412420
mask = np.zeros_like(self, dtype=bool)
413421
return BooleanArray(result, mask, copy=False)
414422

pandas/tests/series/methods/test_isin.py

+21
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,27 @@ def test_isin_float_in_int_series(self, values):
156156
expected = Series([True, False])
157157
tm.assert_series_equal(result, expected)
158158

159+
@pytest.mark.parametrize("dtype", ["boolean", "Int64", "Float64"])
160+
@pytest.mark.parametrize(
161+
"data,values,expected",
162+
[
163+
([0, 1, 0], [1], [False, True, False]),
164+
([0, 1, 0], [1, pd.NA], [False, True, False]),
165+
([0, pd.NA, 0], [1, 0], [True, False, True]),
166+
([0, 1, pd.NA], [1, pd.NA], [False, True, True]),
167+
([0, 1, pd.NA], [1, np.nan], [False, True, False]),
168+
([0, pd.NA, pd.NA], [np.nan, pd.NaT, None], [False, False, False]),
169+
],
170+
)
171+
def test_isin_masked_types(self, dtype, data, values, expected):
172+
# GH#42405
173+
ser = Series(data, dtype=dtype)
174+
175+
result = ser.isin(values)
176+
expected = Series(expected, dtype="boolean")
177+
178+
tm.assert_series_equal(result, expected)
179+
159180

160181
@pytest.mark.slow
161182
def test_isin_large_series_mixed_dtypes_and_nan():

0 commit comments

Comments
 (0)