Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pola-rs
GitHub Repository: pola-rs/polars
Path: blob/main/crates/polars-expr/src/reduce/min_max_by.rs
8424 views
1
#![allow(unsafe_op_in_unsafe_fn)]
2
use std::borrow::Cow;
3
use std::marker::PhantomData;
4
5
use num_traits::Bounded;
6
use polars_core::chunked_array::arg_min_max::{
7
arg_max_binary, arg_max_bool, arg_max_numeric, arg_min_binary, arg_min_bool, arg_min_numeric,
8
};
9
use polars_core::with_match_physical_integer_polars_type;
10
use polars_utils::arg_min_max::ArgMinMax;
11
use polars_utils::float::IsFloat;
12
use polars_utils::min_max::MinMax;
13
14
use super::*;
15
use crate::reduce::first_last::new_last_reduction;
16
17
pub fn new_min_by_reduction(
18
dtype: DataType,
19
by_dtype: DataType,
20
) -> PolarsResult<Box<dyn GroupedReduction>> {
21
// TODO: Move the error checks up and make this function infallible
22
use DataType::*;
23
use SelectPayloadGroupedReduction as SPGR;
24
let payload = new_last_reduction(dtype.clone());
25
Ok(match &by_dtype {
26
Boolean => Box::new(SPGR::new(by_dtype, BooleanMinSelector, payload)),
27
#[cfg(all(feature = "dtype-f16", feature = "propagate_nans"))]
28
#[cfg(feature = "dtype-f16")]
29
Float16 => Box::new(SPGR::new(
30
by_dtype,
31
MinSelector::<Float16Type>(PhantomData),
32
payload,
33
)),
34
Float32 => Box::new(SPGR::new(
35
by_dtype,
36
MinSelector::<Float32Type>(PhantomData),
37
payload,
38
)),
39
Float64 => Box::new(SPGR::new(
40
by_dtype,
41
MinSelector::<Float64Type>(PhantomData),
42
payload,
43
)),
44
Null => Box::new(NullGroupedReduction::new(Scalar::null(dtype))),
45
String | Binary => Box::new(SPGR::new(by_dtype, BinaryMinSelector, payload)),
46
_ if by_dtype.is_integer() || by_dtype.is_temporal() || by_dtype.is_enum() => {
47
with_match_physical_integer_polars_type!(by_dtype.to_physical(), |$T| {
48
Box::new(SPGR::new(by_dtype, MinSelector::<$T>(PhantomData), payload))
49
})
50
},
51
#[cfg(feature = "dtype-decimal")]
52
Decimal(_, _) => Box::new(SPGR::new(
53
by_dtype,
54
MinSelector::<Int128Type>(PhantomData),
55
payload,
56
)),
57
#[cfg(feature = "dtype-categorical")]
58
Categorical(cats, map) => with_match_categorical_physical_type!(cats.physical(), |$C| {
59
let map = map.clone();
60
Box::new(SPGR::new(by_dtype, CatMinSelector::<$C>(map, PhantomData), payload))
61
}),
62
_ => {
63
polars_bail!(InvalidOperation: "`min_by` operation not supported for by dtype `{by_dtype}`")
64
},
65
})
66
}
67
68
pub fn new_max_by_reduction(
69
dtype: DataType,
70
by_dtype: DataType,
71
) -> PolarsResult<Box<dyn GroupedReduction>> {
72
// TODO: Move the error checks up and make this function infallible
73
use DataType::*;
74
use SelectPayloadGroupedReduction as SPGR;
75
let payload = new_last_reduction(dtype.clone());
76
Ok(match &by_dtype {
77
Boolean => Box::new(SPGR::new(by_dtype, BooleanMaxSelector, payload)),
78
#[cfg(all(feature = "dtype-f16", feature = "propagate_nans"))]
79
#[cfg(feature = "dtype-f16")]
80
Float16 => Box::new(SPGR::new(
81
by_dtype,
82
MaxSelector::<Float16Type>(PhantomData),
83
payload,
84
)),
85
Float32 => Box::new(SPGR::new(
86
by_dtype,
87
MaxSelector::<Float32Type>(PhantomData),
88
payload,
89
)),
90
Float64 => Box::new(SPGR::new(
91
by_dtype,
92
MaxSelector::<Float64Type>(PhantomData),
93
payload,
94
)),
95
Null => Box::new(NullGroupedReduction::new(Scalar::null(dtype))),
96
String | Binary => Box::new(SPGR::new(by_dtype, BinaryMaxSelector, payload)),
97
_ if by_dtype.is_integer() || by_dtype.is_temporal() || by_dtype.is_enum() => {
98
with_match_physical_integer_polars_type!(by_dtype.to_physical(), |$T| {
99
Box::new(SPGR::new(by_dtype, MaxSelector::<$T>(PhantomData), payload))
100
})
101
},
102
#[cfg(feature = "dtype-decimal")]
103
Decimal(_, _) => Box::new(SPGR::new(
104
by_dtype,
105
MaxSelector::<Int128Type>(PhantomData),
106
payload,
107
)),
108
#[cfg(feature = "dtype-categorical")]
109
Categorical(cats, map) => with_match_categorical_physical_type!(cats.physical(), |$C| {
110
let map = map.clone();
111
Box::new(SPGR::new(by_dtype, CatMaxSelector::<$C>(map, PhantomData), payload))
112
}),
113
_ => {
114
polars_bail!(InvalidOperation: "`max_by` operation not supported for by dtype `{by_dtype}`")
115
},
116
})
117
}
118
119
trait SelectReducer: Clone + Send + Sync + 'static {
120
type Value: Clone + Send + Sync + 'static;
121
type Dtype: PolarsPhysicalType;
122
123
fn init(&self) -> Self::Value;
124
125
fn cast_series<'a>(&self, s: &'a Series) -> Cow<'a, Series> {
126
Cow::Borrowed(s)
127
}
128
129
fn select_ca(&self, v: &mut Self::Value, ca: &ChunkedArray<Self::Dtype>) -> Option<usize>;
130
131
fn select_one(
132
&self,
133
a: &mut Self::Value,
134
b: <Self::Dtype as PolarsDataType>::Physical<'_>,
135
) -> bool;
136
137
fn select_combine(&self, a: &mut Self::Value, b: &Self::Value) -> bool;
138
}
139
140
struct MinSelector<T>(PhantomData<T>);
141
struct MaxSelector<T>(PhantomData<T>);
142
143
impl<T> Clone for MinSelector<T> {
144
fn clone(&self) -> Self {
145
Self(PhantomData)
146
}
147
}
148
149
impl<T> SelectReducer for MinSelector<T>
150
where
151
T: PolarsNumericType,
152
ChunkedArray<T>: ChunkAgg<T::Native>,
153
for<'b> &'b [T::Native]: ArgMinMax,
154
{
155
type Value = T::Native;
156
type Dtype = T;
157
158
fn init(&self) -> Self::Value {
159
if T::Native::is_float() {
160
T::Native::nan_value()
161
} else {
162
T::Native::max_value()
163
}
164
}
165
166
fn cast_series<'a>(&self, s: &'a Series) -> Cow<'a, Series> {
167
s.to_physical_repr()
168
}
169
170
fn select_ca(&self, v: &mut Self::Value, ca: &ChunkedArray<Self::Dtype>) -> Option<usize> {
171
arg_min_numeric(ca).filter(|idx| {
172
let val = unsafe { ca.value_unchecked(*idx) };
173
self.select_one(v, val)
174
})
175
}
176
177
fn select_one(
178
&self,
179
a: &mut Self::Value,
180
b: <Self::Dtype as PolarsDataType>::Physical<'_>,
181
) -> bool {
182
let better = b.nan_max_lt(a);
183
if better {
184
*a = b;
185
}
186
better
187
}
188
189
fn select_combine(&self, a: &mut Self::Value, b: &Self::Value) -> bool {
190
self.select_one(a, *b)
191
}
192
}
193
194
impl<T> Clone for MaxSelector<T> {
195
fn clone(&self) -> Self {
196
Self(PhantomData)
197
}
198
}
199
200
impl<T> SelectReducer for MaxSelector<T>
201
where
202
T: PolarsNumericType,
203
ChunkedArray<T>: ChunkAgg<T::Native>,
204
for<'b> &'b [T::Native]: ArgMinMax,
205
{
206
type Value = T::Native;
207
type Dtype = T;
208
209
fn init(&self) -> Self::Value {
210
if T::Native::is_float() {
211
T::Native::nan_value()
212
} else {
213
T::Native::min_value()
214
}
215
}
216
217
fn cast_series<'a>(&self, s: &'a Series) -> Cow<'a, Series> {
218
s.to_physical_repr()
219
}
220
221
fn select_ca(&self, v: &mut Self::Value, ca: &ChunkedArray<Self::Dtype>) -> Option<usize> {
222
arg_max_numeric(ca).filter(|idx| {
223
let val = unsafe { ca.value_unchecked(*idx) };
224
self.select_one(v, val)
225
})
226
}
227
228
fn select_one(
229
&self,
230
a: &mut Self::Value,
231
b: <Self::Dtype as PolarsDataType>::Physical<'_>,
232
) -> bool {
233
let better = b.nan_min_gt(a);
234
if better {
235
*a = b;
236
}
237
better
238
}
239
240
fn select_combine(&self, a: &mut Self::Value, b: &Self::Value) -> bool {
241
self.select_one(a, *b)
242
}
243
}
244
245
#[derive(Clone)]
246
struct BinaryMinSelector;
247
#[derive(Clone)]
248
struct BinaryMaxSelector;
249
250
impl SelectReducer for BinaryMinSelector {
251
type Dtype = BinaryType;
252
type Value = Option<Vec<u8>>;
253
254
fn init(&self) -> Self::Value {
255
// There's no "maximum string" initializer.
256
None
257
}
258
259
#[inline(always)]
260
fn cast_series<'a>(&self, s: &'a Series) -> Cow<'a, Series> {
261
Cow::Owned(s.cast(&DataType::Binary).unwrap())
262
}
263
264
fn select_ca(&self, v: &mut Self::Value, ca: &ChunkedArray<Self::Dtype>) -> Option<usize> {
265
arg_min_binary(ca).filter(|idx| {
266
let val = unsafe { ca.value_unchecked(*idx) };
267
self.select_one(v, val)
268
})
269
}
270
271
fn select_one(
272
&self,
273
a: &mut Self::Value,
274
b: <Self::Dtype as PolarsDataType>::Physical<'_>,
275
) -> bool {
276
if let Some(av) = a {
277
if b < av.as_slice() {
278
av.clear();
279
av.extend_from_slice(b);
280
true
281
} else {
282
false
283
}
284
} else {
285
*a = Some(b.to_vec());
286
true
287
}
288
}
289
290
fn select_combine(&self, a: &mut Self::Value, b: &Self::Value) -> bool {
291
if let Some(bv) = b {
292
self.select_one(a, bv)
293
} else {
294
false
295
}
296
}
297
}
298
299
impl SelectReducer for BinaryMaxSelector {
300
type Dtype = BinaryType;
301
type Value = Vec<u8>;
302
303
fn init(&self) -> Self::Value {
304
// Empty string is <= any other string, so can initialize max with it.
305
Vec::new()
306
}
307
308
#[inline(always)]
309
fn cast_series<'a>(&self, s: &'a Series) -> Cow<'a, Series> {
310
Cow::Owned(s.cast(&DataType::Binary).unwrap())
311
}
312
313
fn select_ca(&self, v: &mut Self::Value, ca: &ChunkedArray<Self::Dtype>) -> Option<usize> {
314
arg_max_binary(ca).filter(|idx| {
315
let val = unsafe { ca.value_unchecked(*idx) };
316
self.select_one(v, val)
317
})
318
}
319
320
fn select_one(
321
&self,
322
a: &mut Self::Value,
323
b: <Self::Dtype as PolarsDataType>::Physical<'_>,
324
) -> bool {
325
let better = b > a.as_slice();
326
if better {
327
a.clear();
328
a.extend_from_slice(b);
329
}
330
better
331
}
332
333
fn select_combine(&self, a: &mut Self::Value, b: &Self::Value) -> bool {
334
self.select_one(a, b)
335
}
336
}
337
338
#[derive(Clone)]
339
struct BooleanMinSelector;
340
#[derive(Clone)]
341
struct BooleanMaxSelector;
342
343
impl SelectReducer for BooleanMinSelector {
344
type Value = bool;
345
type Dtype = BooleanType;
346
347
fn init(&self) -> Self::Value {
348
true
349
}
350
351
fn select_ca(&self, v: &mut Self::Value, ca: &ChunkedArray<Self::Dtype>) -> Option<usize> {
352
arg_min_bool(ca).filter(|idx| {
353
let val = unsafe { ca.value_unchecked(*idx) };
354
self.select_one(v, val)
355
})
356
}
357
358
fn select_one(
359
&self,
360
a: &mut Self::Value,
361
b: <Self::Dtype as PolarsDataType>::Physical<'_>,
362
) -> bool {
363
#[allow(clippy::bool_comparison)]
364
let better = b < *a;
365
if better {
366
*a = b;
367
}
368
better
369
}
370
371
fn select_combine(&self, a: &mut Self::Value, b: &Self::Value) -> bool {
372
self.select_one(a, *b)
373
}
374
}
375
376
impl SelectReducer for BooleanMaxSelector {
377
type Value = bool;
378
type Dtype = BooleanType;
379
380
fn init(&self) -> Self::Value {
381
false
382
}
383
384
fn select_ca(&self, v: &mut Self::Value, ca: &ChunkedArray<Self::Dtype>) -> Option<usize> {
385
arg_max_bool(ca).filter(|idx| {
386
let val = unsafe { ca.value_unchecked(*idx) };
387
self.select_one(v, val)
388
})
389
}
390
391
fn select_one(
392
&self,
393
a: &mut Self::Value,
394
b: <Self::Dtype as PolarsDataType>::Physical<'_>,
395
) -> bool {
396
#[allow(clippy::bool_comparison)]
397
let better = b > *a;
398
if better {
399
*a = b;
400
}
401
better
402
}
403
404
fn select_combine(&self, a: &mut Self::Value, b: &Self::Value) -> bool {
405
self.select_one(a, *b)
406
}
407
}
408
409
#[cfg(feature = "dtype-categorical")]
410
struct CatMinSelector<T>(Arc<CategoricalMapping>, PhantomData<T>);
411
412
#[cfg(feature = "dtype-categorical")]
413
impl<T> Clone for CatMinSelector<T> {
414
fn clone(&self) -> Self {
415
Self(self.0.clone(), PhantomData)
416
}
417
}
418
419
#[cfg(feature = "dtype-categorical")]
420
impl<T: PolarsCategoricalType> SelectReducer for CatMinSelector<T> {
421
type Dtype = T::PolarsPhysical;
422
type Value = T::Native;
423
424
fn init(&self) -> Self::Value {
425
T::Native::max_value() // Ensures it's invalid, preferring the other value.
426
}
427
428
#[inline(always)]
429
fn cast_series<'a>(&self, s: &'a Series) -> Cow<'a, Series> {
430
s.to_physical_repr()
431
}
432
433
fn select_ca(&self, v: &mut Self::Value, ca: &ChunkedArray<Self::Dtype>) -> Option<usize> {
434
use polars_core::chunked_array::arg_min_max::arg_min_opt_iter;
435
let arg_min = arg_min_opt_iter(ca.iter().map(|cat| self.0.cat_to_str(cat?.as_cat())));
436
arg_min.filter(|idx| {
437
let val = unsafe { ca.value_unchecked(*idx) };
438
self.select_one(v, val)
439
})
440
}
441
442
fn select_one(
443
&self,
444
a: &mut Self::Value,
445
b: <Self::Dtype as PolarsDataType>::Physical<'_>,
446
) -> bool {
447
let Some(b_s) = self.0.cat_to_str(b.as_cat()) else {
448
return false;
449
};
450
let Some(a_s) = self.0.cat_to_str(a.as_cat()) else {
451
*a = b;
452
return true;
453
};
454
455
let better = b_s < a_s;
456
if better {
457
*a = b;
458
}
459
better
460
}
461
462
fn select_combine(&self, a: &mut Self::Value, b: &Self::Value) -> bool {
463
self.select_one(a, *b)
464
}
465
}
466
467
#[cfg(feature = "dtype-categorical")]
468
struct CatMaxSelector<T>(Arc<CategoricalMapping>, PhantomData<T>);
469
470
#[cfg(feature = "dtype-categorical")]
471
impl<T> Clone for CatMaxSelector<T> {
472
fn clone(&self) -> Self {
473
Self(self.0.clone(), PhantomData)
474
}
475
}
476
477
#[cfg(feature = "dtype-categorical")]
478
impl<T: PolarsCategoricalType> SelectReducer for CatMaxSelector<T> {
479
type Dtype = T::PolarsPhysical;
480
type Value = T::Native;
481
482
fn init(&self) -> Self::Value {
483
T::Native::max_value() // Ensures it's invalid, preferring the other value.
484
}
485
486
#[inline(always)]
487
fn cast_series<'a>(&self, s: &'a Series) -> Cow<'a, Series> {
488
s.to_physical_repr()
489
}
490
491
fn select_ca(&self, v: &mut Self::Value, ca: &ChunkedArray<Self::Dtype>) -> Option<usize> {
492
use polars_core::chunked_array::arg_min_max::arg_max_opt_iter;
493
let arg_max = arg_max_opt_iter(ca.iter().map(|cat| self.0.cat_to_str(cat?.as_cat())));
494
arg_max.filter(|idx| {
495
let val = unsafe { ca.value_unchecked(*idx) };
496
self.select_one(v, val)
497
})
498
}
499
500
fn select_one(
501
&self,
502
a: &mut Self::Value,
503
b: <Self::Dtype as PolarsDataType>::Physical<'_>,
504
) -> bool {
505
let Some(b_s) = self.0.cat_to_str(b.as_cat()) else {
506
return false;
507
};
508
let Some(a_s) = self.0.cat_to_str(a.as_cat()) else {
509
*a = b;
510
return true;
511
};
512
513
let better = b_s > a_s;
514
if better {
515
*a = b;
516
}
517
better
518
}
519
520
fn select_combine(&self, a: &mut Self::Value, b: &Self::Value) -> bool {
521
self.select_one(a, *b)
522
}
523
}
524
525
struct SelectPayloadGroupedReduction<R: SelectReducer> {
526
values: Vec<R::Value>,
527
mask: MutableBitmap,
528
evicted_values: Vec<R::Value>,
529
evicted_mask: BitmapBuilder,
530
in_dtype: DataType,
531
reducer: R,
532
payload: Box<dyn GroupedReduction>,
533
534
tmp_subset: Vec<IdxSize>,
535
tmp_group_idxs: Vec<IdxSize>,
536
}
537
538
impl<R: SelectReducer> SelectPayloadGroupedReduction<R> {
539
fn new(in_dtype: DataType, reducer: R, payload: Box<dyn GroupedReduction>) -> Self {
540
Self {
541
values: Vec::new(),
542
mask: MutableBitmap::new(),
543
evicted_values: Vec::new(),
544
evicted_mask: BitmapBuilder::new(),
545
in_dtype,
546
reducer,
547
payload,
548
tmp_subset: Vec::new(),
549
tmp_group_idxs: Vec::new(),
550
}
551
}
552
}
553
554
impl<R> GroupedReduction for SelectPayloadGroupedReduction<R>
555
where
556
R: SelectReducer,
557
{
558
fn new_empty(&self) -> Box<dyn GroupedReduction> {
559
Box::new(Self::new(
560
self.in_dtype.clone(),
561
self.reducer.clone(),
562
self.payload.new_empty(),
563
))
564
}
565
566
fn reserve(&mut self, additional: usize) {
567
self.values.reserve(additional);
568
self.mask.reserve(additional);
569
self.payload.reserve(additional);
570
}
571
572
fn resize(&mut self, num_groups: IdxSize) {
573
self.values.resize(num_groups as usize, self.reducer.init());
574
self.mask.resize(num_groups as usize, false);
575
self.payload.resize(num_groups);
576
}
577
578
fn update_group(
579
&mut self,
580
values: &[&Column],
581
group_idx: IdxSize,
582
_seq_id: u64,
583
) -> PolarsResult<()> {
584
assert!(values.len() == 2);
585
let payload_values = values[0];
586
let ord_values = values[1];
587
assert_eq!(ord_values.dtype(), &self.in_dtype);
588
589
let ord_values = self
590
.reducer
591
.cast_series(ord_values.as_materialized_series());
592
let ca: &ChunkedArray<R::Dtype> = ord_values.as_ref().as_ref().as_ref();
593
594
if let Some(selected) = self
595
.reducer
596
.select_ca(&mut self.values[group_idx as usize], ca)
597
{
598
self.mask.set(group_idx as usize, true);
599
let selected_val = payload_values.new_from_index(selected, 1);
600
self.payload.update_group(&[&selected_val], group_idx, 0)?;
601
}
602
603
Ok(())
604
}
605
606
unsafe fn update_groups_while_evicting(
607
&mut self,
608
values: &[&Column],
609
subset: &[IdxSize],
610
group_idxs: &[EvictIdx],
611
_seq_id: u64,
612
) -> PolarsResult<()> {
613
assert!(values.len() == 2);
614
let payload_values = values[0];
615
let ord_values = values[1];
616
assert!(ord_values.dtype() == &self.in_dtype);
617
assert!(subset.len() == group_idxs.len());
618
619
// TODO: @scalar-opt
620
let ord_values = self
621
.reducer
622
.cast_series(ord_values.as_materialized_series());
623
let ca: &ChunkedArray<R::Dtype> = ord_values.as_ref().as_ref().as_ref();
624
let arr = ca.downcast_as_array();
625
unsafe {
626
self.tmp_subset.clear();
627
self.tmp_group_idxs.clear();
628
629
// SAFETY: indices are in-bounds guaranteed by trait.
630
for (i, g) in subset.iter().zip(group_idxs) {
631
let ov = arr.get_unchecked(*i as usize);
632
let grp = self.values.get_unchecked_mut(g.idx());
633
if g.should_evict() {
634
self.evicted_values
635
.push(core::mem::replace(grp, self.reducer.init()));
636
self.evicted_mask.push(self.mask.get_unchecked(g.idx()));
637
self.mask.set_unchecked(g.idx(), ov.is_some());
638
if let Some(v) = ov {
639
self.reducer.select_one(grp, v);
640
}
641
self.tmp_subset.push(*i);
642
self.tmp_group_idxs.push(g.0);
643
} else if let Some(v) = ov {
644
if self.mask.get_unchecked(g.idx()) {
645
if self.reducer.select_one(grp, v) {
646
self.tmp_subset.push(*i);
647
self.tmp_group_idxs.push(g.0);
648
}
649
} else {
650
self.mask.set_unchecked(g.idx(), true);
651
self.reducer.select_one(grp, v);
652
self.tmp_subset.push(*i);
653
self.tmp_group_idxs.push(g.0);
654
}
655
}
656
}
657
658
self.payload.update_groups_while_evicting(
659
&[payload_values],
660
&self.tmp_subset,
661
EvictIdx::cast_slice(&self.tmp_group_idxs),
662
0, // seq_id is unused
663
)?;
664
}
665
Ok(())
666
}
667
668
unsafe fn combine_subset(
669
&mut self,
670
other: &dyn GroupedReduction,
671
subset: &[IdxSize],
672
group_idxs: &[IdxSize],
673
) -> PolarsResult<()> {
674
let other = other.as_any().downcast_ref::<Self>().unwrap();
675
assert!(self.in_dtype == other.in_dtype);
676
assert!(subset.len() == group_idxs.len());
677
unsafe {
678
self.tmp_subset.clear();
679
self.tmp_group_idxs.clear();
680
681
// SAFETY: indices are in-bounds guaranteed by trait.
682
for (i, g) in subset.iter().zip(group_idxs) {
683
let o = other.mask.get_unchecked(*i as usize);
684
if o {
685
let v = other.values.get_unchecked(*i as usize);
686
let grp = self.values.get_unchecked_mut(*g as usize);
687
if self.reducer.select_combine(grp, v) | !self.mask.get_unchecked(*g as usize) {
688
self.tmp_subset.push(*i);
689
self.tmp_group_idxs.push(*g);
690
}
691
self.mask.set_unchecked(*g as usize, true);
692
}
693
}
694
695
self.payload.combine_subset(
696
other.payload.as_ref(),
697
&self.tmp_subset,
698
&self.tmp_group_idxs,
699
)?;
700
}
701
Ok(())
702
}
703
704
fn take_evictions(&mut self) -> Box<dyn GroupedReduction> {
705
Box::new(Self {
706
values: core::mem::take(&mut self.evicted_values),
707
mask: core::mem::take(&mut self.evicted_mask).into_mut(),
708
evicted_values: Vec::new(),
709
evicted_mask: BitmapBuilder::new(),
710
in_dtype: self.in_dtype.clone(),
711
reducer: self.reducer.clone(),
712
payload: self.payload.take_evictions(),
713
tmp_group_idxs: Vec::new(),
714
tmp_subset: Vec::new(),
715
})
716
}
717
718
fn finalize(&mut self) -> PolarsResult<Series> {
719
let mask = core::mem::take(&mut self.mask);
720
drop(core::mem::take(&mut self.values));
721
drop(core::mem::take(&mut self.tmp_group_idxs));
722
drop(core::mem::take(&mut self.tmp_subset));
723
724
// TODO @ minmax-by: better way to combine payload and mask.
725
let data = self.payload.finalize()?;
726
let mca = BooleanChunked::from_bitmap(PlSmallStr::EMPTY, mask.freeze());
727
let nulls = Series::full_null(data.name().clone(), 1, data.dtype());
728
data.zip_with(&mca, &nulls)
729
}
730
731
fn as_any(&self) -> &dyn Any {
732
self
733
}
734
}
735
736