@@ -589,19 +589,23 @@ def between(
589
589
def isin (
590
590
self , values : ir .ArrayValue | ir .Column | Iterable [Value ], /
591
591
) -> ir .BooleanValue :
592
- """Check whether this expression's values are in `values`.
592
+ """Check whether this expression is in `values`.
593
593
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.
595
599
596
600
Parameters
597
601
----------
598
602
values
599
- Values or expression to check for membership
603
+ Values or expression to check for membership.
600
604
601
605
Returns
602
606
-------
603
607
BooleanValue
604
- Expression indicating membership
608
+ True if `self` is contained in `values`, False otherwise.
605
609
606
610
See Also
607
611
--------
@@ -611,91 +615,67 @@ def isin(
611
615
--------
612
616
>>> import ibis
613
617
>>> 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
+ ... )
651
625
652
- Check against a column from a different table
626
+ Checking for values in literals:
653
627
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:
667
644
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
+ └───────┴───────┴─────────┴─────────┴───────────────┘
699
679
"""
700
680
from ibis .expr .types import ArrayValue
701
681
@@ -709,49 +689,91 @@ def isin(
709
689
def notin (
710
690
self , values : ir .ArrayValue | ir .Column | Iterable [Value ], /
711
691
) -> ir .BooleanValue :
712
- """Check whether this expression's values are not in `values`.
692
+ """Check whether this expression is not in `values`.
713
693
714
694
Opposite of [`Value.isin()`](./expression-generic.qmd#ibis.expr.types.generic.Value.isin).
715
695
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
+
716
702
Parameters
717
703
----------
718
704
values
719
- Values or expression to check for lack of membership
705
+ Values or expression to check for lack of membership.
720
706
721
707
Returns
722
708
-------
723
709
BooleanValue
724
- Whether ` self`'s values are not contained in `values`
710
+ True if self is not in `values`, False otherwise.
725
711
726
712
Examples
727
713
--------
728
714
>>> import ibis
729
715
>>> 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
+ └───────┴───────┴───────────┴───────────┴──────────────────┘
755
777
"""
756
778
return ~ self .isin (values )
757
779
0 commit comments