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