Skip to main content

polars_python/expr/
general.rs

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