Skip to content

Commit b4e6272

Browse files
authored
docs: add examples of .isin(<contains NULL>) (#11154)
1 parent 458f06d commit b4e6272

File tree

1 file changed

+136
-114
lines changed

1 file changed

+136
-114
lines changed

ibis/expr/types/generic.py

Lines changed: 136 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -589,19 +589,23 @@ def between(
589589
def isin(
590590
self, values: ir.ArrayValue | ir.Column | Iterable[Value], /
591591
) -> ir.BooleanValue:
592-
"""Check whether this expression's values are in `values`.
592+
"""Check whether this expression is in `values`.
593593
594-
`NULL` values are propagated in the output. See examples for details.
594+
`NULL` values in the input are propagated in the output.
595+
If the `values` argument contains any `NULL` values,
596+
then ibis follows the SQL behavior of returning `NULL` (not False)
597+
when `self` is not present.
598+
See examples below for details.
595599
596600
Parameters
597601
----------
598602
values
599-
Values or expression to check for membership
603+
Values or expression to check for membership.
600604
601605
Returns
602606
-------
603607
BooleanValue
604-
Expression indicating membership
608+
True if `self` is contained in `values`, False otherwise.
605609
606610
See Also
607611
--------
@@ -611,91 +615,67 @@ def isin(
611615
--------
612616
>>> import ibis
613617
>>> ibis.options.interactive = True
614-
>>> t = ibis.memtable({"a": [1, 2, 3], "b": [2, 3, 4]})
615-
>>> t
616-
┏━━━━━━━┳━━━━━━━┓
617-
┃ a ┃ b ┃
618-
┡━━━━━━━╇━━━━━━━┩
619-
│ int64 │ int64 │
620-
├───────┼───────┤
621-
│ 1 │ 2 │
622-
│ 2 │ 3 │
623-
│ 3 │ 4 │
624-
└───────┴───────┘
625-
626-
Check against a literal sequence of values
627-
628-
>>> t.a.isin([1, 2])
629-
┏━━━━━━━━━━━━━━━━━━━━━┓
630-
┃ InValues(a, (1, 2)) ┃
631-
┡━━━━━━━━━━━━━━━━━━━━━┩
632-
│ boolean │
633-
├─────────────────────┤
634-
│ True │
635-
│ True │
636-
│ False │
637-
└─────────────────────┘
638-
639-
Check against a derived expression
640-
641-
>>> t.a.isin(t.b + 1)
642-
┏━━━━━━━━━━━━━━━┓
643-
┃ InSubquery(a) ┃
644-
┡━━━━━━━━━━━━━━━┩
645-
│ boolean │
646-
├───────────────┤
647-
│ False │
648-
│ False │
649-
│ True │
650-
└───────────────┘
618+
>>> t = ibis.memtable(
619+
... {
620+
... "a": [1, 2, 3, None],
621+
... "b": [1, 2, 9, None],
622+
... },
623+
... schema={"a": int, "b": int},
624+
... )
651625
652-
Check against a column from a different table
626+
Checking for values in literals:
653627
654-
>>> t2 = ibis.memtable({"x": [99, 2, 99]})
655-
>>> t.a.isin(t2.x)
656-
┏━━━━━━━━━━━━━━━┓
657-
┃ InSubquery(a) ┃
658-
┡━━━━━━━━━━━━━━━┩
659-
│ boolean │
660-
├───────────────┤
661-
│ False │
662-
│ True │
663-
│ False │
664-
└───────────────┘
665-
666-
`NULL` behavior
628+
>>> t.mutate(
629+
... a_in_12=t.a.isin([1, 2]),
630+
... a_in_12None=t.a.isin([1, 2, None]),
631+
... )
632+
┏━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━┓
633+
┃ a ┃ b ┃ a_in_12 ┃ a_in_12None ┃
634+
┡━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━┩
635+
│ int64 │ int64 │ boolean │ boolean │
636+
├───────┼───────┼─────────┼─────────────┤
637+
│ 1 │ 1 │ True │ True │
638+
│ 2 │ 2 │ True │ True │
639+
│ 3 │ 9 │ False │ NULL │
640+
│ NULL │ NULL │ NULL │ NULL │
641+
└───────┴───────┴─────────┴─────────────┘
642+
643+
Checking for values in columns of the same table:
667644
668-
>>> t = ibis.memtable({"x": [1, 2]})
669-
>>> t.x.isin([1, None])
670-
┏━━━━━━━━━━━━━━━━━━━━━━━━┓
671-
┃ InValues(x, (1, None)) ┃
672-
┡━━━━━━━━━━━━━━━━━━━━━━━━┩
673-
│ boolean │
674-
├────────────────────────┤
675-
│ True │
676-
│ NULL │
677-
└────────────────────────┘
678-
>>> t = ibis.memtable({"x": [1, None, 2]})
679-
>>> t.x.isin([1])
680-
┏━━━━━━━━━━━━━━━━━━━┓
681-
┃ InValues(x, (1,)) ┃
682-
┡━━━━━━━━━━━━━━━━━━━┩
683-
│ boolean │
684-
├───────────────────┤
685-
│ True │
686-
│ NULL │
687-
│ False │
688-
└───────────────────┘
689-
>>> t.x.isin([3])
690-
┏━━━━━━━━━━━━━━━━━━━┓
691-
┃ InValues(x, (3,)) ┃
692-
┡━━━━━━━━━━━━━━━━━━━┩
693-
│ boolean │
694-
├───────────────────┤
695-
│ False │
696-
│ NULL │
697-
│ False │
698-
└───────────────────┘
645+
>>> t.mutate(
646+
... a_in_b=t.a.isin(t.b),
647+
... a_in_b_no_null=t.a.isin(t.b.fill_null(0)),
648+
... a_in_b_plus_1=t.a.isin(t.b + 1),
649+
... )
650+
┏━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
651+
┃ a ┃ b ┃ a_in_b ┃ a_in_b_no_null ┃ a_in_b_plus_1 ┃
652+
┡━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
653+
│ int64 │ int64 │ boolean │ boolean │ boolean │
654+
├───────┼───────┼─────────┼────────────────┼───────────────┤
655+
│ 1 │ 1 │ True │ True │ NULL │
656+
│ 2 │ 2 │ True │ True │ True │
657+
│ 3 │ 9 │ NULL │ False │ True │
658+
│ NULL │ NULL │ NULL │ NULL │ NULL │
659+
└───────┴───────┴─────────┴────────────────┴───────────────┘
660+
661+
Checking for values in a column from a different table:
662+
663+
>>> t2 = ibis.memtable({"x": [1, 2, 99], "y": [1, 2, None]})
664+
>>> t.mutate(
665+
... a_in_x=t.a.isin(t2.x),
666+
... a_in_y=t.a.isin(t2.y),
667+
... a_in_y_plus_1=t.a.isin(t2.y + 1),
668+
... )
669+
┏━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━┓
670+
┃ a ┃ b ┃ a_in_x ┃ a_in_y ┃ a_in_y_plus_1 ┃
671+
┡━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━┩
672+
│ int64 │ int64 │ boolean │ boolean │ boolean │
673+
├───────┼───────┼─────────┼─────────┼───────────────┤
674+
│ 1 │ 1 │ True │ True │ NULL │
675+
│ 2 │ 2 │ True │ True │ True │
676+
│ 3 │ 9 │ False │ NULL │ True │
677+
│ NULL │ NULL │ NULL │ NULL │ NULL │
678+
└───────┴───────┴─────────┴─────────┴───────────────┘
699679
"""
700680
from ibis.expr.types import ArrayValue
701681

@@ -709,49 +689,91 @@ def isin(
709689
def notin(
710690
self, values: ir.ArrayValue | ir.Column | Iterable[Value], /
711691
) -> ir.BooleanValue:
712-
"""Check whether this expression's values are not in `values`.
692+
"""Check whether this expression is not in `values`.
713693
714694
Opposite of [`Value.isin()`](./expression-generic.qmd#ibis.expr.types.generic.Value.isin).
715695
696+
`NULL` values in the input are propagated in the output.
697+
If the `values` argument contains any `NULL` values,
698+
then ibis follows the SQL behavior of returning `NULL` (not False)
699+
when `self` is present.
700+
See examples below for details.
701+
716702
Parameters
717703
----------
718704
values
719-
Values or expression to check for lack of membership
705+
Values or expression to check for lack of membership.
720706
721707
Returns
722708
-------
723709
BooleanValue
724-
Whether `self`'s values are not contained in `values`
710+
True if self is not in `values`, False otherwise.
725711
726712
Examples
727713
--------
728714
>>> import ibis
729715
>>> ibis.options.interactive = True
730-
>>> t = ibis.examples.penguins.fetch().limit(5)
731-
>>> t.bill_depth_mm
732-
┏━━━━━━━━━━━━━━━┓
733-
┃ bill_depth_mm ┃
734-
┡━━━━━━━━━━━━━━━┩
735-
│ float64 │
736-
├───────────────┤
737-
│ 18.7 │
738-
│ 17.4 │
739-
│ 18.0 │
740-
│ NULL │
741-
│ 19.3 │
742-
└───────────────┘
743-
>>> t.bill_depth_mm.notin([18.7, 18.1])
744-
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
745-
┃ Not(InValues(bill_depth_mm, (18.7, 18.1))) ┃
746-
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
747-
│ boolean │
748-
├────────────────────────────────────────────┤
749-
│ False │
750-
│ True │
751-
│ True │
752-
│ NULL │
753-
│ True │
754-
└────────────────────────────────────────────┘
716+
>>> t = ibis.memtable(
717+
... {
718+
... "a": [1, 2, 3, None],
719+
... "b": [1, 2, 9, None],
720+
... },
721+
... schema={"a": int, "b": int},
722+
... )
723+
724+
Checking for values in literals:
725+
726+
>>> t.mutate(
727+
... a_notin_12=t.a.notin([1, 2]),
728+
... a_notin_12None=t.a.notin([1, 2, None]),
729+
... )
730+
┏━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
731+
┃ a ┃ b ┃ a_notin_12 ┃ a_notin_12None ┃
732+
┡━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
733+
│ int64 │ int64 │ boolean │ boolean │
734+
├───────┼───────┼────────────┼────────────────┤
735+
│ 1 │ 1 │ False │ False │
736+
│ 2 │ 2 │ False │ False │
737+
│ 3 │ 9 │ True │ NULL │
738+
│ NULL │ NULL │ NULL │ NULL │
739+
└───────┴───────┴────────────┴────────────────┘
740+
741+
Checking for values in columns of the same table:
742+
743+
>>> t.mutate(
744+
... a_notin_b=t.a.notin(t.b),
745+
... a_notin_b_no_null=t.a.notin(t.b.fill_null(0)),
746+
... a_notin_b_plus_1=t.a.notin(t.b + 1),
747+
... )
748+
┏━━━━━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┓
749+
┃ a ┃ b ┃ a_notin_b ┃ a_notin_b_no_null ┃ a_notin_b_plus_1 ┃
750+
┡━━━━━━━╇━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━┩
751+
│ int64 │ int64 │ boolean │ boolean │ boolean │
752+
├───────┼───────┼───────────┼───────────────────┼──────────────────┤
753+
│ 1 │ 1 │ False │ False │ NULL │
754+
│ 2 │ 2 │ False │ False │ False │
755+
│ 3 │ 9 │ NULL │ True │ False │
756+
│ NULL │ NULL │ NULL │ NULL │ NULL │
757+
└───────┴───────┴───────────┴───────────────────┴──────────────────┘
758+
759+
Checking for values in a column from a different table:
760+
761+
>>> t2 = ibis.memtable({"x": [1, 2, 99], "y": [1, 2, None]})
762+
>>> t.mutate(
763+
... a_notin_x=t.a.notin(t2.x),
764+
... a_notin_y=t.a.notin(t2.y),
765+
... a_notin_y_plus_1=t.a.notin(t2.y + 1),
766+
... )
767+
┏━━━━━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┓
768+
┃ a ┃ b ┃ a_notin_x ┃ a_notin_y ┃ a_notin_y_plus_1 ┃
769+
┡━━━━━━━╇━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━┩
770+
│ int64 │ int64 │ boolean │ boolean │ boolean │
771+
├───────┼───────┼───────────┼───────────┼──────────────────┤
772+
│ 1 │ 1 │ False │ False │ NULL │
773+
│ 2 │ 2 │ False │ False │ False │
774+
│ 3 │ 9 │ True │ NULL │ False │
775+
│ NULL │ NULL │ NULL │ NULL │ NULL │
776+
└───────┴───────┴───────────┴───────────┴──────────────────┘
755777
"""
756778
return ~self.isin(values)
757779

0 commit comments

Comments
 (0)