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