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