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 builder;
22#[cfg(feature = "dtype-categorical")]
23pub mod categorical_to_arrow;
24mod comparison;
25mod from;
26pub mod implementations;
27mod into;
28pub use into::ToArrowConverter;
29pub(crate) mod iterator;
30pub mod ops;
31#[cfg(feature = "proptest")]
32pub mod proptest;
33mod series_trait;
34
35use std::borrow::Cow;
36use std::hash::{Hash, Hasher};
37use std::ops::Deref;
38
39use arrow::compute::aggregate::estimated_bytes_size;
40use arrow::offset::Offsets;
41pub use from::*;
42pub use iterator::{SeriesIter, SeriesPhysIter};
43use num_traits::NumCast;
44use polars_error::feature_gated;
45use polars_utils::float::IsFloat;
46pub use series_trait::{IsSorted, *};
47
48use crate::POOL;
49use crate::chunked_array::cast::CastOptions;
50#[cfg(feature = "zip_with")]
51use crate::series::arithmetic::coerce_lhs_rhs;
52use crate::utils::{Wrap, handle_casting_failures, materialize_dyn_int};
53
54#[derive(Clone)]
152#[must_use]
153pub struct Series(pub Arc<dyn SeriesTrait>);
154
155impl PartialEq for Wrap<Series> {
156 fn eq(&self, other: &Self) -> bool {
157 self.0.equals_missing(other)
158 }
159}
160
161impl Eq for Wrap<Series> {}
162
163impl Hash for Wrap<Series> {
164 fn hash<H: Hasher>(&self, state: &mut H) {
165 let rs = PlSeedableRandomStateQuality::fixed();
166 let mut h = vec![];
167 if self.0.vec_hash(rs, &mut h).is_ok() {
168 let h = h.into_iter().fold(0, |a: u64, b| a.wrapping_add(b));
169 h.hash(state)
170 } else {
171 self.len().hash(state);
172 self.null_count().hash(state);
173 self.dtype().hash(state);
174 }
175 }
176}
177
178impl Series {
179 pub fn new_empty(name: PlSmallStr, dtype: &DataType) -> Series {
181 Series::full_null(name, 0, dtype)
182 }
183
184 pub fn clear(&self) -> Series {
185 if self.is_empty() {
186 self.clone()
187 } else {
188 match self.dtype() {
189 #[cfg(feature = "object")]
190 DataType::Object(_) => self
191 .take(&ChunkedArray::<IdxType>::new_vec(PlSmallStr::EMPTY, vec![]))
192 .unwrap(),
193 dt => Series::new_empty(self.name().clone(), dt),
194 }
195 }
196 }
197
198 #[doc(hidden)]
199 pub fn _get_inner_mut(&mut self) -> &mut dyn SeriesTrait {
200 if Arc::weak_count(&self.0) + Arc::strong_count(&self.0) != 1 {
201 self.0 = self.0.clone_inner();
202 }
203 Arc::get_mut(&mut self.0).expect("implementation error")
204 }
205
206 pub fn take_inner<T: PolarsPhysicalType>(self) -> ChunkedArray<T> {
208 let arc_any = self.0.as_arc_any();
209 let downcast = arc_any
210 .downcast::<implementations::SeriesWrap<ChunkedArray<T>>>()
211 .unwrap();
212
213 match Arc::try_unwrap(downcast) {
214 Ok(ca) => ca.0,
215 Err(ca) => ca.as_ref().as_ref().clone(),
216 }
217 }
218
219 pub unsafe fn chunks_mut(&mut self) -> &mut Vec<ArrayRef> {
223 #[allow(unused_mut)]
224 let mut ca = self._get_inner_mut();
225 ca.chunks_mut()
226 }
227
228 pub fn into_chunks(mut self) -> Vec<ArrayRef> {
229 let ca = self._get_inner_mut();
230 let chunks = std::mem::take(unsafe { ca.chunks_mut() });
231 ca.compute_len();
232 chunks
233 }
234
235 pub fn select_chunk(&self, i: usize) -> Self {
237 let mut new = self.clear();
238 let mut flags = self.get_flags();
239
240 use StatisticsFlags as F;
241 flags &= F::IS_SORTED_ANY | F::CAN_FAST_EXPLODE_LIST;
242
243 let mut_new = new._get_inner_mut();
245 let chunks = unsafe { mut_new.chunks_mut() };
246 let chunk = self.chunks()[i].clone();
247 chunks.clear();
248 chunks.push(chunk);
249 mut_new.compute_len();
250 mut_new._set_flags(flags);
251 new
252 }
253
254 pub fn is_sorted_flag(&self) -> IsSorted {
255 if self.len() <= 1 {
256 return IsSorted::Ascending;
257 }
258 self.get_flags().is_sorted()
259 }
260
261 pub fn set_sorted_flag(&mut self, sorted: IsSorted) {
262 let mut flags = self.get_flags();
263 flags.set_sorted(sorted);
264 self.set_flags(flags);
265 }
266
267 pub(crate) fn clear_flags(&mut self) {
268 self.set_flags(StatisticsFlags::empty());
269 }
270 pub fn get_flags(&self) -> StatisticsFlags {
271 self.0._get_flags()
272 }
273
274 pub(crate) fn set_flags(&mut self, flags: StatisticsFlags) {
275 self._get_inner_mut()._set_flags(flags)
276 }
277
278 pub fn into_frame(self) -> DataFrame {
279 unsafe { DataFrame::new_no_checks(self.len(), vec![self.into()]) }
281 }
282
283 pub fn rename(&mut self, name: PlSmallStr) -> &mut Series {
285 self._get_inner_mut().rename(name);
286 self
287 }
288
289 pub fn with_name(mut self, name: PlSmallStr) -> Series {
291 self.rename(name);
292 self
293 }
294
295 pub fn from_arrow_chunks(name: PlSmallStr, arrays: Vec<ArrayRef>) -> PolarsResult<Series> {
296 Self::try_from((name, arrays))
297 }
298
299 pub fn from_arrow(name: PlSmallStr, array: ArrayRef) -> PolarsResult<Series> {
300 Self::try_from((name, array))
301 }
302
303 pub fn shrink_to_fit(&mut self) {
305 self._get_inner_mut().shrink_to_fit()
306 }
307
308 pub fn append(&mut self, other: &Series) -> PolarsResult<&mut Self> {
312 let must_cast = other.dtype().matches_schema_type(self.dtype())?;
313 if must_cast {
314 let other = other.cast(self.dtype())?;
315 self.append_owned(other)?;
316 } else {
317 self._get_inner_mut().append(other)?;
318 }
319 Ok(self)
320 }
321
322 pub fn append_owned(&mut self, other: Series) -> PolarsResult<&mut Self> {
326 let must_cast = other.dtype().matches_schema_type(self.dtype())?;
327 if must_cast {
328 let other = other.cast(self.dtype())?;
329 self._get_inner_mut().append_owned(other)?;
330 } else {
331 self._get_inner_mut().append_owned(other)?;
332 }
333 Ok(self)
334 }
335
336 pub fn compute_len(&mut self) {
338 self._get_inner_mut().compute_len()
339 }
340
341 pub fn extend(&mut self, other: &Series) -> PolarsResult<&mut Self> {
345 let must_cast = other.dtype().matches_schema_type(self.dtype())?;
346 if must_cast {
347 let other = other.cast(self.dtype())?;
348 self._get_inner_mut().extend(&other)?;
349 } else {
350 self._get_inner_mut().extend(other)?;
351 }
352 Ok(self)
353 }
354
355 pub fn sort(&self, sort_options: SortOptions) -> PolarsResult<Self> {
371 self.sort_with(sort_options)
372 }
373
374 pub fn as_single_ptr(&mut self) -> PolarsResult<usize> {
376 self._get_inner_mut().as_single_ptr()
377 }
378
379 pub fn cast(&self, dtype: &DataType) -> PolarsResult<Self> {
380 self.cast_with_options(dtype, CastOptions::NonStrict)
381 }
382
383 pub fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Self> {
385 let slf = self
386 .trim_lists_to_normalized_offsets()
387 .map_or(Cow::Borrowed(self), Cow::Owned);
388 let slf = slf.propagate_nulls().map_or(slf, Cow::Owned);
389
390 use DataType as D;
391 let do_clone = match dtype {
392 D::Unknown(UnknownKind::Any) => true,
393 D::Unknown(UnknownKind::Int(_)) if slf.dtype().is_integer() => true,
394 D::Unknown(UnknownKind::Float) if slf.dtype().is_float() => true,
395 D::Unknown(UnknownKind::Str)
396 if slf.dtype().is_string() | slf.dtype().is_categorical() =>
397 {
398 true
399 },
400 dt if (dt.is_primitive() || dt.is_extension()) && dt == slf.dtype() => true,
401 _ => false,
402 };
403
404 if do_clone {
405 return Ok(slf.into_owned());
406 }
407
408 pub fn cast_dtype(dtype: &DataType) -> Option<DataType> {
409 match dtype {
410 D::Unknown(UnknownKind::Int(v)) => Some(materialize_dyn_int(*v).dtype()),
411 D::Unknown(UnknownKind::Float) => Some(DataType::Float64),
412 D::Unknown(UnknownKind::Str) => Some(DataType::String),
413 D::List(inner) => cast_dtype(inner.as_ref()).map(Box::new).map(D::List),
415 #[cfg(feature = "dtype-struct")]
416 D::Struct(fields) => {
417 let mut field_iter = fields.iter().enumerate();
420 let mut new_fields = loop {
421 let (i, field) = field_iter.next()?;
422
423 if let Some(dtype) = cast_dtype(&field.dtype) {
424 let mut new_fields = Vec::with_capacity(fields.len());
425 new_fields.extend(fields.iter().take(i).cloned());
426 new_fields.push(Field {
427 name: field.name.clone(),
428 dtype,
429 });
430 break new_fields;
431 }
432 };
433
434 new_fields.extend(fields.iter().skip(new_fields.len()).cloned().map(|field| {
435 let dtype = cast_dtype(&field.dtype).unwrap_or(field.dtype);
436 Field {
437 name: field.name,
438 dtype,
439 }
440 }));
441
442 Some(D::Struct(new_fields))
443 },
444 _ => None,
445 }
446 }
447
448 let mut casted = cast_dtype(dtype);
449 if dtype.is_list() && dtype.inner_dtype().is_some_and(|dt| dt.is_null()) {
450 if let Some(from_inner_dtype) = slf.dtype().inner_dtype() {
451 casted = Some(DataType::List(Box::new(from_inner_dtype.clone())));
452 }
453 }
454 let dtype = match casted {
455 None => dtype,
456 Some(ref dtype) => dtype,
457 };
458
459 let len = slf.len();
461 if slf.null_count() == len {
462 return Ok(Series::full_null(slf.name().clone(), len, dtype));
463 }
464
465 let new_options = match options {
466 CastOptions::Strict => 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 DataType::Float32 => Ok(self.f32().unwrap().is_nan()),
655 DataType::Float64 => Ok(self.f64().unwrap().is_nan()),
656 DataType::Null => Ok(BooleanChunked::full_null(self.name().clone(), self.len())),
657 dt if dt.is_primitive_numeric() => {
658 let arr = BooleanArray::full(self.len(), false, ArrowDataType::Boolean)
659 .with_validity(self.rechunk_validity());
660 Ok(BooleanChunked::with_chunk(self.name().clone(), arr))
661 },
662 _ => polars_bail!(opq = is_nan, self.dtype()),
663 }
664 }
665
666 pub fn is_not_nan(&self) -> PolarsResult<BooleanChunked> {
668 match self.dtype() {
669 DataType::Float32 => Ok(self.f32().unwrap().is_not_nan()),
670 DataType::Float64 => Ok(self.f64().unwrap().is_not_nan()),
671 dt if dt.is_primitive_numeric() => {
672 let arr = BooleanArray::full(self.len(), true, ArrowDataType::Boolean)
673 .with_validity(self.rechunk_validity());
674 Ok(BooleanChunked::with_chunk(self.name().clone(), arr))
675 },
676 _ => polars_bail!(opq = is_not_nan, self.dtype()),
677 }
678 }
679
680 pub fn is_finite(&self) -> PolarsResult<BooleanChunked> {
682 match self.dtype() {
683 DataType::Float32 => Ok(self.f32().unwrap().is_finite()),
684 DataType::Float64 => Ok(self.f64().unwrap().is_finite()),
685 DataType::Null => Ok(BooleanChunked::full_null(self.name().clone(), self.len())),
686 dt if dt.is_primitive_numeric() => {
687 let arr = BooleanArray::full(self.len(), true, ArrowDataType::Boolean)
688 .with_validity(self.rechunk_validity());
689 Ok(BooleanChunked::with_chunk(self.name().clone(), arr))
690 },
691 _ => polars_bail!(opq = is_finite, self.dtype()),
692 }
693 }
694
695 pub fn is_infinite(&self) -> PolarsResult<BooleanChunked> {
697 match self.dtype() {
698 DataType::Float32 => Ok(self.f32().unwrap().is_infinite()),
699 DataType::Float64 => Ok(self.f64().unwrap().is_infinite()),
700 DataType::Null => Ok(BooleanChunked::full_null(self.name().clone(), self.len())),
701 dt if dt.is_primitive_numeric() => {
702 let arr = BooleanArray::full(self.len(), false, ArrowDataType::Boolean)
703 .with_validity(self.rechunk_validity());
704 Ok(BooleanChunked::with_chunk(self.name().clone(), arr))
705 },
706 _ => polars_bail!(opq = is_infinite, self.dtype()),
707 }
708 }
709
710 #[cfg(feature = "zip_with")]
714 pub fn zip_with(&self, mask: &BooleanChunked, other: &Series) -> PolarsResult<Series> {
715 let (lhs, rhs) = coerce_lhs_rhs(self, other)?;
716 lhs.zip_with_same_type(mask, rhs.as_ref())
717 }
718
719 pub fn to_physical_repr(&self) -> Cow<'_, Series> {
733 use DataType::*;
734 match self.dtype() {
735 #[cfg(feature = "dtype-date")]
738 Date => Cow::Owned(self.date().unwrap().phys.clone().into_series()),
739 #[cfg(feature = "dtype-datetime")]
740 Datetime(_, _) => Cow::Owned(self.datetime().unwrap().phys.clone().into_series()),
741 #[cfg(feature = "dtype-duration")]
742 Duration(_) => Cow::Owned(self.duration().unwrap().phys.clone().into_series()),
743 #[cfg(feature = "dtype-time")]
744 Time => Cow::Owned(self.time().unwrap().phys.clone().into_series()),
745 #[cfg(feature = "dtype-categorical")]
746 dt @ (Categorical(_, _) | Enum(_, _)) => {
747 with_match_categorical_physical_type!(dt.cat_physical().unwrap(), |$C| {
748 let ca = self.cat::<$C>().unwrap();
749 Cow::Owned(ca.physical().clone().into_series())
750 })
751 },
752 #[cfg(feature = "dtype-decimal")]
753 Decimal(_, _) => Cow::Owned(self.decimal().unwrap().phys.clone().into_series()),
754 List(_) => match self.list().unwrap().to_physical_repr() {
755 Cow::Borrowed(_) => Cow::Borrowed(self),
756 Cow::Owned(ca) => Cow::Owned(ca.into_series()),
757 },
758 #[cfg(feature = "dtype-array")]
759 Array(_, _) => match self.array().unwrap().to_physical_repr() {
760 Cow::Borrowed(_) => Cow::Borrowed(self),
761 Cow::Owned(ca) => Cow::Owned(ca.into_series()),
762 },
763 #[cfg(feature = "dtype-struct")]
764 Struct(_) => match self.struct_().unwrap().to_physical_repr() {
765 Cow::Borrowed(_) => Cow::Borrowed(self),
766 Cow::Owned(ca) => Cow::Owned(ca.into_series()),
767 },
768 #[cfg(feature = "dtype-extension")]
769 Extension(_, _) => self.ext().unwrap().storage().to_physical_repr(),
770 _ => Cow::Borrowed(self),
771 }
772 }
773
774 pub fn to_storage(&self) -> &Series {
777 #[cfg(feature = "dtype-extension")]
778 {
779 if let DataType::Extension(_, _) = self.dtype() {
780 return self.ext().unwrap().storage();
781 }
782 }
783 self
784 }
785
786 pub fn gather_every(&self, n: usize, offset: usize) -> PolarsResult<Series> {
788 polars_ensure!(n > 0, ComputeError: "cannot perform gather every for `n=0`");
789 let idx = ((offset as IdxSize)..self.len() as IdxSize)
790 .step_by(n)
791 .collect_ca(PlSmallStr::EMPTY);
792 Ok(unsafe { self.take_unchecked(&idx) })
794 }
795
796 #[cfg(feature = "dot_product")]
797 pub fn dot(&self, other: &Series) -> PolarsResult<f64> {
798 std::ops::Mul::mul(self, other)?.sum::<f64>()
799 }
800
801 pub fn sum_reduce(&self) -> PolarsResult<Scalar> {
807 use DataType::*;
808 match self.dtype() {
809 Int8 | UInt8 | Int16 | UInt16 => self.cast(&Int64).unwrap().sum_reduce(),
810 _ => self.0.sum_reduce(),
811 }
812 }
813
814 pub fn mean_reduce(&self) -> PolarsResult<Scalar> {
817 self.0.mean_reduce()
818 }
819
820 pub fn product(&self) -> PolarsResult<Scalar> {
825 #[cfg(feature = "product")]
826 {
827 use DataType::*;
828 match self.dtype() {
829 Boolean => self.cast(&DataType::Int64).unwrap().product(),
830 Int8 | UInt8 | Int16 | UInt16 | Int32 | UInt32 => {
831 let s = self.cast(&Int64).unwrap();
832 s.product()
833 },
834 Int64 => Ok(self.i64().unwrap().prod_reduce()),
835 UInt64 => Ok(self.u64().unwrap().prod_reduce()),
836 #[cfg(feature = "dtype-i128")]
837 Int128 => Ok(self.i128().unwrap().prod_reduce()),
838 #[cfg(feature = "dtype-u128")]
839 UInt128 => Ok(self.u128().unwrap().prod_reduce()),
840 Float32 => Ok(self.f32().unwrap().prod_reduce()),
841 Float64 => Ok(self.f64().unwrap().prod_reduce()),
842 dt => {
843 polars_bail!(InvalidOperation: "`product` operation not supported for dtype `{dt}`")
844 },
845 }
846 }
847 #[cfg(not(feature = "product"))]
848 {
849 panic!("activate 'product' feature")
850 }
851 }
852
853 pub fn strict_cast(&self, dtype: &DataType) -> PolarsResult<Series> {
855 self.cast_with_options(dtype, CastOptions::Strict)
856 }
857
858 #[cfg(feature = "dtype-decimal")]
859 pub(crate) fn into_decimal(self, precision: usize, scale: usize) -> PolarsResult<Series> {
860 match self.dtype() {
861 DataType::Int128 => Ok(self
862 .i128()
863 .unwrap()
864 .clone()
865 .into_decimal(precision, scale)?
866 .into_series()),
867 DataType::Decimal(cur_prec, cur_scale)
868 if scale == *cur_scale && precision >= *cur_prec =>
869 {
870 Ok(self)
871 },
872 dt => panic!("into_decimal({precision:?}, {scale}) not implemented for {dt:?}"),
873 }
874 }
875
876 #[cfg(feature = "dtype-time")]
877 pub(crate) fn into_time(self) -> Series {
878 match self.dtype() {
879 DataType::Int64 => self.i64().unwrap().clone().into_time().into_series(),
880 DataType::Time => self
881 .time()
882 .unwrap()
883 .physical()
884 .clone()
885 .into_time()
886 .into_series(),
887 dt => panic!("date not implemented for {dt:?}"),
888 }
889 }
890
891 pub(crate) fn into_date(self) -> Series {
892 #[cfg(not(feature = "dtype-date"))]
893 {
894 panic!("activate feature dtype-date")
895 }
896 #[cfg(feature = "dtype-date")]
897 match self.dtype() {
898 DataType::Int32 => self.i32().unwrap().clone().into_date().into_series(),
899 DataType::Date => self
900 .date()
901 .unwrap()
902 .physical()
903 .clone()
904 .into_date()
905 .into_series(),
906 dt => panic!("date not implemented for {dt:?}"),
907 }
908 }
909
910 #[allow(unused_variables)]
911 pub(crate) fn into_datetime(self, timeunit: TimeUnit, tz: Option<TimeZone>) -> Series {
912 #[cfg(not(feature = "dtype-datetime"))]
913 {
914 panic!("activate feature dtype-datetime")
915 }
916
917 #[cfg(feature = "dtype-datetime")]
918 match self.dtype() {
919 DataType::Int64 => self
920 .i64()
921 .unwrap()
922 .clone()
923 .into_datetime(timeunit, tz)
924 .into_series(),
925 DataType::Datetime(_, _) => self
926 .datetime()
927 .unwrap()
928 .physical()
929 .clone()
930 .into_datetime(timeunit, tz)
931 .into_series(),
932 dt => panic!("into_datetime not implemented for {dt:?}"),
933 }
934 }
935
936 #[allow(unused_variables)]
937 pub(crate) fn into_duration(self, timeunit: TimeUnit) -> Series {
938 #[cfg(not(feature = "dtype-duration"))]
939 {
940 panic!("activate feature dtype-duration")
941 }
942 #[cfg(feature = "dtype-duration")]
943 match self.dtype() {
944 DataType::Int64 => self
945 .i64()
946 .unwrap()
947 .clone()
948 .into_duration(timeunit)
949 .into_series(),
950 DataType::Duration(_) => self
951 .duration()
952 .unwrap()
953 .physical()
954 .clone()
955 .into_duration(timeunit)
956 .into_series(),
957 dt => panic!("into_duration not implemented for {dt:?}"),
958 }
959 }
960
961 pub fn str_value(&self, index: usize) -> PolarsResult<Cow<'_, str>> {
963 Ok(self.0.get(index)?.str_value())
964 }
965 pub fn head(&self, length: Option<usize>) -> Series {
967 let len = length.unwrap_or(HEAD_DEFAULT_LENGTH);
968 self.slice(0, std::cmp::min(len, self.len()))
969 }
970
971 pub fn tail(&self, length: Option<usize>) -> Series {
973 let len = length.unwrap_or(TAIL_DEFAULT_LENGTH);
974 let len = std::cmp::min(len, self.len());
975 self.slice(-(len as i64), len)
976 }
977
978 pub fn unique_stable(&self) -> PolarsResult<Series> {
981 let idx = self.arg_unique()?;
982 unsafe { Ok(self.take_unchecked(&idx)) }
984 }
985
986 pub fn try_idx(&self) -> Option<&IdxCa> {
987 #[cfg(feature = "bigidx")]
988 {
989 self.try_u64()
990 }
991 #[cfg(not(feature = "bigidx"))]
992 {
993 self.try_u32()
994 }
995 }
996
997 pub fn idx(&self) -> PolarsResult<&IdxCa> {
998 #[cfg(feature = "bigidx")]
999 {
1000 self.u64()
1001 }
1002 #[cfg(not(feature = "bigidx"))]
1003 {
1004 self.u32()
1005 }
1006 }
1007
1008 pub fn estimated_size(&self) -> usize {
1021 let mut size = 0;
1022 match self.dtype() {
1023 #[cfg(feature = "object")]
1025 DataType::Object(_) => {
1026 let ArrowDataType::FixedSizeBinary(size) = self.chunks()[0].dtype() else {
1027 unreachable!()
1028 };
1029 return self.len() * *size;
1031 },
1032 _ => {},
1033 }
1034
1035 size += self
1036 .chunks()
1037 .iter()
1038 .map(|arr| estimated_bytes_size(&**arr))
1039 .sum::<usize>();
1040
1041 size
1042 }
1043
1044 pub fn as_list(&self) -> ListChunked {
1046 let s = self.rechunk();
1047 let values = s.chunks()[0].clone();
1049 let offsets = (0i64..(s.len() as i64 + 1)).collect::<Vec<_>>();
1050 let offsets = unsafe { Offsets::new_unchecked(offsets) };
1051
1052 let dtype = LargeListArray::default_datatype(
1053 s.dtype().to_physical().to_arrow(CompatLevel::newest()),
1054 );
1055 let new_arr = LargeListArray::new(dtype, offsets.into(), values, None);
1056 let mut out = ListChunked::with_chunk(s.name().clone(), new_arr);
1057 out.set_inner_dtype(s.dtype().clone());
1058 out
1059 }
1060
1061 pub fn row_encode_unordered(&self) -> PolarsResult<BinaryOffsetChunked> {
1062 row_encode::_get_rows_encoded_ca_unordered(
1063 self.name().clone(),
1064 &[self.clone().into_column()],
1065 )
1066 }
1067
1068 pub fn row_encode_ordered(
1069 &self,
1070 descending: bool,
1071 nulls_last: bool,
1072 ) -> PolarsResult<BinaryOffsetChunked> {
1073 row_encode::_get_rows_encoded_ca(
1074 self.name().clone(),
1075 &[self.clone().into_column()],
1076 &[descending],
1077 &[nulls_last],
1078 )
1079 }
1080}
1081
1082impl Deref for Series {
1083 type Target = dyn SeriesTrait;
1084
1085 fn deref(&self) -> &Self::Target {
1086 self.0.as_ref()
1087 }
1088}
1089
1090impl<'a> AsRef<dyn SeriesTrait + 'a> for Series {
1091 fn as_ref(&self) -> &(dyn SeriesTrait + 'a) {
1092 self.0.as_ref()
1093 }
1094}
1095
1096impl Default for Series {
1097 fn default() -> Self {
1098 Int64Chunked::default().into_series()
1099 }
1100}
1101
1102impl<T: PolarsPhysicalType> AsRef<ChunkedArray<T>> for dyn SeriesTrait + '_ {
1103 fn as_ref(&self) -> &ChunkedArray<T> {
1104 let Some(ca) = self.as_any().downcast_ref::<ChunkedArray<T>>() else {
1107 panic!(
1108 "implementation error, cannot get ref {:?} from {:?}",
1109 T::get_static_dtype(),
1110 self.dtype()
1111 );
1112 };
1113
1114 ca
1115 }
1116}
1117
1118impl<T: PolarsPhysicalType> AsMut<ChunkedArray<T>> for dyn SeriesTrait + '_ {
1119 fn as_mut(&mut self) -> &mut ChunkedArray<T> {
1120 if !self.as_any_mut().is::<ChunkedArray<T>>() {
1121 panic!(
1122 "implementation error, cannot get ref {:?} from {:?}",
1123 T::get_static_dtype(),
1124 self.dtype()
1125 );
1126 }
1127
1128 self.as_any_mut().downcast_mut::<ChunkedArray<T>>().unwrap()
1131 }
1132}
1133
1134#[cfg(test)]
1135mod test {
1136 use crate::prelude::*;
1137 use crate::series::*;
1138
1139 #[test]
1140 fn cast() {
1141 let ar = UInt32Chunked::new("a".into(), &[1, 2]);
1142 let s = ar.into_series();
1143 let s2 = s.cast(&DataType::Int64).unwrap();
1144
1145 assert!(s2.i64().is_ok());
1146 let s2 = s.cast(&DataType::Float32).unwrap();
1147 assert!(s2.f32().is_ok());
1148 }
1149
1150 #[test]
1151 fn new_series() {
1152 let _ = Series::new("boolean series".into(), &vec![true, false, true]);
1153 let _ = Series::new("int series".into(), &[1, 2, 3]);
1154 let ca = Int32Chunked::new("a".into(), &[1, 2, 3]);
1155 let _ = ca.into_series();
1156 }
1157
1158 #[test]
1159 #[cfg(feature = "dtype-date")]
1160 fn roundtrip_list_logical_20311() {
1161 let list = ListChunked::from_chunk_iter(
1162 PlSmallStr::from_static("a"),
1163 [ListArray::new(
1164 ArrowDataType::LargeList(Box::new(ArrowField::new(
1165 LIST_VALUES_NAME,
1166 ArrowDataType::Int32,
1167 true,
1168 ))),
1169 unsafe { Offsets::new_unchecked(vec![0, 1]) }.into(),
1170 PrimitiveArray::new(ArrowDataType::Int32, vec![1i32].into(), None).to_boxed(),
1171 None,
1172 )],
1173 );
1174 let list = unsafe { list.from_physical_unchecked(DataType::Date) }.unwrap();
1175 assert_eq!(list.dtype(), &DataType::List(Box::new(DataType::Date)));
1176 }
1177
1178 #[test]
1179 #[cfg(feature = "dtype-struct")]
1180 fn new_series_from_empty_structs() {
1181 let dtype = DataType::Struct(vec![]);
1182 let empties = vec![AnyValue::StructOwned(Box::new((vec![], vec![]))); 3];
1183 let s = Series::from_any_values_and_dtype("".into(), &empties, &dtype, false).unwrap();
1184 assert_eq!(s.len(), 3);
1185 }
1186 #[test]
1187 fn new_series_from_arrow_primitive_array() {
1188 let array = UInt32Array::from_slice([1, 2, 3, 4, 5]);
1189 let array_ref: ArrayRef = Box::new(array);
1190
1191 let _ = Series::try_new("foo".into(), array_ref).unwrap();
1192 }
1193
1194 #[test]
1195 fn series_append() {
1196 let mut s1 = Series::new("a".into(), &[1, 2]);
1197 let s2 = Series::new("b".into(), &[3]);
1198 s1.append(&s2).unwrap();
1199 assert_eq!(s1.len(), 3);
1200
1201 let s2 = Series::new("b".into(), &[3.0]);
1203 assert!(s1.append(&s2).is_err())
1204 }
1205
1206 #[test]
1207 #[cfg(feature = "dtype-decimal")]
1208 fn series_append_decimal() {
1209 let s1 = Series::new("a".into(), &[1.1, 2.3])
1210 .cast(&DataType::Decimal(38, 2))
1211 .unwrap();
1212 let s2 = Series::new("b".into(), &[3])
1213 .cast(&DataType::Decimal(38, 0))
1214 .unwrap();
1215
1216 {
1217 let mut s1 = s1.clone();
1218 s1.append(&s2).unwrap();
1219 assert_eq!(s1.len(), 3);
1220 assert_eq!(s1.get(2).unwrap(), AnyValue::Decimal(300, 38, 2));
1221 }
1222
1223 {
1224 let mut s2 = s2;
1225 s2.extend(&s1).unwrap();
1226 assert_eq!(s2.get(2).unwrap(), AnyValue::Decimal(2, 38, 0));
1227 }
1228 }
1229
1230 #[test]
1231 fn series_slice_works() {
1232 let series = Series::new("a".into(), &[1i64, 2, 3, 4, 5]);
1233
1234 let slice_1 = series.slice(-3, 3);
1235 let slice_2 = series.slice(-5, 5);
1236 let slice_3 = series.slice(0, 5);
1237
1238 assert_eq!(slice_1.get(0).unwrap(), AnyValue::Int64(3));
1239 assert_eq!(slice_2.get(0).unwrap(), AnyValue::Int64(1));
1240 assert_eq!(slice_3.get(0).unwrap(), AnyValue::Int64(1));
1241 }
1242
1243 #[test]
1244 fn out_of_range_slice_does_not_panic() {
1245 let series = Series::new("a".into(), &[1i64, 2, 3, 4, 5]);
1246
1247 let _ = series.slice(-3, 4);
1248 let _ = series.slice(-6, 2);
1249 let _ = series.slice(4, 2);
1250 }
1251}