polars_core/frame/column/
mod.rs

1use std::borrow::Cow;
2
3use arrow::bitmap::BitmapBuilder;
4use arrow::trusted_len::TrustMyLength;
5use num_traits::{Num, NumCast};
6use polars_compute::rolling::QuantileMethod;
7use polars_error::PolarsResult;
8use polars_utils::aliases::PlSeedableRandomStateQuality;
9use polars_utils::index::check_bounds;
10use polars_utils::pl_str::PlSmallStr;
11pub use scalar::ScalarColumn;
12
13use self::compare_inner::{TotalEqInner, TotalOrdInner};
14use self::gather::check_bounds_ca;
15use self::partitioned::PartitionedColumn;
16use self::series::SeriesColumn;
17use crate::chunked_array::cast::CastOptions;
18use crate::chunked_array::flags::StatisticsFlags;
19use crate::datatypes::ReshapeDimension;
20use crate::prelude::*;
21use crate::series::{BitRepr, IsSorted, SeriesPhysIter};
22use crate::utils::{Container, slice_offsets};
23use crate::{HEAD_DEFAULT_LENGTH, TAIL_DEFAULT_LENGTH};
24
25mod arithmetic;
26mod compare;
27mod partitioned;
28mod scalar;
29mod series;
30
31/// A column within a [`DataFrame`].
32///
33/// This is lazily initialized to a [`Series`] with methods like
34/// [`as_materialized_series`][Column::as_materialized_series] and
35/// [`take_materialized_series`][Column::take_materialized_series].
36///
37/// Currently, there are two ways to represent a [`Column`].
38/// 1. A [`Series`] of values
39/// 2. A [`ScalarColumn`] that repeats a single [`Scalar`]
40#[derive(Debug, Clone)]
41#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
42#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
43pub enum Column {
44    Series(SeriesColumn),
45    Partitioned(PartitionedColumn),
46    Scalar(ScalarColumn),
47}
48
49/// Convert `Self` into a [`Column`]
50pub trait IntoColumn: Sized {
51    fn into_column(self) -> Column;
52}
53
54impl Column {
55    #[inline]
56    #[track_caller]
57    pub fn new<T, Phantom>(name: PlSmallStr, values: T) -> Self
58    where
59        Phantom: ?Sized,
60        Series: NamedFrom<T, Phantom>,
61    {
62        Self::Series(SeriesColumn::new(NamedFrom::new(name, values)))
63    }
64
65    #[inline]
66    pub fn new_empty(name: PlSmallStr, dtype: &DataType) -> Self {
67        Self::new_scalar(name, Scalar::new(dtype.clone(), AnyValue::Null), 0)
68    }
69
70    #[inline]
71    pub fn new_scalar(name: PlSmallStr, scalar: Scalar, length: usize) -> Self {
72        Self::Scalar(ScalarColumn::new(name, scalar, length))
73    }
74
75    #[inline]
76    pub fn new_partitioned(name: PlSmallStr, scalar: Scalar, length: usize) -> Self {
77        Self::Scalar(ScalarColumn::new(name, scalar, length))
78    }
79
80    pub fn new_row_index(name: PlSmallStr, offset: IdxSize, length: usize) -> PolarsResult<Column> {
81        let Ok(length) = IdxSize::try_from(length) else {
82            polars_bail!(
83                ComputeError:
84                "row index length {} overflows IdxSize::MAX ({})",
85                length,
86                IdxSize::MAX,
87            )
88        };
89
90        if offset.checked_add(length).is_none() {
91            polars_bail!(
92                ComputeError:
93                "row index with offset {} overflows on dataframe with height {}",
94                offset, length
95            )
96        }
97
98        let range = offset..offset + length;
99
100        let mut ca = IdxCa::from_vec(name, range.collect());
101        ca.set_sorted_flag(IsSorted::Ascending);
102        let col = ca.into_series().into();
103
104        Ok(col)
105    }
106
107    // # Materialize
108    /// Get a reference to a [`Series`] for this [`Column`]
109    ///
110    /// This may need to materialize the [`Series`] on the first invocation for a specific column.
111    #[inline]
112    pub fn as_materialized_series(&self) -> &Series {
113        match self {
114            Column::Series(s) => s,
115            Column::Partitioned(s) => s.as_materialized_series(),
116            Column::Scalar(s) => s.as_materialized_series(),
117        }
118    }
119
120    /// If the memory repr of this Column is a scalar, a unit-length Series will
121    /// be returned.
122    #[inline]
123    pub fn as_materialized_series_maintain_scalar(&self) -> Series {
124        match self {
125            Column::Scalar(s) => s.as_single_value_series(),
126            v => v.as_materialized_series().clone(),
127        }
128    }
129
130    /// Returns the backing `Series` for the values of this column.
131    ///
132    /// * For `Column::Series` columns, simply returns the inner `Series`.
133    /// * For `Column::Partitioned` columns, returns the series representing the values.
134    /// * For `Column::Scalar` columns, returns an empty or unit length series.
135    ///
136    /// # Note
137    /// This method is safe to use. However, care must be taken when operating on the returned
138    /// `Series` to ensure result correctness. E.g. It is suitable to perform elementwise operations
139    /// on it, however e.g. aggregations will return unspecified results.
140    pub fn _get_backing_series(&self) -> Series {
141        match self {
142            Column::Series(s) => (**s).clone(),
143            Column::Partitioned(s) => s.partitions().clone(),
144            Column::Scalar(s) => s.as_single_value_series(),
145        }
146    }
147
148    /// Constructs a new `Column` of the same variant as `self` from a backing `Series` representing
149    /// the values.
150    ///
151    /// # Panics
152    /// Panics if:
153    /// * `self` is `Column::Series` and the length of `new_s` does not match that of `self`.
154    /// * `self` is `Column::Partitioned` and the length of `new_s` does not match that of the existing partitions.
155    /// * `self` is `Column::Scalar` and if either:
156    ///   * `self` is not empty and `new_s` is not of unit length.
157    ///   * `self` is empty and `new_s` is not empty.
158    pub fn _to_new_from_backing(&self, new_s: Series) -> Self {
159        match self {
160            Column::Series(s) => {
161                assert_eq!(new_s.len(), s.len());
162                Column::Series(SeriesColumn::new(new_s))
163            },
164            Column::Partitioned(s) => {
165                assert_eq!(new_s.len(), s.partitions().len());
166                unsafe {
167                    Column::Partitioned(PartitionedColumn::new_unchecked(
168                        new_s.name().clone(),
169                        new_s,
170                        s.partition_ends_ref().clone(),
171                    ))
172                }
173            },
174            Column::Scalar(s) => {
175                assert_eq!(new_s.len(), s.as_single_value_series().len());
176                Column::Scalar(ScalarColumn::from_single_value_series(new_s, self.len()))
177            },
178        }
179    }
180
181    /// Turn [`Column`] into a [`Column::Series`].
182    ///
183    /// This may need to materialize the [`Series`] on the first invocation for a specific column.
184    #[inline]
185    pub fn into_materialized_series(&mut self) -> &mut Series {
186        match self {
187            Column::Series(s) => s,
188            Column::Partitioned(s) => {
189                let series = std::mem::replace(
190                    s,
191                    PartitionedColumn::new_empty(PlSmallStr::EMPTY, DataType::Null),
192                )
193                .take_materialized_series();
194                *self = Column::Series(series.into());
195                let Column::Series(s) = self else {
196                    unreachable!();
197                };
198                s
199            },
200            Column::Scalar(s) => {
201                let series = std::mem::replace(
202                    s,
203                    ScalarColumn::new_empty(PlSmallStr::EMPTY, DataType::Null),
204                )
205                .take_materialized_series();
206                *self = Column::Series(series.into());
207                let Column::Series(s) = self else {
208                    unreachable!();
209                };
210                s
211            },
212        }
213    }
214    /// Take [`Series`] from a [`Column`]
215    ///
216    /// This may need to materialize the [`Series`] on the first invocation for a specific column.
217    #[inline]
218    pub fn take_materialized_series(self) -> Series {
219        match self {
220            Column::Series(s) => s.take(),
221            Column::Partitioned(s) => s.take_materialized_series(),
222            Column::Scalar(s) => s.take_materialized_series(),
223        }
224    }
225
226    #[inline]
227    pub fn dtype(&self) -> &DataType {
228        match self {
229            Column::Series(s) => s.dtype(),
230            Column::Partitioned(s) => s.dtype(),
231            Column::Scalar(s) => s.dtype(),
232        }
233    }
234
235    #[inline]
236    pub fn field(&self) -> Cow<'_, Field> {
237        match self {
238            Column::Series(s) => s.field(),
239            Column::Partitioned(s) => s.field(),
240            Column::Scalar(s) => match s.lazy_as_materialized_series() {
241                None => Cow::Owned(Field::new(s.name().clone(), s.dtype().clone())),
242                Some(s) => s.field(),
243            },
244        }
245    }
246
247    #[inline]
248    pub fn name(&self) -> &PlSmallStr {
249        match self {
250            Column::Series(s) => s.name(),
251            Column::Partitioned(s) => s.name(),
252            Column::Scalar(s) => s.name(),
253        }
254    }
255
256    #[inline]
257    pub fn len(&self) -> usize {
258        match self {
259            Column::Series(s) => s.len(),
260            Column::Partitioned(s) => s.len(),
261            Column::Scalar(s) => s.len(),
262        }
263    }
264
265    #[inline]
266    pub fn with_name(mut self, name: PlSmallStr) -> Column {
267        self.rename(name);
268        self
269    }
270
271    #[inline]
272    pub fn rename(&mut self, name: PlSmallStr) {
273        match self {
274            Column::Series(s) => _ = s.rename(name),
275            Column::Partitioned(s) => _ = s.rename(name),
276            Column::Scalar(s) => _ = s.rename(name),
277        }
278    }
279
280    // # Downcasting
281    #[inline]
282    pub fn as_series(&self) -> Option<&Series> {
283        match self {
284            Column::Series(s) => Some(s),
285            _ => None,
286        }
287    }
288    #[inline]
289    pub fn as_partitioned_column(&self) -> Option<&PartitionedColumn> {
290        match self {
291            Column::Partitioned(s) => Some(s),
292            _ => None,
293        }
294    }
295    #[inline]
296    pub fn as_scalar_column(&self) -> Option<&ScalarColumn> {
297        match self {
298            Column::Scalar(s) => Some(s),
299            _ => None,
300        }
301    }
302    #[inline]
303    pub fn as_scalar_column_mut(&mut self) -> Option<&mut ScalarColumn> {
304        match self {
305            Column::Scalar(s) => Some(s),
306            _ => None,
307        }
308    }
309
310    // # Try to Chunked Arrays
311    pub fn try_bool(&self) -> Option<&BooleanChunked> {
312        self.as_materialized_series().try_bool()
313    }
314    pub fn try_i8(&self) -> Option<&Int8Chunked> {
315        self.as_materialized_series().try_i8()
316    }
317    pub fn try_i16(&self) -> Option<&Int16Chunked> {
318        self.as_materialized_series().try_i16()
319    }
320    pub fn try_i32(&self) -> Option<&Int32Chunked> {
321        self.as_materialized_series().try_i32()
322    }
323    pub fn try_i64(&self) -> Option<&Int64Chunked> {
324        self.as_materialized_series().try_i64()
325    }
326    pub fn try_u8(&self) -> Option<&UInt8Chunked> {
327        self.as_materialized_series().try_u8()
328    }
329    pub fn try_u16(&self) -> Option<&UInt16Chunked> {
330        self.as_materialized_series().try_u16()
331    }
332    pub fn try_u32(&self) -> Option<&UInt32Chunked> {
333        self.as_materialized_series().try_u32()
334    }
335    pub fn try_u64(&self) -> Option<&UInt64Chunked> {
336        self.as_materialized_series().try_u64()
337    }
338    #[cfg(feature = "dtype-u128")]
339    pub fn try_u128(&self) -> Option<&UInt128Chunked> {
340        self.as_materialized_series().try_u128()
341    }
342    pub fn try_f32(&self) -> Option<&Float32Chunked> {
343        self.as_materialized_series().try_f32()
344    }
345    pub fn try_f64(&self) -> Option<&Float64Chunked> {
346        self.as_materialized_series().try_f64()
347    }
348    pub fn try_str(&self) -> Option<&StringChunked> {
349        self.as_materialized_series().try_str()
350    }
351    pub fn try_list(&self) -> Option<&ListChunked> {
352        self.as_materialized_series().try_list()
353    }
354    pub fn try_binary(&self) -> Option<&BinaryChunked> {
355        self.as_materialized_series().try_binary()
356    }
357    pub fn try_idx(&self) -> Option<&IdxCa> {
358        self.as_materialized_series().try_idx()
359    }
360    pub fn try_binary_offset(&self) -> Option<&BinaryOffsetChunked> {
361        self.as_materialized_series().try_binary_offset()
362    }
363    #[cfg(feature = "dtype-datetime")]
364    pub fn try_datetime(&self) -> Option<&DatetimeChunked> {
365        self.as_materialized_series().try_datetime()
366    }
367    #[cfg(feature = "dtype-struct")]
368    pub fn try_struct(&self) -> Option<&StructChunked> {
369        self.as_materialized_series().try_struct()
370    }
371    #[cfg(feature = "dtype-decimal")]
372    pub fn try_decimal(&self) -> Option<&DecimalChunked> {
373        self.as_materialized_series().try_decimal()
374    }
375    #[cfg(feature = "dtype-array")]
376    pub fn try_array(&self) -> Option<&ArrayChunked> {
377        self.as_materialized_series().try_array()
378    }
379    #[cfg(feature = "dtype-categorical")]
380    pub fn try_cat<T: PolarsCategoricalType>(&self) -> Option<&CategoricalChunked<T>> {
381        self.as_materialized_series().try_cat::<T>()
382    }
383    #[cfg(feature = "dtype-categorical")]
384    pub fn try_cat8(&self) -> Option<&Categorical8Chunked> {
385        self.as_materialized_series().try_cat8()
386    }
387    #[cfg(feature = "dtype-categorical")]
388    pub fn try_cat16(&self) -> Option<&Categorical16Chunked> {
389        self.as_materialized_series().try_cat16()
390    }
391    #[cfg(feature = "dtype-categorical")]
392    pub fn try_cat32(&self) -> Option<&Categorical32Chunked> {
393        self.as_materialized_series().try_cat32()
394    }
395    #[cfg(feature = "dtype-date")]
396    pub fn try_date(&self) -> Option<&DateChunked> {
397        self.as_materialized_series().try_date()
398    }
399    #[cfg(feature = "dtype-duration")]
400    pub fn try_duration(&self) -> Option<&DurationChunked> {
401        self.as_materialized_series().try_duration()
402    }
403
404    // # To Chunked Arrays
405    pub fn bool(&self) -> PolarsResult<&BooleanChunked> {
406        self.as_materialized_series().bool()
407    }
408    pub fn i8(&self) -> PolarsResult<&Int8Chunked> {
409        self.as_materialized_series().i8()
410    }
411    pub fn i16(&self) -> PolarsResult<&Int16Chunked> {
412        self.as_materialized_series().i16()
413    }
414    pub fn i32(&self) -> PolarsResult<&Int32Chunked> {
415        self.as_materialized_series().i32()
416    }
417    pub fn i64(&self) -> PolarsResult<&Int64Chunked> {
418        self.as_materialized_series().i64()
419    }
420    #[cfg(feature = "dtype-i128")]
421    pub fn i128(&self) -> PolarsResult<&Int128Chunked> {
422        self.as_materialized_series().i128()
423    }
424    pub fn u8(&self) -> PolarsResult<&UInt8Chunked> {
425        self.as_materialized_series().u8()
426    }
427    pub fn u16(&self) -> PolarsResult<&UInt16Chunked> {
428        self.as_materialized_series().u16()
429    }
430    pub fn u32(&self) -> PolarsResult<&UInt32Chunked> {
431        self.as_materialized_series().u32()
432    }
433    pub fn u64(&self) -> PolarsResult<&UInt64Chunked> {
434        self.as_materialized_series().u64()
435    }
436    #[cfg(feature = "dtype-u128")]
437    pub fn u128(&self) -> PolarsResult<&UInt128Chunked> {
438        self.as_materialized_series().u128()
439    }
440    pub fn f32(&self) -> PolarsResult<&Float32Chunked> {
441        self.as_materialized_series().f32()
442    }
443    pub fn f64(&self) -> PolarsResult<&Float64Chunked> {
444        self.as_materialized_series().f64()
445    }
446    pub fn str(&self) -> PolarsResult<&StringChunked> {
447        self.as_materialized_series().str()
448    }
449    pub fn list(&self) -> PolarsResult<&ListChunked> {
450        self.as_materialized_series().list()
451    }
452    pub fn binary(&self) -> PolarsResult<&BinaryChunked> {
453        self.as_materialized_series().binary()
454    }
455    pub fn idx(&self) -> PolarsResult<&IdxCa> {
456        self.as_materialized_series().idx()
457    }
458    pub fn binary_offset(&self) -> PolarsResult<&BinaryOffsetChunked> {
459        self.as_materialized_series().binary_offset()
460    }
461    #[cfg(feature = "dtype-datetime")]
462    pub fn datetime(&self) -> PolarsResult<&DatetimeChunked> {
463        self.as_materialized_series().datetime()
464    }
465    #[cfg(feature = "dtype-struct")]
466    pub fn struct_(&self) -> PolarsResult<&StructChunked> {
467        self.as_materialized_series().struct_()
468    }
469    #[cfg(feature = "dtype-decimal")]
470    pub fn decimal(&self) -> PolarsResult<&DecimalChunked> {
471        self.as_materialized_series().decimal()
472    }
473    #[cfg(feature = "dtype-array")]
474    pub fn array(&self) -> PolarsResult<&ArrayChunked> {
475        self.as_materialized_series().array()
476    }
477    #[cfg(feature = "dtype-categorical")]
478    pub fn cat<T: PolarsCategoricalType>(&self) -> PolarsResult<&CategoricalChunked<T>> {
479        self.as_materialized_series().cat::<T>()
480    }
481    #[cfg(feature = "dtype-categorical")]
482    pub fn cat8(&self) -> PolarsResult<&Categorical8Chunked> {
483        self.as_materialized_series().cat8()
484    }
485    #[cfg(feature = "dtype-categorical")]
486    pub fn cat16(&self) -> PolarsResult<&Categorical16Chunked> {
487        self.as_materialized_series().cat16()
488    }
489    #[cfg(feature = "dtype-categorical")]
490    pub fn cat32(&self) -> PolarsResult<&Categorical32Chunked> {
491        self.as_materialized_series().cat32()
492    }
493    #[cfg(feature = "dtype-date")]
494    pub fn date(&self) -> PolarsResult<&DateChunked> {
495        self.as_materialized_series().date()
496    }
497    #[cfg(feature = "dtype-duration")]
498    pub fn duration(&self) -> PolarsResult<&DurationChunked> {
499        self.as_materialized_series().duration()
500    }
501
502    // # Casting
503    pub fn cast_with_options(&self, dtype: &DataType, options: CastOptions) -> PolarsResult<Self> {
504        match self {
505            Column::Series(s) => s.cast_with_options(dtype, options).map(Column::from),
506            Column::Partitioned(s) => s.cast_with_options(dtype, options).map(Column::from),
507            Column::Scalar(s) => s.cast_with_options(dtype, options).map(Column::from),
508        }
509    }
510    pub fn strict_cast(&self, dtype: &DataType) -> PolarsResult<Self> {
511        match self {
512            Column::Series(s) => s.strict_cast(dtype).map(Column::from),
513            Column::Partitioned(s) => s.strict_cast(dtype).map(Column::from),
514            Column::Scalar(s) => s.strict_cast(dtype).map(Column::from),
515        }
516    }
517    pub fn cast(&self, dtype: &DataType) -> PolarsResult<Column> {
518        match self {
519            Column::Series(s) => s.cast(dtype).map(Column::from),
520            Column::Partitioned(s) => s.cast(dtype).map(Column::from),
521            Column::Scalar(s) => s.cast(dtype).map(Column::from),
522        }
523    }
524    /// # Safety
525    ///
526    /// This can lead to invalid memory access in downstream code.
527    pub unsafe fn cast_unchecked(&self, dtype: &DataType) -> PolarsResult<Column> {
528        match self {
529            Column::Series(s) => unsafe { s.cast_unchecked(dtype) }.map(Column::from),
530            Column::Partitioned(s) => unsafe { s.cast_unchecked(dtype) }.map(Column::from),
531            Column::Scalar(s) => unsafe { s.cast_unchecked(dtype) }.map(Column::from),
532        }
533    }
534
535    pub fn clear(&self) -> Self {
536        match self {
537            Column::Series(s) => s.clear().into(),
538            Column::Partitioned(s) => s.clear().into(),
539            Column::Scalar(s) => s.resize(0).into(),
540        }
541    }
542
543    #[inline]
544    pub fn shrink_to_fit(&mut self) {
545        match self {
546            Column::Series(s) => s.shrink_to_fit(),
547            // @partition-opt
548            Column::Partitioned(_) => {},
549            Column::Scalar(_) => {},
550        }
551    }
552
553    #[inline]
554    pub fn new_from_index(&self, index: usize, length: usize) -> Self {
555        if index >= self.len() {
556            return Self::full_null(self.name().clone(), length, self.dtype());
557        }
558
559        match self {
560            Column::Series(s) => {
561                // SAFETY: Bounds check done before.
562                let av = unsafe { s.get_unchecked(index) };
563                let scalar = Scalar::new(self.dtype().clone(), av.into_static());
564                Self::new_scalar(self.name().clone(), scalar, length)
565            },
566            Column::Partitioned(s) => {
567                // SAFETY: Bounds check done before.
568                let av = unsafe { s.get_unchecked(index) };
569                let scalar = Scalar::new(self.dtype().clone(), av.into_static());
570                Self::new_scalar(self.name().clone(), scalar, length)
571            },
572            Column::Scalar(s) => s.resize(length).into(),
573        }
574    }
575
576    #[inline]
577    pub fn has_nulls(&self) -> bool {
578        match self {
579            Self::Series(s) => s.has_nulls(),
580            // @partition-opt
581            Self::Partitioned(s) => s.as_materialized_series().has_nulls(),
582            Self::Scalar(s) => s.has_nulls(),
583        }
584    }
585
586    #[inline]
587    pub fn is_null(&self) -> BooleanChunked {
588        match self {
589            Self::Series(s) => s.is_null(),
590            // @partition-opt
591            Self::Partitioned(s) => s.as_materialized_series().is_null(),
592            Self::Scalar(s) => {
593                BooleanChunked::full(s.name().clone(), s.scalar().is_null(), s.len())
594            },
595        }
596    }
597    #[inline]
598    pub fn is_not_null(&self) -> BooleanChunked {
599        match self {
600            Self::Series(s) => s.is_not_null(),
601            // @partition-opt
602            Self::Partitioned(s) => s.as_materialized_series().is_not_null(),
603            Self::Scalar(s) => {
604                BooleanChunked::full(s.name().clone(), !s.scalar().is_null(), s.len())
605            },
606        }
607    }
608
609    pub fn to_physical_repr(&self) -> Column {
610        // @scalar-opt
611        self.as_materialized_series()
612            .to_physical_repr()
613            .into_owned()
614            .into()
615    }
616    /// # Safety
617    ///
618    /// This can lead to invalid memory access in downstream code.
619    pub unsafe fn from_physical_unchecked(&self, dtype: &DataType) -> PolarsResult<Column> {
620        // @scalar-opt
621        self.as_materialized_series()
622            .from_physical_unchecked(dtype)
623            .map(Column::from)
624    }
625
626    pub fn head(&self, length: Option<usize>) -> Column {
627        let len = length.unwrap_or(HEAD_DEFAULT_LENGTH);
628        let len = usize::min(len, self.len());
629        self.slice(0, len)
630    }
631    pub fn tail(&self, length: Option<usize>) -> Column {
632        let len = length.unwrap_or(TAIL_DEFAULT_LENGTH);
633        let len = usize::min(len, self.len());
634        debug_assert!(len <= i64::MAX as usize);
635        self.slice(-(len as i64), len)
636    }
637    pub fn slice(&self, offset: i64, length: usize) -> Column {
638        match self {
639            Column::Series(s) => s.slice(offset, length).into(),
640            // @partition-opt
641            Column::Partitioned(s) => s.as_materialized_series().slice(offset, length).into(),
642            Column::Scalar(s) => {
643                let (_, length) = slice_offsets(offset, length, s.len());
644                s.resize(length).into()
645            },
646        }
647    }
648
649    pub fn split_at(&self, offset: i64) -> (Column, Column) {
650        // @scalar-opt
651        let (l, r) = self.as_materialized_series().split_at(offset);
652        (l.into(), r.into())
653    }
654
655    #[inline]
656    pub fn null_count(&self) -> usize {
657        match self {
658            Self::Series(s) => s.null_count(),
659            Self::Partitioned(s) => s.null_count(),
660            Self::Scalar(s) if s.scalar().is_null() => s.len(),
661            Self::Scalar(_) => 0,
662        }
663    }
664
665    pub fn take(&self, indices: &IdxCa) -> PolarsResult<Column> {
666        check_bounds_ca(indices, self.len() as IdxSize)?;
667        Ok(unsafe { self.take_unchecked(indices) })
668    }
669    pub fn take_slice(&self, indices: &[IdxSize]) -> PolarsResult<Column> {
670        check_bounds(indices, self.len() as IdxSize)?;
671        Ok(unsafe { self.take_slice_unchecked(indices) })
672    }
673    /// # Safety
674    ///
675    /// No bounds on the indexes are performed.
676    pub unsafe fn take_unchecked(&self, indices: &IdxCa) -> Column {
677        debug_assert!(check_bounds_ca(indices, self.len() as IdxSize).is_ok());
678
679        match self {
680            Self::Series(s) => unsafe { s.take_unchecked(indices) }.into(),
681            Self::Partitioned(s) => {
682                let s = s.as_materialized_series();
683                unsafe { s.take_unchecked(indices) }.into()
684            },
685            Self::Scalar(s) => {
686                let idxs_length = indices.len();
687                let idxs_null_count = indices.null_count();
688
689                let scalar = ScalarColumn::from_single_value_series(
690                    s.as_single_value_series().take_unchecked(&IdxCa::new(
691                        indices.name().clone(),
692                        &[0][..s.len().min(1)],
693                    )),
694                    idxs_length,
695                );
696
697                // We need to make sure that null values in `idx` become null values in the result
698                if idxs_null_count == 0 || scalar.has_nulls() {
699                    scalar.into_column()
700                } else if idxs_null_count == idxs_length {
701                    scalar.into_nulls().into_column()
702                } else {
703                    let validity = indices.rechunk_validity();
704                    let series = scalar.take_materialized_series();
705                    let name = series.name().clone();
706                    let dtype = series.dtype().clone();
707                    let mut chunks = series.into_chunks();
708                    assert_eq!(chunks.len(), 1);
709                    chunks[0] = chunks[0].with_validity(validity);
710                    unsafe { Series::from_chunks_and_dtype_unchecked(name, chunks, &dtype) }
711                        .into_column()
712                }
713            },
714        }
715    }
716    /// # Safety
717    ///
718    /// No bounds on the indexes are performed.
719    pub unsafe fn take_slice_unchecked(&self, indices: &[IdxSize]) -> Column {
720        debug_assert!(check_bounds(indices, self.len() as IdxSize).is_ok());
721
722        match self {
723            Self::Series(s) => unsafe { s.take_slice_unchecked(indices) }.into(),
724            Self::Partitioned(s) => {
725                let s = s.as_materialized_series();
726                unsafe { s.take_slice_unchecked(indices) }.into()
727            },
728            Self::Scalar(s) => ScalarColumn::from_single_value_series(
729                s.as_single_value_series()
730                    .take_slice_unchecked(&[0][..s.len().min(1)]),
731                indices.len(),
732            )
733            .into(),
734        }
735    }
736
737    /// General implementation for aggregation where a non-missing scalar would map to itself.
738    #[inline(always)]
739    #[cfg(any(feature = "algorithm_group_by", feature = "bitwise"))]
740    fn agg_with_unit_scalar(
741        &self,
742        groups: &GroupsType,
743        series_agg: impl Fn(&Series, &GroupsType) -> Series,
744    ) -> Column {
745        match self {
746            Column::Series(s) => series_agg(s, groups).into_column(),
747            // @partition-opt
748            Column::Partitioned(s) => series_agg(s.as_materialized_series(), groups).into_column(),
749            Column::Scalar(s) => {
750                if s.is_empty() {
751                    return series_agg(s.as_materialized_series(), groups).into_column();
752                }
753
754                // We utilize the aggregation on Series to see:
755                // 1. the output datatype of the aggregation
756                // 2. whether this aggregation is even defined
757                let series_aggregation = series_agg(
758                    &s.as_single_value_series(),
759                    &GroupsType::Slice {
760                        // @NOTE: this group is always valid since s is non-empty.
761                        groups: vec![[0, 1]],
762                        rolling: false,
763                    },
764                );
765
766                // If the aggregation is not defined, just return all nulls.
767                if series_aggregation.has_nulls() {
768                    return Self::new_scalar(
769                        series_aggregation.name().clone(),
770                        Scalar::new(series_aggregation.dtype().clone(), AnyValue::Null),
771                        groups.len(),
772                    );
773                }
774
775                let mut scalar_col = s.resize(groups.len());
776                // The aggregation might change the type (e.g. mean changes int -> float), so we do
777                // a cast here to the output type.
778                if series_aggregation.dtype() != s.dtype() {
779                    scalar_col = scalar_col.cast(series_aggregation.dtype()).unwrap();
780                }
781
782                let Some(first_empty_idx) = groups.iter().position(|g| g.is_empty()) else {
783                    // Fast path: no empty groups. keep the scalar intact.
784                    return scalar_col.into_column();
785                };
786
787                // All empty groups produce a *missing* or `null` value.
788                let mut validity = BitmapBuilder::with_capacity(groups.len());
789                validity.extend_constant(first_empty_idx, true);
790                // SAFETY: We trust the length of this iterator.
791                let iter = unsafe {
792                    TrustMyLength::new(
793                        groups.iter().skip(first_empty_idx).map(|g| !g.is_empty()),
794                        groups.len() - first_empty_idx,
795                    )
796                };
797                validity.extend_trusted_len_iter(iter);
798
799                let mut s = scalar_col.take_materialized_series().rechunk();
800                // SAFETY: We perform a compute_len afterwards.
801                let chunks = unsafe { s.chunks_mut() };
802                let arr = &mut chunks[0];
803                *arr = arr.with_validity(validity.into_opt_validity());
804                s.compute_len();
805
806                s.into_column()
807            },
808        }
809    }
810
811    /// # Safety
812    ///
813    /// Does no bounds checks, groups must be correct.
814    #[cfg(feature = "algorithm_group_by")]
815    pub unsafe fn agg_min(&self, groups: &GroupsType) -> Self {
816        self.agg_with_unit_scalar(groups, |s, g| unsafe { s.agg_min(g) })
817    }
818
819    /// # Safety
820    ///
821    /// Does no bounds checks, groups must be correct.
822    #[cfg(feature = "algorithm_group_by")]
823    pub unsafe fn agg_max(&self, groups: &GroupsType) -> Self {
824        self.agg_with_unit_scalar(groups, |s, g| unsafe { s.agg_max(g) })
825    }
826
827    /// # Safety
828    ///
829    /// Does no bounds checks, groups must be correct.
830    #[cfg(feature = "algorithm_group_by")]
831    pub unsafe fn agg_mean(&self, groups: &GroupsType) -> Self {
832        self.agg_with_unit_scalar(groups, |s, g| unsafe { s.agg_mean(g) })
833    }
834
835    /// # Safety
836    ///
837    /// Does no bounds checks, groups must be correct.
838    #[cfg(feature = "algorithm_group_by")]
839    pub unsafe fn agg_sum(&self, groups: &GroupsType) -> Self {
840        // @scalar-opt
841        unsafe { self.as_materialized_series().agg_sum(groups) }.into()
842    }
843
844    /// # Safety
845    ///
846    /// Does no bounds checks, groups must be correct.
847    #[cfg(feature = "algorithm_group_by")]
848    pub unsafe fn agg_first(&self, groups: &GroupsType) -> Self {
849        self.agg_with_unit_scalar(groups, |s, g| unsafe { s.agg_first(g) })
850    }
851
852    /// # Safety
853    ///
854    /// Does no bounds checks, groups must be correct.
855    #[cfg(feature = "algorithm_group_by")]
856    pub unsafe fn agg_last(&self, groups: &GroupsType) -> Self {
857        self.agg_with_unit_scalar(groups, |s, g| unsafe { s.agg_last(g) })
858    }
859
860    /// # Safety
861    ///
862    /// Does no bounds checks, groups must be correct.
863    #[cfg(feature = "algorithm_group_by")]
864    pub unsafe fn agg_n_unique(&self, groups: &GroupsType) -> Self {
865        // @scalar-opt
866        unsafe { self.as_materialized_series().agg_n_unique(groups) }.into()
867    }
868
869    /// # Safety
870    ///
871    /// Does no bounds checks, groups must be correct.
872    #[cfg(feature = "algorithm_group_by")]
873    pub unsafe fn agg_quantile(
874        &self,
875        groups: &GroupsType,
876        quantile: f64,
877        method: QuantileMethod,
878    ) -> Self {
879        // @scalar-opt
880
881        unsafe {
882            self.as_materialized_series()
883                .agg_quantile(groups, quantile, method)
884        }
885        .into()
886    }
887
888    /// # Safety
889    ///
890    /// Does no bounds checks, groups must be correct.
891    #[cfg(feature = "algorithm_group_by")]
892    pub unsafe fn agg_median(&self, groups: &GroupsType) -> Self {
893        self.agg_with_unit_scalar(groups, |s, g| unsafe { s.agg_median(g) })
894    }
895
896    /// # Safety
897    ///
898    /// Does no bounds checks, groups must be correct.
899    #[cfg(feature = "algorithm_group_by")]
900    pub unsafe fn agg_var(&self, groups: &GroupsType, ddof: u8) -> Self {
901        // @scalar-opt
902        unsafe { self.as_materialized_series().agg_var(groups, ddof) }.into()
903    }
904
905    /// # Safety
906    ///
907    /// Does no bounds checks, groups must be correct.
908    #[cfg(feature = "algorithm_group_by")]
909    pub unsafe fn agg_std(&self, groups: &GroupsType, ddof: u8) -> Self {
910        // @scalar-opt
911        unsafe { self.as_materialized_series().agg_std(groups, ddof) }.into()
912    }
913
914    /// # Safety
915    ///
916    /// Does no bounds checks, groups must be correct.
917    #[cfg(feature = "algorithm_group_by")]
918    pub unsafe fn agg_list(&self, groups: &GroupsType) -> Self {
919        // @scalar-opt
920        unsafe { self.as_materialized_series().agg_list(groups) }.into()
921    }
922
923    /// # Safety
924    ///
925    /// Does no bounds checks, groups must be correct.
926    #[cfg(feature = "algorithm_group_by")]
927    pub fn agg_valid_count(&self, groups: &GroupsType) -> Self {
928        // @partition-opt
929        // @scalar-opt
930        unsafe { self.as_materialized_series().agg_valid_count(groups) }.into()
931    }
932
933    /// # Safety
934    ///
935    /// Does no bounds checks, groups must be correct.
936    #[cfg(feature = "bitwise")]
937    pub fn agg_and(&self, groups: &GroupsType) -> Self {
938        self.agg_with_unit_scalar(groups, |s, g| unsafe { s.agg_and(g) })
939    }
940    /// # Safety
941    ///
942    /// Does no bounds checks, groups must be correct.
943    #[cfg(feature = "bitwise")]
944    pub fn agg_or(&self, groups: &GroupsType) -> Self {
945        self.agg_with_unit_scalar(groups, |s, g| unsafe { s.agg_or(g) })
946    }
947    /// # Safety
948    ///
949    /// Does no bounds checks, groups must be correct.
950    #[cfg(feature = "bitwise")]
951    pub fn agg_xor(&self, groups: &GroupsType) -> Self {
952        // @partition-opt
953        // @scalar-opt
954        unsafe { self.as_materialized_series().agg_xor(groups) }.into()
955    }
956
957    pub fn full_null(name: PlSmallStr, size: usize, dtype: &DataType) -> Self {
958        Self::new_scalar(name, Scalar::new(dtype.clone(), AnyValue::Null), size)
959    }
960
961    pub fn is_empty(&self) -> bool {
962        self.len() == 0
963    }
964
965    pub fn reverse(&self) -> Column {
966        match self {
967            Column::Series(s) => s.reverse().into(),
968            Column::Partitioned(s) => s.reverse().into(),
969            Column::Scalar(_) => self.clone(),
970        }
971    }
972
973    pub fn equals(&self, other: &Column) -> bool {
974        // @scalar-opt
975        self.as_materialized_series()
976            .equals(other.as_materialized_series())
977    }
978
979    pub fn equals_missing(&self, other: &Column) -> bool {
980        // @scalar-opt
981        self.as_materialized_series()
982            .equals_missing(other.as_materialized_series())
983    }
984
985    pub fn set_sorted_flag(&mut self, sorted: IsSorted) {
986        // @scalar-opt
987        match self {
988            Column::Series(s) => s.set_sorted_flag(sorted),
989            Column::Partitioned(s) => s.set_sorted_flag(sorted),
990            Column::Scalar(_) => {},
991        }
992    }
993
994    pub fn get_flags(&self) -> StatisticsFlags {
995        match self {
996            Column::Series(s) => s.get_flags(),
997            // @partition-opt
998            Column::Partitioned(_) => StatisticsFlags::empty(),
999            Column::Scalar(_) => {
1000                StatisticsFlags::IS_SORTED_ASC | StatisticsFlags::CAN_FAST_EXPLODE_LIST
1001            },
1002        }
1003    }
1004
1005    /// Returns whether the flags were set
1006    pub fn set_flags(&mut self, flags: StatisticsFlags) -> bool {
1007        match self {
1008            Column::Series(s) => {
1009                s.set_flags(flags);
1010                true
1011            },
1012            // @partition-opt
1013            Column::Partitioned(_) => false,
1014            Column::Scalar(_) => false,
1015        }
1016    }
1017
1018    pub fn vec_hash(
1019        &self,
1020        build_hasher: PlSeedableRandomStateQuality,
1021        buf: &mut Vec<u64>,
1022    ) -> PolarsResult<()> {
1023        // @scalar-opt?
1024        self.as_materialized_series().vec_hash(build_hasher, buf)
1025    }
1026
1027    pub fn vec_hash_combine(
1028        &self,
1029        build_hasher: PlSeedableRandomStateQuality,
1030        hashes: &mut [u64],
1031    ) -> PolarsResult<()> {
1032        // @scalar-opt?
1033        self.as_materialized_series()
1034            .vec_hash_combine(build_hasher, hashes)
1035    }
1036
1037    pub fn append(&mut self, other: &Column) -> PolarsResult<&mut Self> {
1038        // @scalar-opt
1039        self.into_materialized_series()
1040            .append(other.as_materialized_series())?;
1041        Ok(self)
1042    }
1043    pub fn append_owned(&mut self, other: Column) -> PolarsResult<&mut Self> {
1044        self.into_materialized_series()
1045            .append_owned(other.take_materialized_series())?;
1046        Ok(self)
1047    }
1048
1049    pub fn arg_sort(&self, options: SortOptions) -> IdxCa {
1050        if self.is_empty() {
1051            return IdxCa::from_vec(self.name().clone(), Vec::new());
1052        }
1053
1054        if self.null_count() == self.len() {
1055            // We might need to maintain order so just respect the descending parameter.
1056            let values = if options.descending {
1057                (0..self.len() as IdxSize).rev().collect()
1058            } else {
1059                (0..self.len() as IdxSize).collect()
1060            };
1061
1062            return IdxCa::from_vec(self.name().clone(), values);
1063        }
1064
1065        let is_sorted = Some(self.is_sorted_flag());
1066        let Some(is_sorted) = is_sorted.filter(|v| !matches!(v, IsSorted::Not)) else {
1067            return self.as_materialized_series().arg_sort(options);
1068        };
1069
1070        // Fast path: the data is sorted.
1071        let is_sorted_dsc = matches!(is_sorted, IsSorted::Descending);
1072        let invert = options.descending != is_sorted_dsc;
1073
1074        let mut values = Vec::with_capacity(self.len());
1075
1076        #[inline(never)]
1077        fn extend(
1078            start: IdxSize,
1079            end: IdxSize,
1080            slf: &Column,
1081            values: &mut Vec<IdxSize>,
1082            is_only_nulls: bool,
1083            invert: bool,
1084            maintain_order: bool,
1085        ) {
1086            debug_assert!(start <= end);
1087            debug_assert!(start as usize <= slf.len());
1088            debug_assert!(end as usize <= slf.len());
1089
1090            if !invert || is_only_nulls {
1091                values.extend(start..end);
1092                return;
1093            }
1094
1095            // If we don't have to maintain order but we have to invert. Just flip it around.
1096            if !maintain_order {
1097                values.extend((start..end).rev());
1098                return;
1099            }
1100
1101            // If we want to maintain order but we also needs to invert, we need to invert
1102            // per group of items.
1103            //
1104            // @NOTE: Since the column is sorted, arg_unique can also take a fast path and
1105            // just do a single traversal.
1106            let arg_unique = slf
1107                .slice(start as i64, (end - start) as usize)
1108                .arg_unique()
1109                .unwrap();
1110
1111            assert!(!arg_unique.has_nulls());
1112
1113            let num_unique = arg_unique.len();
1114
1115            // Fast path: all items are unique.
1116            if num_unique == (end - start) as usize {
1117                values.extend((start..end).rev());
1118                return;
1119            }
1120
1121            if num_unique == 1 {
1122                values.extend(start..end);
1123                return;
1124            }
1125
1126            let mut prev_idx = end - start;
1127            for chunk in arg_unique.downcast_iter() {
1128                for &idx in chunk.values().as_slice().iter().rev() {
1129                    values.extend(start + idx..start + prev_idx);
1130                    prev_idx = idx;
1131                }
1132            }
1133        }
1134        macro_rules! extend {
1135            ($start:expr, $end:expr) => {
1136                extend!($start, $end, is_only_nulls = false);
1137            };
1138            ($start:expr, $end:expr, is_only_nulls = $is_only_nulls:expr) => {
1139                extend(
1140                    $start,
1141                    $end,
1142                    self,
1143                    &mut values,
1144                    $is_only_nulls,
1145                    invert,
1146                    options.maintain_order,
1147                );
1148            };
1149        }
1150
1151        let length = self.len() as IdxSize;
1152        let null_count = self.null_count() as IdxSize;
1153
1154        if null_count == 0 {
1155            extend!(0, length);
1156        } else {
1157            let has_nulls_last = self.get(self.len() - 1).unwrap().is_null();
1158            match (options.nulls_last, has_nulls_last) {
1159                (true, true) => {
1160                    // Current: Nulls last, Wanted: Nulls last
1161                    extend!(0, length - null_count);
1162                    extend!(length - null_count, length, is_only_nulls = true);
1163                },
1164                (true, false) => {
1165                    // Current: Nulls first, Wanted: Nulls last
1166                    extend!(null_count, length);
1167                    extend!(0, null_count, is_only_nulls = true);
1168                },
1169                (false, true) => {
1170                    // Current: Nulls last, Wanted: Nulls first
1171                    extend!(length - null_count, length, is_only_nulls = true);
1172                    extend!(0, length - null_count);
1173                },
1174                (false, false) => {
1175                    // Current: Nulls first, Wanted: Nulls first
1176                    extend!(0, null_count, is_only_nulls = true);
1177                    extend!(null_count, length);
1178                },
1179            }
1180        }
1181
1182        // @NOTE: This can theoretically be pushed into the previous operation but it is really
1183        // worth it... probably not...
1184        if let Some(limit) = options.limit {
1185            let limit = limit.min(length);
1186            values.truncate(limit as usize);
1187        }
1188
1189        IdxCa::from_vec(self.name().clone(), values)
1190    }
1191
1192    pub fn arg_sort_multiple(
1193        &self,
1194        by: &[Column],
1195        options: &SortMultipleOptions,
1196    ) -> PolarsResult<IdxCa> {
1197        // @scalar-opt
1198        self.as_materialized_series().arg_sort_multiple(by, options)
1199    }
1200
1201    pub fn arg_unique(&self) -> PolarsResult<IdxCa> {
1202        match self {
1203            Column::Scalar(s) => Ok(IdxCa::new_vec(s.name().clone(), vec![0])),
1204            _ => self.as_materialized_series().arg_unique(),
1205        }
1206    }
1207
1208    pub fn bit_repr(&self) -> Option<BitRepr> {
1209        // @scalar-opt
1210        self.as_materialized_series().bit_repr()
1211    }
1212
1213    pub fn into_frame(self) -> DataFrame {
1214        // SAFETY: A single-column dataframe cannot have length mismatches or duplicate names
1215        unsafe { DataFrame::new_no_checks(self.len(), vec![self]) }
1216    }
1217
1218    pub fn extend(&mut self, other: &Column) -> PolarsResult<&mut Self> {
1219        // @scalar-opt
1220        self.into_materialized_series()
1221            .extend(other.as_materialized_series())?;
1222        Ok(self)
1223    }
1224
1225    pub fn rechunk(&self) -> Column {
1226        match self {
1227            Column::Series(s) => s.rechunk().into(),
1228            Column::Partitioned(s) => {
1229                if let Some(s) = s.lazy_as_materialized_series() {
1230                    // This should always hold for partitioned.
1231                    debug_assert_eq!(s.n_chunks(), 1)
1232                }
1233                self.clone()
1234            },
1235            Column::Scalar(s) => {
1236                if s.lazy_as_materialized_series()
1237                    .filter(|x| x.n_chunks() > 1)
1238                    .is_some()
1239                {
1240                    Column::Scalar(ScalarColumn::new(
1241                        s.name().clone(),
1242                        s.scalar().clone(),
1243                        s.len(),
1244                    ))
1245                } else {
1246                    self.clone()
1247                }
1248            },
1249        }
1250    }
1251
1252    pub fn explode(&self, skip_empty: bool) -> PolarsResult<Column> {
1253        self.as_materialized_series()
1254            .explode(skip_empty)
1255            .map(Column::from)
1256    }
1257    pub fn implode(&self) -> PolarsResult<ListChunked> {
1258        self.as_materialized_series().implode()
1259    }
1260
1261    pub fn fill_null(&self, strategy: FillNullStrategy) -> PolarsResult<Self> {
1262        // @scalar-opt
1263        self.as_materialized_series()
1264            .fill_null(strategy)
1265            .map(Column::from)
1266    }
1267
1268    pub fn divide(&self, rhs: &Column) -> PolarsResult<Self> {
1269        // @scalar-opt
1270        self.as_materialized_series()
1271            .divide(rhs.as_materialized_series())
1272            .map(Column::from)
1273    }
1274
1275    pub fn shift(&self, periods: i64) -> Column {
1276        // @scalar-opt
1277        self.as_materialized_series().shift(periods).into()
1278    }
1279
1280    #[cfg(feature = "zip_with")]
1281    pub fn zip_with(&self, mask: &BooleanChunked, other: &Self) -> PolarsResult<Self> {
1282        // @scalar-opt
1283        self.as_materialized_series()
1284            .zip_with(mask, other.as_materialized_series())
1285            .map(Self::from)
1286    }
1287
1288    #[cfg(feature = "zip_with")]
1289    pub fn zip_with_same_type(
1290        &self,
1291        mask: &ChunkedArray<BooleanType>,
1292        other: &Column,
1293    ) -> PolarsResult<Column> {
1294        // @scalar-opt
1295        self.as_materialized_series()
1296            .zip_with_same_type(mask, other.as_materialized_series())
1297            .map(Column::from)
1298    }
1299
1300    pub fn drop_nulls(&self) -> Column {
1301        match self {
1302            Column::Series(s) => s.drop_nulls().into_column(),
1303            // @partition-opt
1304            Column::Partitioned(s) => s.as_materialized_series().drop_nulls().into_column(),
1305            Column::Scalar(s) => s.drop_nulls().into_column(),
1306        }
1307    }
1308
1309    /// Packs every element into a list.
1310    pub fn as_list(&self) -> ListChunked {
1311        // @scalar-opt
1312        // @partition-opt
1313        self.as_materialized_series().as_list()
1314    }
1315
1316    pub fn is_sorted_flag(&self) -> IsSorted {
1317        match self {
1318            Column::Series(s) => s.is_sorted_flag(),
1319            Column::Partitioned(s) => s.partitions().is_sorted_flag(),
1320            Column::Scalar(_) => IsSorted::Ascending,
1321        }
1322    }
1323
1324    pub fn unique(&self) -> PolarsResult<Column> {
1325        match self {
1326            Column::Series(s) => s.unique().map(Column::from),
1327            // @partition-opt
1328            Column::Partitioned(s) => s.as_materialized_series().unique().map(Column::from),
1329            Column::Scalar(s) => {
1330                _ = s.as_single_value_series().unique()?;
1331                if s.is_empty() {
1332                    return Ok(s.clone().into_column());
1333                }
1334
1335                Ok(s.resize(1).into_column())
1336            },
1337        }
1338    }
1339    pub fn unique_stable(&self) -> PolarsResult<Column> {
1340        match self {
1341            Column::Series(s) => s.unique_stable().map(Column::from),
1342            // @partition-opt
1343            Column::Partitioned(s) => s.as_materialized_series().unique_stable().map(Column::from),
1344            Column::Scalar(s) => {
1345                _ = s.as_single_value_series().unique_stable()?;
1346                if s.is_empty() {
1347                    return Ok(s.clone().into_column());
1348                }
1349
1350                Ok(s.resize(1).into_column())
1351            },
1352        }
1353    }
1354
1355    pub fn reshape_list(&self, dimensions: &[ReshapeDimension]) -> PolarsResult<Self> {
1356        // @scalar-opt
1357        self.as_materialized_series()
1358            .reshape_list(dimensions)
1359            .map(Self::from)
1360    }
1361
1362    #[cfg(feature = "dtype-array")]
1363    pub fn reshape_array(&self, dimensions: &[ReshapeDimension]) -> PolarsResult<Self> {
1364        // @scalar-opt
1365        self.as_materialized_series()
1366            .reshape_array(dimensions)
1367            .map(Self::from)
1368    }
1369
1370    pub fn sort(&self, sort_options: SortOptions) -> PolarsResult<Self> {
1371        // @scalar-opt
1372        self.as_materialized_series()
1373            .sort(sort_options)
1374            .map(Self::from)
1375    }
1376
1377    pub fn filter(&self, filter: &BooleanChunked) -> PolarsResult<Self> {
1378        match self {
1379            Column::Series(s) => s.filter(filter).map(Column::from),
1380            Column::Partitioned(s) => s.as_materialized_series().filter(filter).map(Column::from),
1381            Column::Scalar(s) => {
1382                if s.is_empty() {
1383                    return Ok(s.clone().into_column());
1384                }
1385
1386                // Broadcasting
1387                if filter.len() == 1 {
1388                    return match filter.get(0) {
1389                        Some(true) => Ok(s.clone().into_column()),
1390                        _ => Ok(s.resize(0).into_column()),
1391                    };
1392                }
1393
1394                Ok(s.resize(filter.sum().unwrap() as usize).into_column())
1395            },
1396        }
1397    }
1398
1399    #[cfg(feature = "random")]
1400    pub fn shuffle(&self, seed: Option<u64>) -> Self {
1401        // @scalar-opt
1402        self.as_materialized_series().shuffle(seed).into()
1403    }
1404
1405    #[cfg(feature = "random")]
1406    pub fn sample_frac(
1407        &self,
1408        frac: f64,
1409        with_replacement: bool,
1410        shuffle: bool,
1411        seed: Option<u64>,
1412    ) -> PolarsResult<Self> {
1413        self.as_materialized_series()
1414            .sample_frac(frac, with_replacement, shuffle, seed)
1415            .map(Self::from)
1416    }
1417
1418    #[cfg(feature = "random")]
1419    pub fn sample_n(
1420        &self,
1421        n: usize,
1422        with_replacement: bool,
1423        shuffle: bool,
1424        seed: Option<u64>,
1425    ) -> PolarsResult<Self> {
1426        self.as_materialized_series()
1427            .sample_n(n, with_replacement, shuffle, seed)
1428            .map(Self::from)
1429    }
1430
1431    pub fn gather_every(&self, n: usize, offset: usize) -> PolarsResult<Column> {
1432        polars_ensure!(n > 0, InvalidOperation: "gather_every(n): n should be positive");
1433        if self.len().saturating_sub(offset) == 0 {
1434            return Ok(self.clear());
1435        }
1436
1437        match self {
1438            Column::Series(s) => Ok(s.gather_every(n, offset)?.into()),
1439            Column::Partitioned(s) => {
1440                Ok(s.as_materialized_series().gather_every(n, offset)?.into())
1441            },
1442            Column::Scalar(s) => {
1443                let total = s.len() - offset;
1444                Ok(s.resize(1 + (total - 1) / n).into())
1445            },
1446        }
1447    }
1448
1449    pub fn extend_constant(&self, value: AnyValue, n: usize) -> PolarsResult<Self> {
1450        if self.is_empty() {
1451            return Ok(Self::new_scalar(
1452                self.name().clone(),
1453                Scalar::new(self.dtype().clone(), value.into_static()),
1454                n,
1455            ));
1456        }
1457
1458        match self {
1459            Column::Series(s) => s.extend_constant(value, n).map(Column::from),
1460            Column::Partitioned(s) => s.extend_constant(value, n).map(Column::from),
1461            Column::Scalar(s) => {
1462                if s.scalar().as_any_value() == value {
1463                    Ok(s.resize(s.len() + n).into())
1464                } else {
1465                    s.as_materialized_series()
1466                        .extend_constant(value, n)
1467                        .map(Column::from)
1468                }
1469            },
1470        }
1471    }
1472
1473    pub fn is_finite(&self) -> PolarsResult<BooleanChunked> {
1474        self.try_map_unary_elementwise_to_bool(|s| s.is_finite())
1475    }
1476    pub fn is_infinite(&self) -> PolarsResult<BooleanChunked> {
1477        self.try_map_unary_elementwise_to_bool(|s| s.is_infinite())
1478    }
1479    pub fn is_nan(&self) -> PolarsResult<BooleanChunked> {
1480        self.try_map_unary_elementwise_to_bool(|s| s.is_nan())
1481    }
1482    pub fn is_not_nan(&self) -> PolarsResult<BooleanChunked> {
1483        self.try_map_unary_elementwise_to_bool(|s| s.is_not_nan())
1484    }
1485
1486    pub fn wrapping_trunc_div_scalar<T>(&self, rhs: T) -> Self
1487    where
1488        T: Num + NumCast,
1489    {
1490        // @scalar-opt
1491        self.as_materialized_series()
1492            .wrapping_trunc_div_scalar(rhs)
1493            .into()
1494    }
1495
1496    pub fn product(&self) -> PolarsResult<Scalar> {
1497        // @scalar-opt
1498        self.as_materialized_series().product()
1499    }
1500
1501    pub fn phys_iter(&self) -> SeriesPhysIter<'_> {
1502        // @scalar-opt
1503        self.as_materialized_series().phys_iter()
1504    }
1505
1506    #[inline]
1507    pub fn get(&self, index: usize) -> PolarsResult<AnyValue<'_>> {
1508        polars_ensure!(index < self.len(), oob = index, self.len());
1509
1510        // SAFETY: Bounds check done just before.
1511        Ok(unsafe { self.get_unchecked(index) })
1512    }
1513    /// # Safety
1514    ///
1515    /// Does not perform bounds check on `index`
1516    #[inline(always)]
1517    pub unsafe fn get_unchecked(&self, index: usize) -> AnyValue<'_> {
1518        debug_assert!(index < self.len());
1519
1520        match self {
1521            Column::Series(s) => unsafe { s.get_unchecked(index) },
1522            Column::Partitioned(s) => unsafe { s.get_unchecked(index) },
1523            Column::Scalar(s) => s.scalar().as_any_value(),
1524        }
1525    }
1526
1527    #[cfg(feature = "object")]
1528    pub fn get_object(
1529        &self,
1530        index: usize,
1531    ) -> Option<&dyn crate::chunked_array::object::PolarsObjectSafe> {
1532        self.as_materialized_series().get_object(index)
1533    }
1534
1535    pub fn bitand(&self, rhs: &Self) -> PolarsResult<Self> {
1536        self.try_apply_broadcasting_binary_elementwise(rhs, |l, r| l & r)
1537    }
1538    pub fn bitor(&self, rhs: &Self) -> PolarsResult<Self> {
1539        self.try_apply_broadcasting_binary_elementwise(rhs, |l, r| l | r)
1540    }
1541    pub fn bitxor(&self, rhs: &Self) -> PolarsResult<Self> {
1542        self.try_apply_broadcasting_binary_elementwise(rhs, |l, r| l ^ r)
1543    }
1544
1545    pub fn try_add_owned(self, other: Self) -> PolarsResult<Self> {
1546        match (self, other) {
1547            (Column::Series(lhs), Column::Series(rhs)) => {
1548                lhs.take().try_add_owned(rhs.take()).map(Column::from)
1549            },
1550            (lhs, rhs) => lhs + rhs,
1551        }
1552    }
1553    pub fn try_sub_owned(self, other: Self) -> PolarsResult<Self> {
1554        match (self, other) {
1555            (Column::Series(lhs), Column::Series(rhs)) => {
1556                lhs.take().try_sub_owned(rhs.take()).map(Column::from)
1557            },
1558            (lhs, rhs) => lhs - rhs,
1559        }
1560    }
1561    pub fn try_mul_owned(self, other: Self) -> PolarsResult<Self> {
1562        match (self, other) {
1563            (Column::Series(lhs), Column::Series(rhs)) => {
1564                lhs.take().try_mul_owned(rhs.take()).map(Column::from)
1565            },
1566            (lhs, rhs) => lhs * rhs,
1567        }
1568    }
1569
1570    pub(crate) fn str_value(&self, index: usize) -> PolarsResult<Cow<'_, str>> {
1571        Ok(self.get(index)?.str_value())
1572    }
1573
1574    pub fn min_reduce(&self) -> PolarsResult<Scalar> {
1575        match self {
1576            Column::Series(s) => s.min_reduce(),
1577            Column::Partitioned(s) => s.min_reduce(),
1578            Column::Scalar(s) => {
1579                // We don't really want to deal with handling the full semantics here so we just
1580                // cast to a single value series. This is a tiny bit wasteful, but probably fine.
1581                s.as_single_value_series().min_reduce()
1582            },
1583        }
1584    }
1585    pub fn max_reduce(&self) -> PolarsResult<Scalar> {
1586        match self {
1587            Column::Series(s) => s.max_reduce(),
1588            Column::Partitioned(s) => s.max_reduce(),
1589            Column::Scalar(s) => {
1590                // We don't really want to deal with handling the full semantics here so we just
1591                // cast to a single value series. This is a tiny bit wasteful, but probably fine.
1592                s.as_single_value_series().max_reduce()
1593            },
1594        }
1595    }
1596    pub fn median_reduce(&self) -> PolarsResult<Scalar> {
1597        match self {
1598            Column::Series(s) => s.median_reduce(),
1599            Column::Partitioned(s) => s.as_materialized_series().median_reduce(),
1600            Column::Scalar(s) => {
1601                // We don't really want to deal with handling the full semantics here so we just
1602                // cast to a single value series. This is a tiny bit wasteful, but probably fine.
1603                s.as_single_value_series().median_reduce()
1604            },
1605        }
1606    }
1607    pub fn mean_reduce(&self) -> Scalar {
1608        match self {
1609            Column::Series(s) => s.mean_reduce(),
1610            Column::Partitioned(s) => s.as_materialized_series().mean_reduce(),
1611            Column::Scalar(s) => {
1612                // We don't really want to deal with handling the full semantics here so we just
1613                // cast to a single value series. This is a tiny bit wasteful, but probably fine.
1614                s.as_single_value_series().mean_reduce()
1615            },
1616        }
1617    }
1618    pub fn std_reduce(&self, ddof: u8) -> PolarsResult<Scalar> {
1619        match self {
1620            Column::Series(s) => s.std_reduce(ddof),
1621            Column::Partitioned(s) => s.as_materialized_series().std_reduce(ddof),
1622            Column::Scalar(s) => {
1623                // We don't really want to deal with handling the full semantics here so we just
1624                // cast to a small series. This is a tiny bit wasteful, but probably fine.
1625                let n = s.len().min(ddof as usize + 1);
1626                s.as_n_values_series(n).std_reduce(ddof)
1627            },
1628        }
1629    }
1630    pub fn var_reduce(&self, ddof: u8) -> PolarsResult<Scalar> {
1631        match self {
1632            Column::Series(s) => s.var_reduce(ddof),
1633            Column::Partitioned(s) => s.as_materialized_series().var_reduce(ddof),
1634            Column::Scalar(s) => {
1635                // We don't really want to deal with handling the full semantics here so we just
1636                // cast to a small series. This is a tiny bit wasteful, but probably fine.
1637                let n = s.len().min(ddof as usize + 1);
1638                s.as_n_values_series(n).var_reduce(ddof)
1639            },
1640        }
1641    }
1642    pub fn sum_reduce(&self) -> PolarsResult<Scalar> {
1643        // @partition-opt
1644        // @scalar-opt
1645        self.as_materialized_series().sum_reduce()
1646    }
1647    pub fn and_reduce(&self) -> PolarsResult<Scalar> {
1648        match self {
1649            Column::Series(s) => s.and_reduce(),
1650            Column::Partitioned(s) => s.and_reduce(),
1651            Column::Scalar(s) => {
1652                // We don't really want to deal with handling the full semantics here so we just
1653                // cast to a single value series. This is a tiny bit wasteful, but probably fine.
1654                s.as_single_value_series().and_reduce()
1655            },
1656        }
1657    }
1658    pub fn or_reduce(&self) -> PolarsResult<Scalar> {
1659        match self {
1660            Column::Series(s) => s.or_reduce(),
1661            Column::Partitioned(s) => s.or_reduce(),
1662            Column::Scalar(s) => {
1663                // We don't really want to deal with handling the full semantics here so we just
1664                // cast to a single value series. This is a tiny bit wasteful, but probably fine.
1665                s.as_single_value_series().or_reduce()
1666            },
1667        }
1668    }
1669    pub fn xor_reduce(&self) -> PolarsResult<Scalar> {
1670        match self {
1671            Column::Series(s) => s.xor_reduce(),
1672            // @partition-opt
1673            Column::Partitioned(s) => s.as_materialized_series().xor_reduce(),
1674            Column::Scalar(s) => {
1675                // We don't really want to deal with handling the full semantics here so we just
1676                // cast to a single value series. This is a tiny bit wasteful, but probably fine.
1677                //
1678                // We have to deal with the fact that xor is 0 if there is an even number of
1679                // elements and the value if there is an odd number of elements. If there are zero
1680                // elements the result should be `null`.
1681                s.as_n_values_series(2 - s.len() % 2).xor_reduce()
1682            },
1683        }
1684    }
1685    pub fn n_unique(&self) -> PolarsResult<usize> {
1686        match self {
1687            Column::Series(s) => s.n_unique(),
1688            Column::Partitioned(s) => s.partitions().n_unique(),
1689            Column::Scalar(s) => s.as_single_value_series().n_unique(),
1690        }
1691    }
1692    pub fn quantile_reduce(&self, quantile: f64, method: QuantileMethod) -> PolarsResult<Scalar> {
1693        self.as_materialized_series()
1694            .quantile_reduce(quantile, method)
1695    }
1696
1697    pub(crate) fn estimated_size(&self) -> usize {
1698        // @scalar-opt
1699        self.as_materialized_series().estimated_size()
1700    }
1701
1702    pub fn sort_with(&self, options: SortOptions) -> PolarsResult<Self> {
1703        match self {
1704            Column::Series(s) => s.sort_with(options).map(Self::from),
1705            // @partition-opt
1706            Column::Partitioned(s) => s
1707                .as_materialized_series()
1708                .sort_with(options)
1709                .map(Self::from),
1710            Column::Scalar(s) => {
1711                // This makes this function throw the same errors as Series::sort_with
1712                _ = s.as_single_value_series().sort_with(options)?;
1713
1714                Ok(self.clone())
1715            },
1716        }
1717    }
1718
1719    pub fn map_unary_elementwise_to_bool(
1720        &self,
1721        f: impl Fn(&Series) -> BooleanChunked,
1722    ) -> BooleanChunked {
1723        self.try_map_unary_elementwise_to_bool(|s| Ok(f(s)))
1724            .unwrap()
1725    }
1726    pub fn try_map_unary_elementwise_to_bool(
1727        &self,
1728        f: impl Fn(&Series) -> PolarsResult<BooleanChunked>,
1729    ) -> PolarsResult<BooleanChunked> {
1730        match self {
1731            Column::Series(s) => f(s),
1732            Column::Partitioned(s) => f(s.as_materialized_series()),
1733            Column::Scalar(s) => Ok(f(&s.as_single_value_series())?.new_from_index(0, s.len())),
1734        }
1735    }
1736
1737    pub fn apply_unary_elementwise(&self, f: impl Fn(&Series) -> Series) -> Column {
1738        self.try_apply_unary_elementwise(|s| Ok(f(s))).unwrap()
1739    }
1740    pub fn try_apply_unary_elementwise(
1741        &self,
1742        f: impl Fn(&Series) -> PolarsResult<Series>,
1743    ) -> PolarsResult<Column> {
1744        match self {
1745            Column::Series(s) => f(s).map(Column::from),
1746            Column::Partitioned(s) => s.try_apply_unary_elementwise(f).map(Self::from),
1747            Column::Scalar(s) => Ok(ScalarColumn::from_single_value_series(
1748                f(&s.as_single_value_series())?,
1749                s.len(),
1750            )
1751            .into()),
1752        }
1753    }
1754
1755    pub fn apply_broadcasting_binary_elementwise(
1756        &self,
1757        other: &Self,
1758        op: impl Fn(&Series, &Series) -> Series,
1759    ) -> PolarsResult<Column> {
1760        self.try_apply_broadcasting_binary_elementwise(other, |lhs, rhs| Ok(op(lhs, rhs)))
1761    }
1762    pub fn try_apply_broadcasting_binary_elementwise(
1763        &self,
1764        other: &Self,
1765        op: impl Fn(&Series, &Series) -> PolarsResult<Series>,
1766    ) -> PolarsResult<Column> {
1767        fn output_length(a: &Column, b: &Column) -> PolarsResult<usize> {
1768            match (a.len(), b.len()) {
1769                // broadcasting
1770                (1, o) | (o, 1) => Ok(o),
1771                // equal
1772                (a, b) if a == b => Ok(a),
1773                // unequal
1774                (a, b) => {
1775                    polars_bail!(InvalidOperation: "cannot do a binary operation on columns of different lengths: got {} and {}", a, b)
1776                },
1777            }
1778        }
1779
1780        // Here we rely on the underlying broadcast operations.
1781        let length = output_length(self, other)?;
1782        match (self, other) {
1783            (Column::Series(lhs), Column::Series(rhs)) => op(lhs, rhs).map(Column::from),
1784            (Column::Series(lhs), Column::Scalar(rhs)) => {
1785                op(lhs, &rhs.as_single_value_series()).map(Column::from)
1786            },
1787            (Column::Scalar(lhs), Column::Series(rhs)) => {
1788                op(&lhs.as_single_value_series(), rhs).map(Column::from)
1789            },
1790            (Column::Scalar(lhs), Column::Scalar(rhs)) => {
1791                let lhs = lhs.as_single_value_series();
1792                let rhs = rhs.as_single_value_series();
1793
1794                Ok(ScalarColumn::from_single_value_series(op(&lhs, &rhs)?, length).into_column())
1795            },
1796            // @partition-opt
1797            (lhs, rhs) => {
1798                op(lhs.as_materialized_series(), rhs.as_materialized_series()).map(Column::from)
1799            },
1800        }
1801    }
1802
1803    pub fn apply_binary_elementwise(
1804        &self,
1805        other: &Self,
1806        f: impl Fn(&Series, &Series) -> Series,
1807        f_lb: impl Fn(&Scalar, &Series) -> Series,
1808        f_rb: impl Fn(&Series, &Scalar) -> Series,
1809    ) -> Column {
1810        self.try_apply_binary_elementwise(
1811            other,
1812            |lhs, rhs| Ok(f(lhs, rhs)),
1813            |lhs, rhs| Ok(f_lb(lhs, rhs)),
1814            |lhs, rhs| Ok(f_rb(lhs, rhs)),
1815        )
1816        .unwrap()
1817    }
1818    pub fn try_apply_binary_elementwise(
1819        &self,
1820        other: &Self,
1821        f: impl Fn(&Series, &Series) -> PolarsResult<Series>,
1822        f_lb: impl Fn(&Scalar, &Series) -> PolarsResult<Series>,
1823        f_rb: impl Fn(&Series, &Scalar) -> PolarsResult<Series>,
1824    ) -> PolarsResult<Column> {
1825        debug_assert_eq!(self.len(), other.len());
1826
1827        match (self, other) {
1828            (Column::Series(lhs), Column::Series(rhs)) => f(lhs, rhs).map(Column::from),
1829            (Column::Series(lhs), Column::Scalar(rhs)) => f_rb(lhs, rhs.scalar()).map(Column::from),
1830            (Column::Scalar(lhs), Column::Series(rhs)) => f_lb(lhs.scalar(), rhs).map(Column::from),
1831            (Column::Scalar(lhs), Column::Scalar(rhs)) => {
1832                let lhs = lhs.as_single_value_series();
1833                let rhs = rhs.as_single_value_series();
1834
1835                Ok(
1836                    ScalarColumn::from_single_value_series(f(&lhs, &rhs)?, self.len())
1837                        .into_column(),
1838                )
1839            },
1840            // @partition-opt
1841            (lhs, rhs) => {
1842                f(lhs.as_materialized_series(), rhs.as_materialized_series()).map(Column::from)
1843            },
1844        }
1845    }
1846
1847    #[cfg(feature = "approx_unique")]
1848    pub fn approx_n_unique(&self) -> PolarsResult<IdxSize> {
1849        match self {
1850            Column::Series(s) => s.approx_n_unique(),
1851            // @partition-opt
1852            Column::Partitioned(s) => s.as_materialized_series().approx_n_unique(),
1853            Column::Scalar(s) => {
1854                // @NOTE: We do this for the error handling.
1855                s.as_single_value_series().approx_n_unique()?;
1856                Ok(1)
1857            },
1858        }
1859    }
1860
1861    pub fn n_chunks(&self) -> usize {
1862        match self {
1863            Column::Series(s) => s.n_chunks(),
1864            Column::Scalar(s) => s.lazy_as_materialized_series().map_or(1, |x| x.n_chunks()),
1865            Column::Partitioned(s) => {
1866                if let Some(s) = s.lazy_as_materialized_series() {
1867                    // This should always hold for partitioned.
1868                    debug_assert_eq!(s.n_chunks(), 1)
1869                }
1870                1
1871            },
1872        }
1873    }
1874
1875    #[expect(clippy::wrong_self_convention)]
1876    pub(crate) fn into_total_ord_inner<'a>(&'a self) -> Box<dyn TotalOrdInner + 'a> {
1877        // @scalar-opt
1878        self.as_materialized_series().into_total_ord_inner()
1879    }
1880    #[expect(unused, clippy::wrong_self_convention)]
1881    pub(crate) fn into_total_eq_inner<'a>(&'a self) -> Box<dyn TotalEqInner + 'a> {
1882        // @scalar-opt
1883        self.as_materialized_series().into_total_eq_inner()
1884    }
1885
1886    pub fn rechunk_to_arrow(self, compat_level: CompatLevel) -> Box<dyn Array> {
1887        // Rechunk to one chunk if necessary
1888        let mut series = self.take_materialized_series();
1889        if series.n_chunks() > 1 {
1890            series = series.rechunk();
1891        }
1892        series.to_arrow(0, compat_level)
1893    }
1894
1895    pub fn trim_lists_to_normalized_offsets(&self) -> Option<Column> {
1896        self.as_materialized_series()
1897            .trim_lists_to_normalized_offsets()
1898            .map(Column::from)
1899    }
1900
1901    pub fn propagate_nulls(&self) -> Option<Column> {
1902        self.as_materialized_series()
1903            .propagate_nulls()
1904            .map(Column::from)
1905    }
1906}
1907
1908impl Default for Column {
1909    fn default() -> Self {
1910        Self::new_scalar(
1911            PlSmallStr::EMPTY,
1912            Scalar::new(DataType::Int64, AnyValue::Null),
1913            0,
1914        )
1915    }
1916}
1917
1918impl PartialEq for Column {
1919    fn eq(&self, other: &Self) -> bool {
1920        // @scalar-opt
1921        self.as_materialized_series()
1922            .eq(other.as_materialized_series())
1923    }
1924}
1925
1926impl From<Series> for Column {
1927    #[inline]
1928    fn from(series: Series) -> Self {
1929        // We instantiate a Scalar Column if the Series is length is 1. This makes it possible for
1930        // future operations to be faster.
1931        if series.len() == 1 {
1932            return Self::Scalar(ScalarColumn::unit_scalar_from_series(series));
1933        }
1934
1935        Self::Series(SeriesColumn::new(series))
1936    }
1937}
1938
1939impl<T: IntoSeries> IntoColumn for T {
1940    #[inline]
1941    fn into_column(self) -> Column {
1942        self.into_series().into()
1943    }
1944}
1945
1946impl IntoColumn for Column {
1947    #[inline(always)]
1948    fn into_column(self) -> Column {
1949        self
1950    }
1951}
1952
1953/// We don't want to serialize the scalar columns. So this helps pretend that columns are always
1954/// initialized without implementing From<Column> for Series.
1955///
1956/// Those casts should be explicit.
1957#[derive(Clone)]
1958#[cfg_attr(feature = "serde", derive(serde::Serialize))]
1959#[cfg_attr(feature = "serde", serde(into = "Series"))]
1960struct _SerdeSeries(Series);
1961
1962impl From<Column> for _SerdeSeries {
1963    #[inline]
1964    fn from(value: Column) -> Self {
1965        Self(value.take_materialized_series())
1966    }
1967}
1968
1969impl From<_SerdeSeries> for Series {
1970    #[inline]
1971    fn from(value: _SerdeSeries) -> Self {
1972        value.0
1973    }
1974}