From 213e3fb5dc67a8246883eeeeeb9fa195ec8b85d9 Mon Sep 17 00:00:00 2001 From: galipremsagar Date: Wed, 1 Jul 2026 00:51:34 +0000 Subject: [PATCH 1/4] fix --- python/cudf/cudf/core/column/numerical.py | 16 +++++ python/cudf/cudf/core/series.py | 38 ++++++++++- .../pandas/scripts/pandas-testing-plugin.py | 12 ---- .../cudf/tests/series/methods/test_isin.py | 67 ++++++++++++++++++- 4 files changed, 118 insertions(+), 15 deletions(-) diff --git a/python/cudf/cudf/core/column/numerical.py b/python/cudf/cudf/core/column/numerical.py index cf534a779828..66d4e885b5d5 100644 --- a/python/cudf/cudf/core/column/numerical.py +++ b/python/cudf/cudf/core/column/numerical.py @@ -943,6 +943,22 @@ def _process_values_for_isin( rhs = rhs.astype(lhs.dtype) elif lhs.can_cast_safely(rhs.dtype): lhs = lhs.astype(rhs.dtype) + elif ( + isinstance(lhs.dtype, np.dtype) + and isinstance(rhs.dtype, np.dtype) + and lhs.dtype.kind in "biuf" + and rhs.dtype.kind in "biuf" + and "b" in (lhs.dtype.kind, rhs.dtype.kind) + ): + # A boolean column compares by value against numeric needles + # (``True == 1``) like numpy/pandas. ``can_cast_safely`` reports + # bool<->numeric as unsafe, so promote the boolean side to the + # numeric dtype (a bool always fits) rather than bailing out to + # an all-False result. + if lhs.dtype.kind == "b": + lhs = lhs.astype(rhs.dtype) + else: + rhs = rhs.astype(lhs.dtype) return lhs, rhs def _can_return_nan(self, skipna: bool | None = None) -> bool: diff --git a/python/cudf/cudf/core/series.py b/python/cudf/cudf/core/series.py index a222185ff55b..4a4e128ba21c 100644 --- a/python/cudf/cudf/core/series.py +++ b/python/cudf/cudf/core/series.py @@ -70,9 +70,10 @@ get_dtype_of_same_kind, is_mixed_with_object_dtype, is_pandas_nullable_extension_dtype, + is_pandas_nullable_numpy_dtype, ) from cudf.utils.performance_tracking import _performance_tracking -from cudf.utils.utils import _EQUALITY_OPS, _is_same_name +from cudf.utils.utils import _EQUALITY_OPS, _is_same_name, is_na_like if TYPE_CHECKING: from collections.abc import Hashable, Iterable, MutableMapping @@ -3141,8 +3142,41 @@ def isin(self, values): f"to isin(), you passed a [{type(values).__name__}]" ) + if is_pandas_nullable_numpy_dtype(self.dtype) and not isinstance( + self.dtype, pd.StringDtype + ): + # Mirror pandas' BaseMaskedArray.isin for masked (nullable + # integer/float/boolean) dtypes: + # * matching is done on the underlying numpy values, so e.g. a + # boolean element equals the integer 1, + # * an NA element is considered present only when pd.NA itself + # is one of the passed values (a plain NaN/None/NaT does not + # match), and + # * the result is a nullable BooleanDtype with no missing values. + numpy_dtype = self.dtype.numpy_dtype + na_mask = self._column.isnull() + placeholder = False if numpy_dtype.kind == "b" else 0 + data_col = self._column.astype(numpy_dtype).fillna(placeholder) + # NA-like sentinels (pd.NA/None/NaT) never match a real value and + # would otherwise raise when mixed with numeric values; NA rows + # are handled by the mask below instead. + cleaned_values = [ + value for value in values if not is_na_like(value) + ] + result_col = data_col.isin(cleaned_values) + if na_mask.any(): + values_have_NA = any(value is pd.NA for value in values) + result_col = ( + result_col | na_mask + if values_have_NA + else result_col & ~na_mask + ) + result_col = result_col.astype(pd.BooleanDtype()) + else: + result_col = self._column.isin(values) + return Series._from_column( - self._column.isin(values), + result_col, name=self.name, index=self.index, attrs=self.attrs, diff --git a/python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py b/python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py index d42a89acdec8..353a09677827 100644 --- a/python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py +++ b/python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py @@ -4116,18 +4116,6 @@ def pytest_unconfigure(config): "tests/series/methods/test_info.py::test_info_series[False]": "TODO: Add a reason for failure", "tests/series/methods/test_info.py::test_info_series[True]": "TODO: Add a reason for failure", "tests/series/methods/test_info.py::test_info_show_counts_false": "assert ' Date: Wed, 1 Jul 2026 23:42:00 +0000 Subject: [PATCH 2/4] fix --- python/cudf/cudf/core/series.py | 78 +++++++++-- .../cudf/tests/series/methods/test_isin.py | 121 ++++++++++++++++++ 2 files changed, 188 insertions(+), 11 deletions(-) diff --git a/python/cudf/cudf/core/series.py b/python/cudf/cudf/core/series.py index 0efe41449524..7a483cade521 100644 --- a/python/cudf/cudf/core/series.py +++ b/python/cudf/cudf/core/series.py @@ -3154,18 +3154,74 @@ def isin(self, values): # match), and # * the result is a nullable BooleanDtype with no missing values. numpy_dtype = self.dtype.numpy_dtype - na_mask = self._column.isnull() - placeholder = False if numpy_dtype.kind == "b" else 0 - data_col = self._column.astype(numpy_dtype).fillna(placeholder) - # NA-like sentinels (pd.NA/None/NaT) never match a real value and - # would otherwise raise when mixed with numeric values; NA rows - # are handled by the mask below instead. - cleaned_values = [ - value for value in values if not is_na_like(value) - ] - result_col = data_col.isin(cleaned_values) + # Validity-only nulls (ColumnBase.isnull): a genuine NaN value + # in a masked float column is data, not NA - it must keep + # matching a NaN needle instead of being folded into the mask + # (NumericalColumn.isnull would count it as null). + na_mask = ColumnBase.isnull(self._column) + # View the column through its numpy dtype (identical physical + # layout). astype is not used because casting a masked column + # with nulls to numpy raises in pandas-compatible mode, and + # its short-circuit mutates the live column's dtype in place; + # fillna is not used because it folds genuine NaN values into + # the fill. Null rows come back False from ``isin`` (the + # needles below never contain nulls) and are then corrected + # from ``na_mask``. + data_col = ColumnBase.create(self._column.plc_column, numpy_dtype) + # Bring device-backed ``values`` to host in a single transfer; + # the per-element inspection below would otherwise read them + # element-wise or fail on non-iterable cudf objects. + if isinstance(values, (Series, Index)): + values = values.to_pandas() + elif isinstance(values, cp.ndarray): + values = cp.asnumpy(values) + elif isinstance(values, (pa.Array, pa.ChunkedArray)): + values = values.to_pylist() + if isinstance( + values, (pd.Series, pd.Index, pd.api.extensions.ExtensionArray) + ): + # np.asarray mirrors pandas' BaseMaskedArray.isin: a masked + # (or other extension) container decays to numpy, where + # pd.NA becomes NaN and no longer matches NA rows - only + # object containers can carry pd.NA identity. + values = np.asarray(values) + elif not isinstance(values, np.ndarray): + # Materialize one-shot iterators so the single pass below + # is the only traversal. + values = list(values) + # NA-like sentinels never match a real value and would otherwise + # raise when mixed with numeric values, so they are dropped from + # the needles; NA rows are handled by the mask below instead. + if isinstance(values, np.ndarray) and values.dtype != object: + # A non-object array cannot hold pd.NA, and NaT is the only + # NA-like value it can hold (NaN is a matchable value), so + # no Python-level scan is needed. + values_have_NA = False + cleaned_values = ( + values[~np.isnat(values)] + if values.dtype.kind in "mM" + else values + ) + else: + # ``values`` is a list or an object array here; a single + # Python pass mirrors pandas' pd.NA identity scan. + cleaned_values = [] + values_have_NA = False + for value in values: + if value is pd.NA: + values_have_NA = True + elif not is_na_like(value): + cleaned_values.append(value) + if len(cleaned_values) == 0: + # Nothing can match; also avoids the object-dtype column an + # empty needle list produces, which an all-null data_col + # cannot be compared against in pandas-compatible mode. + result_col = as_column( + False, length=len(self), dtype=np.dtype(np.bool_) + ) + else: + result_col = data_col.isin(cleaned_values) if na_mask.any(): - values_have_NA = any(value is pd.NA for value in values) result_col = ( result_col | na_mask if values_have_NA diff --git a/python/cudf/cudf/tests/series/methods/test_isin.py b/python/cudf/cudf/tests/series/methods/test_isin.py index 8c1d3b8637c9..f47d1e731737 100644 --- a/python/cudf/cudf/tests/series/methods/test_isin.py +++ b/python/cudf/cudf/tests/series/methods/test_isin.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 +import cupy as cp import numpy as np import pandas as pd import pyarrow as pa @@ -207,6 +208,126 @@ def test_isin_bool_against_numeric(values): assert_eq(got, psr.isin(values)) +@pytest.mark.parametrize( + "values", + [ + lambda: (value for value in [1, 2]), + lambda: cudf.Series([1, 2]), + lambda: cudf.Index([1, 2]), + lambda: cp.array([1, 2]), + lambda: pa.chunked_array([[1, 2]]), + ], + ids=["generator", "cudf-series", "cudf-index", "cupy", "pyarrow"], +) +def test_isin_masked_values_containers(values): + # ``values`` is materialized on host once for masked dtypes: + # device-backed inputs are moved in a single transfer instead of + # element-wise reads (cudf objects are not Python-iterable at all). + psr = pd.Series([0, 1, pd.NA], dtype="Int64") + gsr = cudf.Series([0, 1, pd.NA], dtype="Int64") + + assert_eq(gsr.isin(values()), psr.isin([1, 2])) + + +@pytest.mark.parametrize( + "values", + [ + pd.Series([1, pd.NA], dtype="Int64"), + pd.Index([1, pd.NA], dtype="Int64"), + pd.array([1, pd.NA], dtype="Int64"), + ], + ids=["pd-series", "pd-index", "pd-array"], +) +def test_isin_masked_values_container_NA_does_not_match(values): + # pandas materializes pandas-container ``values`` with np.asarray, where + # pd.NA decays to NaN: an NA needle inside a masked container does NOT + # match NA rows, unlike a literal pd.NA in a list. + psr = pd.Series([0, 1, pd.NA], dtype="Int64") + gsr = cudf.Series([0, 1, pd.NA], dtype="Int64") + + assert_eq(gsr.isin(values), psr.isin(values)) + + +def test_isin_masked_one_shot_iterator_with_NA(): + # A one-shot iterator must not be exhausted before the pd.NA + # inspection: the NA row still matches when pd.NA is one of ``values``. + psr = pd.Series([0, 1, pd.NA], dtype="Int64") + gsr = cudf.Series([0, 1, pd.NA], dtype="Int64") + + got = gsr.isin(value for value in [1, pd.NA]) + expected = psr.isin([1, pd.NA]) + assert_eq(got, expected) + + +def test_isin_masked_nan_is_value_not_na(): + # A genuine NaN value in a masked float column is data, not NA: it + # matches a NaN needle and does not match pd.NA (mirrors pandas, where + # only the mask is NA). + psr = pd.Series( + pd.arrays.FloatingArray( + np.array([np.nan, 1.0, 0.0]), + np.array([False, False, True]), + ) + ) + gsr = cudf.Series([np.nan, 1.0, None], dtype="Float64", nan_as_null=False) + + assert_eq(gsr.isin([np.nan]), psr.isin([np.nan])) + assert_eq(gsr.isin([pd.NA]), psr.isin([pd.NA])) + + +def test_isin_masked_pandas_compatible_mode(): + # The masked path must work in pandas-compatible mode (the + # masked-with-nulls to numpy astype guard must not trigger) and must + # not mutate the input Series' dtype via the in-place astype + # short-circuit. + with cudf.option_context("mode.pandas_compatible", True): + gsr = cudf.Series([1, pd.NA], dtype="Int64") + got = gsr.isin([1]) + assert gsr.dtype == pd.Int64Dtype() + assert got.dtype == pd.BooleanDtype() + assert got.to_pandas().tolist() == [True, False] + + gsr = cudf.Series([True, pd.NA], dtype="boolean") + got = gsr.isin([True]) + assert gsr.dtype == pd.BooleanDtype() + assert got.to_pandas().tolist() == [True, False] + + gsr = cudf.Series([1.5, 2.5], dtype="Float64") + got = gsr.isin([1.5]) + assert gsr.dtype == pd.Float64Dtype() + assert got.to_pandas().tolist() == [True, False] + + +@pytest.mark.parametrize("values", [[], [pd.NA], [None], [1]]) +def test_isin_masked_all_na(values): + # An all-NA masked Series with needles that clean to empty used to + # raise in pandas-compatible mode (an empty needle list produces an + # object-dtype column that an all-null column cannot be cast to there). + psr = pd.Series([pd.NA, pd.NA], dtype="Int64") + expected = psr.isin(values) + + gsr = cudf.Series([pd.NA, pd.NA], dtype="Int64") + assert_eq(gsr.isin(values), expected) + + with cudf.option_context("mode.pandas_compatible", True): + gsr = cudf.Series([pd.NA, pd.NA], dtype="Int64") + got = gsr.isin(values) + assert_eq(got, expected) + + +def test_isin_masked_does_not_mutate_dtype(): + # Regression test: the astype in the masked path used to flip the + # input's dtype from Int64 to int64 in pandas-compatible mode. + gsr = cudf.Series([1, 2], dtype="Int64") + gsr.isin([1]) + assert gsr.dtype == pd.Int64Dtype() + + with cudf.option_context("mode.pandas_compatible", True): + gsr = cudf.Series([1, 2], dtype="Int64") + gsr.isin([1]) + assert gsr.dtype == pd.Int64Dtype() + + @pytest.mark.parametrize( "psr", [ From 307bbe2dad02fb18c296d8d615464c67964c6b8f Mon Sep 17 00:00:00 2001 From: galipremsagar Date: Fri, 3 Jul 2026 13:11:20 +0000 Subject: [PATCH 3/4] address reviews --- python/cudf/cudf/core/column/numerical.py | 99 +++++++++++++++++++++++ python/cudf/cudf/core/series.py | 92 +-------------------- 2 files changed, 101 insertions(+), 90 deletions(-) diff --git a/python/cudf/cudf/core/column/numerical.py b/python/cudf/cudf/core/column/numerical.py index 66d4e885b5d5..bb15cf34526a 100644 --- a/python/cudf/cudf/core/column/numerical.py +++ b/python/cudf/cudf/core/column/numerical.py @@ -40,6 +40,7 @@ find_common_type, get_dtype_of_same_kind, is_pandas_nullable_extension_dtype, + is_pandas_nullable_numpy_dtype, min_signed_type, min_unsigned_type, ) @@ -926,6 +927,104 @@ def nan_count(self) -> int: return super().nan_count return self.isnan().sum() + def isin(self, values: Sequence | ColumnBase) -> ColumnBase: + if isinstance(self.dtype, np.dtype) or not ( + is_pandas_nullable_numpy_dtype(self.dtype) + ): + return super().isin(values) + # Mirror pandas' BaseMaskedArray.isin for masked (nullable + # integer/float/boolean) dtypes: + # * matching is done on the underlying numpy values, so e.g. a + # boolean element equals the integer 1, + # * an NA element is considered present only when pd.NA itself + # is one of the passed values (a plain NaN/None/NaT does not + # match), and + # * the result is a nullable BooleanDtype with no missing values. + # Validity-only nulls (ColumnBase.isnull): a genuine NaN value in a + # masked float column is data, not NA - it must keep matching a NaN + # needle instead of being folded into the mask + # (NumericalColumn.isnull would count it as null). + na_mask = ColumnBase.isnull(self) + # View the column through its numpy dtype (identical physical + # layout). astype is not used because casting a masked column with + # nulls to numpy raises in pandas-compatible mode, and its + # short-circuit mutates the live column's dtype in place; fillna is + # not used because it folds genuine NaN values into the fill. Null + # rows come back False from ``isin`` (the needles below never + # contain nulls) and are then corrected from ``na_mask``. + data_col = ColumnBase.create(self.plc_column, self.dtype.numpy_dtype) + cleaned_values, values_have_na = self._process_values_for_masked_isin( + values + ) + if len(cleaned_values) == 0: + # Nothing can match; also avoids the object-dtype column an + # empty needle list produces, which an all-null data column + # cannot be compared against in pandas-compatible mode. + result = as_column( + False, length=len(self), dtype=np.dtype(np.bool_) + ) + else: + result = data_col.isin(cleaned_values) + if na_mask.any(): + result = result | na_mask if values_have_na else result & ~na_mask + return result.astype(pd.BooleanDtype()) + + @staticmethod + def _process_values_for_masked_isin( + values: Sequence | ColumnBase, + ) -> tuple[Sequence, bool]: + """Normalize ``values`` for a masked ``isin``. + + Drops NA-like needles (they are handled through the mask by the + caller) and reports whether ``pd.NA`` itself was among them. + """ + # The normalization below rebinds across container types, so use a + # loosely typed alias. + host_values: Any = values + # Bring device-backed ``values`` to host in a single transfer; the + # per-element inspection below would otherwise read them + # element-wise or fail on non-iterable cudf objects. + if isinstance(host_values, (ColumnBase, cudf.Series, cudf.Index)): + host_values = host_values.to_pandas() + elif isinstance(host_values, cp.ndarray): + host_values = cp.asnumpy(host_values) + elif isinstance(host_values, (pa.Array, pa.ChunkedArray)): + host_values = host_values.to_pylist() + if isinstance( + host_values, + (pd.Series, pd.Index, pd.api.extensions.ExtensionArray), + ): + # np.asarray mirrors pandas' BaseMaskedArray.isin: a masked (or + # other extension) container decays to numpy, where pd.NA + # becomes NaN and no longer matches NA rows - only object + # containers can carry pd.NA identity. + host_values = np.asarray(host_values) + elif not isinstance(host_values, np.ndarray): + # Materialize one-shot iterators so the single pass below is + # the only traversal. + host_values = list(host_values) + # NA-like sentinels never match a real value and would otherwise + # raise when mixed with numeric values, so they are dropped from + # the needles; NA rows are handled by the caller through the mask + # instead. + if isinstance(host_values, np.ndarray) and host_values.dtype != object: + # A non-object array cannot hold pd.NA, and NaT is the only + # NA-like value it can hold (NaN is a matchable value), so no + # Python-level scan is needed. + if host_values.dtype.kind in "mM": + host_values = host_values[~np.isnat(host_values)] + return cast("Sequence", host_values), False + # ``host_values`` is a list or an object array here; a single + # Python pass mirrors pandas' pd.NA identity scan. + cleaned: list[Any] = [] + values_have_na = False + for value in host_values: + if value is pd.NA: + values_have_na = True + elif not is_na_like(value): + cleaned.append(value) + return cleaned, values_have_na + def _process_values_for_isin( self, values: Sequence | ColumnBase ) -> tuple[ColumnBase, ColumnBase]: diff --git a/python/cudf/cudf/core/series.py b/python/cudf/cudf/core/series.py index 7745a185d39b..9b4572da71f0 100644 --- a/python/cudf/cudf/core/series.py +++ b/python/cudf/cudf/core/series.py @@ -70,10 +70,9 @@ get_dtype_of_same_kind, is_mixed_with_object_dtype, is_pandas_nullable_extension_dtype, - is_pandas_nullable_numpy_dtype, ) from cudf.utils.performance_tracking import _performance_tracking -from cudf.utils.utils import _EQUALITY_OPS, _is_same_name, is_na_like +from cudf.utils.utils import _EQUALITY_OPS, _is_same_name if TYPE_CHECKING: from collections.abc import Hashable, Iterable, MutableMapping @@ -3134,94 +3133,7 @@ def isin(self, values): f"to isin(), you passed a [{type(values).__name__}]" ) - if is_pandas_nullable_numpy_dtype(self.dtype) and not isinstance( - self.dtype, pd.StringDtype - ): - # Mirror pandas' BaseMaskedArray.isin for masked (nullable - # integer/float/boolean) dtypes: - # * matching is done on the underlying numpy values, so e.g. a - # boolean element equals the integer 1, - # * an NA element is considered present only when pd.NA itself - # is one of the passed values (a plain NaN/None/NaT does not - # match), and - # * the result is a nullable BooleanDtype with no missing values. - numpy_dtype = self.dtype.numpy_dtype - # Validity-only nulls (ColumnBase.isnull): a genuine NaN value - # in a masked float column is data, not NA - it must keep - # matching a NaN needle instead of being folded into the mask - # (NumericalColumn.isnull would count it as null). - na_mask = ColumnBase.isnull(self._column) - # View the column through its numpy dtype (identical physical - # layout). astype is not used because casting a masked column - # with nulls to numpy raises in pandas-compatible mode, and - # its short-circuit mutates the live column's dtype in place; - # fillna is not used because it folds genuine NaN values into - # the fill. Null rows come back False from ``isin`` (the - # needles below never contain nulls) and are then corrected - # from ``na_mask``. - data_col = ColumnBase.create(self._column.plc_column, numpy_dtype) - # Bring device-backed ``values`` to host in a single transfer; - # the per-element inspection below would otherwise read them - # element-wise or fail on non-iterable cudf objects. - if isinstance(values, (Series, Index)): - values = values.to_pandas() - elif isinstance(values, cp.ndarray): - values = cp.asnumpy(values) - elif isinstance(values, (pa.Array, pa.ChunkedArray)): - values = values.to_pylist() - if isinstance( - values, (pd.Series, pd.Index, pd.api.extensions.ExtensionArray) - ): - # np.asarray mirrors pandas' BaseMaskedArray.isin: a masked - # (or other extension) container decays to numpy, where - # pd.NA becomes NaN and no longer matches NA rows - only - # object containers can carry pd.NA identity. - values = np.asarray(values) - elif not isinstance(values, np.ndarray): - # Materialize one-shot iterators so the single pass below - # is the only traversal. - values = list(values) - # NA-like sentinels never match a real value and would otherwise - # raise when mixed with numeric values, so they are dropped from - # the needles; NA rows are handled by the mask below instead. - if isinstance(values, np.ndarray) and values.dtype != object: - # A non-object array cannot hold pd.NA, and NaT is the only - # NA-like value it can hold (NaN is a matchable value), so - # no Python-level scan is needed. - values_have_NA = False - cleaned_values = ( - values[~np.isnat(values)] - if values.dtype.kind in "mM" - else values - ) - else: - # ``values`` is a list or an object array here; a single - # Python pass mirrors pandas' pd.NA identity scan. - cleaned_values = [] - values_have_NA = False - for value in values: - if value is pd.NA: - values_have_NA = True - elif not is_na_like(value): - cleaned_values.append(value) - if len(cleaned_values) == 0: - # Nothing can match; also avoids the object-dtype column an - # empty needle list produces, which an all-null data_col - # cannot be compared against in pandas-compatible mode. - result_col = as_column( - False, length=len(self), dtype=np.dtype(np.bool_) - ) - else: - result_col = data_col.isin(cleaned_values) - if na_mask.any(): - result_col = ( - result_col | na_mask - if values_have_NA - else result_col & ~na_mask - ) - result_col = result_col.astype(pd.BooleanDtype()) - else: - result_col = self._column.isin(values) + result_col = self._column.isin(values) return Series._from_column( result_col, From 9ec10f4be24366de3043818771df9b78155dbc2d Mon Sep 17 00:00:00 2001 From: galipremsagar Date: Tue, 14 Jul 2026 08:39:48 +0000 Subject: [PATCH 4/4] Address review feedback * Simplify the masked-isin dispatch to an isinstance check on np.dtype/pd.ArrowDtype (equivalent for NumericalColumn dtypes) and drop the now-unused import. * Inline the isin result column into Series._from_column. --- python/cudf/cudf/core/column/numerical.py | 5 +---- python/cudf/cudf/core/series.py | 4 +--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/python/cudf/cudf/core/column/numerical.py b/python/cudf/cudf/core/column/numerical.py index bb15cf34526a..229443530eb3 100644 --- a/python/cudf/cudf/core/column/numerical.py +++ b/python/cudf/cudf/core/column/numerical.py @@ -40,7 +40,6 @@ find_common_type, get_dtype_of_same_kind, is_pandas_nullable_extension_dtype, - is_pandas_nullable_numpy_dtype, min_signed_type, min_unsigned_type, ) @@ -928,9 +927,7 @@ def nan_count(self) -> int: return self.isnan().sum() def isin(self, values: Sequence | ColumnBase) -> ColumnBase: - if isinstance(self.dtype, np.dtype) or not ( - is_pandas_nullable_numpy_dtype(self.dtype) - ): + if isinstance(self.dtype, (np.dtype, pd.ArrowDtype)): return super().isin(values) # Mirror pandas' BaseMaskedArray.isin for masked (nullable # integer/float/boolean) dtypes: diff --git a/python/cudf/cudf/core/series.py b/python/cudf/cudf/core/series.py index 3ad7b11388f9..d9fa4fcc9c41 100644 --- a/python/cudf/cudf/core/series.py +++ b/python/cudf/cudf/core/series.py @@ -3133,10 +3133,8 @@ def isin(self, values): f"to isin(), you passed a [{type(values).__name__}]" ) - result_col = self._column.isin(values) - return Series._from_column( - result_col, + self._column.isin(values), name=self.name, index=self.index, attrs=self.attrs,