@@ -585,19 +585,23 @@ def between(
585
585
return ops .Between (self , lower , upper ).to_expr ()
586
586
587
587
def isin (self , values : Value | Sequence [Value ], / ) -> ir .BooleanValue :
588
- """Check whether this expression's values are in `values`.
588
+ """Check whether this expression is in `values`.
589
589
590
- `NULL` values are propagated in the output. See examples for details.
590
+ `NULL` values in the input are propagated in the output.
591
+ If the `values` argument contains any `NULL` values,
592
+ then ibis follows the SQL behavior of returning `NULL` (not False)
593
+ when `self` is not present.
594
+ See examples below for details.
591
595
592
596
Parameters
593
597
----------
594
598
values
595
- Values or expression to check for membership
599
+ Values or expression to check for membership.
596
600
597
601
Returns
598
602
-------
599
603
BooleanValue
600
- Expression indicating membership
604
+ True if `self` is contained in `values`, False otherwise.
601
605
602
606
See Also
603
607
--------
@@ -607,91 +611,67 @@ def isin(self, values: Value | Sequence[Value], /) -> ir.BooleanValue:
607
611
--------
608
612
>>> import ibis
609
613
>>> ibis.options.interactive = True
610
- >>> t = ibis.memtable({"a": [1, 2, 3], "b": [2, 3, 4]})
611
- >>> t
612
- ┏━━━━━━━┳━━━━━━━┓
613
- ┃ a ┃ b ┃
614
- ┡━━━━━━━╇━━━━━━━┩
615
- │ int64 │ int64 │
616
- ├───────┼───────┤
617
- │ 1 │ 2 │
618
- │ 2 │ 3 │
619
- │ 3 │ 4 │
620
- └───────┴───────┘
621
-
622
- Check against a literal sequence of values
623
-
624
- >>> t.a.isin([1, 2])
625
- ┏━━━━━━━━━━━━━━━━━━━━━┓
626
- ┃ InValues(a, (1, 2)) ┃
627
- ┡━━━━━━━━━━━━━━━━━━━━━┩
628
- │ boolean │
629
- ├─────────────────────┤
630
- │ True │
631
- │ True │
632
- │ False │
633
- └─────────────────────┘
634
-
635
- Check against a derived expression
636
-
637
- >>> t.a.isin(t.b + 1)
638
- ┏━━━━━━━━━━━━━━━┓
639
- ┃ InSubquery(a) ┃
640
- ┡━━━━━━━━━━━━━━━┩
641
- │ boolean │
642
- ├───────────────┤
643
- │ False │
644
- │ False │
645
- │ True │
646
- └───────────────┘
614
+ >>> t = ibis.memtable(
615
+ ... {
616
+ ... "a": [1, 2, 3, None],
617
+ ... "b": [1, 2, 9, None],
618
+ ... },
619
+ ... schema={"a": int, "b": int},
620
+ ... )
647
621
648
- Check against a column from a different table
622
+ Checking for values in literals:
649
623
650
- >>> t2 = ibis.memtable({"x": [99, 2, 99]})
651
- >>> t.a.isin(t2.x)
652
- ┏━━━━━━━━━━━━━━━┓
653
- ┃ InSubquery(a) ┃
654
- ┡━━━━━━━━━━━━━━━┩
655
- │ boolean │
656
- ├───────────────┤
657
- │ False │
658
- │ True │
659
- │ False │
660
- └───────────────┘
661
-
662
- `NULL` behavior
624
+ >>> t.mutate(
625
+ ... a_in_12=t.a.isin([1, 2]),
626
+ ... a_in_12None=t.a.isin([1, 2, None]),
627
+ ... )
628
+ ┏━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━┓
629
+ ┃ a ┃ b ┃ a_in_12 ┃ a_in_12None ┃
630
+ ┡━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━┩
631
+ │ int64 │ int64 │ boolean │ boolean │
632
+ ├───────┼───────┼─────────┼─────────────┤
633
+ │ 1 │ 1 │ True │ True │
634
+ │ 2 │ 2 │ True │ True │
635
+ │ 3 │ 9 │ False │ NULL │
636
+ │ NULL │ NULL │ NULL │ NULL │
637
+ └───────┴───────┴─────────┴─────────────┘
638
+
639
+ Checking for values in columns of the same table:
663
640
664
- >>> t = ibis.memtable({"x": [1, 2]})
665
- >>> t.x.isin([1, None])
666
- ┏━━━━━━━━━━━━━━━━━━━━━━━━┓
667
- ┃ InValues(x, (1, None)) ┃
668
- ┡━━━━━━━━━━━━━━━━━━━━━━━━┩
669
- │ boolean │
670
- ├────────────────────────┤
671
- │ True │
672
- │ NULL │
673
- └────────────────────────┘
674
- >>> t = ibis.memtable({"x": [1, None, 2]})
675
- >>> t.x.isin([1])
676
- ┏━━━━━━━━━━━━━━━━━━━┓
677
- ┃ InValues(x, (1,)) ┃
678
- ┡━━━━━━━━━━━━━━━━━━━┩
679
- │ boolean │
680
- ├───────────────────┤
681
- │ True │
682
- │ NULL │
683
- │ False │
684
- └───────────────────┘
685
- >>> t.x.isin([3])
686
- ┏━━━━━━━━━━━━━━━━━━━┓
687
- ┃ InValues(x, (3,)) ┃
688
- ┡━━━━━━━━━━━━━━━━━━━┩
689
- │ boolean │
690
- ├───────────────────┤
691
- │ False │
692
- │ NULL │
693
- │ False │
694
- └───────────────────┘
641
+ >>> t.mutate(
642
+ ... a_in_b=t.a.isin(t.b),
643
+ ... a_in_b_no_null=t.a.isin(t.b.fill_null(0)),
644
+ ... a_in_b_plus_1=t.a.isin(t.b + 1),
645
+ ... )
646
+ ┏━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
647
+ ┃ a ┃ b ┃ a_in_b ┃ a_in_b_no_null ┃ a_in_b_plus_1 ┃
648
+ ┡━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
649
+ │ int64 │ int64 │ boolean │ boolean │ boolean │
650
+ ├───────┼───────┼─────────┼────────────────┼───────────────┤
651
+ │ 1 │ 1 │ True │ True │ NULL │
652
+ │ 2 │ 2 │ True │ True │ True │
653
+ │ 3 │ 9 │ NULL │ False │ True │
654
+ │ NULL │ NULL │ NULL │ NULL │ NULL │
655
+ └───────┴───────┴─────────┴────────────────┴───────────────┘
656
+
657
+ Checking for values in a column from a different table:
658
+
659
+ >>> t2 = ibis.memtable({"x": [1, 2, 99], "y": [1, 2, None]})
660
+ >>> t.mutate(
661
+ ... a_in_x=t.a.isin(t2.x),
662
+ ... a_in_y=t.a.isin(t2.y),
663
+ ... a_in_y_plus_1=t.a.isin(t2.y + 1),
664
+ ... )
665
+ ┏━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━┓
666
+ ┃ a ┃ b ┃ a_in_x ┃ a_in_y ┃ a_in_y_plus_1 ┃
667
+ ┡━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━┩
668
+ │ int64 │ int64 │ boolean │ boolean │ boolean │
669
+ ├───────┼───────┼─────────┼─────────┼───────────────┤
670
+ │ 1 │ 1 │ True │ True │ NULL │
671
+ │ 2 │ 2 │ True │ True │ True │
672
+ │ 3 │ 9 │ False │ NULL │ True │
673
+ │ NULL │ NULL │ NULL │ NULL │ NULL │
674
+ └───────┴───────┴─────────┴─────────┴───────────────┘
695
675
"""
696
676
from ibis .expr .types import ArrayValue
697
677
@@ -703,49 +683,91 @@ def isin(self, values: Value | Sequence[Value], /) -> ir.BooleanValue:
703
683
return ops .InValues (self , values ).to_expr ()
704
684
705
685
def notin (self , values : Value | Sequence [Value ], / ) -> ir .BooleanValue :
706
- """Check whether this expression's values are not in `values`.
686
+ """Check whether this expression is not in `values`.
707
687
708
688
Opposite of [`Value.isin()`](./expression-generic.qmd#ibis.expr.types.generic.Value.isin).
709
689
690
+ `NULL` values in the input are propagated in the output.
691
+ If the `values` argument contains any `NULL` values,
692
+ then ibis follows the SQL behavior of returning `NULL` (not False)
693
+ when `self` is present.
694
+ See examples below for details.
695
+
710
696
Parameters
711
697
----------
712
698
values
713
- Values or expression to check for lack of membership
699
+ Values or expression to check for lack of membership.
714
700
715
701
Returns
716
702
-------
717
703
BooleanValue
718
- Whether ` self`'s values are not contained in `values`
704
+ True if self is not in `values`, False otherwise.
719
705
720
706
Examples
721
707
--------
722
708
>>> import ibis
723
709
>>> ibis.options.interactive = True
724
- >>> t = ibis.examples.penguins.fetch().limit(5)
725
- >>> t.bill_depth_mm
726
- ┏━━━━━━━━━━━━━━━┓
727
- ┃ bill_depth_mm ┃
728
- ┡━━━━━━━━━━━━━━━┩
729
- │ float64 │
730
- ├───────────────┤
731
- │ 18.7 │
732
- │ 17.4 │
733
- │ 18.0 │
734
- │ NULL │
735
- │ 19.3 │
736
- └───────────────┘
737
- >>> t.bill_depth_mm.notin([18.7, 18.1])
738
- ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
739
- ┃ Not(InValues(bill_depth_mm, (18.7, 18.1))) ┃
740
- ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
741
- │ boolean │
742
- ├────────────────────────────────────────────┤
743
- │ False │
744
- │ True │
745
- │ True │
746
- │ NULL │
747
- │ True │
748
- └────────────────────────────────────────────┘
710
+ >>> t = ibis.memtable(
711
+ ... {
712
+ ... "a": [1, 2, 3, None],
713
+ ... "b": [1, 2, 9, None],
714
+ ... },
715
+ ... schema={"a": int, "b": int},
716
+ ... )
717
+
718
+ Checking for values in literals:
719
+
720
+ >>> t.mutate(
721
+ ... a_notin_12=t.a.notin([1, 2]),
722
+ ... a_notin_12None=t.a.notin([1, 2, None]),
723
+ ... )
724
+ ┏━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
725
+ ┃ a ┃ b ┃ a_notin_12 ┃ a_notin_12None ┃
726
+ ┡━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
727
+ │ int64 │ int64 │ boolean │ boolean │
728
+ ├───────┼───────┼────────────┼────────────────┤
729
+ │ 1 │ 1 │ False │ False │
730
+ │ 2 │ 2 │ False │ False │
731
+ │ 3 │ 9 │ True │ NULL │
732
+ │ NULL │ NULL │ NULL │ NULL │
733
+ └───────┴───────┴────────────┴────────────────┘
734
+
735
+ Checking for values in columns of the same table:
736
+
737
+ >>> t.mutate(
738
+ ... a_notin_b=t.a.notin(t.b),
739
+ ... a_notin_b_no_null=t.a.notin(t.b.fill_null(0)),
740
+ ... a_notin_b_plus_1=t.a.notin(t.b + 1),
741
+ ... )
742
+ ┏━━━━━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┓
743
+ ┃ a ┃ b ┃ a_notin_b ┃ a_notin_b_no_null ┃ a_notin_b_plus_1 ┃
744
+ ┡━━━━━━━╇━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━┩
745
+ │ int64 │ int64 │ boolean │ boolean │ boolean │
746
+ ├───────┼───────┼───────────┼───────────────────┼──────────────────┤
747
+ │ 1 │ 1 │ False │ False │ NULL │
748
+ │ 2 │ 2 │ False │ False │ False │
749
+ │ 3 │ 9 │ NULL │ True │ False │
750
+ │ NULL │ NULL │ NULL │ NULL │ NULL │
751
+ └───────┴───────┴───────────┴───────────────────┴──────────────────┘
752
+
753
+ Checking for values in a column from a different table:
754
+
755
+ >>> t2 = ibis.memtable({"x": [1, 2, 99], "y": [1, 2, None]})
756
+ >>> t.mutate(
757
+ ... a_notin_x=t.a.notin(t2.x),
758
+ ... a_notin_y=t.a.notin(t2.y),
759
+ ... a_notin_y_plus_1=t.a.notin(t2.y + 1),
760
+ ... )
761
+ ┏━━━━━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┓
762
+ ┃ a ┃ b ┃ a_notin_x ┃ a_notin_y ┃ a_notin_y_plus_1 ┃
763
+ ┡━━━━━━━╇━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━┩
764
+ │ int64 │ int64 │ boolean │ boolean │ boolean │
765
+ ├───────┼───────┼───────────┼───────────┼──────────────────┤
766
+ │ 1 │ 1 │ False │ False │ NULL │
767
+ │ 2 │ 2 │ False │ False │ False │
768
+ │ 3 │ 9 │ True │ NULL │ False │
769
+ │ NULL │ NULL │ NULL │ NULL │ NULL │
770
+ └───────┴───────┴───────────┴───────────┴──────────────────┘
749
771
"""
750
772
return ~ self .isin (values )
751
773
0 commit comments