Path: blob/main/py-polars/tests/unit/operations/test_is_in.py
6939 views
from __future__ import annotations12from collections.abc import Collection3from datetime import date4from decimal import Decimal as D5from typing import TYPE_CHECKING67import pytest89import polars as pl10from polars.exceptions import InvalidOperationError11from polars.testing import assert_frame_equal, assert_series_equal1213if TYPE_CHECKING:14from collections.abc import Iterator1516from polars._typing import PolarsDataType171819def test_struct_logical_is_in() -> None:20df1 = pl.DataFrame(21{22"x": pl.date_range(date(2022, 1, 1), date(2022, 1, 7), eager=True),23"y": [0, 4, 6, 2, 3, 4, 5],24}25)26df2 = pl.DataFrame(27{28"x": pl.date_range(date(2022, 1, 3), date(2022, 1, 9), eager=True),29"y": [6, 2, 3, 4, 5, 0, 1],30}31)3233s1 = df1.select(pl.struct(["x", "y"])).to_series()34s2 = df2.select(pl.struct(["x", "y"])).to_series()35assert s1.is_in(s2).to_list() == [False, False, True, True, True, True, True]363738def test_struct_logical_is_in_nonullpropagate() -> None:39s = pl.Series([date(2022, 1, 1), date(2022, 1, 2), date(2022, 1, 3), None])40df1 = pl.DataFrame(41{42"x": s,43"y": [0, 4, 6, None],44}45)46s = pl.Series([date(2022, 2, 1), date(2022, 1, 2), date(2022, 2, 3), None])47df2 = pl.DataFrame(48{49"x": s,50"y": [6, 4, 3, None],51}52)5354# Left has no nulls, right has nulls55s1 = df1.select(pl.struct(["x", "y"])).to_series()56s1 = s1.extend_constant(s1[0], 1)57s2 = df2.select(pl.struct(["x", "y"])).to_series().extend_constant(None, 1)58assert s1.is_in(s2, nulls_equal=False).to_list() == [59False,60True,61False,62True,63False,64]65assert s1.is_in(s2, nulls_equal=True).to_list() == [66False,67True,68False,69True,70False,71]7273# Left has nulls, right has no nulls74s1 = df1.select(pl.struct(["x", "y"])).to_series().extend_constant(None, 1)75s2 = df2.select(pl.struct(["x", "y"])).to_series()76s2 = s2.extend_constant(s2[0], 1)77assert s1.is_in(s2, nulls_equal=False).to_list() == [78False,79True,80False,81True,82None,83]84assert s1.is_in(s2, nulls_equal=True).to_list() == [85False,86True,87False,88True,89False,90]9192# Both have nulls93# {None, None} is a valid element unaffected by the missing parameter.94s1 = df1.select(pl.struct(["x", "y"])).to_series().extend_constant(None, 1)95s2 = df2.select(pl.struct(["x", "y"])).to_series().extend_constant(None, 1)96assert s1.is_in(s2, nulls_equal=False).to_list() == [97False,98True,99False,100True,101None,102]103assert s1.is_in(s2, nulls_equal=True).to_list() == [104False,105True,106False,107True,108True,109]110111112@pytest.mark.parametrize("nulls_equal", [False, True])113def test_is_in_bool(nulls_equal: bool) -> None:114vals = [True, None]115df = pl.DataFrame({"A": [True, False, None]})116missing_value = True if nulls_equal else None117assert df.select(pl.col("A").is_in(vals, nulls_equal=nulls_equal)).to_dict(118as_series=False119) == {"A": [True, False, missing_value]}120121122def test_is_in_bool_11216() -> None:123s = pl.Series([False]).is_in([False, None])124expected = pl.Series([True])125assert_series_equal(s, expected)126127128@pytest.mark.parametrize("nulls_equal", [False, True])129def test_is_in_empty_list_4559(nulls_equal: bool) -> None:130assert pl.Series(["a"]).is_in([], nulls_equal=nulls_equal).to_list() == [False]131132133def test_is_in_empty_list_4639() -> None:134df = pl.DataFrame({"a": [1, None]})135empty_list: list[int] = []136137result = df.with_columns([pl.col("a").is_in(empty_list).alias("a_in_list")])138expected = pl.DataFrame({"a": [1, None], "a_in_list": [False, None]})139assert_frame_equal(result, expected)140141142def test_is_in_struct() -> None:143df = pl.DataFrame(144{145"struct_elem": [{"a": 1, "b": 11}, {"a": 1, "b": 90}],146"struct_list": [147[{"a": 1, "b": 11}, {"a": 2, "b": 12}, {"a": 3, "b": 13}],148[{"a": 3, "b": 3}],149],150}151)152153assert df.filter(pl.col("struct_elem").is_in("struct_list")).to_dict(154as_series=False155) == {156"struct_elem": [{"a": 1, "b": 11}],157"struct_list": [[{"a": 1, "b": 11}, {"a": 2, "b": 12}, {"a": 3, "b": 13}]],158}159160161def test_is_in_null_prop() -> None:162assert pl.Series([None], dtype=pl.Float32).is_in(pl.Series([42])).item() is None163assert pl.Series([{"a": None}, None], dtype=pl.Struct({"a": pl.Float32})).is_in(164pl.Series([{"a": 42}], dtype=pl.Struct({"a": pl.Float32}))165).to_list() == [False, None]166167assert pl.Series([{"a": None}, None], dtype=pl.Struct({"a": pl.Boolean})).is_in(168pl.Series([{"a": 42}], dtype=pl.Struct({"a": pl.Boolean}))169).to_list() == [False, None]170171172def test_is_in_9070() -> None:173assert not pl.Series([1]).is_in(pl.Series([1.99])).item()174175176def test_is_in_float_list_10764() -> None:177df = pl.DataFrame(178{179"lst": [[1.0, 2.0, 3.0, 4.0, 5.0], [3.14, 5.28]],180"n": [3.0, 2.0],181}182)183assert df.select(pl.col("n").is_in("lst").alias("is_in")).to_dict(184as_series=False185) == {"is_in": [True, False]}186187188def test_is_in_df() -> None:189df = pl.DataFrame({"a": [1, 2, 3]})190assert df.select(pl.col("a").is_in([1, 2]))["a"].to_list() == [True, True, False]191192193def test_is_in_series() -> None:194s = pl.Series(["a", "b", "c"])195196out = s.is_in(["a", "b"])197assert out.to_list() == [True, True, False]198199# Check if empty list is converted to pl.String200out = s.is_in([])201assert out.to_list() == [False] * out.len()202203for x_y_z in (["x", "y", "z"], {"x", "y", "z"}):204out = s.is_in(x_y_z)205assert out.to_list() == [False, False, False]206207df = pl.DataFrame({"a": [1.0, 2.0], "b": [1, 4], "c": ["e", "d"]})208assert df.select(pl.col("a").is_in(pl.col("b"))).to_series().to_list() == [209True,210False,211]212assert df.select(pl.col("b").is_in([])).to_series().to_list() == [False] * df.height213214with pytest.raises(215InvalidOperationError,216match=r"'is_in' cannot check for List\(String\) values in Int64 data",217):218df.select(pl.col("b").is_in(["x", "x"]))219220# check we don't shallow-copy and accidentally modify 'a' (see: #10072)221a = pl.Series("a", [1, 2])222b = pl.Series("b", [1, 3]).is_in(a)223224assert a.name == "a"225assert_series_equal(b, pl.Series("b", [True, False]))226227228@pytest.mark.parametrize("nulls_equal", [False, True])229def test_is_in_null(nulls_equal: bool) -> None:230# No nulls in right231s = pl.Series([None, None], dtype=pl.Null)232result = s.is_in([1, 2], nulls_equal=nulls_equal)233missing_value = False if nulls_equal else None234expected = pl.Series([missing_value, missing_value], dtype=pl.Boolean)235assert_series_equal(result, expected)236237# Nulls in right238s = pl.Series([None, None], dtype=pl.Null)239result = s.is_in([None, None], nulls_equal=nulls_equal)240missing_value = True if nulls_equal else None241expected = pl.Series([missing_value, missing_value], dtype=pl.Boolean)242assert_series_equal(result, expected)243244245@pytest.mark.parametrize("nulls_equal", [False, True])246def test_is_in_boolean(nulls_equal: bool) -> None:247# Nulls in neither left nor right248s = pl.Series([True, False])249result = s.is_in([True, False], nulls_equal=nulls_equal)250expected = pl.Series([True, True])251assert_series_equal(result, expected)252253# Nulls in left only254s = pl.Series([True, None])255result = s.is_in([False, False], nulls_equal=nulls_equal)256missing_value = False if nulls_equal else None257expected = pl.Series([False, missing_value])258assert_series_equal(result, expected)259260# Nulls in right only261s = pl.Series([True, False])262result = s.is_in([True, None], nulls_equal=nulls_equal)263expected = pl.Series([True, False])264assert_series_equal(result, expected)265266# Nulls in both267s = pl.Series([True, False, None])268result = s.is_in([True, None], nulls_equal=nulls_equal)269missing_value = True if nulls_equal else None270expected = pl.Series([True, False, missing_value])271assert_series_equal(result, expected)272273274@pytest.mark.parametrize("dtype", [pl.List(pl.Boolean), pl.Array(pl.Boolean, 2)])275@pytest.mark.parametrize("nulls_equal", [False, True])276def test_is_in_boolean_list(dtype: PolarsDataType, nulls_equal: bool) -> None:277# Note list is_in does not propagate nulls.278df = pl.DataFrame(279{280"a": [True, False, None, None, None],281"b": pl.Series(282[283[True, False],284[True, True],285[None, True],286[False, True],287[True, True],288],289dtype=dtype,290),291}292)293missing_true = True if nulls_equal else None294missing_false = False if nulls_equal else None295result = df.select(pl.col("a").is_in("b", nulls_equal=nulls_equal))["a"]296expected = pl.Series("a", [True, False, missing_true, missing_false, missing_false])297assert_series_equal(result, expected)298299300def test_is_in_invalid_shape() -> None:301with pytest.raises(InvalidOperationError):302pl.Series("a", [1, 2, 3]).is_in([[], []])303304305def test_is_in_list_rhs() -> None:306assert_series_equal(307pl.Series([1, 2, 3, 4, 5]).is_in(pl.Series([[1], [2, 9], [None], None, None])),308pl.Series([True, True, False, None, None]),309)310311312@pytest.mark.parametrize("dtype", [pl.Float32, pl.Float64])313def test_is_in_float(dtype: PolarsDataType) -> None:314s = pl.Series([float("nan"), 0.0], dtype=dtype)315result = s.is_in([-0.0, -float("nan")])316expected = pl.Series([True, True], dtype=pl.Boolean)317assert_series_equal(result, expected)318319320@pytest.mark.parametrize(321("df", "matches", "expected_error"),322[323(324pl.DataFrame({"a": [1, 2], "b": [[1.0, 2.5], [3.0, 4.0]]}),325[True, False],326None,327),328(329pl.DataFrame({"a": [2.5, 3.0], "b": [[1, 2], [3, 4]]}),330[False, True],331None,332),333(334pl.DataFrame(335{"a": [None, None], "b": [[1, 2], [3, 4]]},336schema_overrides={"a": pl.Null},337),338[None, None],339None,340),341(342pl.DataFrame({"a": ["1", "2"], "b": [[1, 2], [3, 4]]}),343None,344r"'is_in' cannot check for List\(Int64\) values in String data",345),346(347pl.DataFrame({"a": [date.today(), None], "b": [[1, 2], [3, 4]]}),348None,349r"'is_in' cannot check for List\(Int64\) values in Date data",350),351],352)353def test_is_in_expr_list_series(354df: pl.DataFrame, matches: list[bool] | None, expected_error: str | None355) -> None:356expr_is_in = pl.col("a").is_in(pl.col("b"))357if matches:358assert df.select(expr_is_in).to_series().to_list() == matches359else:360with pytest.raises(InvalidOperationError, match=expected_error):361df.select(expr_is_in)362363364@pytest.mark.parametrize(365("df", "matches"),366[367(368pl.DataFrame({"a": [1, None], "b": [[1.0, 2.5, 4.0], [3.0, 4.0, 5.0]]}),369[True, False],370),371(372pl.DataFrame({"a": [1, None], "b": [[0.0, 2.5, None], [3.0, 4.0, None]]}),373[False, True],374),375(376pl.DataFrame(377{"a": [None, None], "b": [[1, 2], [3, 4]]},378schema_overrides={"a": pl.Null},379),380[False, False],381),382(383pl.DataFrame(384{"a": [None, None], "b": [[1, 2], [3, None]]},385schema_overrides={"a": pl.Null},386),387[False, True],388),389],390)391def test_is_in_expr_list_series_nonullpropagate(392df: pl.DataFrame, matches: list[bool]393) -> None:394expr_is_in = pl.col("a").is_in(pl.col("b"), nulls_equal=True)395assert df.select(expr_is_in).to_series().to_list() == matches396397398@pytest.mark.parametrize("nulls_equal", [False, True])399def test_is_in_null_series(nulls_equal: bool) -> None:400df = pl.DataFrame({"a": ["a", "b", None]})401result = df.select(pl.col("a").is_in([None], nulls_equal=nulls_equal))402missing_value = True if nulls_equal else None403expected = pl.DataFrame({"a": [False, False, missing_value]})404assert_frame_equal(result, expected)405406407def test_is_in_int_range() -> None:408r = pl.int_range(0, 3, eager=False)409out = pl.select(r.is_in([1, 2])).to_series()410assert out.to_list() == [False, True, True]411412r = pl.int_range(0, 3, eager=True) # type: ignore[assignment]413out = r.is_in([1, 2]) # type: ignore[assignment]414assert out.to_list() == [False, True, True]415416417def test_is_in_date_range() -> None:418r = pl.date_range(date(2023, 1, 1), date(2023, 1, 3), eager=False)419out = pl.select(r.is_in([date(2023, 1, 2), date(2023, 1, 3)])).to_series()420assert out.to_list() == [False, True, True]421422r = pl.date_range(date(2023, 1, 1), date(2023, 1, 3), eager=True) # type: ignore[assignment]423out = r.is_in([date(2023, 1, 2), date(2023, 1, 3)]) # type: ignore[assignment]424assert out.to_list() == [False, True, True]425426427@pytest.mark.parametrize("dtype", [pl.Categorical, pl.Enum(["a", "b", "c"])])428@pytest.mark.parametrize("nulls_equal", [False, True])429def test_cat_is_in_series(dtype: pl.DataType, nulls_equal: bool) -> None:430s = pl.Series(["a", "b", "c", None], dtype=dtype)431s2 = pl.Series(["b", "c"], dtype=dtype)432missing_value = False if nulls_equal else None433expected = pl.Series([False, True, True, missing_value])434assert_series_equal(s.is_in(s2, nulls_equal=nulls_equal), expected)435436s2_str = s2.cast(pl.String)437assert_series_equal(s.is_in(s2_str, nulls_equal=nulls_equal), expected)438439440@pytest.mark.parametrize("nulls_equal", [False, True])441def test_cat_is_in_series_non_existent(nulls_equal: bool) -> None:442dtype = pl.Categorical443s = pl.Series(["a", "b", "c", None], dtype=dtype)444s2 = pl.Series(["a", "d", "e"], dtype=dtype)445missing_value = False if nulls_equal else None446expected = pl.Series([True, False, False, missing_value])447assert_series_equal(s.is_in(s2, nulls_equal=nulls_equal), expected)448449s2_str = s2.cast(pl.String)450assert_series_equal(s.is_in(s2_str, nulls_equal=nulls_equal), expected)451452453@pytest.mark.parametrize(454"nulls_equal",455[False, True],456)457def test_enum_is_in_series_non_existent(nulls_equal: bool) -> None:458dtype = pl.Enum(["a", "b", "c"])459missing_value = False if nulls_equal else None460s = pl.Series(["a", "b", "c", None], dtype=dtype)461s2_str = pl.Series(["a", "d", "e"])462expected = pl.Series([True, False, False, missing_value])463464with pytest.raises(InvalidOperationError):465s.is_in(s2_str, nulls_equal=nulls_equal)466with pytest.raises(InvalidOperationError):467s.is_in(["a", "d", "e"], nulls_equal=nulls_equal)468469out = s.is_in(["a"], nulls_equal=nulls_equal)470assert_series_equal(out, expected)471472473@pytest.mark.parametrize("dtype", [pl.Categorical, pl.Enum(["a", "b", "c"])])474@pytest.mark.parametrize("nulls_equal", [False, True])475def test_cat_is_in_with_lit_str(dtype: pl.DataType, nulls_equal: bool) -> None:476missing_value = False if nulls_equal else None477s = pl.Series(["a", "b", "c", None], dtype=dtype)478lit = ["b"]479expected = pl.Series([False, True, False, missing_value])480481assert_series_equal(s.is_in(lit, nulls_equal=nulls_equal), expected)482483484@pytest.mark.parametrize("nulls_equal", [False, True])485def test_cat_is_in_with_lit_str_non_existent(nulls_equal: bool) -> None:486dtype = pl.Categorical()487missing_value = False if nulls_equal else None488s = pl.Series(["a", "b", "c", None], dtype=dtype)489lit = ["d"]490expected = pl.Series([False, False, False, missing_value])491492assert_series_equal(s.is_in(lit, nulls_equal=nulls_equal), expected)493494495@pytest.mark.parametrize("dtype", [pl.Categorical, pl.Enum(["a", "b", "c"])])496def test_cat_is_in_with_lit_str_cache_setup(dtype: pl.DataType) -> None:497# init the global cache498_ = pl.Series(["c", "b", "a"], dtype=dtype)499500assert_series_equal(pl.Series(["a"], dtype=dtype).is_in(["a"]), pl.Series([True]))501assert_series_equal(pl.Series(["b"], dtype=dtype).is_in(["b"]), pl.Series([True]))502assert_series_equal(pl.Series(["c"], dtype=dtype).is_in(["c"]), pl.Series([True]))503504505def test_is_in_with_wildcard_13809() -> None:506out = pl.DataFrame({"A": ["B"]}).select(pl.all().is_in(["C"]))507expected = pl.DataFrame({"A": [False]})508assert_frame_equal(out, expected)509510511@pytest.mark.parametrize(512"dtype",513[514pl.Categorical,515pl.Enum(["a", "b", "c", "d"]),516],517)518def test_cat_is_in_from_str(dtype: pl.DataType) -> None:519s = pl.Series(["c", "c", "b"], dtype=dtype)520521# test local522assert_series_equal(523pl.Series(["a", "d", "b"]).is_in(s),524pl.Series([False, False, True]),525)526527528@pytest.mark.parametrize("dtype", [pl.Categorical, pl.Enum(["a", "b", "c", "d"])])529def test_cat_list_is_in_from_cat(dtype: pl.DataType) -> None:530df = pl.DataFrame(531[532(["a", "b"], "c"),533(["a", "b"], "a"),534(["a", None], None),535(["a", "c"], None),536(["a"], "d"),537],538schema={"li": pl.List(dtype), "x": dtype},539orient="row",540)541res = df.select(pl.col("li").list.contains(pl.col("x")))542expected_df = pl.DataFrame({"li": [False, True, True, False, False]})543assert_frame_equal(res, expected_df)544545546@pytest.mark.parametrize(547("val", "expected"),548[549("b", [True, False, False, None, True]),550(None, [False, False, True, None, False]),551("e", [False, False, False, None, False]),552],553)554def test_cat_list_is_in_from_cat_single(val: str | None, expected: list[bool]) -> None:555df = pl.Series(556"li",557[["a", "b"], ["a", "c"], ["a", None], None, ["b"]],558dtype=pl.List(pl.Categorical),559).to_frame()560res = df.select(pl.col("li").list.contains(pl.lit(val, dtype=pl.Categorical)))561expected_df = pl.DataFrame({"li": expected})562assert_frame_equal(res, expected_df)563564565def test_cat_list_is_in_from_str() -> None:566df = pl.DataFrame(567[568(["a", "b"], "c"),569(["a", "b"], "a"),570(["a", None], None),571(["a", "c"], None),572(["a"], "d"),573],574schema={"li": pl.List(pl.Categorical), "x": pl.String},575orient="row",576)577res = df.select(pl.col("li").list.contains(pl.col("x")))578expected_df = pl.DataFrame({"li": [False, True, True, False, False]})579assert_frame_equal(res, expected_df)580581582@pytest.mark.parametrize(583("val", "expected"),584[585("b", [True, False, False, None, True]),586(None, [False, False, True, None, False]),587("e", [False, False, False, None, False]),588],589)590def test_cat_list_is_in_from_single_str(val: str | None, expected: list[bool]) -> None:591df = pl.Series(592"li",593[["a", "b"], ["a", "c"], ["a", None], None, ["b"]],594dtype=pl.List(pl.Categorical),595).to_frame()596res = df.select(pl.col("li").list.contains(pl.lit(val, dtype=pl.String)))597expected_df = pl.DataFrame({"li": expected})598assert_frame_equal(res, expected_df)599600601@pytest.mark.parametrize("nulls_equal", [False, True])602def test_is_in_struct_enum_17618(nulls_equal: bool) -> None:603df = pl.DataFrame()604dtype = pl.Enum(categories=["HBS"])605df = df.insert_column(0, pl.Series("category", [], dtype=dtype))606assert df.filter(607pl.struct("category").is_in(608pl.Series(609[{"category": "HBS"}],610dtype=pl.Struct({"category": df["category"].dtype}),611),612nulls_equal=nulls_equal,613)614).shape == (0, 1)615616617@pytest.mark.parametrize("nulls_equal", [False, True])618def test_is_in_decimal(nulls_equal: bool) -> None:619assert pl.DataFrame({"a": [D("0.0"), D("0.2"), D("0.1")]}).select(620pl.col("a").is_in([0.0, 0.1], nulls_equal=nulls_equal)621)["a"].to_list() == [True, False, True]622assert pl.DataFrame({"a": [D("0.0"), D("0.2"), D("0.1")]}).select(623pl.col("a").is_in([D("0.0"), D("0.1")], nulls_equal=nulls_equal)624)["a"].to_list() == [True, False, True]625assert pl.DataFrame({"a": [D("0.0"), D("0.2"), D("0.1")]}).select(626pl.col("a").is_in([1, 0, 2], nulls_equal=nulls_equal)627)["a"].to_list() == [True, False, False]628missing_value = True if nulls_equal else None629assert pl.DataFrame({"a": [D("0.0"), D("0.2"), None]}).select(630pl.col("a").is_in([0.0, 0.1, None], nulls_equal=nulls_equal)631)["a"].to_list() == [True, False, missing_value]632missing_value = False if nulls_equal else None633assert pl.DataFrame({"a": [D("0.0"), D("0.2"), None]}).select(634pl.col("a").is_in([0.0, 0.1], nulls_equal=nulls_equal)635)["a"].to_list() == [True, False, missing_value]636637638def test_is_in_collection() -> None:639df = pl.DataFrame(640{641"lbl": ["aa", "bb", "cc", "dd", "ee"],642"val": [0, 1, 2, 3, 4],643}644)645646class CustomCollection(Collection[int]):647def __init__(self, vals: Collection[int]) -> None:648super().__init__()649self.vals = vals650651def __contains__(self, x: object) -> bool:652return x in self.vals653654def __iter__(self) -> Iterator[int]:655yield from self.vals656657def __len__(self) -> int:658return len(self.vals)659660for constraint_values in (661{3, 2, 1},662frozenset({3, 2, 1}),663CustomCollection([3, 2, 1]),664):665res = df.filter(pl.col("val").is_in(constraint_values))666assert set(res["lbl"]) == {"bb", "cc", "dd"}667668669@pytest.mark.parametrize("nulls_equal", [False, True])670def test_null_propagate_all_paths(nulls_equal: bool) -> None:671# No nulls in either672s = pl.Series([1, 2, 3])673result = s.is_in([1, 3, 8], nulls_equal=nulls_equal)674expected = pl.Series([True, False, True])675assert_series_equal(result, expected)676677# Nulls in left only678s = pl.Series([1, 2, None])679result = s.is_in([1, 3, 8], nulls_equal=nulls_equal)680missing_value = False if nulls_equal else None681expected = pl.Series([True, False, missing_value])682assert_series_equal(result, expected)683684# Nulls in right only685s = pl.Series([1, 2, 3])686result = s.is_in([1, 3, None], nulls_equal=nulls_equal)687expected = pl.Series([True, False, True])688assert_series_equal(result, expected)689690# Nulls in both691s = pl.Series([1, 2, None])692result = s.is_in([1, 3, None], nulls_equal=nulls_equal)693missing_value = True if nulls_equal else None694expected = pl.Series([True, False, missing_value])695assert_series_equal(result, expected)696697698@pytest.mark.parametrize("nulls_equal", [False, True])699def test_null_propagate_all_paths_cat(nulls_equal: bool) -> None:700# No nulls in either701s = pl.Series(["1", "2", "3"])702result = s.is_in(["1", "3", "8"], nulls_equal=nulls_equal)703expected = pl.Series([True, False, True])704assert_series_equal(result, expected)705706# Nulls in left only707s = pl.Series(["1", "2", None])708result = s.is_in(["1", "3", "8"], nulls_equal=nulls_equal)709missing_value = False if nulls_equal else None710expected = pl.Series([True, False, missing_value])711assert_series_equal(result, expected)712713# Nulls in right only714s = pl.Series(["1", "2", "3"])715result = s.is_in(["1", "3", None], nulls_equal=nulls_equal)716expected = pl.Series([True, False, True])717assert_series_equal(result, expected)718719# Nulls in both720s = pl.Series(["1", "2", None])721result = s.is_in(["1", "3", None], nulls_equal=nulls_equal)722missing_value = True if nulls_equal else None723expected = pl.Series([True, False, missing_value])724assert_series_equal(result, expected)725726727