polars_core/series/
series_trait.rs

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