From ee063334de36ac407164b80964e40ad2048aa64a Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:17:46 +0000 Subject: [PATCH 01/11] type `isin`, remove redundant `Iterable | Series` --- pandas-stubs/core/frame.pyi | 4 +++- pandas-stubs/core/indexes/base.pyi | 2 +- pandas-stubs/core/series.pyi | 2 +- tests/indexes/test_indexes.py | 6 ++++-- tests/series/test_series.py | 10 ++++++++++ tests/test_frame.py | 10 ++++++++++ 6 files changed, 29 insertions(+), 5 deletions(-) diff --git a/pandas-stubs/core/frame.pyi b/pandas-stubs/core/frame.pyi index d03189dca..41fc1ac40 100644 --- a/pandas-stubs/core/frame.pyi +++ b/pandas-stubs/core/frame.pyi @@ -1783,7 +1783,9 @@ class DataFrame(NDFrame, OpsMixin, _GetItemHack): axis: Axis = 0, copy: _bool = True, ) -> Self: ... - def isin(self, values: Iterable | Series | DataFrame | dict) -> Self: ... + def isin( + self, values: Iterable | DataFrame | Mapping[Hashable, Iterable] + ) -> Self: ... @property def plot(self) -> PlotAccessor: ... def hist( diff --git a/pandas-stubs/core/indexes/base.pyi b/pandas-stubs/core/indexes/base.pyi index 42ef02183..36fb25aa9 100644 --- a/pandas-stubs/core/indexes/base.pyi +++ b/pandas-stubs/core/indexes/base.pyi @@ -554,7 +554,7 @@ class Index(IndexOpsMixin[S1], ElementOpsMixin[S1]): @final def get_indexer_for(self, target, **kwargs: Any): ... def map(self, mapper, na_action=...) -> Index: ... - def isin(self, values, level=...) -> np_1darray_bool: ... + def isin(self, values: Iterable, level: Level | None = None) -> np_1darray_bool: ... def slice_indexer( self, start: Label | None = None, diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 9b5fc9619..fe1744b5c 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -1302,7 +1302,7 @@ class Series(IndexOpsMixin[S1], ElementOpsMixin[S1], NDFrame): show_counts: bool | None = ..., ) -> None: ... def memory_usage(self, index: _bool = True, deep: _bool = False) -> int: ... - def isin(self, values: Iterable | Series[S1] | dict) -> Series[_bool]: ... + def isin(self, values: Iterable) -> Series[_bool]: ... def between( self, left: Scalar | ListLikeU, diff --git a/tests/indexes/test_indexes.py b/tests/indexes/test_indexes.py index f155efaff..6f877351e 100644 --- a/tests/indexes/test_indexes.py +++ b/tests/indexes/test_indexes.py @@ -56,8 +56,10 @@ def test_index_duplicated() -> None: def test_index_isin() -> None: ind = pd.Index([1, 2, 3, 4, 5]) - isin = ind.isin([2, 4]) - check(assert_type(isin, np_1darray_bool), np_1darray_bool) + check(assert_type(ind.isin([2, 4]), np_1darray_bool), np_1darray_bool) + check(assert_type(ind.isin({2, 4}), np_1darray_bool), np_1darray_bool) + check(assert_type(ind.isin(pd.Series([2, 4])), np_1darray_bool), np_1darray_bool) + check(assert_type(ind.isin(pd.Index([2, 4])), np_1darray_bool), np_1darray_bool) def test_index_astype() -> None: diff --git a/tests/series/test_series.py b/tests/series/test_series.py index dc2417a8d..9d7e69e00 100644 --- a/tests/series/test_series.py +++ b/tests/series/test_series.py @@ -1610,6 +1610,16 @@ def test_series_min_max_sub_axis() -> None: check(assert_type(df.max(axis=1), pd.Series), pd.Series) +def test_series_isin() -> None: + s = pd.Series([1, 2, 3, 4, 5]) + check(assert_type(s.isin([3, 4]), "pd.Series[bool]"), pd.Series, np.bool_) + check(assert_type(s.isin({3, 4}), "pd.Series[bool]"), pd.Series, np.bool_) + check( + assert_type(s.isin(pd.Series([3, 4])), "pd.Series[bool]"), pd.Series, np.bool_ + ) + check(assert_type(s.isin(pd.Index([3, 4])), "pd.Series[bool]"), pd.Series, np.bool_) + + def test_series_index_isin() -> None: s = pd.Series([1, 2, 3, 4, 5], index=[1, 2, 2, 3, 3]) t1 = s.loc[s.index.isin([1, 3])] diff --git a/tests/test_frame.py b/tests/test_frame.py index e4b8797a3..95a29f076 100644 --- a/tests/test_frame.py +++ b/tests/test_frame.py @@ -2940,6 +2940,16 @@ def test_getmultiindex_columns() -> None: check(assert_type(df[li[0]], pd.Series), pd.Series) +def test_frame_isin() -> None: + df = pd.DataFrame({"x": [1, 2, 3, 4, 5]}, index=[1, 2, 3, 4, 5]) + check(assert_type(df.isin([1, 3, 5]), pd.DataFrame), pd.DataFrame) + check(assert_type(df.isin({1, 3, 5}), pd.DataFrame), pd.DataFrame) + check(assert_type(df.isin(pd.Series([1, 3, 5])), pd.DataFrame), pd.DataFrame) + check(assert_type(df.isin(pd.Index([1, 3, 5])), pd.DataFrame), pd.DataFrame) + check(assert_type(df.isin(df), pd.DataFrame), pd.DataFrame) + check(assert_type(df.isin({"x": [1, 2]}), pd.DataFrame), pd.DataFrame) + + def test_frame_getitem_isin() -> None: df = pd.DataFrame({"x": [1, 2, 3, 4, 5]}, index=[1, 2, 3, 4, 5]) check(assert_type(df[df.index.isin([1, 3, 5])], pd.DataFrame), pd.DataFrame) From b47951add8c5d59c84f7f964b4ba6c4c07d1b0f1 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:32:16 +0000 Subject: [PATCH 02/11] Iterable -> Iterable[Any], extra test cases --- pandas-stubs/core/frame.pyi | 2 +- pandas-stubs/core/indexes/base.pyi | 4 +++- pandas-stubs/core/series.pyi | 2 +- tests/indexes/test_indexes.py | 1 + tests/series/test_series.py | 1 + tests/test_frame.py | 5 +++++ 6 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pandas-stubs/core/frame.pyi b/pandas-stubs/core/frame.pyi index 41fc1ac40..294045714 100644 --- a/pandas-stubs/core/frame.pyi +++ b/pandas-stubs/core/frame.pyi @@ -1784,7 +1784,7 @@ class DataFrame(NDFrame, OpsMixin, _GetItemHack): copy: _bool = True, ) -> Self: ... def isin( - self, values: Iterable | DataFrame | Mapping[Hashable, Iterable] + self, values: Iterable[Any] | Mapping[Hashable, Iterable[Any]] | DataFrame ) -> Self: ... @property def plot(self) -> PlotAccessor: ... diff --git a/pandas-stubs/core/indexes/base.pyi b/pandas-stubs/core/indexes/base.pyi index 36fb25aa9..d3999b71a 100644 --- a/pandas-stubs/core/indexes/base.pyi +++ b/pandas-stubs/core/indexes/base.pyi @@ -554,7 +554,9 @@ class Index(IndexOpsMixin[S1], ElementOpsMixin[S1]): @final def get_indexer_for(self, target, **kwargs: Any): ... def map(self, mapper, na_action=...) -> Index: ... - def isin(self, values: Iterable, level: Level | None = None) -> np_1darray_bool: ... + def isin( + self, values: Iterable[Any], level: Level | None = None + ) -> np_1darray_bool: ... def slice_indexer( self, start: Label | None = None, diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index fe1744b5c..3dd95fbaa 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -1302,7 +1302,7 @@ class Series(IndexOpsMixin[S1], ElementOpsMixin[S1], NDFrame): show_counts: bool | None = ..., ) -> None: ... def memory_usage(self, index: _bool = True, deep: _bool = False) -> int: ... - def isin(self, values: Iterable) -> Series[_bool]: ... + def isin(self, values: Iterable[Any]) -> Series[_bool]: ... def between( self, left: Scalar | ListLikeU, diff --git a/tests/indexes/test_indexes.py b/tests/indexes/test_indexes.py index 6f877351e..6ad5bf61e 100644 --- a/tests/indexes/test_indexes.py +++ b/tests/indexes/test_indexes.py @@ -60,6 +60,7 @@ def test_index_isin() -> None: check(assert_type(ind.isin({2, 4}), np_1darray_bool), np_1darray_bool) check(assert_type(ind.isin(pd.Series([2, 4])), np_1darray_bool), np_1darray_bool) check(assert_type(ind.isin(pd.Index([2, 4])), np_1darray_bool), np_1darray_bool) + check(assert_type(ind.isin(iter([2, "4"])), np_1darray_bool), np_1darray_bool) def test_index_astype() -> None: diff --git a/tests/series/test_series.py b/tests/series/test_series.py index 9d7e69e00..64601559d 100644 --- a/tests/series/test_series.py +++ b/tests/series/test_series.py @@ -1618,6 +1618,7 @@ def test_series_isin() -> None: assert_type(s.isin(pd.Series([3, 4])), "pd.Series[bool]"), pd.Series, np.bool_ ) check(assert_type(s.isin(pd.Index([3, 4])), "pd.Series[bool]"), pd.Series, np.bool_) + check(assert_type(s.isin(iter([3, "4"])), "pd.Series[bool]"), pd.Series, np.bool_) def test_series_index_isin() -> None: diff --git a/tests/test_frame.py b/tests/test_frame.py index 95a29f076..8584ea8ea 100644 --- a/tests/test_frame.py +++ b/tests/test_frame.py @@ -2,6 +2,7 @@ from collections import ( OrderedDict, + UserDict, UserList, defaultdict, deque, @@ -2948,6 +2949,10 @@ def test_frame_isin() -> None: check(assert_type(df.isin(pd.Index([1, 3, 5])), pd.DataFrame), pd.DataFrame) check(assert_type(df.isin(df), pd.DataFrame), pd.DataFrame) check(assert_type(df.isin({"x": [1, 2]}), pd.DataFrame), pd.DataFrame) + check( + assert_type(df.isin(UserDict({"x": iter([1, "2"])})), pd.DataFrame), + pd.DataFrame, + ) def test_frame_getitem_isin() -> None: From 07cd4859647e2dc2b5e326ebcfce166fb3066642 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Thu, 27 Nov 2025 11:53:13 +0000 Subject: [PATCH 03/11] add overloads for multiindex.isin --- pandas-stubs/core/indexes/multi.pyi | 7 ++++++- tests/indexes/test_indexes.py | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pandas-stubs/core/indexes/multi.pyi b/pandas-stubs/core/indexes/multi.pyi index 3b3564295..278f2e17d 100644 --- a/pandas-stubs/core/indexes/multi.pyi +++ b/pandas-stubs/core/indexes/multi.pyi @@ -165,7 +165,12 @@ class MultiIndex(Index): def equal_levels(self, other): ... def insert(self, loc, item): ... def delete(self, loc): ... - def isin(self, values, level=...) -> np_1darray_bool: ... + @overload # type: ignore[override] + def isin(self, values: Iterable[Any], level: Level) -> np_1darray_bool: ... + @overload + def isin( + self, values: Iterable[Iterable[Any]], level: None = None + ) -> np_1darray_bool: ... # pyright: ignore[reportIncompatibleMethodOverride] def set_names( self, names: Hashable | Sequence[Hashable] | Mapping[Any, Hashable], diff --git a/tests/indexes/test_indexes.py b/tests/indexes/test_indexes.py index 6ad5bf61e..280fb8481 100644 --- a/tests/indexes/test_indexes.py +++ b/tests/indexes/test_indexes.py @@ -62,6 +62,11 @@ def test_index_isin() -> None: check(assert_type(ind.isin(pd.Index([2, 4])), np_1darray_bool), np_1darray_bool) check(assert_type(ind.isin(iter([2, "4"])), np_1darray_bool), np_1darray_bool) + mi = pd.MultiIndex.from_arrays([[1, 2, 3]]) + check(assert_type(mi.isin([[3]]), np_1darray_bool), np_1darray_bool) + if TYPE_CHECKING_INVALID_USAGE: + mi.isin({3}) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + def test_index_astype() -> None: indi = pd.Index([1, 2, 3]) From fdbe5f027d09ecb7a2b98a7cb8236d44bad95f4f Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Thu, 27 Nov 2025 15:51:26 +0000 Subject: [PATCH 04/11] fix pyrefly --- pandas-stubs/core/indexes/multi.pyi | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas-stubs/core/indexes/multi.pyi b/pandas-stubs/core/indexes/multi.pyi index 278f2e17d..a63bb4d36 100644 --- a/pandas-stubs/core/indexes/multi.pyi +++ b/pandas-stubs/core/indexes/multi.pyi @@ -166,7 +166,9 @@ class MultiIndex(Index): def insert(self, loc, item): ... def delete(self, loc): ... @overload # type: ignore[override] - def isin(self, values: Iterable[Any], level: Level) -> np_1darray_bool: ... + def isin( + self, values: Iterable[Any], level: Level + ) -> np_1darray_bool: ... # pyrefly: ignore[bad-override] @overload def isin( self, values: Iterable[Iterable[Any]], level: None = None From 145807f356254837b24495a008b9aa86e79db20c Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Thu, 27 Nov 2025 15:51:37 +0000 Subject: [PATCH 05/11] fix pyright --- pandas-stubs/core/indexes/multi.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas-stubs/core/indexes/multi.pyi b/pandas-stubs/core/indexes/multi.pyi index a63bb4d36..0439e4230 100644 --- a/pandas-stubs/core/indexes/multi.pyi +++ b/pandas-stubs/core/indexes/multi.pyi @@ -170,9 +170,9 @@ class MultiIndex(Index): self, values: Iterable[Any], level: Level ) -> np_1darray_bool: ... # pyrefly: ignore[bad-override] @overload - def isin( + def isin( # pyright: ignore[reportIncompatibleMethodOverride] self, values: Iterable[Iterable[Any]], level: None = None - ) -> np_1darray_bool: ... # pyright: ignore[reportIncompatibleMethodOverride] + ) -> np_1darray_bool: ... def set_names( self, names: Hashable | Sequence[Hashable] | Mapping[Any, Hashable], From 96fbc5ee5f5ce2135fe1041a1cbd8b899200da82 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Thu, 27 Nov 2025 15:54:04 +0000 Subject: [PATCH 06/11] fix pyrefly --- pandas-stubs/core/indexes/multi.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas-stubs/core/indexes/multi.pyi b/pandas-stubs/core/indexes/multi.pyi index 0439e4230..e83cc04d1 100644 --- a/pandas-stubs/core/indexes/multi.pyi +++ b/pandas-stubs/core/indexes/multi.pyi @@ -166,9 +166,9 @@ class MultiIndex(Index): def insert(self, loc, item): ... def delete(self, loc): ... @overload # type: ignore[override] - def isin( + def isin( # pyrefly: ignore[bad-override] self, values: Iterable[Any], level: Level - ) -> np_1darray_bool: ... # pyrefly: ignore[bad-override] + ) -> np_1darray_bool: ... @overload def isin( # pyright: ignore[reportIncompatibleMethodOverride] self, values: Iterable[Iterable[Any]], level: None = None From ef6ab94147f27f19fba0997cc314560d92cc0ccb Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:45:40 +0000 Subject: [PATCH 07/11] extra test case, use Collection --- pandas-stubs/core/indexes/multi.pyi | 3 ++- tests/indexes/test_indexes.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas-stubs/core/indexes/multi.pyi b/pandas-stubs/core/indexes/multi.pyi index 7719e0a90..5681d6ab9 100644 --- a/pandas-stubs/core/indexes/multi.pyi +++ b/pandas-stubs/core/indexes/multi.pyi @@ -1,5 +1,6 @@ from collections.abc import ( Callable, + Collection, Hashable, Iterable, Mapping, @@ -171,7 +172,7 @@ class MultiIndex(Index): ) -> np_1darray_bool: ... @overload def isin( # pyright: ignore[reportIncompatibleMethodOverride] - self, values: Iterable[Iterable[Any]], level: None = None + self, values: Collection[Iterable[Any]], level: None = None ) -> np_1darray_bool: ... def set_names( self, diff --git a/tests/indexes/test_indexes.py b/tests/indexes/test_indexes.py index 6776608ed..7b1a5bfec 100644 --- a/tests/indexes/test_indexes.py +++ b/tests/indexes/test_indexes.py @@ -67,6 +67,7 @@ def test_index_isin() -> None: check(assert_type(mi.isin([[3]]), np_1darray_bool), np_1darray_bool) if TYPE_CHECKING_INVALID_USAGE: mi.isin({3}) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + mi.isin(iter({3})) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] def test_index_astype() -> None: From 29ba4a2212673e20ca46e6bdc0ddcffa90d73bf5 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:50:59 +0000 Subject: [PATCH 08/11] remove extra test case --- tests/indexes/test_indexes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/indexes/test_indexes.py b/tests/indexes/test_indexes.py index 7b1a5bfec..6776608ed 100644 --- a/tests/indexes/test_indexes.py +++ b/tests/indexes/test_indexes.py @@ -67,7 +67,6 @@ def test_index_isin() -> None: check(assert_type(mi.isin([[3]]), np_1darray_bool), np_1darray_bool) if TYPE_CHECKING_INVALID_USAGE: mi.isin({3}) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] - mi.isin(iter({3})) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] def test_index_astype() -> None: From 815e68f29b02a0ac6ca138a064328dbb9d566026 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:52:42 +0000 Subject: [PATCH 09/11] extra test cases --- tests/indexes/test_indexes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/indexes/test_indexes.py b/tests/indexes/test_indexes.py index 6776608ed..518528061 100644 --- a/tests/indexes/test_indexes.py +++ b/tests/indexes/test_indexes.py @@ -65,8 +65,10 @@ def test_index_isin() -> None: mi = pd.MultiIndex.from_arrays([[1, 2, 3]]) check(assert_type(mi.isin([[3]]), np_1darray_bool), np_1darray_bool) + check(assert_type(mi.isin({iter([3])}), np_1darray_bool), np_1darray_bool) if TYPE_CHECKING_INVALID_USAGE: mi.isin({3}) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + mi.isin(iter([[3]])) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] def test_index_astype() -> None: From 3279cd1c9e20446b1e52831a978cfcc8d3f7f679 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:56:43 +0000 Subject: [PATCH 10/11] mypy --- tests/indexes/test_indexes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/indexes/test_indexes.py b/tests/indexes/test_indexes.py index 518528061..eda08354e 100644 --- a/tests/indexes/test_indexes.py +++ b/tests/indexes/test_indexes.py @@ -68,7 +68,7 @@ def test_index_isin() -> None: check(assert_type(mi.isin({iter([3])}), np_1darray_bool), np_1darray_bool) if TYPE_CHECKING_INVALID_USAGE: mi.isin({3}) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] - mi.isin(iter([[3]])) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + mi.isin(iter([[3]])) # type: ignore[call-overload] # pyright: ignore[reportArgumentType] def test_index_astype() -> None: From c868e8d5d599fd403a780c034511e5ef3ba0a3ab Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:36:52 +0000 Subject: [PATCH 11/11] ty --- pandas-stubs/core/indexes/multi.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas-stubs/core/indexes/multi.pyi b/pandas-stubs/core/indexes/multi.pyi index 5681d6ab9..dbb9b766e 100644 --- a/pandas-stubs/core/indexes/multi.pyi +++ b/pandas-stubs/core/indexes/multi.pyi @@ -171,7 +171,7 @@ class MultiIndex(Index): self, values: Iterable[Any], level: Level ) -> np_1darray_bool: ... @overload - def isin( # pyright: ignore[reportIncompatibleMethodOverride] + def isin( # ty: ignore[invalid-method-override] # pyright: ignore[reportIncompatibleMethodOverride] self, values: Collection[Iterable[Any]], level: None = None ) -> np_1darray_bool: ... def set_names(