1#![allow(unsafe_op_in_unsafe_fn)]
2use crate::chunked_array::flags::StatisticsFlags;
4pub use crate::prelude::ChunkCompareEq;
5use crate::prelude::*;
6use crate::{HEAD_DEFAULT_LENGTH, TAIL_DEFAULT_LENGTH};
7
8macro_rules! invalid_operation_panic {
9 ($op:ident, $s:expr) => {
10 panic!(
11 "`{}` operation not supported for dtype `{}`",
12 stringify!($op),
13 $s._dtype()
14 )
15 };
16}
17
18pub mod amortized_iter;
19mod any_value;
20pub mod arithmetic;
21pub mod arrow_export;
22pub mod builder;
23
24mod comparison;
25mod from;
26pub mod implementations;
27pub(crate) mod iterator;
28pub mod ops;
29#[cfg(feature = "proptest")]
30pub mod proptest;
31mod series_trait;
32
33use std::borrow::Cow;
34use std::hash::{Hash, Hasher};
35use std::ops::Deref;
36
37use arrow::compute::aggregate::estimated_bytes_size;
38use arrow::offset::Offsets;
39pub use from::*;
40pub use iterator::{SeriesIter, SeriesPhysIter};
41use num_traits::NumCast;
42use polars_error::feature_gated;
43use polars_utils::float::IsFloat;
44pub use series_trait::{IsSorted, *};
45
46use crate::POOL;
47use crate::chunked_array::cast::CastOptions;
48#[cfg(feature = "zip_with")]
49use crate::series::arithmetic::coerce_lhs_rhs;
50use crate::utils::{Wrap, handle_casting_failures, materialize_dyn_int};
51
52#[derive(Clone)]
150#[must_use]
151pub struct Series(pub Arc<dyn SeriesTrait>);
152
153impl PartialEq for Wrap<Series> {
154 fn eq(&self, other: &Self) -> bool {
155 self.0.equals_missing(other)
156 }
157}
158
159impl Eq for Wrap<Series> {}
160
161impl Hash for Wrap<Series> {
162 fn hash<H: Hasher>(&self, state: &mut H) {
163 let rs = PlSeedableRandomStateQuality::fixed();
164 let mut h = vec![];
165 if self.0.vec_hash(rs, &mut h).is_ok() {
166 let h = h.into_iter().fold(0, |a: u64, b| a.wrapping_add(b));
167 h.hash(state)
168 } else {
169 self.len().hash(state);
170 self.null_count().hash(state);
171 self.dtype().hash(state);
172 }
173 }
174}
175
176impl Series {
177 pub fn new_empty(name: PlSmallStr, dtype: &DataType) -> Series {
179 Series::full_null(name, 0, dtype)
180 }
181
182 pub fn clear(&self) -> Series {
183 if self.is_empty() {
184 self.clone()
185 } else {
186 match self.dtype() {
187 #[cfg(feature = "object")]
188 DataType::Object(_) => self
189 .take(&ChunkedArray::<IdxType>::new_vec(PlSmallStr::EMPTY, vec![]))
190 .unwrap(),
191 dt => Series::new_empty(self.name().clone(), dt),
192 }
193 }
194 }
195
196 #[doc(hidden)]
197 pub fn _get_inner_mut(&mut self) -> &mut dyn SeriesTrait {
198 if Arc::weak_count(&self.0) + Arc::strong_count(&self.0) != 1 {
199 self.0 = self.0.clone_inner();
200 }
201 Arc::get_mut(&mut self.0).expect("implementation error")
202 }
203
204 pub fn take_inner<T: PolarsPhysicalType>(self) -> ChunkedArray<T> {
206 let arc_any = self.0.as_arc_any();
207 let downcast = arc_any
208 .downcast::<implementations::SeriesWrap<ChunkedArray<T>>>()
209 .unwrap();
210
211 match Arc::try_unwrap(downcast) {
212 Ok(ca) => ca.0,
213 Err(ca) => ca.as_ref().as_ref().clone(),
214 }
215 }
216
217 #[inline]
219 pub fn array_ref(&self, chunk_idx: usize) -> &ArrayRef {
220 &self.chunks()[chunk_idx] as &ArrayRef
221 }
222
223 pub unsafe fn chunks_mut(&mut self) -> &mut Vec<ArrayRef> {
227 #[allow(unused_mut)]
228 let mut ca = self._get_inner_mut();
229 ca.chunks_mut()
230 }
231
232 pub fn into_chunks(mut self) -> Vec<ArrayRef> {
233 let ca = self._get_inner_mut();
234 let chunks = std::mem::take(unsafe { ca.chunks_mut() });
235 ca.compute_len();
236 chunks
237 }
238
239 pub fn select_chunk(&self, i: usize) -> Self {
241 let mut new = self.clear();
242 let mut flags = self.get_flags();
243
244 use StatisticsFlags as F;
245 flags &= F::IS_SORTED_ANY | F::CAN_FAST_EXPLODE_LIST;
246
247 let mut_new = new._get_inner_mut();
249 let chunks = unsafe { mut_new.chunks_mut() };
250 let chunk = self.chunks()[i].clone();
251 chunks.clear();
252 chunks.push(chunk);
253 mut_new.compute_len();
254 mut_new._set_flags(flags);
255 new
256 }
257
258 pub fn is_sorted_flag(&self) -> IsSorted {
259 if self.len() <= 1 {
260 return IsSorted::Ascending;
261 }
262 self.get_flags().is_sorted()
263 }
264
265 pub fn set_sorted_flag(&mut self, sorted: IsSorted) {
266 let mut flags = self.get_flags();
267 flags.set_sorted(sorted);
268 self.set_flags(flags);
269 }
270
271 pub(crate) fn clear_flags(&mut self) {
272 self.set_flags(StatisticsFlags::empty());
273 }
274 pub fn get_flags(&self) -> StatisticsFlags {
275 self.0._get_flags()
276 }
277
278 pub(crate) fn set_flags(&mut self, flags: StatisticsFlags) {
279 self._get_inner_mut()._set_flags(flags)
280 }
281
282 pub fn into_frame(self) -> DataFrame {
283 unsafe { DataFrame::new_unchecked(self.len(), vec![self.into()]) }
285 }
286
287 pub fn rename(&mut self, name: PlSmallStr) -> &mut Series {
289 self._get_inner_mut().rename(name);
290 self
291 }
292
293 pub fn with_name(mut self, name: PlSmallStr) -> Series {
295 self.rename(name);
296 self
297 }
298
299 pub fn from_arrow_chunks(name: PlSmallStr, arrays: Vec<ArrayRef>) -> PolarsResult<Series> {
300 Self::try_from((name, arrays))
301 }
302
303 pub fn from_arrow(name: PlSmallStr, array: ArrayRef) -> PolarsResult<Series> {
304 Self::try_from((name, array))
305 }
306
307 pub fn shrink_to_fit(&mut self) {
309 self._get_inner_mut().shrink_to_fit()
310 }
311
312 pub fn append(&mut self, other: &Series) -> PolarsResult<&mut Self> {
316 let must_cast = other.dtype().matches_schema_type(self.dtype())?;
317 if must_cast {
318 let other = other.cast(self.dtype())?;
319 self.append_owned(other)?;
320 } else {
321 self._get_inner_mut().append(other)?;
322 }
323 Ok(self)
324 }
325
326 pub fn append_owned(&mut self, other: Series) -> PolarsResult<&mut Self> {
330 let must_cast = other.dtype().matches_schema_type(self.dtype())?;
331 if must_cast {
332 let other = other.cast(self.dtype())?;
333 self._get_inner_mut().append_owned(other)?;
334 } else {
335 self._get_inner_mut().append_owned(other)?;
336 }
337 Ok(self)
338 }
339
340 pub fn compute_len(&mut self) {
342 self._get_inner_mut().compute_len()
343 }
344
345 pub fn extend(&mut self, other: &Series) -> PolarsResult<&mut Self> {
349 let must_cast = other.dtype().matches_schema_type(self.dtype())?;
350 if must_cast {
351 let other = other.cast(self.dtype())?;
352 self._get_inner_mut().extend(&other)?;
353 } else {
354 self._get_inner_mut().extend(other)?;
355 }
356 Ok(self)
357 }
358
359 pub fn sort(&self, sort_options: SortOptions) -> PolarsResult<Self> {
375 self.sort_with(sort_options)
376 }
377
378 pub fn as_single_ptr(&mut self) -> PolarsResult<usize> {
380 self._get_inner_mut().as_single_ptr()
381 }
382
383 pub fn cast(&self, dtype: &DataType) -> PolarsResult<Self> {
384 self.cast_with_options(dtype, CastOptions::NonStrict)
385 }
386
387 pub fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Self> {
389 let slf = self
390 .trim_lists_to_normalized_offsets()
391 .map_or(Cow::Borrowed(self), Cow::Owned);
392 let slf = slf.propagate_nulls().map_or(slf, Cow::Owned);
393
394 use DataType as D;
395 let do_clone = match dtype {
396 D::Unknown(UnknownKind::Any) => true,
397 D::Unknown(UnknownKind::Int(_)) if slf.dtype().is_integer() => true,
398 D::Unknown(UnknownKind::Float) if slf.dtype().is_float() => true,
399 D::Unknown(UnknownKind::Str)
400 if slf.dtype().is_string() | slf.dtype().is_categorical() =>
401 {
402 true
403 },
404 dt if (dt.is_primitive() || dt.is_extension()) && dt == slf.dtype() => true,
405 _ => false,
406 };
407
408 if do_clone {
409 return Ok(slf.into_owned());
410 }
411
412 pub fn cast_dtype(dtype: &DataType) -> Option<DataType> {
413 match dtype {
414 D::Unknown(UnknownKind::Int(v)) => Some(materialize_dyn_int(*v).dtype()),
415 D::Unknown(UnknownKind::Float) => Some(DataType::Float64),
416 D::Unknown(UnknownKind::Str) => Some(DataType::String),
417 D::List(inner) => cast_dtype(inner.as_ref()).map(Box::new).map(D::List),
419 #[cfg(feature = "dtype-struct")]
420 D::Struct(fields) => {
421 let mut field_iter = fields.iter().enumerate();
424 let mut new_fields = loop {
425 let (i, field) = field_iter.next()?;
426
427 if let Some(dtype) = cast_dtype(&field.dtype) {
428 let mut new_fields = Vec::with_capacity(fields.len());
429 new_fields.extend(fields.iter().take(i).cloned());
430 new_fields.push(Field {
431 name: field.name.clone(),
432 dtype,
433 });
434 break new_fields;
435 }
436 };
437
438 new_fields.extend(fields.iter().skip(new_fields.len()).cloned().map(|field| {
439 let dtype = cast_dtype(&field.dtype).unwrap_or(field.dtype);
440 Field {
441 name: field.name,
442 dtype,
443 }
444 }));
445
446 Some(D::Struct(new_fields))
447 },
448 _ => None,
449 }
450 }
451
452 let mut casted = cast_dtype(dtype);
453 if dtype.is_list() && dtype.inner_dtype().is_some_and(|dt| dt.is_null()) {
454 if let Some(from_inner_dtype) = slf.dtype().inner_dtype() {
455 casted = Some(DataType::List(Box::new(from_inner_dtype.clone())));
456 }
457 }
458 let dtype = match casted {
459 None => dtype,
460 Some(ref dtype) => dtype,
461 };
462
463 let len = slf.len();
465 if slf.null_count() == len {
466 return Ok(Series::full_null(slf.name().clone(), len, dtype));
467 }
468
469 let new_options = match options {
470 CastOptions::Strict if !dtype.is_nested() => CastOptions::NonStrict,
473 opt => opt,
474 };
475
476 let out = slf.0.cast(dtype, new_options)?;
477 if options.is_strict() {
478 handle_casting_failures(slf.as_ref(), &out)?;
479 }
480 Ok(out)
481 }
482
483 pub unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Self> {
489 match self.dtype() {
490 #[cfg(feature = "dtype-struct")]
491 DataType::Struct(_) => self.struct_().unwrap().cast_unchecked(dtype),
492 DataType::List(_) => self.list().unwrap().cast_unchecked(dtype),
493 dt if dt.is_primitive_numeric() => {
494 with_match_physical_numeric_polars_type!(dt, |$T| {
495 let ca: &ChunkedArray<$T> = self.as_ref().as_ref().as_ref();
496 ca.cast_unchecked(dtype)
497 })
498 },
499 DataType::Binary => self.binary().unwrap().cast_unchecked(dtype),
500 _ => self.cast_with_options(dtype, CastOptions::Overflowing),
501 }
502 }
503
504 pub unsafe fn from_physical_unchecked(&self, dtype: &DataType) -> PolarsResult<Self> {
510 debug_assert!(!self.dtype().is_logical(), "{:?}", self.dtype());
511
512 if self.dtype() == dtype {
513 return Ok(self.clone());
514 }
515
516 use DataType as D;
517 match (self.dtype(), dtype) {
518 #[cfg(feature = "dtype-decimal")]
519 (D::Int128, D::Decimal(precision, scale)) => {
520 let ca = self.i128().unwrap();
521 Ok(ca
522 .clone()
523 .into_decimal_unchecked(*precision, *scale)
524 .into_series())
525 },
526
527 #[cfg(feature = "dtype-categorical")]
528 (phys, D::Categorical(cats, _)) if &cats.physical().dtype() == phys => {
529 with_match_categorical_physical_type!(cats.physical(), |$C| {
530 type CA = ChunkedArray<<$C as PolarsCategoricalType>::PolarsPhysical>;
531 let ca = self.as_ref().as_any().downcast_ref::<CA>().unwrap();
532 Ok(CategoricalChunked::<$C>::from_cats_and_dtype_unchecked(
533 ca.clone(),
534 dtype.clone(),
535 )
536 .into_series())
537 })
538 },
539 #[cfg(feature = "dtype-categorical")]
540 (phys, D::Enum(fcats, _)) if &fcats.physical().dtype() == phys => {
541 with_match_categorical_physical_type!(fcats.physical(), |$C| {
542 type CA = ChunkedArray<<$C as PolarsCategoricalType>::PolarsPhysical>;
543 let ca = self.as_ref().as_any().downcast_ref::<CA>().unwrap();
544 Ok(CategoricalChunked::<$C>::from_cats_and_dtype_unchecked(
545 ca.clone(),
546 dtype.clone(),
547 )
548 .into_series())
549 })
550 },
551
552 (D::Int32, D::Date) => feature_gated!("dtype-time", Ok(self.clone().into_date())),
553 (D::Int64, D::Datetime(tu, tz)) => feature_gated!(
554 "dtype-datetime",
555 Ok(self.clone().into_datetime(*tu, tz.clone()))
556 ),
557 (D::Int64, D::Duration(tu)) => {
558 feature_gated!("dtype-duration", Ok(self.clone().into_duration(*tu)))
559 },
560 (D::Int64, D::Time) => feature_gated!("dtype-time", Ok(self.clone().into_time())),
561
562 (D::List(_), D::List(to)) => unsafe {
563 self.list()
564 .unwrap()
565 .from_physical_unchecked(to.as_ref().clone())
566 .map(|ca| ca.into_series())
567 },
568 #[cfg(feature = "dtype-array")]
569 (D::Array(_, lw), D::Array(to, rw)) if lw == rw => unsafe {
570 self.array()
571 .unwrap()
572 .from_physical_unchecked(to.as_ref().clone())
573 .map(|ca| ca.into_series())
574 },
575 #[cfg(feature = "dtype-struct")]
576 (D::Struct(_), D::Struct(to)) => unsafe {
577 self.struct_()
578 .unwrap()
579 .from_physical_unchecked(to.as_slice())
580 .map(|ca| ca.into_series())
581 },
582
583 #[cfg(feature = "dtype-extension")]
584 (_, D::Extension(typ, storage)) => {
585 let storage_series = self.from_physical_unchecked(storage.as_ref())?;
586 let ext = ExtensionChunked::from_storage(typ.clone(), storage_series);
587 Ok(ext.into_series())
588 },
589
590 _ => panic!("invalid from_physical({dtype:?}) for {:?}", self.dtype()),
591 }
592 }
593
594 #[cfg(feature = "dtype-extension")]
595 pub fn into_extension(self, typ: ExtensionTypeInstance) -> Series {
596 assert!(!self.dtype().is_extension());
597 let ext = ExtensionChunked::from_storage(typ, self);
598 ext.into_series()
599 }
600
601 pub fn to_float(&self) -> PolarsResult<Series> {
603 match self.dtype() {
604 DataType::Float32 | DataType::Float64 => Ok(self.clone()),
605 _ => self.cast_with_options(&DataType::Float64, CastOptions::Overflowing),
606 }
607 }
608
609 pub fn sum<T>(&self) -> PolarsResult<T>
616 where
617 T: NumCast + IsFloat,
618 {
619 let sum = self.sum_reduce()?;
620 let sum = sum.value().extract().unwrap();
621 Ok(sum)
622 }
623
624 pub fn min<T>(&self) -> PolarsResult<Option<T>>
627 where
628 T: NumCast + IsFloat,
629 {
630 let min = self.min_reduce()?;
631 let min = min.value().extract::<T>();
632 Ok(min)
633 }
634
635 pub fn max<T>(&self) -> PolarsResult<Option<T>>
638 where
639 T: NumCast + IsFloat,
640 {
641 let max = self.max_reduce()?;
642 let max = max.value().extract::<T>();
643 Ok(max)
644 }
645
646 pub fn explode(&self, options: ExplodeOptions) -> PolarsResult<Series> {
648 match self.dtype() {
649 DataType::List(_) => self.list().unwrap().explode(options),
650 #[cfg(feature = "dtype-array")]
651 DataType::Array(_, _) => self.array().unwrap().explode(options),
652 _ => Ok(self.clone()),
653 }
654 }
655
656 pub fn is_nan(&self) -> PolarsResult<BooleanChunked> {
658 match self.dtype() {
659 #[cfg(feature = "dtype-f16")]
660 DataType::Float16 => Ok(self.f16().unwrap().is_nan()),
661 DataType::Float32 => Ok(self.f32().unwrap().is_nan()),
662 DataType::Float64 => Ok(self.f64().unwrap().is_nan()),
663 DataType::Null => Ok(BooleanChunked::full_null(self.name().clone(), self.len())),
664 dt if dt.is_primitive_numeric() => {
665 let arr = BooleanArray::full(self.len(), false, ArrowDataType::Boolean)
666 .with_validity(self.rechunk_validity());
667 Ok(BooleanChunked::with_chunk(self.name().clone(), arr))
668 },
669 _ => polars_bail!(opq = is_nan, self.dtype()),
670 }
671 }
672
673 pub fn is_not_nan(&self) -> PolarsResult<BooleanChunked> {
675 match self.dtype() {
676 #[cfg(feature = "dtype-f16")]
677 DataType::Float16 => Ok(self.f16().unwrap().is_not_nan()),
678 DataType::Float32 => Ok(self.f32().unwrap().is_not_nan()),
679 DataType::Float64 => Ok(self.f64().unwrap().is_not_nan()),
680 dt if dt.is_primitive_numeric() => {
681 let arr = BooleanArray::full(self.len(), true, ArrowDataType::Boolean)
682 .with_validity(self.rechunk_validity());
683 Ok(BooleanChunked::with_chunk(self.name().clone(), arr))
684 },
685 _ => polars_bail!(opq = is_not_nan, self.dtype()),
686 }
687 }
688
689 pub fn is_finite(&self) -> PolarsResult<BooleanChunked> {
691 match self.dtype() {
692 #[cfg(feature = "dtype-f16")]
693 DataType::Float16 => Ok(self.f16().unwrap().is_finite()),
694 DataType::Float32 => Ok(self.f32().unwrap().is_finite()),
695 DataType::Float64 => Ok(self.f64().unwrap().is_finite()),
696 DataType::Null => Ok(BooleanChunked::full_null(self.name().clone(), self.len())),
697 dt if dt.is_primitive_numeric() => {
698 let arr = BooleanArray::full(self.len(), true, ArrowDataType::Boolean)
699 .with_validity(self.rechunk_validity());
700 Ok(BooleanChunked::with_chunk(self.name().clone(), arr))
701 },
702 _ => polars_bail!(opq = is_finite, self.dtype()),
703 }
704 }
705
706 pub fn is_infinite(&self) -> PolarsResult<BooleanChunked> {
708 match self.dtype() {
709 #[cfg(feature = "dtype-f16")]
710 DataType::Float16 => Ok(self.f16().unwrap().is_infinite()),
711 DataType::Float32 => Ok(self.f32().unwrap().is_infinite()),
712 DataType::Float64 => Ok(self.f64().unwrap().is_infinite()),
713 DataType::Null => Ok(BooleanChunked::full_null(self.name().clone(), self.len())),
714 dt if dt.is_primitive_numeric() => {
715 let arr = BooleanArray::full(self.len(), false, ArrowDataType::Boolean)
716 .with_validity(self.rechunk_validity());
717 Ok(BooleanChunked::with_chunk(self.name().clone(), arr))
718 },
719 _ => polars_bail!(opq = is_infinite, self.dtype()),
720 }
721 }
722
723 #[cfg(feature = "zip_with")]
727 pub fn zip_with(&self, mask: &BooleanChunked, other: &Series) -> PolarsResult<Series> {
728 let (lhs, rhs) = coerce_lhs_rhs(self, other)?;
729 lhs.zip_with_same_type(mask, rhs.as_ref())
730 }
731
732 pub fn to_physical_repr(&self) -> Cow<'_, Series> {
746 use DataType::*;
747 match self.dtype() {
748 #[cfg(feature = "dtype-date")]
751 Date => Cow::Owned(self.date().unwrap().phys.clone().into_series()),
752 #[cfg(feature = "dtype-datetime")]
753 Datetime(_, _) => Cow::Owned(self.datetime().unwrap().phys.clone().into_series()),
754 #[cfg(feature = "dtype-duration")]
755 Duration(_) => Cow::Owned(self.duration().unwrap().phys.clone().into_series()),
756 #[cfg(feature = "dtype-time")]
757 Time => Cow::Owned(self.time().unwrap().phys.clone().into_series()),
758 #[cfg(feature = "dtype-categorical")]
759 dt @ (Categorical(_, _) | Enum(_, _)) => {
760 with_match_categorical_physical_type!(dt.cat_physical().unwrap(), |$C| {
761 let ca = self.cat::<$C>().unwrap();
762 Cow::Owned(ca.physical().clone().into_series())
763 })
764 },
765 #[cfg(feature = "dtype-decimal")]
766 Decimal(_, _) => Cow::Owned(self.decimal().unwrap().phys.clone().into_series()),
767 List(_) => match self.list().unwrap().to_physical_repr() {
768 Cow::Borrowed(_) => Cow::Borrowed(self),
769 Cow::Owned(ca) => Cow::Owned(ca.into_series()),
770 },
771 #[cfg(feature = "dtype-array")]
772 Array(_, _) => match self.array().unwrap().to_physical_repr() {
773 Cow::Borrowed(_) => Cow::Borrowed(self),
774 Cow::Owned(ca) => Cow::Owned(ca.into_series()),
775 },
776 #[cfg(feature = "dtype-struct")]
777 Struct(_) => match self.struct_().unwrap().to_physical_repr() {
778 Cow::Borrowed(_) => Cow::Borrowed(self),
779 Cow::Owned(ca) => Cow::Owned(ca.into_series()),
780 },
781 #[cfg(feature = "dtype-extension")]
782 Extension(_, _) => self.ext().unwrap().storage().to_physical_repr(),
783 _ => Cow::Borrowed(self),
784 }
785 }
786
787 pub fn to_storage(&self) -> &Series {
790 #[cfg(feature = "dtype-extension")]
791 {
792 if let DataType::Extension(_, _) = self.dtype() {
793 return self.ext().unwrap().storage();
794 }
795 }
796 self
797 }
798
799 pub fn gather_every(&self, n: usize, offset: usize) -> PolarsResult<Series> {
801 polars_ensure!(n > 0, ComputeError: "cannot perform gather every for `n=0`");
802 let idx = ((offset as IdxSize)..self.len() as IdxSize)
803 .step_by(n)
804 .collect_ca(PlSmallStr::EMPTY);
805 Ok(unsafe { self.take_unchecked(&idx) })
807 }
808
809 #[cfg(feature = "dot_product")]
810 pub fn dot(&self, other: &Series) -> PolarsResult<f64> {
811 std::ops::Mul::mul(self, other)?.sum::<f64>()
812 }
813
814 pub fn sum_reduce(&self) -> PolarsResult<Scalar> {
820 use DataType::*;
821 match self.dtype() {
822 Int8 | UInt8 | Int16 | UInt16 => self.cast(&Int64).unwrap().sum_reduce(),
823 _ => self.0.sum_reduce(),
824 }
825 }
826
827 pub fn mean_reduce(&self) -> PolarsResult<Scalar> {
830 self.0.mean_reduce()
831 }
832
833 pub fn product(&self) -> PolarsResult<Scalar> {
838 #[cfg(feature = "product")]
839 {
840 use DataType::*;
841 match self.dtype() {
842 Boolean => self.cast(&DataType::Int64).unwrap().product(),
843 Int8 | UInt8 | Int16 | UInt16 | Int32 | UInt32 => {
844 let s = self.cast(&Int64).unwrap();
845 s.product()
846 },
847 Int64 => Ok(self.i64().unwrap().prod_reduce()),
848 UInt64 => Ok(self.u64().unwrap().prod_reduce()),
849 #[cfg(feature = "dtype-i128")]
850 Int128 => Ok(self.i128().unwrap().prod_reduce()),
851 #[cfg(feature = "dtype-u128")]
852 UInt128 => Ok(self.u128().unwrap().prod_reduce()),
853 #[cfg(feature = "dtype-f16")]
854 Float16 => Ok(self.f16().unwrap().prod_reduce()),
855 Float32 => Ok(self.f32().unwrap().prod_reduce()),
856 Float64 => Ok(self.f64().unwrap().prod_reduce()),
857 #[cfg(feature = "dtype-decimal")]
858 Decimal(..) => Ok(self.decimal().unwrap().prod_reduce()),
859 dt => {
860 polars_bail!(InvalidOperation: "`product` operation not supported for dtype `{dt}`")
861 },
862 }
863 }
864 #[cfg(not(feature = "product"))]
865 {
866 panic!("activate 'product' feature")
867 }
868 }
869
870 pub fn strict_cast(&self, dtype: &DataType) -> PolarsResult<Series> {
872 self.cast_with_options(dtype, CastOptions::Strict)
873 }
874
875 #[cfg(feature = "dtype-decimal")]
876 pub fn into_decimal(self, precision: usize, scale: usize) -> PolarsResult<Series> {
877 match self.dtype() {
878 DataType::Int128 => Ok(self
879 .i128()
880 .unwrap()
881 .clone()
882 .into_decimal(precision, scale)?
883 .into_series()),
884 DataType::Decimal(cur_prec, cur_scale)
885 if scale == *cur_scale && precision >= *cur_prec =>
886 {
887 Ok(self)
888 },
889 dt => panic!("into_decimal({precision:?}, {scale}) not implemented for {dt:?}"),
890 }
891 }
892
893 #[cfg(feature = "dtype-time")]
894 pub fn into_time(self) -> Series {
895 match self.dtype() {
896 DataType::Int64 => self.i64().unwrap().clone().into_time().into_series(),
897 DataType::Time => self
898 .time()
899 .unwrap()
900 .physical()
901 .clone()
902 .into_time()
903 .into_series(),
904 dt => panic!("date not implemented for {dt:?}"),
905 }
906 }
907
908 pub fn into_date(self) -> Series {
909 #[cfg(not(feature = "dtype-date"))]
910 {
911 panic!("activate feature dtype-date")
912 }
913 #[cfg(feature = "dtype-date")]
914 match self.dtype() {
915 DataType::Int32 => self.i32().unwrap().clone().into_date().into_series(),
916 DataType::Date => self
917 .date()
918 .unwrap()
919 .physical()
920 .clone()
921 .into_date()
922 .into_series(),
923 dt => panic!("date not implemented for {dt:?}"),
924 }
925 }
926
927 #[allow(unused_variables)]
928 pub fn into_datetime(self, timeunit: TimeUnit, tz: Option<TimeZone>) -> Series {
929 #[cfg(not(feature = "dtype-datetime"))]
930 {
931 panic!("activate feature dtype-datetime")
932 }
933
934 #[cfg(feature = "dtype-datetime")]
935 match self.dtype() {
936 DataType::Int64 => self
937 .i64()
938 .unwrap()
939 .clone()
940 .into_datetime(timeunit, tz)
941 .into_series(),
942 DataType::Datetime(_, _) => self
943 .datetime()
944 .unwrap()
945 .physical()
946 .clone()
947 .into_datetime(timeunit, tz)
948 .into_series(),
949 dt => panic!("into_datetime not implemented for {dt:?}"),
950 }
951 }
952
953 #[allow(unused_variables)]
954 pub fn into_duration(self, timeunit: TimeUnit) -> Series {
955 #[cfg(not(feature = "dtype-duration"))]
956 {
957 panic!("activate feature dtype-duration")
958 }
959 #[cfg(feature = "dtype-duration")]
960 match self.dtype() {
961 DataType::Int64 => self
962 .i64()
963 .unwrap()
964 .clone()
965 .into_duration(timeunit)
966 .into_series(),
967 DataType::Duration(_) => self
968 .duration()
969 .unwrap()
970 .physical()
971 .clone()
972 .into_duration(timeunit)
973 .into_series(),
974 dt => panic!("into_duration not implemented for {dt:?}"),
975 }
976 }
977
978 pub fn str_value(&self, index: usize) -> PolarsResult<Cow<'_, str>> {
980 Ok(self.0.get(index)?.str_value())
981 }
982 pub fn head(&self, length: Option<usize>) -> Series {
984 let len = length.unwrap_or(HEAD_DEFAULT_LENGTH);
985 self.slice(0, std::cmp::min(len, self.len()))
986 }
987
988 pub fn tail(&self, length: Option<usize>) -> Series {
990 let len = length.unwrap_or(TAIL_DEFAULT_LENGTH);
991 let len = std::cmp::min(len, self.len());
992 self.slice(-(len as i64), len)
993 }
994
995 pub fn unique_stable(&self) -> PolarsResult<Series> {
998 let idx = self.arg_unique()?;
999 unsafe { Ok(self.take_unchecked(&idx)) }
1001 }
1002
1003 pub fn try_idx(&self) -> Option<&IdxCa> {
1004 #[cfg(feature = "bigidx")]
1005 {
1006 self.try_u64()
1007 }
1008 #[cfg(not(feature = "bigidx"))]
1009 {
1010 self.try_u32()
1011 }
1012 }
1013
1014 pub fn idx(&self) -> PolarsResult<&IdxCa> {
1015 #[cfg(feature = "bigidx")]
1016 {
1017 self.u64()
1018 }
1019 #[cfg(not(feature = "bigidx"))]
1020 {
1021 self.u32()
1022 }
1023 }
1024
1025 pub fn estimated_size(&self) -> usize {
1038 let mut size = 0;
1039 match self.dtype() {
1040 #[cfg(feature = "object")]
1042 DataType::Object(_) => {
1043 let ArrowDataType::FixedSizeBinary(size) = self.chunks()[0].dtype() else {
1044 unreachable!()
1045 };
1046 return self.len() * *size;
1048 },
1049 _ => {},
1050 }
1051
1052 size += self
1053 .chunks()
1054 .iter()
1055 .map(|arr| estimated_bytes_size(&**arr))
1056 .sum::<usize>();
1057
1058 size
1059 }
1060
1061 pub fn as_list(&self) -> ListChunked {
1063 let s = self.rechunk();
1064 let values = s.chunks()[0].clone();
1066 let offsets = (0i64..(s.len() as i64 + 1)).collect::<Vec<_>>();
1067 let offsets = unsafe { Offsets::new_unchecked(offsets) };
1068
1069 let dtype = LargeListArray::default_datatype(
1070 s.dtype().to_physical().to_arrow(CompatLevel::newest()),
1071 );
1072 let new_arr = LargeListArray::new(dtype, offsets.into(), values, None);
1073 let mut out = ListChunked::with_chunk(s.name().clone(), new_arr);
1074 out.set_inner_dtype(s.dtype().clone());
1075 out
1076 }
1077
1078 pub fn row_encode_unordered(&self) -> PolarsResult<BinaryOffsetChunked> {
1079 row_encode::_get_rows_encoded_ca_unordered(
1080 self.name().clone(),
1081 &[self.clone().into_column()],
1082 )
1083 }
1084
1085 pub fn row_encode_ordered(
1086 &self,
1087 descending: bool,
1088 nulls_last: bool,
1089 ) -> PolarsResult<BinaryOffsetChunked> {
1090 row_encode::_get_rows_encoded_ca(
1091 self.name().clone(),
1092 &[self.clone().into_column()],
1093 &[descending],
1094 &[nulls_last],
1095 false,
1096 )
1097 }
1098}
1099
1100impl Default for Series {
1101 fn default() -> Self {
1102 NullChunked::new(PlSmallStr::EMPTY, 0).into_series()
1103 }
1104}
1105
1106impl Deref for Series {
1107 type Target = dyn SeriesTrait;
1108
1109 fn deref(&self) -> &Self::Target {
1110 self.0.as_ref()
1111 }
1112}
1113
1114impl<'a> AsRef<dyn SeriesTrait + 'a> for Series {
1115 fn as_ref(&self) -> &(dyn SeriesTrait + 'a) {
1116 self.0.as_ref()
1117 }
1118}
1119
1120impl<T: PolarsPhysicalType> AsRef<ChunkedArray<T>> for dyn SeriesTrait + '_ {
1121 fn as_ref(&self) -> &ChunkedArray<T> {
1122 let Some(ca) = self.as_any().downcast_ref::<ChunkedArray<T>>() else {
1125 panic!(
1126 "implementation error, cannot get ref {:?} from {:?}",
1127 T::get_static_dtype(),
1128 self.dtype()
1129 );
1130 };
1131
1132 ca
1133 }
1134}
1135
1136impl<T: PolarsPhysicalType> AsMut<ChunkedArray<T>> for dyn SeriesTrait + '_ {
1137 fn as_mut(&mut self) -> &mut ChunkedArray<T> {
1138 if !self.as_any_mut().is::<ChunkedArray<T>>() {
1139 panic!(
1140 "implementation error, cannot get ref {:?} from {:?}",
1141 T::get_static_dtype(),
1142 self.dtype()
1143 );
1144 }
1145
1146 self.as_any_mut().downcast_mut::<ChunkedArray<T>>().unwrap()
1149 }
1150}
1151
1152#[cfg(test)]
1153mod test {
1154 use crate::prelude::*;
1155 use crate::series::*;
1156
1157 #[test]
1158 fn cast() {
1159 let ar = UInt32Chunked::new("a".into(), &[1, 2]);
1160 let s = ar.into_series();
1161 let s2 = s.cast(&DataType::Int64).unwrap();
1162
1163 assert!(s2.i64().is_ok());
1164 let s2 = s.cast(&DataType::Float32).unwrap();
1165 assert!(s2.f32().is_ok());
1166 }
1167
1168 #[test]
1169 fn new_series() {
1170 let _ = Series::new("boolean series".into(), &vec![true, false, true]);
1171 let _ = Series::new("int series".into(), &[1, 2, 3]);
1172 let ca = Int32Chunked::new("a".into(), &[1, 2, 3]);
1173 let _ = ca.into_series();
1174 }
1175
1176 #[test]
1177 #[cfg(feature = "dtype-date")]
1178 fn roundtrip_list_logical_20311() {
1179 let list = ListChunked::from_chunk_iter(
1180 PlSmallStr::from_static("a"),
1181 [ListArray::new(
1182 ArrowDataType::LargeList(Box::new(ArrowField::new(
1183 LIST_VALUES_NAME,
1184 ArrowDataType::Int32,
1185 true,
1186 ))),
1187 unsafe { Offsets::new_unchecked(vec![0, 1]) }.into(),
1188 PrimitiveArray::new(ArrowDataType::Int32, vec![1i32].into(), None).to_boxed(),
1189 None,
1190 )],
1191 );
1192 let list = unsafe { list.from_physical_unchecked(DataType::Date) }.unwrap();
1193 assert_eq!(list.dtype(), &DataType::List(Box::new(DataType::Date)));
1194 }
1195
1196 #[test]
1197 #[cfg(feature = "dtype-struct")]
1198 fn new_series_from_empty_structs() {
1199 let dtype = DataType::Struct(vec![]);
1200 let empties = vec![AnyValue::StructOwned(Box::new((vec![], vec![]))); 3];
1201 let s = Series::from_any_values_and_dtype("".into(), &empties, &dtype, false).unwrap();
1202 assert_eq!(s.len(), 3);
1203 }
1204 #[test]
1205 fn new_series_from_arrow_primitive_array() {
1206 let array = UInt32Array::from_slice([1, 2, 3, 4, 5]);
1207 let array_ref: ArrayRef = Box::new(array);
1208
1209 let _ = Series::try_new("foo".into(), array_ref).unwrap();
1210 }
1211
1212 #[test]
1213 fn series_append() {
1214 let mut s1 = Series::new("a".into(), &[1, 2]);
1215 let s2 = Series::new("b".into(), &[3]);
1216 s1.append(&s2).unwrap();
1217 assert_eq!(s1.len(), 3);
1218
1219 let s2 = Series::new("b".into(), &[3.0]);
1221 assert!(s1.append(&s2).is_err())
1222 }
1223
1224 #[test]
1225 #[cfg(feature = "dtype-decimal")]
1226 fn series_append_decimal() {
1227 let s1 = Series::new("a".into(), &[1.1, 2.3])
1228 .cast(&DataType::Decimal(38, 2))
1229 .unwrap();
1230 let s2 = Series::new("b".into(), &[3])
1231 .cast(&DataType::Decimal(38, 0))
1232 .unwrap();
1233
1234 {
1235 let mut s1 = s1.clone();
1236 s1.append(&s2).unwrap();
1237 assert_eq!(s1.len(), 3);
1238 assert_eq!(s1.get(2).unwrap(), AnyValue::Decimal(300, 38, 2));
1239 }
1240
1241 {
1242 let mut s2 = s2;
1243 s2.extend(&s1).unwrap();
1244 assert_eq!(s2.get(2).unwrap(), AnyValue::Decimal(2, 38, 0));
1245 }
1246 }
1247
1248 #[test]
1249 fn series_slice_works() {
1250 let series = Series::new("a".into(), &[1i64, 2, 3, 4, 5]);
1251
1252 let slice_1 = series.slice(-3, 3);
1253 let slice_2 = series.slice(-5, 5);
1254 let slice_3 = series.slice(0, 5);
1255
1256 assert_eq!(slice_1.get(0).unwrap(), AnyValue::Int64(3));
1257 assert_eq!(slice_2.get(0).unwrap(), AnyValue::Int64(1));
1258 assert_eq!(slice_3.get(0).unwrap(), AnyValue::Int64(1));
1259 }
1260
1261 #[test]
1262 fn out_of_range_slice_does_not_panic() {
1263 let series = Series::new("a".into(), &[1i64, 2, 3, 4, 5]);
1264
1265 let _ = series.slice(-3, 4);
1266 let _ = series.slice(-6, 2);
1267 let _ = series.slice(4, 2);
1268 }
1269}