polars_python/expr/
general.rs

1use std::ops::Neg;
2
3use polars::lazy::dsl;
4use polars::prelude::*;
5use polars::series::ops::NullBehavior;
6use polars_core::chunked_array::cast::CastOptions;
7use polars_core::series::IsSorted;
8use polars_plan::plans::predicates::aexpr_to_skip_batch_predicate;
9use polars_plan::plans::{ExprToIRContext, RowEncodingVariant, node_to_expr, to_expr_ir};
10use polars_utils::arena::Arena;
11use pyo3::class::basic::CompareOp;
12use pyo3::prelude::*;
13
14use super::datatype::PyDataTypeExpr;
15use super::selector::PySelector;
16use crate::PyExpr;
17use crate::conversion::{Wrap, parse_fill_null_strategy};
18use crate::error::PyPolarsErr;
19use crate::utils::EnterPolarsExt;
20
21#[pymethods]
22impl PyExpr {
23    fn __richcmp__(&self, other: Self, op: CompareOp) -> Self {
24        match op {
25            CompareOp::Eq => self.eq(other),
26            CompareOp::Ne => self.neq(other),
27            CompareOp::Gt => self.gt(other),
28            CompareOp::Lt => self.lt(other),
29            CompareOp::Ge => self.gt_eq(other),
30            CompareOp::Le => self.lt_eq(other),
31        }
32    }
33
34    fn __add__(&self, rhs: Self) -> PyResult<Self> {
35        Ok(dsl::binary_expr(self.inner.clone(), Operator::Plus, rhs.inner).into())
36    }
37    fn __sub__(&self, rhs: Self) -> PyResult<Self> {
38        Ok(dsl::binary_expr(self.inner.clone(), Operator::Minus, rhs.inner).into())
39    }
40    fn __mul__(&self, rhs: Self) -> PyResult<Self> {
41        Ok(dsl::binary_expr(self.inner.clone(), Operator::Multiply, rhs.inner).into())
42    }
43    fn __truediv__(&self, rhs: Self) -> PyResult<Self> {
44        Ok(dsl::binary_expr(self.inner.clone(), Operator::TrueDivide, rhs.inner).into())
45    }
46    fn __mod__(&self, rhs: Self) -> PyResult<Self> {
47        Ok(dsl::binary_expr(self.inner.clone(), Operator::Modulus, rhs.inner).into())
48    }
49    fn __floordiv__(&self, rhs: Self) -> PyResult<Self> {
50        Ok(dsl::binary_expr(self.inner.clone(), Operator::FloorDivide, rhs.inner).into())
51    }
52    fn __neg__(&self) -> PyResult<Self> {
53        Ok(self.inner.clone().neg().into())
54    }
55
56    fn to_str(&self) -> String {
57        format!("{:?}", self.inner)
58    }
59    fn eq(&self, other: Self) -> Self {
60        self.inner.clone().eq(other.inner).into()
61    }
62
63    fn eq_missing(&self, other: Self) -> Self {
64        self.inner.clone().eq_missing(other.inner).into()
65    }
66    fn neq(&self, other: Self) -> Self {
67        self.inner.clone().neq(other.inner).into()
68    }
69    fn neq_missing(&self, other: Self) -> Self {
70        self.inner.clone().neq_missing(other.inner).into()
71    }
72    fn gt(&self, other: Self) -> Self {
73        self.inner.clone().gt(other.inner).into()
74    }
75    fn gt_eq(&self, other: Self) -> Self {
76        self.inner.clone().gt_eq(other.inner).into()
77    }
78    fn lt_eq(&self, other: Self) -> Self {
79        self.inner.clone().lt_eq(other.inner).into()
80    }
81    fn lt(&self, other: Self) -> Self {
82        self.inner.clone().lt(other.inner).into()
83    }
84
85    fn alias(&self, name: &str) -> Self {
86        self.inner.clone().alias(name).into()
87    }
88    fn not_(&self) -> Self {
89        self.inner.clone().not().into()
90    }
91    fn is_null(&self) -> Self {
92        self.inner.clone().is_null().into()
93    }
94    fn is_not_null(&self) -> Self {
95        self.inner.clone().is_not_null().into()
96    }
97
98    fn is_infinite(&self) -> Self {
99        self.inner.clone().is_infinite().into()
100    }
101
102    fn is_finite(&self) -> Self {
103        self.inner.clone().is_finite().into()
104    }
105
106    fn is_nan(&self) -> Self {
107        self.inner.clone().is_nan().into()
108    }
109
110    fn is_not_nan(&self) -> Self {
111        self.inner.clone().is_not_nan().into()
112    }
113
114    fn min(&self) -> Self {
115        self.inner.clone().min().into()
116    }
117    fn max(&self) -> Self {
118        self.inner.clone().max().into()
119    }
120    #[cfg(feature = "propagate_nans")]
121    fn nan_max(&self) -> Self {
122        self.inner.clone().nan_max().into()
123    }
124    #[cfg(feature = "propagate_nans")]
125    fn nan_min(&self) -> Self {
126        self.inner.clone().nan_min().into()
127    }
128    fn mean(&self) -> Self {
129        self.inner.clone().mean().into()
130    }
131    fn median(&self) -> Self {
132        self.inner.clone().median().into()
133    }
134    fn sum(&self) -> Self {
135        self.inner.clone().sum().into()
136    }
137    fn n_unique(&self) -> Self {
138        self.inner.clone().n_unique().into()
139    }
140    fn arg_unique(&self) -> Self {
141        self.inner.clone().arg_unique().into()
142    }
143    fn unique(&self) -> Self {
144        self.inner.clone().unique().into()
145    }
146    fn unique_stable(&self) -> Self {
147        self.inner.clone().unique_stable().into()
148    }
149    fn first(&self) -> Self {
150        self.inner.clone().first().into()
151    }
152    fn last(&self) -> Self {
153        self.inner.clone().last().into()
154    }
155    fn implode(&self) -> Self {
156        self.inner.clone().implode().into()
157    }
158    fn quantile(&self, quantile: Self, interpolation: Wrap<QuantileMethod>) -> Self {
159        self.inner
160            .clone()
161            .quantile(quantile.inner, interpolation.0)
162            .into()
163    }
164
165    #[pyo3(signature = (breaks, labels, left_closed, include_breaks))]
166    #[cfg(feature = "cutqcut")]
167    fn cut(
168        &self,
169        breaks: Vec<f64>,
170        labels: Option<Vec<String>>,
171        left_closed: bool,
172        include_breaks: bool,
173    ) -> Self {
174        self.inner
175            .clone()
176            .cut(breaks, labels, left_closed, include_breaks)
177            .into()
178    }
179    #[pyo3(signature = (probs, labels, left_closed, allow_duplicates, include_breaks))]
180    #[cfg(feature = "cutqcut")]
181    fn qcut(
182        &self,
183        probs: Vec<f64>,
184        labels: Option<Vec<String>>,
185        left_closed: bool,
186        allow_duplicates: bool,
187        include_breaks: bool,
188    ) -> Self {
189        self.inner
190            .clone()
191            .qcut(probs, labels, left_closed, allow_duplicates, include_breaks)
192            .into()
193    }
194    #[pyo3(signature = (n_bins, labels, left_closed, allow_duplicates, include_breaks))]
195    #[cfg(feature = "cutqcut")]
196    fn qcut_uniform(
197        &self,
198        n_bins: usize,
199        labels: Option<Vec<String>>,
200        left_closed: bool,
201        allow_duplicates: bool,
202        include_breaks: bool,
203    ) -> Self {
204        self.inner
205            .clone()
206            .qcut_uniform(
207                n_bins,
208                labels,
209                left_closed,
210                allow_duplicates,
211                include_breaks,
212            )
213            .into()
214    }
215
216    #[cfg(feature = "rle")]
217    fn rle(&self) -> Self {
218        self.inner.clone().rle().into()
219    }
220    #[cfg(feature = "rle")]
221    fn rle_id(&self) -> Self {
222        self.inner.clone().rle_id().into()
223    }
224
225    fn agg_groups(&self) -> Self {
226        self.inner.clone().agg_groups().into()
227    }
228    fn count(&self) -> Self {
229        self.inner.clone().count().into()
230    }
231    fn len(&self) -> Self {
232        self.inner.clone().len().into()
233    }
234    fn value_counts(&self, sort: bool, parallel: bool, name: String, normalize: bool) -> Self {
235        self.inner
236            .clone()
237            .value_counts(sort, parallel, name.as_str(), normalize)
238            .into()
239    }
240    fn unique_counts(&self) -> Self {
241        self.inner.clone().unique_counts().into()
242    }
243    fn null_count(&self) -> Self {
244        self.inner.clone().null_count().into()
245    }
246    fn cast(&self, dtype: PyDataTypeExpr, strict: bool, wrap_numerical: bool) -> Self {
247        let options = if wrap_numerical {
248            CastOptions::Overflowing
249        } else if strict {
250            CastOptions::Strict
251        } else {
252            CastOptions::NonStrict
253        };
254
255        let expr = self.inner.clone().cast_with_options(dtype.inner, options);
256        expr.into()
257    }
258    fn sort_with(&self, descending: bool, nulls_last: bool) -> Self {
259        self.inner
260            .clone()
261            .sort(SortOptions {
262                descending,
263                nulls_last,
264                multithreaded: true,
265                maintain_order: false,
266                limit: None,
267            })
268            .into()
269    }
270
271    fn arg_sort(&self, descending: bool, nulls_last: bool) -> Self {
272        self.inner.clone().arg_sort(descending, nulls_last).into()
273    }
274
275    #[cfg(feature = "top_k")]
276    fn top_k(&self, k: Self) -> Self {
277        self.inner.clone().top_k(k.inner).into()
278    }
279
280    #[cfg(feature = "top_k")]
281    fn top_k_by(&self, by: Vec<Self>, k: Self, reverse: Vec<bool>) -> Self {
282        let by = by.into_iter().map(|e| e.inner).collect::<Vec<_>>();
283        self.inner.clone().top_k_by(k.inner, by, reverse).into()
284    }
285
286    #[cfg(feature = "top_k")]
287    fn bottom_k(&self, k: Self) -> Self {
288        self.inner.clone().bottom_k(k.inner).into()
289    }
290
291    #[cfg(feature = "top_k")]
292    fn bottom_k_by(&self, by: Vec<Self>, k: Self, reverse: Vec<bool>) -> Self {
293        let by = by.into_iter().map(|e| e.inner).collect::<Vec<_>>();
294        self.inner.clone().bottom_k_by(k.inner, by, reverse).into()
295    }
296
297    #[cfg(feature = "peaks")]
298    fn peak_min(&self) -> Self {
299        self.inner.clone().peak_min().into()
300    }
301
302    #[cfg(feature = "peaks")]
303    fn peak_max(&self) -> Self {
304        self.inner.clone().peak_max().into()
305    }
306
307    fn arg_max(&self) -> Self {
308        self.inner.clone().arg_max().into()
309    }
310
311    fn arg_min(&self) -> Self {
312        self.inner.clone().arg_min().into()
313    }
314
315    #[cfg(feature = "index_of")]
316    fn index_of(&self, element: Self) -> Self {
317        self.inner.clone().index_of(element.inner).into()
318    }
319
320    #[cfg(feature = "search_sorted")]
321    #[pyo3(signature = (element, side, descending))]
322    fn search_sorted(&self, element: Self, side: Wrap<SearchSortedSide>, descending: bool) -> Self {
323        self.inner
324            .clone()
325            .search_sorted(element.inner, side.0, descending)
326            .into()
327    }
328
329    fn gather(&self, idx: Self) -> Self {
330        self.inner.clone().gather(idx.inner).into()
331    }
332
333    fn get(&self, idx: Self) -> Self {
334        self.inner.clone().get(idx.inner).into()
335    }
336
337    fn sort_by(
338        &self,
339        by: Vec<Self>,
340        descending: Vec<bool>,
341        nulls_last: Vec<bool>,
342        multithreaded: bool,
343        maintain_order: bool,
344    ) -> Self {
345        let by = by.into_iter().map(|e| e.inner).collect::<Vec<_>>();
346        self.inner
347            .clone()
348            .sort_by(
349                by,
350                SortMultipleOptions {
351                    descending,
352                    nulls_last,
353                    multithreaded,
354                    maintain_order,
355                    limit: None,
356                },
357            )
358            .into()
359    }
360
361    #[pyo3(signature = (n, fill_value))]
362    fn shift(&self, n: Self, fill_value: Option<Self>) -> Self {
363        let expr = self.inner.clone();
364        let out = match fill_value {
365            Some(v) => expr.shift_and_fill(n.inner, v.inner),
366            None => expr.shift(n.inner),
367        };
368        out.into()
369    }
370
371    fn fill_null(&self, expr: Self) -> Self {
372        self.inner.clone().fill_null(expr.inner).into()
373    }
374
375    fn fill_null_with_strategy(&self, strategy: &str, limit: FillNullLimit) -> PyResult<Self> {
376        let strategy = parse_fill_null_strategy(strategy, limit)?;
377        Ok(self.inner.clone().fill_null_with_strategy(strategy).into())
378    }
379
380    fn fill_nan(&self, expr: Self) -> Self {
381        self.inner.clone().fill_nan(expr.inner).into()
382    }
383
384    fn drop_nulls(&self) -> Self {
385        self.inner.clone().drop_nulls().into()
386    }
387
388    fn drop_nans(&self) -> Self {
389        self.inner.clone().drop_nans().into()
390    }
391
392    fn filter(&self, predicate: Self) -> Self {
393        self.inner.clone().filter(predicate.inner).into()
394    }
395
396    fn reverse(&self) -> Self {
397        self.inner.clone().reverse().into()
398    }
399
400    fn std(&self, ddof: u8) -> Self {
401        self.inner.clone().std(ddof).into()
402    }
403
404    fn var(&self, ddof: u8) -> Self {
405        self.inner.clone().var(ddof).into()
406    }
407
408    fn is_unique(&self) -> Self {
409        self.inner.clone().is_unique().into()
410    }
411
412    fn is_between(&self, lower: Self, upper: Self, closed: Wrap<ClosedInterval>) -> Self {
413        self.inner
414            .clone()
415            .is_between(lower.inner, upper.inner, closed.0)
416            .into()
417    }
418
419    fn is_close(&self, other: Self, abs_tol: f64, rel_tol: f64, nans_equal: bool) -> Self {
420        self.inner
421            .clone()
422            .is_close(other.inner, abs_tol, rel_tol, nans_equal)
423            .into()
424    }
425
426    #[cfg(feature = "approx_unique")]
427    fn approx_n_unique(&self) -> Self {
428        self.inner.clone().approx_n_unique().into()
429    }
430
431    fn is_first_distinct(&self) -> Self {
432        self.inner.clone().is_first_distinct().into()
433    }
434
435    fn is_last_distinct(&self) -> Self {
436        self.inner.clone().is_last_distinct().into()
437    }
438
439    fn explode(&self) -> Self {
440        self.inner.clone().explode().into()
441    }
442
443    fn gather_every(&self, n: usize, offset: usize) -> Self {
444        self.inner.clone().gather_every(n, offset).into()
445    }
446
447    fn slice(&self, offset: Self, length: Self) -> Self {
448        self.inner.clone().slice(offset.inner, length.inner).into()
449    }
450
451    fn append(&self, other: Self, upcast: bool) -> Self {
452        self.inner.clone().append(other.inner, upcast).into()
453    }
454
455    fn rechunk(&self) -> Self {
456        self.inner.clone().rechunk().into()
457    }
458
459    fn round(&self, decimals: u32, mode: Wrap<RoundMode>) -> Self {
460        self.inner.clone().round(decimals, mode.0).into()
461    }
462
463    fn round_sig_figs(&self, digits: i32) -> Self {
464        self.clone().inner.round_sig_figs(digits).into()
465    }
466
467    fn floor(&self) -> Self {
468        self.inner.clone().floor().into()
469    }
470
471    fn ceil(&self) -> Self {
472        self.inner.clone().ceil().into()
473    }
474
475    #[pyo3(signature = (min, max))]
476    fn clip(&self, min: Option<Self>, max: Option<Self>) -> Self {
477        let expr = self.inner.clone();
478        let out = match (min, max) {
479            (Some(min), Some(max)) => expr.clip(min.inner, max.inner),
480            (Some(min), None) => expr.clip_min(min.inner),
481            (None, Some(max)) => expr.clip_max(max.inner),
482            (None, None) => expr,
483        };
484        out.into()
485    }
486
487    fn abs(&self) -> Self {
488        self.inner.clone().abs().into()
489    }
490
491    #[cfg(feature = "trigonometry")]
492    fn sin(&self) -> Self {
493        self.inner.clone().sin().into()
494    }
495
496    #[cfg(feature = "trigonometry")]
497    fn cos(&self) -> Self {
498        self.inner.clone().cos().into()
499    }
500
501    #[cfg(feature = "trigonometry")]
502    fn tan(&self) -> Self {
503        self.inner.clone().tan().into()
504    }
505
506    #[cfg(feature = "trigonometry")]
507    fn cot(&self) -> Self {
508        self.inner.clone().cot().into()
509    }
510
511    #[cfg(feature = "trigonometry")]
512    fn arcsin(&self) -> Self {
513        self.inner.clone().arcsin().into()
514    }
515
516    #[cfg(feature = "trigonometry")]
517    fn arccos(&self) -> Self {
518        self.inner.clone().arccos().into()
519    }
520
521    #[cfg(feature = "trigonometry")]
522    fn arctan(&self) -> Self {
523        self.inner.clone().arctan().into()
524    }
525
526    #[cfg(feature = "trigonometry")]
527    fn arctan2(&self, y: Self) -> Self {
528        self.inner.clone().arctan2(y.inner).into()
529    }
530
531    #[cfg(feature = "trigonometry")]
532    fn sinh(&self) -> Self {
533        self.inner.clone().sinh().into()
534    }
535
536    #[cfg(feature = "trigonometry")]
537    fn cosh(&self) -> Self {
538        self.inner.clone().cosh().into()
539    }
540
541    #[cfg(feature = "trigonometry")]
542    fn tanh(&self) -> Self {
543        self.inner.clone().tanh().into()
544    }
545
546    #[cfg(feature = "trigonometry")]
547    fn arcsinh(&self) -> Self {
548        self.inner.clone().arcsinh().into()
549    }
550
551    #[cfg(feature = "trigonometry")]
552    fn arccosh(&self) -> Self {
553        self.inner.clone().arccosh().into()
554    }
555
556    #[cfg(feature = "trigonometry")]
557    fn arctanh(&self) -> Self {
558        self.inner.clone().arctanh().into()
559    }
560
561    #[cfg(feature = "trigonometry")]
562    pub fn degrees(&self) -> Self {
563        self.inner.clone().degrees().into()
564    }
565
566    #[cfg(feature = "trigonometry")]
567    pub fn radians(&self) -> Self {
568        self.inner.clone().radians().into()
569    }
570
571    #[cfg(feature = "sign")]
572    fn sign(&self) -> Self {
573        self.inner.clone().sign().into()
574    }
575
576    fn is_duplicated(&self) -> Self {
577        self.inner.clone().is_duplicated().into()
578    }
579
580    #[pyo3(signature = (partition_by, order_by, order_by_descending, order_by_nulls_last, mapping_strategy))]
581    fn over(
582        &self,
583        partition_by: Option<Vec<Self>>,
584        order_by: Option<Vec<Self>>,
585        order_by_descending: bool,
586        order_by_nulls_last: bool,
587        mapping_strategy: Wrap<WindowMapping>,
588    ) -> PyResult<Self> {
589        let partition_by = partition_by.map(|partition_by| {
590            partition_by
591                .into_iter()
592                .map(|e| e.inner)
593                .collect::<Vec<Expr>>()
594        });
595
596        let order_by = order_by.map(|order_by| {
597            (
598                order_by.into_iter().map(|e| e.inner).collect::<Vec<Expr>>(),
599                SortOptions {
600                    descending: order_by_descending,
601                    nulls_last: order_by_nulls_last,
602                    maintain_order: false,
603                    ..Default::default()
604                },
605            )
606        });
607
608        Ok(self
609            .inner
610            .clone()
611            .over_with_options(partition_by, order_by, mapping_strategy.0)
612            .map_err(PyPolarsErr::from)?
613            .into())
614    }
615
616    fn rolling(
617        &self,
618        index_column: &str,
619        period: &str,
620        offset: &str,
621        closed: Wrap<ClosedWindow>,
622    ) -> PyResult<Self> {
623        let options = RollingGroupOptions {
624            index_column: index_column.into(),
625            period: Duration::try_parse(period).map_err(PyPolarsErr::from)?,
626            offset: Duration::try_parse(offset).map_err(PyPolarsErr::from)?,
627            closed_window: closed.0,
628        };
629
630        Ok(self.inner.clone().rolling(options).into())
631    }
632
633    fn and_(&self, expr: Self) -> Self {
634        self.inner.clone().and(expr.inner).into()
635    }
636
637    fn or_(&self, expr: Self) -> Self {
638        self.inner.clone().or(expr.inner).into()
639    }
640
641    fn xor_(&self, expr: Self) -> Self {
642        self.inner.clone().xor(expr.inner).into()
643    }
644
645    #[cfg(feature = "is_in")]
646    fn is_in(&self, expr: Self, nulls_equal: bool) -> Self {
647        self.inner.clone().is_in(expr.inner, nulls_equal).into()
648    }
649
650    #[cfg(feature = "repeat_by")]
651    fn repeat_by(&self, by: Self) -> Self {
652        self.inner.clone().repeat_by(by.inner).into()
653    }
654
655    fn pow(&self, exponent: Self) -> Self {
656        self.inner.clone().pow(exponent.inner).into()
657    }
658
659    fn sqrt(&self) -> Self {
660        self.inner.clone().sqrt().into()
661    }
662
663    fn cbrt(&self) -> Self {
664        self.inner.clone().cbrt().into()
665    }
666
667    fn cum_sum(&self, reverse: bool) -> Self {
668        self.inner.clone().cum_sum(reverse).into()
669    }
670    fn cum_max(&self, reverse: bool) -> Self {
671        self.inner.clone().cum_max(reverse).into()
672    }
673    fn cum_min(&self, reverse: bool) -> Self {
674        self.inner.clone().cum_min(reverse).into()
675    }
676    fn cum_prod(&self, reverse: bool) -> Self {
677        self.inner.clone().cum_prod(reverse).into()
678    }
679    fn cum_count(&self, reverse: bool) -> Self {
680        self.inner.clone().cum_count(reverse).into()
681    }
682
683    fn cumulative_eval(&self, expr: Self, min_samples: usize) -> Self {
684        self.inner
685            .clone()
686            .cumulative_eval(expr.inner, min_samples)
687            .into()
688    }
689
690    fn product(&self) -> Self {
691        self.inner.clone().product().into()
692    }
693
694    fn dot(&self, other: Self) -> Self {
695        self.inner.clone().dot(other.inner).into()
696    }
697
698    fn reinterpret(&self, signed: bool) -> Self {
699        self.inner.clone().reinterpret(signed).into()
700    }
701    fn mode(&self) -> Self {
702        self.inner.clone().mode().into()
703    }
704    fn interpolate(&self, method: Wrap<InterpolationMethod>) -> Self {
705        self.inner.clone().interpolate(method.0).into()
706    }
707    fn interpolate_by(&self, by: PyExpr) -> Self {
708        self.inner.clone().interpolate_by(by.inner).into()
709    }
710
711    fn lower_bound(&self) -> Self {
712        self.inner.clone().lower_bound().into()
713    }
714
715    fn upper_bound(&self) -> Self {
716        self.inner.clone().upper_bound().into()
717    }
718
719    #[pyo3(signature = (method, descending, seed))]
720    fn rank(&self, method: Wrap<RankMethod>, descending: bool, seed: Option<u64>) -> Self {
721        let options = RankOptions {
722            method: method.0,
723            descending,
724        };
725        self.inner.clone().rank(options, seed).into()
726    }
727
728    fn diff(&self, n: PyExpr, null_behavior: Wrap<NullBehavior>) -> Self {
729        self.inner.clone().diff(n.inner, null_behavior.0).into()
730    }
731
732    #[cfg(feature = "pct_change")]
733    fn pct_change(&self, n: Self) -> Self {
734        self.inner.clone().pct_change(n.inner).into()
735    }
736
737    fn skew(&self, bias: bool) -> Self {
738        self.inner.clone().skew(bias).into()
739    }
740    fn kurtosis(&self, fisher: bool, bias: bool) -> Self {
741        self.inner.clone().kurtosis(fisher, bias).into()
742    }
743
744    #[cfg(feature = "dtype-array")]
745    fn reshape(&self, dims: Vec<i64>) -> Self {
746        self.inner.clone().reshape(&dims).into()
747    }
748
749    fn to_physical(&self) -> Self {
750        self.inner.clone().to_physical().into()
751    }
752
753    #[pyo3(signature = (seed))]
754    fn shuffle(&self, seed: Option<u64>) -> Self {
755        self.inner.clone().shuffle(seed).into()
756    }
757
758    #[pyo3(signature = (n, with_replacement, shuffle, seed))]
759    fn sample_n(&self, n: Self, with_replacement: bool, shuffle: bool, seed: Option<u64>) -> Self {
760        self.inner
761            .clone()
762            .sample_n(n.inner, with_replacement, shuffle, seed)
763            .into()
764    }
765
766    #[pyo3(signature = (frac, with_replacement, shuffle, seed))]
767    fn sample_frac(
768        &self,
769        frac: Self,
770        with_replacement: bool,
771        shuffle: bool,
772        seed: Option<u64>,
773    ) -> Self {
774        self.inner
775            .clone()
776            .sample_frac(frac.inner, with_replacement, shuffle, seed)
777            .into()
778    }
779
780    fn ewm_mean(&self, alpha: f64, adjust: bool, min_periods: usize, ignore_nulls: bool) -> Self {
781        let options = EWMOptions {
782            alpha,
783            adjust,
784            bias: false,
785            min_periods,
786            ignore_nulls,
787        };
788        self.inner.clone().ewm_mean(options).into()
789    }
790    fn ewm_mean_by(&self, times: PyExpr, half_life: &str) -> PyResult<Self> {
791        let half_life = Duration::try_parse(half_life).map_err(PyPolarsErr::from)?;
792        Ok(self
793            .inner
794            .clone()
795            .ewm_mean_by(times.inner, half_life)
796            .into())
797    }
798
799    fn ewm_std(
800        &self,
801        alpha: f64,
802        adjust: bool,
803        bias: bool,
804        min_periods: usize,
805        ignore_nulls: bool,
806    ) -> Self {
807        let options = EWMOptions {
808            alpha,
809            adjust,
810            bias,
811            min_periods,
812            ignore_nulls,
813        };
814        self.inner.clone().ewm_std(options).into()
815    }
816    fn ewm_var(
817        &self,
818        alpha: f64,
819        adjust: bool,
820        bias: bool,
821        min_periods: usize,
822        ignore_nulls: bool,
823    ) -> Self {
824        let options = EWMOptions {
825            alpha,
826            adjust,
827            bias,
828            min_periods,
829            ignore_nulls,
830        };
831        self.inner.clone().ewm_var(options).into()
832    }
833    fn extend_constant(&self, value: PyExpr, n: PyExpr) -> Self {
834        self.inner
835            .clone()
836            .extend_constant(value.inner, n.inner)
837            .into()
838    }
839
840    fn any(&self, ignore_nulls: bool) -> Self {
841        self.inner.clone().any(ignore_nulls).into()
842    }
843    fn all(&self, ignore_nulls: bool) -> Self {
844        self.inner.clone().all(ignore_nulls).into()
845    }
846
847    fn log(&self, base: PyExpr) -> Self {
848        self.inner.clone().log(base.inner).into()
849    }
850
851    fn log1p(&self) -> Self {
852        self.inner.clone().log1p().into()
853    }
854
855    fn exp(&self) -> Self {
856        self.inner.clone().exp().into()
857    }
858
859    fn entropy(&self, base: f64, normalize: bool) -> Self {
860        self.inner.clone().entropy(base, normalize).into()
861    }
862    fn hash(&self, seed: u64, seed_1: u64, seed_2: u64, seed_3: u64) -> Self {
863        self.inner.clone().hash(seed, seed_1, seed_2, seed_3).into()
864    }
865    fn set_sorted_flag(&self, descending: bool) -> Self {
866        let is_sorted = if descending {
867            IsSorted::Descending
868        } else {
869            IsSorted::Ascending
870        };
871        self.inner.clone().set_sorted_flag(is_sorted).into()
872    }
873
874    fn replace(&self, old: PyExpr, new: PyExpr) -> Self {
875        self.inner.clone().replace(old.inner, new.inner).into()
876    }
877
878    #[pyo3(signature = (old, new, default, return_dtype))]
879    fn replace_strict(
880        &self,
881        old: PyExpr,
882        new: PyExpr,
883        default: Option<PyExpr>,
884        return_dtype: Option<PyDataTypeExpr>,
885    ) -> Self {
886        self.inner
887            .clone()
888            .replace_strict(
889                old.inner,
890                new.inner,
891                default.map(|e| e.inner),
892                return_dtype.map(|dt| dt.inner),
893            )
894            .into()
895    }
896
897    #[cfg(feature = "hist")]
898    #[pyo3(signature = (bins, bin_count, include_category, include_breakpoint))]
899    fn hist(
900        &self,
901        bins: Option<PyExpr>,
902        bin_count: Option<usize>,
903        include_category: bool,
904        include_breakpoint: bool,
905    ) -> Self {
906        let bins = bins.map(|e| e.inner);
907        self.inner
908            .clone()
909            .hist(bins, bin_count, include_category, include_breakpoint)
910            .into()
911    }
912
913    #[pyo3(signature = (schema))]
914    fn skip_batch_predicate(&self, py: Python<'_>, schema: Wrap<Schema>) -> PyResult<Option<Self>> {
915        let mut aexpr_arena = Arena::new();
916        py.enter_polars(|| {
917            let mut ctx = ExprToIRContext::new(&mut aexpr_arena, &schema.0);
918            ctx.allow_unknown = true;
919            let node = to_expr_ir(self.inner.clone(), &mut ctx)?.node();
920            let Some(node) = aexpr_to_skip_batch_predicate(node, &mut aexpr_arena, &schema.0)
921            else {
922                return Ok(None);
923            };
924            let skip_batch_predicate = node_to_expr(node, &aexpr_arena);
925            PolarsResult::Ok(Some(Self {
926                inner: skip_batch_predicate,
927            }))
928        })
929    }
930
931    #[staticmethod]
932    fn row_encode_unordered(exprs: Vec<Self>) -> Self {
933        Expr::n_ary(
934            FunctionExpr::RowEncode(RowEncodingVariant::Unordered),
935            exprs.into_iter().map(|e| e.inner.clone()).collect(),
936        )
937        .into()
938    }
939
940    #[staticmethod]
941    fn row_encode_ordered(
942        exprs: Vec<Self>,
943        descending: Option<Vec<bool>>,
944        nulls_last: Option<Vec<bool>>,
945    ) -> Self {
946        Expr::n_ary(
947            FunctionExpr::RowEncode(RowEncodingVariant::Ordered {
948                descending,
949                nulls_last,
950            }),
951            exprs.into_iter().map(|e| e.inner.clone()).collect(),
952        )
953        .into()
954    }
955
956    fn row_decode_unordered(&self, names: Vec<String>, datatypes: Vec<PyDataTypeExpr>) -> Self {
957        let fields = names
958            .into_iter()
959            .zip(datatypes)
960            .map(|(name, dtype)| (PlSmallStr::from_string(name), dtype.inner))
961            .collect();
962        self.inner
963            .clone()
964            .map_unary(FunctionExpr::RowDecode(
965                fields,
966                RowEncodingVariant::Unordered,
967            ))
968            .into()
969    }
970
971    fn row_decode_ordered(
972        &self,
973        names: Vec<String>,
974        datatypes: Vec<PyDataTypeExpr>,
975        descending: Option<Vec<bool>>,
976        nulls_last: Option<Vec<bool>>,
977    ) -> Self {
978        let fields = names
979            .into_iter()
980            .zip(datatypes)
981            .map(|(name, dtype)| (PlSmallStr::from_string(name), dtype.inner))
982            .collect::<Vec<_>>();
983        self.inner
984            .clone()
985            .map_unary(FunctionExpr::RowDecode(
986                fields,
987                RowEncodingVariant::Ordered {
988                    descending,
989                    nulls_last,
990                },
991            ))
992            .into()
993    }
994
995    #[allow(clippy::wrong_self_convention)]
996    fn into_selector(&self) -> PyResult<PySelector> {
997        Ok(self
998            .inner
999            .clone()
1000            .into_selector()
1001            .ok_or_else(
1002                || polars_err!(InvalidOperation: "expr `{}` is not a selector", &self.inner),
1003            )
1004            .map_err(PyPolarsErr::from)?
1005            .into())
1006    }
1007
1008    #[staticmethod]
1009    fn new_selector(selector: PySelector) -> Self {
1010        Expr::Selector(selector.inner).into()
1011    }
1012}