Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pola-rs
GitHub Repository: pola-rs/polars
Path: blob/main/crates/polars-ops/src/series/ops/is_in.rs
6939 views
1
use std::hash::Hash;
2
3
use arrow::array::BooleanArray;
4
use arrow::bitmap::BitmapBuilder;
5
use polars_core::prelude::arity::{unary_elementwise, unary_elementwise_values};
6
use polars_core::prelude::*;
7
use polars_core::{with_match_categorical_physical_type, with_match_physical_numeric_polars_type};
8
use polars_utils::total_ord::{ToTotalOrd, TotalEq, TotalHash};
9
10
use self::row_encode::_get_rows_encoded_ca_unordered;
11
12
fn is_in_helper_ca<'a, T>(
13
ca: &'a ChunkedArray<T>,
14
other: &'a ChunkedArray<T>,
15
nulls_equal: bool,
16
) -> PolarsResult<BooleanChunked>
17
where
18
T: PolarsDataType,
19
T::Physical<'a>: TotalHash + TotalEq + ToTotalOrd + Copy,
20
<T::Physical<'a> as ToTotalOrd>::TotalOrdItem: Hash + Eq + Copy,
21
{
22
let mut set = PlHashSet::with_capacity(other.len());
23
other.downcast_iter().for_each(|iter| {
24
iter.iter().for_each(|opt_val| {
25
if let Some(v) = opt_val {
26
set.insert(v.to_total_ord());
27
}
28
})
29
});
30
31
if nulls_equal {
32
if other.has_nulls() {
33
// If the rhs has nulls, then nulls in the left set evaluates to true.
34
Ok(unary_elementwise(ca, |val| {
35
val.is_none_or(|v| set.contains(&v.to_total_ord()))
36
}))
37
} else {
38
// The rhs has no nulls; nulls in the left evaluates to false.
39
Ok(unary_elementwise(ca, |val| {
40
val.is_some_and(|v| set.contains(&v.to_total_ord()))
41
}))
42
}
43
} else {
44
Ok(
45
unary_elementwise_values(ca, |v| set.contains(&v.to_total_ord()))
46
.with_name(ca.name().clone()),
47
)
48
}
49
}
50
51
fn is_in_helper_list_ca<'a, T>(
52
ca_in: &'a ChunkedArray<T>,
53
other: &'a ListChunked,
54
nulls_equal: bool,
55
) -> PolarsResult<BooleanChunked>
56
where
57
T: PolarsPhysicalType,
58
for<'b> T::Physical<'b>: TotalHash + TotalEq + ToTotalOrd + Copy,
59
for<'b> <T::Physical<'b> as ToTotalOrd>::TotalOrdItem: Hash + Eq + Copy,
60
{
61
let offsets = other.offsets()?;
62
let inner = other.get_inner();
63
let inner: &ChunkedArray<T> = inner.as_ref().as_ref();
64
let validity = other.rechunk_validity();
65
66
let mut ca: BooleanChunked = if ca_in.len() == 1 && other.len() != 1 {
67
let value = ca_in.get(0);
68
69
match value {
70
None if !nulls_equal => BooleanChunked::full_null(PlSmallStr::EMPTY, other.len()),
71
value => {
72
let mut builder = BitmapBuilder::with_capacity(other.len());
73
74
for (start, length) in offsets.offset_and_length_iter() {
75
let mut is_in = false;
76
for i in 0..length {
77
is_in |= value.to_total_ord() == inner.get(start + i).to_total_ord();
78
}
79
builder.push(is_in);
80
}
81
82
let values = builder.freeze();
83
84
let result = BooleanArray::new(ArrowDataType::Boolean, values, validity);
85
BooleanChunked::from_chunk_iter(PlSmallStr::EMPTY, [result])
86
},
87
}
88
} else {
89
assert_eq!(ca_in.len(), offsets.len_proxy());
90
{
91
if nulls_equal {
92
let mut builder = BitmapBuilder::with_capacity(ca_in.len());
93
94
for (value, (start, length)) in ca_in.iter().zip(offsets.offset_and_length_iter()) {
95
let mut is_in = false;
96
for i in 0..length {
97
is_in |= value.to_total_ord() == inner.get(start + i).to_total_ord();
98
}
99
builder.push(is_in);
100
}
101
102
let values = builder.freeze();
103
104
let result = BooleanArray::new(ArrowDataType::Boolean, values, validity);
105
BooleanChunked::from_chunk_iter(PlSmallStr::EMPTY, [result])
106
} else {
107
let mut builder = BitmapBuilder::with_capacity(ca_in.len());
108
109
for (value, (start, length)) in ca_in.iter().zip(offsets.offset_and_length_iter()) {
110
let mut is_in = false;
111
if value.is_some() {
112
for i in 0..length {
113
is_in |= value.to_total_ord() == inner.get(start + i).to_total_ord();
114
}
115
}
116
builder.push(is_in);
117
}
118
119
let values = builder.freeze();
120
121
let validity = match (validity, ca_in.rechunk_validity()) {
122
(None, None) => None,
123
(Some(v), None) | (None, Some(v)) => Some(v),
124
(Some(l), Some(r)) => Some(arrow::bitmap::and(&l, &r)),
125
};
126
127
let result = BooleanArray::new(ArrowDataType::Boolean, values, validity);
128
BooleanChunked::from_chunk_iter(PlSmallStr::EMPTY, [result])
129
}
130
}
131
};
132
ca.rename(ca_in.name().clone());
133
Ok(ca)
134
}
135
136
#[cfg(feature = "dtype-array")]
137
fn is_in_helper_array_ca<'a, T>(
138
ca_in: &'a ChunkedArray<T>,
139
other: &'a ArrayChunked,
140
nulls_equal: bool,
141
) -> PolarsResult<BooleanChunked>
142
where
143
T: PolarsPhysicalType,
144
for<'b> T::Physical<'b>: TotalHash + TotalEq + ToTotalOrd + Copy,
145
for<'b> <T::Physical<'b> as ToTotalOrd>::TotalOrdItem: Hash + Eq + Copy,
146
{
147
let width = other.width();
148
let inner = other.get_inner();
149
let inner: &ChunkedArray<T> = inner.as_ref().as_ref();
150
let validity = other.rechunk_validity();
151
152
let mut ca: BooleanChunked = if ca_in.len() == 1 && other.len() != 1 {
153
let value = ca_in.get(0);
154
155
match value {
156
None if !nulls_equal => BooleanChunked::full_null(PlSmallStr::EMPTY, other.len()),
157
value => {
158
let mut builder = BitmapBuilder::with_capacity(other.len());
159
160
for i in 0..other.len() {
161
let mut is_in = false;
162
for j in 0..width {
163
is_in |= value.to_total_ord() == inner.get(i * width + j).to_total_ord();
164
}
165
builder.push(is_in);
166
}
167
168
let values = builder.freeze();
169
170
let result = BooleanArray::new(ArrowDataType::Boolean, values, validity);
171
BooleanChunked::from_chunk_iter(PlSmallStr::EMPTY, [result])
172
},
173
}
174
} else {
175
assert_eq!(ca_in.len(), other.len());
176
{
177
if nulls_equal {
178
let mut builder = BitmapBuilder::with_capacity(ca_in.len());
179
180
for (i, value) in ca_in.iter().enumerate() {
181
let mut is_in = false;
182
for j in 0..width {
183
is_in |= value.to_total_ord() == inner.get(i * width + j).to_total_ord();
184
}
185
builder.push(is_in);
186
}
187
188
let values = builder.freeze();
189
190
let result = BooleanArray::new(ArrowDataType::Boolean, values, validity);
191
BooleanChunked::from_chunk_iter(PlSmallStr::EMPTY, [result])
192
} else {
193
let mut builder = BitmapBuilder::with_capacity(ca_in.len());
194
195
for (i, value) in ca_in.iter().enumerate() {
196
let mut is_in = false;
197
if value.is_some() {
198
for j in 0..width {
199
is_in |=
200
value.to_total_ord() == inner.get(i * width + j).to_total_ord();
201
}
202
}
203
builder.push(is_in);
204
}
205
206
let values = builder.freeze();
207
208
let validity = match (validity, ca_in.rechunk_validity()) {
209
(None, None) => None,
210
(Some(v), None) | (None, Some(v)) => Some(v),
211
(Some(l), Some(r)) => Some(arrow::bitmap::and(&l, &r)),
212
};
213
214
let result = BooleanArray::new(ArrowDataType::Boolean, values, validity);
215
BooleanChunked::from_chunk_iter(PlSmallStr::EMPTY, [result])
216
}
217
}
218
};
219
ca.rename(ca_in.name().clone());
220
Ok(ca)
221
}
222
223
fn is_in_numeric<T>(
224
ca_in: &ChunkedArray<T>,
225
other: &Series,
226
nulls_equal: bool,
227
) -> PolarsResult<BooleanChunked>
228
where
229
T: PolarsNumericType,
230
T::Native: TotalHash + TotalEq + ToTotalOrd,
231
<T::Native as ToTotalOrd>::TotalOrdItem: Hash + Eq + Copy,
232
{
233
match other.dtype() {
234
DataType::List(..) => {
235
let other = other.list()?;
236
if other.len() == 1 {
237
if other.has_nulls() {
238
return Ok(BooleanChunked::full_null(ca_in.name().clone(), ca_in.len()));
239
}
240
241
let other = other.explode(true)?;
242
let other = other.as_ref().as_ref();
243
is_in_helper_ca(ca_in, other, nulls_equal)
244
} else {
245
is_in_helper_list_ca(ca_in, other, nulls_equal)
246
}
247
},
248
#[cfg(feature = "dtype-array")]
249
DataType::Array(..) => {
250
let other = other.array()?;
251
if other.len() == 1 {
252
if other.has_nulls() {
253
return Ok(BooleanChunked::full_null(ca_in.name().clone(), ca_in.len()));
254
}
255
256
let other = other.explode(true)?;
257
let other = other.as_ref().as_ref();
258
is_in_helper_ca(ca_in, other, nulls_equal)
259
} else {
260
is_in_helper_array_ca(ca_in, other, nulls_equal)
261
}
262
},
263
_ => polars_bail!(opq = is_in, ca_in.dtype(), other.dtype()),
264
}
265
}
266
267
fn is_in_string(
268
ca_in: &StringChunked,
269
other: &Series,
270
nulls_equal: bool,
271
) -> PolarsResult<BooleanChunked> {
272
let other = match other.dtype() {
273
DataType::List(dt) if dt.is_string() || dt.is_enum() || dt.is_categorical() => {
274
let other = other.list()?;
275
other
276
.apply_to_inner(&|mut s| {
277
if dt.is_enum() || dt.is_categorical() {
278
s = s.cast(&DataType::String)?;
279
}
280
let s = s.str()?;
281
Ok(s.as_binary().into_series())
282
})?
283
.into_series()
284
},
285
#[cfg(feature = "dtype-array")]
286
DataType::Array(dt, _) if dt.is_string() || dt.is_enum() || dt.is_categorical() => {
287
let other = other.array()?;
288
other
289
.apply_to_inner(&|mut s| {
290
if dt.is_enum() || dt.is_categorical() {
291
s = s.cast(&DataType::String)?;
292
}
293
Ok(s.str()?.as_binary().into_series())
294
})?
295
.into_series()
296
},
297
_ => polars_bail!(opq = is_in, ca_in.dtype(), other.dtype()),
298
};
299
is_in_binary(&ca_in.as_binary(), &other, nulls_equal)
300
}
301
302
fn is_in_binary(
303
ca_in: &BinaryChunked,
304
other: &Series,
305
nulls_equal: bool,
306
) -> PolarsResult<BooleanChunked> {
307
match other.dtype() {
308
DataType::List(dt) if DataType::Binary == **dt => {
309
let other = other.list()?;
310
if other.len() == 1 {
311
if other.has_nulls() {
312
return Ok(BooleanChunked::full_null(ca_in.name().clone(), ca_in.len()));
313
}
314
315
let other = other.explode(true)?;
316
let other = other.binary()?;
317
is_in_helper_ca(ca_in, other, nulls_equal)
318
} else {
319
is_in_helper_list_ca(ca_in, other, nulls_equal)
320
}
321
},
322
#[cfg(feature = "dtype-array")]
323
DataType::Array(dt, _) if DataType::Binary == **dt => {
324
let other = other.array()?;
325
if other.len() == 1 {
326
if other.has_nulls() {
327
return Ok(BooleanChunked::full_null(ca_in.name().clone(), ca_in.len()));
328
}
329
330
let other = other.explode(true)?;
331
let other = other.binary()?;
332
is_in_helper_ca(ca_in, other, nulls_equal)
333
} else {
334
is_in_helper_array_ca(ca_in, other, nulls_equal)
335
}
336
},
337
_ => polars_bail!(opq = is_in, ca_in.dtype(), other.dtype()),
338
}
339
}
340
341
fn is_in_boolean(
342
ca_in: &BooleanChunked,
343
other: &Series,
344
nulls_equal: bool,
345
) -> PolarsResult<BooleanChunked> {
346
fn is_in_boolean_broadcast(
347
ca_in: &BooleanChunked,
348
other: &BooleanChunked,
349
nulls_equal: bool,
350
) -> PolarsResult<BooleanChunked> {
351
let has_true = other.any();
352
let nc = other.null_count();
353
354
let has_false = if nc == 0 {
355
!other.all()
356
} else {
357
(other.sum().unwrap() as usize + nc) != other.len()
358
};
359
let value_map = |v| if v { has_true } else { has_false };
360
if nulls_equal {
361
if other.has_nulls() {
362
// If the rhs has nulls, then nulls in the left set evaluates to true.
363
Ok(ca_in.apply(|opt_v| Some(opt_v.is_none_or(value_map))))
364
} else {
365
// The rhs has no nulls; nulls in the left evaluates to false.
366
Ok(ca_in.apply(|opt_v| Some(opt_v.is_some_and(value_map))))
367
}
368
} else {
369
Ok(ca_in
370
.apply_values(value_map)
371
.with_name(ca_in.name().clone()))
372
}
373
}
374
375
match other.dtype() {
376
DataType::List(dt) if ca_in.dtype() == &**dt => {
377
let other = other.list()?;
378
if other.len() == 1 {
379
if other.has_nulls() {
380
return Ok(BooleanChunked::full_null(ca_in.name().clone(), ca_in.len()));
381
}
382
383
let other = other.explode(true)?;
384
let other = other.bool()?;
385
is_in_boolean_broadcast(ca_in, other, nulls_equal)
386
} else {
387
is_in_helper_list_ca(ca_in, other, nulls_equal)
388
}
389
},
390
#[cfg(feature = "dtype-array")]
391
DataType::Array(dt, _) if ca_in.dtype() == &**dt => {
392
let other = other.array()?;
393
if other.len() == 1 {
394
if other.has_nulls() {
395
return Ok(BooleanChunked::full_null(ca_in.name().clone(), ca_in.len()));
396
}
397
398
let other = other.explode(true)?;
399
let other = other.bool()?;
400
is_in_boolean_broadcast(ca_in, other, nulls_equal)
401
} else {
402
is_in_helper_array_ca(ca_in, other, nulls_equal)
403
}
404
},
405
_ => polars_bail!(opq = is_in, ca_in.dtype(), other.dtype()),
406
}
407
}
408
409
#[cfg(feature = "dtype-categorical")]
410
fn is_in_cat_and_enum<T: PolarsCategoricalType>(
411
ca_in: &CategoricalChunked<T>,
412
other: &Series,
413
nulls_equal: bool,
414
) -> PolarsResult<BooleanChunked>
415
where
416
T::Native: ToTotalOrd<TotalOrdItem = T::Native>,
417
{
418
let to_categories = match (ca_in.dtype(), other.dtype().inner_dtype().unwrap()) {
419
(DataType::Enum(_, mapping) | DataType::Categorical(_, mapping), DataType::String) => {
420
(&|s: Series| {
421
let ca = s.str()?;
422
let ca: ChunkedArray<T::PolarsPhysical> = ca
423
.iter()
424
.flat_map(|opt_s| {
425
if let Some(s) = opt_s {
426
Some(mapping.get_cat(s).map(T::Native::from_cat))
427
} else {
428
Some(None)
429
}
430
})
431
.collect_ca(PlSmallStr::EMPTY);
432
Ok(ca.into_series())
433
}) as _
434
},
435
(DataType::Categorical(lcats, _), DataType::Categorical(rcats, _)) => {
436
ensure_same_categories(lcats, rcats)?;
437
(&|s: Series| Ok(s.cat::<T>()?.physical().clone().into_series())) as _
438
},
439
(DataType::Enum(lfcats, _), DataType::Enum(rfcats, _)) => {
440
ensure_same_frozen_categories(lfcats, rfcats)?;
441
(&|s: Series| Ok(s.cat::<T>()?.physical().clone().into_series())) as _
442
},
443
_ => polars_bail!(opq = is_in, ca_in.dtype(), other.dtype()),
444
};
445
446
let other = match other.dtype() {
447
DataType::List(_) => other.list()?.apply_to_inner(to_categories)?.into_series(),
448
#[cfg(feature = "dtype-array")]
449
DataType::Array(_, _) => other.array()?.apply_to_inner(to_categories)?.into_series(),
450
_ => polars_bail!(opq = is_in, ca_in.dtype(), other.dtype()),
451
};
452
453
is_in_numeric(ca_in.physical(), &other, nulls_equal)
454
}
455
456
fn is_in_null(s: &Series, other: &Series, nulls_equal: bool) -> PolarsResult<BooleanChunked> {
457
if nulls_equal {
458
let ca_in = s.null()?;
459
Ok(match other.dtype() {
460
DataType::List(_) => {
461
let other = other.list()?;
462
if other.len() == 1 {
463
if other.has_nulls() {
464
return Ok(BooleanChunked::full_null(ca_in.name().clone(), ca_in.len()));
465
}
466
467
let other = other.explode(true)?;
468
BooleanChunked::from_iter_values(
469
ca_in.name().clone(),
470
std::iter::repeat_n(other.has_nulls(), ca_in.len()),
471
)
472
} else {
473
other.apply_amortized_generic(|opt_s| {
474
Some(opt_s.map(|s| s.as_ref().has_nulls()) == Some(true))
475
})
476
}
477
},
478
#[cfg(feature = "dtype-array")]
479
DataType::Array(_, _) => {
480
let other = other.array()?;
481
if other.len() == 1 {
482
if other.has_nulls() {
483
return Ok(BooleanChunked::full_null(ca_in.name().clone(), ca_in.len()));
484
}
485
486
let other = other.explode(true)?;
487
BooleanChunked::from_iter_values(
488
ca_in.name().clone(),
489
std::iter::repeat_n(other.has_nulls(), ca_in.len()),
490
)
491
} else {
492
other.apply_amortized_generic(|opt_s| {
493
Some(opt_s.map(|s| s.as_ref().has_nulls()) == Some(true))
494
})
495
}
496
},
497
_ => polars_bail!(opq = is_in, ca_in.dtype(), other.dtype()),
498
})
499
} else {
500
let out = s.cast(&DataType::Boolean)?;
501
let ca_bool = out.bool()?.clone();
502
Ok(ca_bool)
503
}
504
}
505
506
#[cfg(feature = "dtype-decimal")]
507
fn is_in_decimal(
508
ca_in: &DecimalChunked,
509
other: &Series,
510
nulls_equal: bool,
511
) -> PolarsResult<BooleanChunked> {
512
let Some(DataType::Decimal(_, other_scale)) = other.dtype().inner_dtype() else {
513
polars_bail!(opq = is_in, ca_in.dtype(), other.dtype());
514
};
515
let other_scale = other_scale.unwrap();
516
let scale = ca_in.scale().max(other_scale);
517
let ca_in = ca_in.to_scale(scale)?;
518
519
match other.dtype() {
520
DataType::List(_) => {
521
let other = other.list()?;
522
let other = other.apply_to_inner(&|s| {
523
let s = s.decimal()?;
524
let s = s.to_scale(scale)?;
525
let s = s.physical();
526
Ok(s.to_owned().into_series())
527
})?;
528
let other = other.into_series();
529
is_in_numeric(ca_in.physical(), &other, nulls_equal)
530
},
531
#[cfg(feature = "dtype-array")]
532
DataType::Array(_, _) => {
533
let other = other.array()?;
534
let other = other.apply_to_inner(&|s| {
535
let s = s.decimal()?;
536
let s = s.to_scale(scale)?;
537
let s = s.physical();
538
Ok(s.to_owned().into_series())
539
})?;
540
let other = other.into_series();
541
is_in_numeric(ca_in.physical(), &other, nulls_equal)
542
},
543
_ => unreachable!(),
544
}
545
}
546
547
fn is_in_row_encoded(
548
s: &Series,
549
other: &Series,
550
nulls_equal: bool,
551
) -> PolarsResult<BooleanChunked> {
552
let ca_in = _get_rows_encoded_ca_unordered(s.name().clone(), &[s.clone().into_column()])?;
553
let mut mask = match other.dtype() {
554
DataType::List(_) => {
555
let other = other.list()?;
556
let other = other.apply_to_inner(&|s| {
557
Ok(
558
_get_rows_encoded_ca_unordered(s.name().clone(), &[s.into_column()])?
559
.into_series(),
560
)
561
})?;
562
if other.len() == 1 {
563
if other.has_nulls() {
564
return Ok(BooleanChunked::full_null(ca_in.name().clone(), ca_in.len()));
565
}
566
567
let other = other.explode(true)?;
568
let other = other.binary_offset()?;
569
is_in_helper_ca(&ca_in, other, nulls_equal)
570
} else {
571
is_in_helper_list_ca(&ca_in, &other, nulls_equal)
572
}
573
},
574
#[cfg(feature = "dtype-array")]
575
DataType::Array(_, _) => {
576
let other = other.array()?;
577
let other = other.apply_to_inner(&|s| {
578
Ok(
579
_get_rows_encoded_ca_unordered(s.name().clone(), &[s.into_column()])?
580
.into_series(),
581
)
582
})?;
583
if other.len() == 1 {
584
if other.has_nulls() {
585
return Ok(BooleanChunked::full_null(ca_in.name().clone(), ca_in.len()));
586
}
587
588
let other = other.explode(true)?;
589
let other = other.binary_offset()?;
590
is_in_helper_ca(&ca_in, other, nulls_equal)
591
} else {
592
is_in_helper_array_ca(&ca_in, &other, nulls_equal)
593
}
594
},
595
_ => unreachable!(),
596
}?;
597
598
let mut validity = other.rechunk_validity();
599
if !nulls_equal {
600
validity = match (validity, s.rechunk_validity()) {
601
(None, None) => None,
602
(Some(v), None) | (None, Some(v)) => Some(v),
603
(Some(l), Some(r)) => Some(arrow::bitmap::and(&l, &r)),
604
};
605
}
606
607
assert_eq!(mask.null_count(), 0);
608
mask.with_validities(&[validity]);
609
610
Ok(mask)
611
}
612
613
pub fn is_in(s: &Series, other: &Series, nulls_equal: bool) -> PolarsResult<BooleanChunked> {
614
polars_ensure!(
615
s.len() == other.len() || s.len() == 1 || other.len() == 1,
616
length_mismatch = "is_in",
617
s.len(),
618
other.len()
619
);
620
621
#[allow(unused_mut)]
622
let mut other_is_valid_type = matches!(other.dtype(), DataType::List(_));
623
#[cfg(feature = "dtype-array")]
624
{
625
other_is_valid_type |= matches!(other.dtype(), DataType::Array(..))
626
}
627
polars_ensure!(other_is_valid_type, opq = is_in, s.dtype(), other.dtype());
628
629
match s.dtype() {
630
#[cfg(feature = "dtype-categorical")]
631
dt @ DataType::Categorical(_, _) | dt @ DataType::Enum(_, _) => {
632
with_match_categorical_physical_type!(dt.cat_physical().unwrap(), |$C| {
633
is_in_cat_and_enum(s.cat::<$C>().unwrap(), other, nulls_equal)
634
})
635
},
636
DataType::String => {
637
let ca = s.str().unwrap();
638
is_in_string(ca, other, nulls_equal)
639
},
640
DataType::Binary => {
641
let ca = s.binary().unwrap();
642
is_in_binary(ca, other, nulls_equal)
643
},
644
DataType::Boolean => {
645
let ca = s.bool().unwrap();
646
is_in_boolean(ca, other, nulls_equal)
647
},
648
DataType::Null => is_in_null(s, other, nulls_equal),
649
#[cfg(feature = "dtype-decimal")]
650
DataType::Decimal(_, _) => {
651
let ca_in = s.decimal()?;
652
is_in_decimal(ca_in, other, nulls_equal)
653
},
654
dt if dt.is_nested() => is_in_row_encoded(s, other, nulls_equal),
655
dt if dt.to_physical().is_primitive_numeric() => {
656
let s = s.to_physical_repr();
657
let other = other.to_physical_repr();
658
let other = other.as_ref();
659
with_match_physical_numeric_polars_type!(s.dtype(), |$T| {
660
let ca: &ChunkedArray<$T> = s.as_ref().as_ref().as_ref();
661
is_in_numeric(ca, other, nulls_equal)
662
})
663
},
664
dt => polars_bail!(opq = is_in, dt),
665
}
666
}
667
668