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