polars_core/series/
series_trait.rs

1use std::any::Any;
2use std::borrow::Cow;
3
4use arrow::bitmap::{Bitmap, BitmapBuilder};
5use polars_compute::rolling::QuantileMethod;
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9use crate::chunked_array::cast::CastOptions;
10#[cfg(feature = "object")]
11use crate::chunked_array::object::PolarsObjectSafe;
12use crate::prelude::*;
13use crate::utils::{first_non_null, last_non_null};
14
15#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
16#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
17#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
18pub enum IsSorted {
19    Ascending,
20    Descending,
21    Not,
22}
23
24impl IsSorted {
25    pub fn reverse(self) -> Self {
26        use IsSorted::*;
27        match self {
28            Ascending => Descending,
29            Descending => Ascending,
30            Not => Not,
31        }
32    }
33}
34
35pub enum BitRepr {
36    U8(UInt8Chunked),
37    U16(UInt16Chunked),
38    U32(UInt32Chunked),
39    U64(UInt64Chunked),
40    #[cfg(feature = "dtype-u128")]
41    U128(UInt128Chunked),
42}
43
44pub(crate) mod private {
45    use polars_utils::aliases::PlSeedableRandomStateQuality;
46
47    use super::*;
48    use crate::chunked_array::flags::StatisticsFlags;
49    use crate::chunked_array::ops::compare_inner::{TotalEqInner, TotalOrdInner};
50
51    pub trait PrivateSeriesNumeric {
52        /// Return a bit representation
53        ///
54        /// If there is no available bit representation this returns `None`.
55        fn bit_repr(&self) -> Option<BitRepr>;
56    }
57
58    pub trait PrivateSeries {
59        #[cfg(feature = "object")]
60        fn get_list_builder(
61            &self,
62            _name: PlSmallStr,
63            _values_capacity: usize,
64            _list_capacity: usize,
65        ) -> Box<dyn ListBuilderTrait> {
66            invalid_operation_panic!(get_list_builder, self)
67        }
68
69        /// Get field (used in schema)
70        fn _field(&self) -> Cow<'_, Field>;
71
72        fn _dtype(&self) -> &DataType;
73
74        fn compute_len(&mut self);
75
76        fn _get_flags(&self) -> StatisticsFlags;
77
78        fn _set_flags(&mut self, flags: StatisticsFlags);
79
80        unsafe fn equal_element(
81            &self,
82            _idx_self: usize,
83            _idx_other: usize,
84            _other: &Series,
85        ) -> bool {
86            invalid_operation_panic!(equal_element, self)
87        }
88        #[expect(clippy::wrong_self_convention)]
89        fn into_total_eq_inner<'a>(&'a self) -> Box<dyn TotalEqInner + 'a>;
90        #[expect(clippy::wrong_self_convention)]
91        fn into_total_ord_inner<'a>(&'a self) -> Box<dyn TotalOrdInner + 'a>;
92
93        fn vec_hash(
94            &self,
95            _build_hasher: PlSeedableRandomStateQuality,
96            _buf: &mut Vec<u64>,
97        ) -> PolarsResult<()>;
98        fn vec_hash_combine(
99            &self,
100            _build_hasher: PlSeedableRandomStateQuality,
101            _hashes: &mut [u64],
102        ) -> PolarsResult<()>;
103
104        /// # Safety
105        ///
106        /// Does no bounds checks, groups must be correct.
107        #[cfg(feature = "algorithm_group_by")]
108        unsafe fn agg_min(&self, groups: &GroupsType) -> Series {
109            Series::full_null(self._field().name().clone(), groups.len(), self._dtype())
110        }
111        /// # Safety
112        ///
113        /// Does no bounds checks, groups must be correct.
114        #[cfg(feature = "algorithm_group_by")]
115        unsafe fn agg_max(&self, groups: &GroupsType) -> Series {
116            Series::full_null(self._field().name().clone(), groups.len(), self._dtype())
117        }
118        /// If the [`DataType`] is one of `{Int8, UInt8, Int16, UInt16}` the `Series` is
119        /// first cast to `Int64` to prevent overflow issues.
120        #[cfg(feature = "algorithm_group_by")]
121        unsafe fn agg_sum(&self, groups: &GroupsType) -> Series {
122            Series::full_null(self._field().name().clone(), groups.len(), self._dtype())
123        }
124        /// # Safety
125        ///
126        /// Does no bounds checks, groups must be correct.
127        #[cfg(feature = "algorithm_group_by")]
128        unsafe fn agg_std(&self, groups: &GroupsType, _ddof: u8) -> Series {
129            Series::full_null(self._field().name().clone(), groups.len(), self._dtype())
130        }
131        /// # Safety
132        ///
133        /// Does no bounds checks, groups must be correct.
134        #[cfg(feature = "algorithm_group_by")]
135        unsafe fn agg_var(&self, groups: &GroupsType, _ddof: u8) -> Series {
136            Series::full_null(self._field().name().clone(), groups.len(), self._dtype())
137        }
138        /// # Safety
139        ///
140        /// Does no bounds checks, groups must be correct.
141        #[cfg(feature = "algorithm_group_by")]
142        unsafe fn agg_list(&self, groups: &GroupsType) -> Series {
143            Series::full_null(self._field().name().clone(), groups.len(), self._dtype())
144        }
145
146        /// # Safety
147        ///
148        /// Does no bounds checks, groups must be correct.
149        #[cfg(feature = "bitwise")]
150        unsafe fn agg_and(&self, groups: &GroupsType) -> Series {
151            Series::full_null(self._field().name().clone(), groups.len(), self._dtype())
152        }
153
154        /// # Safety
155        ///
156        /// Does no bounds checks, groups must be correct.
157        #[cfg(feature = "bitwise")]
158        unsafe fn agg_or(&self, groups: &GroupsType) -> Series {
159            Series::full_null(self._field().name().clone(), groups.len(), self._dtype())
160        }
161
162        /// # Safety
163        ///
164        /// Does no bounds checks, groups must be correct.
165        #[cfg(feature = "bitwise")]
166        unsafe fn agg_xor(&self, groups: &GroupsType) -> Series {
167            Series::full_null(self._field().name().clone(), groups.len(), self._dtype())
168        }
169
170        fn subtract(&self, _rhs: &Series) -> PolarsResult<Series> {
171            polars_bail!(opq = subtract, self._dtype());
172        }
173        fn add_to(&self, _rhs: &Series) -> PolarsResult<Series> {
174            polars_bail!(opq = add, self._dtype());
175        }
176        fn multiply(&self, _rhs: &Series) -> PolarsResult<Series> {
177            polars_bail!(opq = multiply, self._dtype());
178        }
179        fn divide(&self, _rhs: &Series) -> PolarsResult<Series> {
180            polars_bail!(opq = divide, self._dtype());
181        }
182        fn remainder(&self, _rhs: &Series) -> PolarsResult<Series> {
183            polars_bail!(opq = remainder, self._dtype());
184        }
185        #[cfg(feature = "algorithm_group_by")]
186        fn group_tuples(&self, _multithreaded: bool, _sorted: bool) -> PolarsResult<GroupsType> {
187            polars_bail!(opq = group_tuples, self._dtype());
188        }
189        #[cfg(feature = "zip_with")]
190        fn zip_with_same_type(
191            &self,
192            _mask: &BooleanChunked,
193            _other: &Series,
194        ) -> PolarsResult<Series> {
195            polars_bail!(opq = zip_with_same_type, self._dtype());
196        }
197
198        #[allow(unused_variables)]
199        fn arg_sort_multiple(
200            &self,
201            by: &[Column],
202            _options: &SortMultipleOptions,
203        ) -> PolarsResult<IdxCa> {
204            polars_bail!(opq = arg_sort_multiple, self._dtype());
205        }
206    }
207}
208
209pub trait SeriesTrait:
210    Send + Sync + private::PrivateSeries + private::PrivateSeriesNumeric
211{
212    /// Rename the Series.
213    fn rename(&mut self, name: PlSmallStr);
214
215    /// Get the lengths of the underlying chunks
216    fn chunk_lengths(&self) -> ChunkLenIter<'_>;
217
218    /// Name of series.
219    fn name(&self) -> &PlSmallStr;
220
221    /// Get field (used in schema)
222    fn field(&self) -> Cow<'_, Field> {
223        self._field()
224    }
225
226    /// Get datatype of series.
227    fn dtype(&self) -> &DataType {
228        self._dtype()
229    }
230
231    /// Underlying chunks.
232    fn chunks(&self) -> &Vec<ArrayRef>;
233
234    /// Underlying chunks.
235    ///
236    /// # Safety
237    /// The caller must ensure the length and the data types of `ArrayRef` does not change.
238    unsafe fn chunks_mut(&mut self) -> &mut Vec<ArrayRef>;
239
240    /// Number of chunks in this Series
241    fn n_chunks(&self) -> usize {
242        self.chunks().len()
243    }
244
245    /// Shrink the capacity of this array to fit its length.
246    fn shrink_to_fit(&mut self) {
247        // no-op
248    }
249
250    /// Take `num_elements` from the top as a zero copy view.
251    fn limit(&self, num_elements: usize) -> Series {
252        self.slice(0, num_elements)
253    }
254
255    /// Get a zero copy view of the data.
256    ///
257    /// When offset is negative the offset is counted from the
258    /// end of the array
259    fn slice(&self, _offset: i64, _length: usize) -> Series;
260
261    /// Get a zero copy view of the data.
262    ///
263    /// When offset is negative the offset is counted from the
264    /// end of the array
265    fn split_at(&self, _offset: i64) -> (Series, Series);
266
267    fn append(&mut self, other: &Series) -> PolarsResult<()>;
268    fn append_owned(&mut self, other: Series) -> PolarsResult<()>;
269
270    #[doc(hidden)]
271    fn extend(&mut self, _other: &Series) -> PolarsResult<()>;
272
273    /// Filter by boolean mask. This operation clones data.
274    fn filter(&self, _filter: &BooleanChunked) -> PolarsResult<Series>;
275
276    /// Take from `self` at the indexes given by `idx`.
277    ///
278    /// Null values in `idx` because null values in the output array.
279    ///
280    /// This operation is clone.
281    fn take(&self, _indices: &IdxCa) -> PolarsResult<Series>;
282
283    /// Take from `self` at the indexes given by `idx`.
284    ///
285    /// Null values in `idx` because null values in the output array.
286    ///
287    /// # Safety
288    /// This doesn't check any bounds.
289    unsafe fn take_unchecked(&self, _idx: &IdxCa) -> Series;
290
291    /// Take from `self` at the indexes given by `idx`.
292    ///
293    /// This operation is clone.
294    fn take_slice(&self, _indices: &[IdxSize]) -> PolarsResult<Series>;
295
296    /// Take from `self` at the indexes given by `idx`.
297    ///
298    /// # Safety
299    /// This doesn't check any bounds.
300    unsafe fn take_slice_unchecked(&self, _idx: &[IdxSize]) -> Series;
301
302    /// Get length of series.
303    fn len(&self) -> usize;
304
305    /// Check if Series is empty.
306    fn is_empty(&self) -> bool {
307        self.len() == 0
308    }
309
310    /// Aggregate all chunks to a contiguous array of memory.
311    fn rechunk(&self) -> Series;
312
313    fn rechunk_validity(&self) -> Option<Bitmap> {
314        if self.chunks().len() == 1 {
315            return self.chunks()[0].validity().cloned();
316        }
317
318        if !self.has_nulls() || self.is_empty() {
319            return None;
320        }
321
322        let mut bm = BitmapBuilder::with_capacity(self.len());
323        for arr in self.chunks() {
324            if let Some(v) = arr.validity() {
325                bm.extend_from_bitmap(v);
326            } else {
327                bm.extend_constant(arr.len(), true);
328            }
329        }
330        bm.into_opt_validity()
331    }
332
333    /// Drop all null values and return a new Series.
334    fn drop_nulls(&self) -> Series {
335        if self.null_count() == 0 {
336            Series(self.clone_inner())
337        } else {
338            self.filter(&self.is_not_null()).unwrap()
339        }
340    }
341
342    /// Returns the sum of the array as an f64.
343    fn _sum_as_f64(&self) -> f64 {
344        invalid_operation_panic!(_sum_as_f64, self)
345    }
346
347    /// Returns the mean value in the array
348    /// Returns an option because the array is nullable.
349    fn mean(&self) -> Option<f64> {
350        None
351    }
352
353    /// Returns the std value in the array
354    /// Returns an option because the array is nullable.
355    fn std(&self, _ddof: u8) -> Option<f64> {
356        None
357    }
358
359    /// Returns the var value in the array
360    /// Returns an option because the array is nullable.
361    fn var(&self, _ddof: u8) -> Option<f64> {
362        None
363    }
364
365    /// Returns the median value in the array
366    /// Returns an option because the array is nullable.
367    fn median(&self) -> Option<f64> {
368        None
369    }
370
371    /// Create a new Series filled with values from the given index.
372    ///
373    /// # Example
374    ///
375    /// ```rust
376    /// use polars_core::prelude::*;
377    /// let s = Series::new("a".into(), [0i32, 1, 8]);
378    /// let s2 = s.new_from_index(2, 4);
379    /// assert_eq!(Vec::from(s2.i32().unwrap()), &[Some(8), Some(8), Some(8), Some(8)])
380    /// ```
381    fn new_from_index(&self, _index: usize, _length: usize) -> Series;
382
383    /// Trim all lists of unused start and end elements recursively.
384    ///
385    /// - `None` if nothing needed to be done.
386    /// - `Some(series)` if something changed.
387    fn trim_lists_to_normalized_offsets(&self) -> Option<Series> {
388        None
389    }
390
391    /// Propagate down nulls in nested types.
392    ///
393    /// - `None` if nothing needed to be done.
394    /// - `Some(series)` if something changed.
395    fn propagate_nulls(&self) -> Option<Series> {
396        None
397    }
398
399    fn deposit(&self, validity: &Bitmap) -> Series;
400
401    /// Find the indices of elements where the null masks are different recursively.
402    fn find_validity_mismatch(&self, other: &Series, idxs: &mut Vec<IdxSize>);
403
404    fn cast(&self, _dtype: &DataType, options: CastOptions) -> PolarsResult<Series>;
405
406    /// Get a single value by index. Don't use this operation for loops as a runtime cast is
407    /// needed for every iteration.
408    fn get(&self, index: usize) -> PolarsResult<AnyValue<'_>> {
409        polars_ensure!(index < self.len(), oob = index, self.len());
410        // SAFETY: Just did bounds check
411        let value = unsafe { self.get_unchecked(index) };
412        Ok(value)
413    }
414
415    /// Get a single value by index. Don't use this operation for loops as a runtime cast is
416    /// needed for every iteration.
417    ///
418    /// This may refer to physical types
419    ///
420    /// # Safety
421    /// Does not do any bounds checking
422    unsafe fn get_unchecked(&self, _index: usize) -> AnyValue<'_>;
423
424    fn sort_with(&self, _options: SortOptions) -> PolarsResult<Series> {
425        polars_bail!(opq = sort_with, self._dtype());
426    }
427
428    /// Retrieve the indexes needed for a sort.
429    #[allow(unused)]
430    fn arg_sort(&self, options: SortOptions) -> IdxCa {
431        invalid_operation_panic!(arg_sort, self)
432    }
433
434    /// Count the null values.
435    fn null_count(&self) -> usize;
436
437    /// Return if any the chunks in this [`ChunkedArray`] have nulls.
438    fn has_nulls(&self) -> bool;
439
440    /// Get unique values in the Series.
441    fn unique(&self) -> PolarsResult<Series> {
442        polars_bail!(opq = unique, self._dtype());
443    }
444
445    /// Get unique values in the Series.
446    ///
447    /// A `null` value also counts as a unique value.
448    fn n_unique(&self) -> PolarsResult<usize> {
449        polars_bail!(opq = n_unique, self._dtype());
450    }
451
452    /// Get first indexes of unique values.
453    fn arg_unique(&self) -> PolarsResult<IdxCa> {
454        polars_bail!(opq = arg_unique, self._dtype());
455    }
456
457    /// Get dense ids for each unique value.
458    ///
459    /// Returns: (n_unique, unique_ids)
460    fn unique_id(&self) -> PolarsResult<(IdxSize, Vec<IdxSize>)>;
461
462    /// Get a mask of the null values.
463    fn is_null(&self) -> BooleanChunked;
464
465    /// Get a mask of the non-null values.
466    fn is_not_null(&self) -> BooleanChunked;
467
468    /// return a Series in reversed order
469    fn reverse(&self) -> Series;
470
471    /// Rechunk and return a pointer to the start of the Series.
472    /// Only implemented for numeric types
473    fn as_single_ptr(&mut self) -> PolarsResult<usize> {
474        polars_bail!(opq = as_single_ptr, self._dtype());
475    }
476
477    /// Shift the values by a given period and fill the parts that will be empty due to this operation
478    /// with `Nones`.
479    ///
480    /// *NOTE: If you want to fill the Nones with a value use the
481    /// [`shift` operation on `ChunkedArray<T>`](../chunked_array/ops/trait.ChunkShift.html).*
482    ///
483    /// # Example
484    ///
485    /// ```rust
486    /// # use polars_core::prelude::*;
487    /// fn example() -> PolarsResult<()> {
488    ///     let s = Series::new("series".into(), &[1, 2, 3]);
489    ///
490    ///     let shifted = s.shift(1);
491    ///     assert_eq!(Vec::from(shifted.i32()?), &[None, Some(1), Some(2)]);
492    ///
493    ///     let shifted = s.shift(-1);
494    ///     assert_eq!(Vec::from(shifted.i32()?), &[Some(2), Some(3), None]);
495    ///
496    ///     let shifted = s.shift(2);
497    ///     assert_eq!(Vec::from(shifted.i32()?), &[None, None, Some(1)]);
498    ///
499    ///     Ok(())
500    /// }
501    /// example();
502    /// ```
503    fn shift(&self, _periods: i64) -> Series;
504
505    /// Get the sum of the Series as a new Scalar.
506    ///
507    /// If the [`DataType`] is one of `{Int8, UInt8, Int16, UInt16}` the `Series` is
508    /// first cast to `Int64` to prevent overflow issues.
509    fn sum_reduce(&self) -> PolarsResult<Scalar> {
510        polars_bail!(opq = sum, self._dtype());
511    }
512    /// Get the max of the Series as a new Series of length 1.
513    fn max_reduce(&self) -> PolarsResult<Scalar> {
514        polars_bail!(opq = max, self._dtype());
515    }
516    /// Get the min of the Series as a new Series of length 1.
517    fn min_reduce(&self) -> PolarsResult<Scalar> {
518        polars_bail!(opq = min, self._dtype());
519    }
520    /// Get the median of the Series as a new Series of length 1.
521    fn median_reduce(&self) -> PolarsResult<Scalar> {
522        polars_bail!(opq = median, self._dtype());
523    }
524    /// Get the mean of the Series as a new Scalar
525    fn mean_reduce(&self) -> PolarsResult<Scalar> {
526        polars_bail!(opq = mean, self._dtype());
527    }
528    /// Get the variance of the Series as a new Series of length 1.
529    fn var_reduce(&self, _ddof: u8) -> PolarsResult<Scalar> {
530        polars_bail!(opq = var, self._dtype());
531    }
532    /// Get the standard deviation of the Series as a new Series of length 1.
533    fn std_reduce(&self, _ddof: u8) -> PolarsResult<Scalar> {
534        polars_bail!(opq = std, self._dtype());
535    }
536    /// Get the quantile of the ChunkedArray as a new Series of length 1.
537    fn quantile_reduce(&self, _quantile: f64, _method: QuantileMethod) -> PolarsResult<Scalar> {
538        polars_bail!(opq = quantile, self._dtype());
539    }
540    /// Get the bitwise AND of the Series as a new Series of length 1,
541    fn and_reduce(&self) -> PolarsResult<Scalar> {
542        polars_bail!(opq = and_reduce, self._dtype());
543    }
544    /// Get the bitwise OR of the Series as a new Series of length 1,
545    fn or_reduce(&self) -> PolarsResult<Scalar> {
546        polars_bail!(opq = or_reduce, self._dtype());
547    }
548    /// Get the bitwise XOR of the Series as a new Series of length 1,
549    fn xor_reduce(&self) -> PolarsResult<Scalar> {
550        polars_bail!(opq = xor_reduce, self._dtype());
551    }
552
553    /// Get the first element of the [`Series`] as a [`Scalar`]
554    ///
555    /// If the [`Series`] is empty, a [`Scalar`] with a [`AnyValue::Null`] is returned.
556    fn first(&self) -> Scalar {
557        let dt = self.dtype();
558        let av = self.get(0).map_or(AnyValue::Null, AnyValue::into_static);
559
560        Scalar::new(dt.clone(), av)
561    }
562
563    /// Get the first non-null element of the [`Series`] as a [`Scalar`]
564    ///
565    /// If the [`Series`] is empty, a [`Scalar`] with a [`AnyValue::Null`] is returned.
566    fn first_non_null(&self) -> Scalar {
567        let av = if self.len() == 0 {
568            AnyValue::Null
569        } else {
570            let idx = if self.has_nulls() {
571                first_non_null(self.chunks().iter().map(|c| c.as_ref())).unwrap_or(0)
572            } else {
573                0
574            };
575            self.get(idx).map_or(AnyValue::Null, AnyValue::into_static)
576        };
577        Scalar::new(self.dtype().clone(), av)
578    }
579
580    /// Get the last element of the [`Series`] as a [`Scalar`]
581    ///
582    /// If the [`Series`] is empty, a [`Scalar`] with a [`AnyValue::Null`] is returned.
583    fn last(&self) -> Scalar {
584        let dt = self.dtype();
585        let av = if self.len() == 0 {
586            AnyValue::Null
587        } else {
588            // SAFETY: len-1 < len if len != 0
589            unsafe { self.get_unchecked(self.len() - 1) }.into_static()
590        };
591
592        Scalar::new(dt.clone(), av)
593    }
594
595    /// Get the last non-null element of the [`Series`] as a [`Scalar`]
596    ///
597    /// If the [`Series`] is empty, a [`Scalar`] with a [`AnyValue::Null`] is returned.
598    fn last_non_null(&self) -> Scalar {
599        let n = self.len();
600        let av = if n == 0 {
601            AnyValue::Null
602        } else {
603            let idx = if self.has_nulls() {
604                last_non_null(self.chunks().iter().map(|c| c.as_ref()), n).unwrap_or(n - 1)
605            } else {
606                n - 1
607            };
608            // SAFETY: len-1 < len if len != 0
609            unsafe { self.get_unchecked(idx) }.into_static()
610        };
611        Scalar::new(self.dtype().clone(), av)
612    }
613
614    #[cfg(feature = "approx_unique")]
615    fn approx_n_unique(&self) -> PolarsResult<IdxSize> {
616        polars_bail!(opq = approx_n_unique, self._dtype());
617    }
618
619    /// Clone inner ChunkedArray and wrap in a new Arc
620    fn clone_inner(&self) -> Arc<dyn SeriesTrait>;
621
622    #[cfg(feature = "object")]
623    /// Get the value at this index as a downcastable Any trait ref.
624    fn get_object(&self, _index: usize) -> Option<&dyn PolarsObjectSafe> {
625        invalid_operation_panic!(get_object, self)
626    }
627
628    #[cfg(feature = "object")]
629    /// Get the value at this index as a downcastable Any trait ref.
630    ///
631    /// # Safety
632    /// This function doesn't do any bound checks.
633    unsafe fn get_object_chunked_unchecked(
634        &self,
635        _chunk: usize,
636        _index: usize,
637    ) -> Option<&dyn PolarsObjectSafe> {
638        invalid_operation_panic!(get_object_chunked_unchecked, self)
639    }
640
641    /// Get a hold of the [`ChunkedArray`], [`Logical`] or `NullChunked` as an `Any` trait
642    /// reference.
643    fn as_any(&self) -> &dyn Any;
644
645    /// Get a hold of the [`ChunkedArray`], [`Logical`] or `NullChunked` as an `Any` trait mutable
646    /// reference.
647    fn as_any_mut(&mut self) -> &mut dyn Any;
648
649    /// Get a hold of the [`ChunkedArray`] or `NullChunked` as an `Any` trait reference. This
650    /// pierces through `Logical` types to get the underlying physical array.
651    fn as_phys_any(&self) -> &dyn Any;
652
653    fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
654
655    #[cfg(feature = "checked_arithmetic")]
656    fn checked_div(&self, _rhs: &Series) -> PolarsResult<Series> {
657        polars_bail!(opq = checked_div, self._dtype());
658    }
659
660    #[cfg(feature = "rolling_window")]
661    /// Apply a custom function over a rolling/ moving window of the array.
662    /// This has quite some dynamic dispatch, so prefer rolling_min, max, mean, sum over this.
663    fn rolling_map(
664        &self,
665        _f: &dyn Fn(&Series) -> PolarsResult<Series>,
666        _options: RollingOptionsFixedWindow,
667    ) -> PolarsResult<Series> {
668        polars_bail!(opq = rolling_map, self._dtype());
669    }
670}
671
672impl dyn SeriesTrait + '_ {
673    pub fn unpack<T: PolarsPhysicalType>(&self) -> PolarsResult<&ChunkedArray<T>> {
674        polars_ensure!(&T::get_static_dtype() == self.dtype(), unpack);
675        Ok(self.as_ref())
676    }
677}