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