polars_core/frame/column/
mod.rs

1use std::borrow::Cow;
2
3use arrow::bitmap::{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    #[must_use]
536    pub fn clear(&self) -> Self {
537        match self {
538            Column::Series(s) => s.clear().into(),
539            Column::Partitioned(s) => s.clear().into(),
540            Column::Scalar(s) => s.resize(0).into(),
541        }
542    }
543
544    #[inline]
545    pub fn shrink_to_fit(&mut self) {
546        match self {
547            Column::Series(s) => s.shrink_to_fit(),
548            // @partition-opt
549            Column::Partitioned(_) => {},
550            Column::Scalar(_) => {},
551        }
552    }
553
554    #[inline]
555    pub fn new_from_index(&self, index: usize, length: usize) -> Self {
556        if index >= self.len() {
557            return Self::full_null(self.name().clone(), length, self.dtype());
558        }
559
560        match self {
561            Column::Series(s) => {
562                // SAFETY: Bounds check done before.
563                let av = unsafe { s.get_unchecked(index) };
564                let scalar = Scalar::new(self.dtype().clone(), av.into_static());
565                Self::new_scalar(self.name().clone(), scalar, length)
566            },
567            Column::Partitioned(s) => {
568                // SAFETY: Bounds check done before.
569                let av = unsafe { s.get_unchecked(index) };
570                let scalar = Scalar::new(self.dtype().clone(), av.into_static());
571                Self::new_scalar(self.name().clone(), scalar, length)
572            },
573            Column::Scalar(s) => s.resize(length).into(),
574        }
575    }
576
577    #[inline]
578    pub fn has_nulls(&self) -> bool {
579        match self {
580            Self::Series(s) => s.has_nulls(),
581            // @partition-opt
582            Self::Partitioned(s) => s.as_materialized_series().has_nulls(),
583            Self::Scalar(s) => s.has_nulls(),
584        }
585    }
586
587    #[inline]
588    pub fn is_null(&self) -> BooleanChunked {
589        match self {
590            Self::Series(s) => s.is_null(),
591            // @partition-opt
592            Self::Partitioned(s) => s.as_materialized_series().is_null(),
593            Self::Scalar(s) => {
594                BooleanChunked::full(s.name().clone(), s.scalar().is_null(), s.len())
595            },
596        }
597    }
598    #[inline]
599    pub fn is_not_null(&self) -> BooleanChunked {
600        match self {
601            Self::Series(s) => s.is_not_null(),
602            // @partition-opt
603            Self::Partitioned(s) => s.as_materialized_series().is_not_null(),
604            Self::Scalar(s) => {
605                BooleanChunked::full(s.name().clone(), !s.scalar().is_null(), s.len())
606            },
607        }
608    }
609
610    pub fn to_physical_repr(&self) -> Column {
611        // @scalar-opt
612        self.as_materialized_series()
613            .to_physical_repr()
614            .into_owned()
615            .into()
616    }
617    /// # Safety
618    ///
619    /// This can lead to invalid memory access in downstream code.
620    pub unsafe fn from_physical_unchecked(&self, dtype: &DataType) -> PolarsResult<Column> {
621        // @scalar-opt
622        self.as_materialized_series()
623            .from_physical_unchecked(dtype)
624            .map(Column::from)
625    }
626
627    pub fn head(&self, length: Option<usize>) -> Column {
628        let len = length.unwrap_or(HEAD_DEFAULT_LENGTH);
629        let len = usize::min(len, self.len());
630        self.slice(0, len)
631    }
632    pub fn tail(&self, length: Option<usize>) -> Column {
633        let len = length.unwrap_or(TAIL_DEFAULT_LENGTH);
634        let len = usize::min(len, self.len());
635        debug_assert!(len <= i64::MAX as usize);
636        self.slice(-(len as i64), len)
637    }
638    pub fn slice(&self, offset: i64, length: usize) -> Column {
639        match self {
640            Column::Series(s) => s.slice(offset, length).into(),
641            // @partition-opt
642            Column::Partitioned(s) => s.as_materialized_series().slice(offset, length).into(),
643            Column::Scalar(s) => {
644                let (_, length) = slice_offsets(offset, length, s.len());
645                s.resize(length).into()
646            },
647        }
648    }
649
650    pub fn split_at(&self, offset: i64) -> (Column, Column) {
651        // @scalar-opt
652        let (l, r) = self.as_materialized_series().split_at(offset);
653        (l.into(), r.into())
654    }
655
656    #[inline]
657    pub fn null_count(&self) -> usize {
658        match self {
659            Self::Series(s) => s.null_count(),
660            Self::Partitioned(s) => s.null_count(),
661            Self::Scalar(s) if s.scalar().is_null() => s.len(),
662            Self::Scalar(_) => 0,
663        }
664    }
665
666    pub fn take(&self, indices: &IdxCa) -> PolarsResult<Column> {
667        check_bounds_ca(indices, self.len() as IdxSize)?;
668        Ok(unsafe { self.take_unchecked(indices) })
669    }
670    pub fn take_slice(&self, indices: &[IdxSize]) -> PolarsResult<Column> {
671        check_bounds(indices, self.len() as IdxSize)?;
672        Ok(unsafe { self.take_slice_unchecked(indices) })
673    }
674    /// # Safety
675    ///
676    /// No bounds on the indexes are performed.
677    pub unsafe fn take_unchecked(&self, indices: &IdxCa) -> Column {
678        debug_assert!(check_bounds_ca(indices, self.len() as IdxSize).is_ok());
679
680        match self {
681            Self::Series(s) => unsafe { s.take_unchecked(indices) }.into(),
682            Self::Partitioned(s) => {
683                let s = s.as_materialized_series();
684                unsafe { s.take_unchecked(indices) }.into()
685            },
686            Self::Scalar(s) => {
687                let idxs_length = indices.len();
688                let idxs_null_count = indices.null_count();
689
690                let scalar = ScalarColumn::from_single_value_series(
691                    s.as_single_value_series().take_unchecked(&IdxCa::new(
692                        indices.name().clone(),
693                        &[0][..s.len().min(1)],
694                    )),
695                    idxs_length,
696                );
697
698                // We need to make sure that null values in `idx` become null values in the result
699                if idxs_null_count == 0 || scalar.has_nulls() {
700                    scalar.into_column()
701                } else if idxs_null_count == idxs_length {
702                    scalar.into_nulls().into_column()
703                } else {
704                    let validity = indices.rechunk_validity();
705                    let series = scalar.take_materialized_series();
706                    let name = series.name().clone();
707                    let dtype = series.dtype().clone();
708                    let mut chunks = series.into_chunks();
709                    assert_eq!(chunks.len(), 1);
710                    chunks[0] = chunks[0].with_validity(validity);
711                    unsafe { Series::from_chunks_and_dtype_unchecked(name, chunks, &dtype) }
712                        .into_column()
713                }
714            },
715        }
716    }
717    /// # Safety
718    ///
719    /// No bounds on the indexes are performed.
720    pub unsafe fn take_slice_unchecked(&self, indices: &[IdxSize]) -> Column {
721        debug_assert!(check_bounds(indices, self.len() as IdxSize).is_ok());
722
723        match self {
724            Self::Series(s) => unsafe { s.take_slice_unchecked(indices) }.into(),
725            Self::Partitioned(s) => {
726                let s = s.as_materialized_series();
727                unsafe { s.take_slice_unchecked(indices) }.into()
728            },
729            Self::Scalar(s) => ScalarColumn::from_single_value_series(
730                s.as_single_value_series()
731                    .take_slice_unchecked(&[0][..s.len().min(1)]),
732                indices.len(),
733            )
734            .into(),
735        }
736    }
737
738    /// General implementation for aggregation where a non-missing scalar would map to itself.
739    #[inline(always)]
740    #[cfg(any(feature = "algorithm_group_by", feature = "bitwise"))]
741    fn agg_with_unit_scalar(
742        &self,
743        groups: &GroupsType,
744        series_agg: impl Fn(&Series, &GroupsType) -> Series,
745    ) -> Column {
746        match self {
747            Column::Series(s) => series_agg(s, groups).into_column(),
748            // @partition-opt
749            Column::Partitioned(s) => series_agg(s.as_materialized_series(), groups).into_column(),
750            Column::Scalar(s) => {
751                if s.is_empty() {
752                    return series_agg(s.as_materialized_series(), groups).into_column();
753                }
754
755                // We utilize the aggregation on Series to see:
756                // 1. the output datatype of the aggregation
757                // 2. whether this aggregation is even defined
758                let series_aggregation = series_agg(
759                    &s.as_single_value_series(),
760                    &GroupsType::Slice {
761                        // @NOTE: this group is always valid since s is non-empty.
762                        groups: vec![[0, 1]],
763                        overlapping: false,
764                    },
765                );
766
767                // If the aggregation is not defined, just return all nulls.
768                if series_aggregation.has_nulls() {
769                    return Self::new_scalar(
770                        series_aggregation.name().clone(),
771                        Scalar::new(series_aggregation.dtype().clone(), AnyValue::Null),
772                        groups.len(),
773                    );
774                }
775
776                let mut scalar_col = s.resize(groups.len());
777                // The aggregation might change the type (e.g. mean changes int -> float), so we do
778                // a cast here to the output type.
779                if series_aggregation.dtype() != s.dtype() {
780                    scalar_col = scalar_col.cast(series_aggregation.dtype()).unwrap();
781                }
782
783                let Some(first_empty_idx) = groups.iter().position(|g| g.is_empty()) else {
784                    // Fast path: no empty groups. keep the scalar intact.
785                    return scalar_col.into_column();
786                };
787
788                // All empty groups produce a *missing* or `null` value.
789                let mut validity = BitmapBuilder::with_capacity(groups.len());
790                validity.extend_constant(first_empty_idx, true);
791                // SAFETY: We trust the length of this iterator.
792                let iter = unsafe {
793                    TrustMyLength::new(
794                        groups.iter().skip(first_empty_idx).map(|g| !g.is_empty()),
795                        groups.len() - first_empty_idx,
796                    )
797                };
798                validity.extend_trusted_len_iter(iter);
799
800                let mut s = scalar_col.take_materialized_series().rechunk();
801                // SAFETY: We perform a compute_len afterwards.
802                let chunks = unsafe { s.chunks_mut() };
803                let arr = &mut chunks[0];
804                *arr = arr.with_validity(validity.into_opt_validity());
805                s.compute_len();
806
807                s.into_column()
808            },
809        }
810    }
811
812    /// # Safety
813    ///
814    /// Does no bounds checks, groups must be correct.
815    #[cfg(feature = "algorithm_group_by")]
816    pub unsafe fn agg_min(&self, groups: &GroupsType) -> Self {
817        self.agg_with_unit_scalar(groups, |s, g| unsafe { s.agg_min(g) })
818    }
819
820    /// # Safety
821    ///
822    /// Does no bounds checks, groups must be correct.
823    #[cfg(feature = "algorithm_group_by")]
824    pub unsafe fn agg_max(&self, groups: &GroupsType) -> Self {
825        self.agg_with_unit_scalar(groups, |s, g| unsafe { s.agg_max(g) })
826    }
827
828    /// # Safety
829    ///
830    /// Does no bounds checks, groups must be correct.
831    #[cfg(feature = "algorithm_group_by")]
832    pub unsafe fn agg_mean(&self, groups: &GroupsType) -> Self {
833        self.agg_with_unit_scalar(groups, |s, g| unsafe { s.agg_mean(g) })
834    }
835
836    /// # Safety
837    ///
838    /// Does no bounds checks, groups must be correct.
839    #[cfg(feature = "algorithm_group_by")]
840    pub unsafe fn agg_sum(&self, groups: &GroupsType) -> Self {
841        // @scalar-opt
842        unsafe { self.as_materialized_series().agg_sum(groups) }.into()
843    }
844
845    /// # Safety
846    ///
847    /// Does no bounds checks, groups must be correct.
848    #[cfg(feature = "algorithm_group_by")]
849    pub unsafe fn agg_first(&self, groups: &GroupsType) -> Self {
850        self.agg_with_unit_scalar(groups, |s, g| unsafe { s.agg_first(g) })
851    }
852
853    /// # Safety
854    ///
855    /// Does no bounds checks, groups must be correct.
856    #[cfg(feature = "algorithm_group_by")]
857    pub unsafe fn agg_last(&self, groups: &GroupsType) -> Self {
858        self.agg_with_unit_scalar(groups, |s, g| unsafe { s.agg_last(g) })
859    }
860
861    /// # Safety
862    ///
863    /// Does no bounds checks, groups must be correct.
864    #[cfg(feature = "algorithm_group_by")]
865    pub unsafe fn agg_n_unique(&self, groups: &GroupsType) -> Self {
866        // @scalar-opt
867        unsafe { self.as_materialized_series().agg_n_unique(groups) }.into()
868    }
869
870    /// # Safety
871    ///
872    /// Does no bounds checks, groups must be correct.
873    #[cfg(feature = "algorithm_group_by")]
874    pub unsafe fn agg_quantile(
875        &self,
876        groups: &GroupsType,
877        quantile: f64,
878        method: QuantileMethod,
879    ) -> Self {
880        // @scalar-opt
881
882        unsafe {
883            self.as_materialized_series()
884                .agg_quantile(groups, quantile, method)
885        }
886        .into()
887    }
888
889    /// # Safety
890    ///
891    /// Does no bounds checks, groups must be correct.
892    #[cfg(feature = "algorithm_group_by")]
893    pub unsafe fn agg_median(&self, groups: &GroupsType) -> Self {
894        self.agg_with_unit_scalar(groups, |s, g| unsafe { s.agg_median(g) })
895    }
896
897    /// # Safety
898    ///
899    /// Does no bounds checks, groups must be correct.
900    #[cfg(feature = "algorithm_group_by")]
901    pub unsafe fn agg_var(&self, groups: &GroupsType, ddof: u8) -> Self {
902        // @scalar-opt
903        unsafe { self.as_materialized_series().agg_var(groups, ddof) }.into()
904    }
905
906    /// # Safety
907    ///
908    /// Does no bounds checks, groups must be correct.
909    #[cfg(feature = "algorithm_group_by")]
910    pub unsafe fn agg_std(&self, groups: &GroupsType, ddof: u8) -> Self {
911        // @scalar-opt
912        unsafe { self.as_materialized_series().agg_std(groups, ddof) }.into()
913    }
914
915    /// # Safety
916    ///
917    /// Does no bounds checks, groups must be correct.
918    #[cfg(feature = "algorithm_group_by")]
919    pub unsafe fn agg_list(&self, groups: &GroupsType) -> Self {
920        // @scalar-opt
921        unsafe { self.as_materialized_series().agg_list(groups) }.into()
922    }
923
924    /// # Safety
925    ///
926    /// Does no bounds checks, groups must be correct.
927    #[cfg(feature = "algorithm_group_by")]
928    pub fn agg_valid_count(&self, groups: &GroupsType) -> Self {
929        // @partition-opt
930        // @scalar-opt
931        unsafe { self.as_materialized_series().agg_valid_count(groups) }.into()
932    }
933
934    /// # Safety
935    ///
936    /// Does no bounds checks, groups must be correct.
937    #[cfg(feature = "bitwise")]
938    pub unsafe fn agg_and(&self, groups: &GroupsType) -> Self {
939        self.agg_with_unit_scalar(groups, |s, g| unsafe { s.agg_and(g) })
940    }
941    /// # Safety
942    ///
943    /// Does no bounds checks, groups must be correct.
944    #[cfg(feature = "bitwise")]
945    pub unsafe fn agg_or(&self, groups: &GroupsType) -> Self {
946        self.agg_with_unit_scalar(groups, |s, g| unsafe { s.agg_or(g) })
947    }
948    /// # Safety
949    ///
950    /// Does no bounds checks, groups must be correct.
951    #[cfg(feature = "bitwise")]
952    pub unsafe fn agg_xor(&self, groups: &GroupsType) -> Self {
953        // @partition-opt
954        // @scalar-opt
955        unsafe { self.as_materialized_series().agg_xor(groups) }.into()
956    }
957
958    pub fn full_null(name: PlSmallStr, size: usize, dtype: &DataType) -> Self {
959        Self::new_scalar(name, Scalar::new(dtype.clone(), AnyValue::Null), size)
960    }
961
962    pub fn is_empty(&self) -> bool {
963        self.len() == 0
964    }
965
966    pub fn reverse(&self) -> Column {
967        match self {
968            Column::Series(s) => s.reverse().into(),
969            Column::Partitioned(s) => s.reverse().into(),
970            Column::Scalar(_) => self.clone(),
971        }
972    }
973
974    pub fn equals(&self, other: &Column) -> bool {
975        // @scalar-opt
976        self.as_materialized_series()
977            .equals(other.as_materialized_series())
978    }
979
980    pub fn equals_missing(&self, other: &Column) -> bool {
981        // @scalar-opt
982        self.as_materialized_series()
983            .equals_missing(other.as_materialized_series())
984    }
985
986    pub fn set_sorted_flag(&mut self, sorted: IsSorted) {
987        // @scalar-opt
988        match self {
989            Column::Series(s) => s.set_sorted_flag(sorted),
990            Column::Partitioned(s) => s.set_sorted_flag(sorted),
991            Column::Scalar(_) => {},
992        }
993    }
994
995    pub fn get_flags(&self) -> StatisticsFlags {
996        match self {
997            Column::Series(s) => s.get_flags(),
998            // @partition-opt
999            Column::Partitioned(_) => StatisticsFlags::empty(),
1000            Column::Scalar(_) => {
1001                StatisticsFlags::IS_SORTED_ASC | StatisticsFlags::CAN_FAST_EXPLODE_LIST
1002            },
1003        }
1004    }
1005
1006    /// Returns whether the flags were set
1007    pub fn set_flags(&mut self, flags: StatisticsFlags) -> bool {
1008        match self {
1009            Column::Series(s) => {
1010                s.set_flags(flags);
1011                true
1012            },
1013            // @partition-opt
1014            Column::Partitioned(_) => false,
1015            Column::Scalar(_) => false,
1016        }
1017    }
1018
1019    pub fn vec_hash(
1020        &self,
1021        build_hasher: PlSeedableRandomStateQuality,
1022        buf: &mut Vec<u64>,
1023    ) -> PolarsResult<()> {
1024        // @scalar-opt?
1025        self.as_materialized_series().vec_hash(build_hasher, buf)
1026    }
1027
1028    pub fn vec_hash_combine(
1029        &self,
1030        build_hasher: PlSeedableRandomStateQuality,
1031        hashes: &mut [u64],
1032    ) -> PolarsResult<()> {
1033        // @scalar-opt?
1034        self.as_materialized_series()
1035            .vec_hash_combine(build_hasher, hashes)
1036    }
1037
1038    pub fn append(&mut self, other: &Column) -> PolarsResult<&mut Self> {
1039        // @scalar-opt
1040        self.into_materialized_series()
1041            .append(other.as_materialized_series())?;
1042        Ok(self)
1043    }
1044    pub fn append_owned(&mut self, other: Column) -> PolarsResult<&mut Self> {
1045        self.into_materialized_series()
1046            .append_owned(other.take_materialized_series())?;
1047        Ok(self)
1048    }
1049
1050    pub fn arg_sort(&self, options: SortOptions) -> IdxCa {
1051        if self.is_empty() {
1052            return IdxCa::from_vec(self.name().clone(), Vec::new());
1053        }
1054
1055        if self.null_count() == self.len() {
1056            // We might need to maintain order so just respect the descending parameter.
1057            let values = if options.descending {
1058                (0..self.len() as IdxSize).rev().collect()
1059            } else {
1060                (0..self.len() as IdxSize).collect()
1061            };
1062
1063            return IdxCa::from_vec(self.name().clone(), values);
1064        }
1065
1066        let is_sorted = Some(self.is_sorted_flag());
1067        let Some(is_sorted) = is_sorted.filter(|v| !matches!(v, IsSorted::Not)) else {
1068            return self.as_materialized_series().arg_sort(options);
1069        };
1070
1071        // Fast path: the data is sorted.
1072        let is_sorted_dsc = matches!(is_sorted, IsSorted::Descending);
1073        let invert = options.descending != is_sorted_dsc;
1074
1075        let mut values = Vec::with_capacity(self.len());
1076
1077        #[inline(never)]
1078        fn extend(
1079            start: IdxSize,
1080            end: IdxSize,
1081            slf: &Column,
1082            values: &mut Vec<IdxSize>,
1083            is_only_nulls: bool,
1084            invert: bool,
1085            maintain_order: bool,
1086        ) {
1087            debug_assert!(start <= end);
1088            debug_assert!(start as usize <= slf.len());
1089            debug_assert!(end as usize <= slf.len());
1090
1091            if !invert || is_only_nulls {
1092                values.extend(start..end);
1093                return;
1094            }
1095
1096            // If we don't have to maintain order but we have to invert. Just flip it around.
1097            if !maintain_order {
1098                values.extend((start..end).rev());
1099                return;
1100            }
1101
1102            // If we want to maintain order but we also needs to invert, we need to invert
1103            // per group of items.
1104            //
1105            // @NOTE: Since the column is sorted, arg_unique can also take a fast path and
1106            // just do a single traversal.
1107            let arg_unique = slf
1108                .slice(start as i64, (end - start) as usize)
1109                .arg_unique()
1110                .unwrap();
1111
1112            assert!(!arg_unique.has_nulls());
1113
1114            let num_unique = arg_unique.len();
1115
1116            // Fast path: all items are unique.
1117            if num_unique == (end - start) as usize {
1118                values.extend((start..end).rev());
1119                return;
1120            }
1121
1122            if num_unique == 1 {
1123                values.extend(start..end);
1124                return;
1125            }
1126
1127            let mut prev_idx = end - start;
1128            for chunk in arg_unique.downcast_iter() {
1129                for &idx in chunk.values().as_slice().iter().rev() {
1130                    values.extend(start + idx..start + prev_idx);
1131                    prev_idx = idx;
1132                }
1133            }
1134        }
1135        macro_rules! extend {
1136            ($start:expr, $end:expr) => {
1137                extend!($start, $end, is_only_nulls = false);
1138            };
1139            ($start:expr, $end:expr, is_only_nulls = $is_only_nulls:expr) => {
1140                extend(
1141                    $start,
1142                    $end,
1143                    self,
1144                    &mut values,
1145                    $is_only_nulls,
1146                    invert,
1147                    options.maintain_order,
1148                );
1149            };
1150        }
1151
1152        let length = self.len() as IdxSize;
1153        let null_count = self.null_count() as IdxSize;
1154
1155        if null_count == 0 {
1156            extend!(0, length);
1157        } else {
1158            let has_nulls_last = self.get(self.len() - 1).unwrap().is_null();
1159            match (options.nulls_last, has_nulls_last) {
1160                (true, true) => {
1161                    // Current: Nulls last, Wanted: Nulls last
1162                    extend!(0, length - null_count);
1163                    extend!(length - null_count, length, is_only_nulls = true);
1164                },
1165                (true, false) => {
1166                    // Current: Nulls first, Wanted: Nulls last
1167                    extend!(null_count, length);
1168                    extend!(0, null_count, is_only_nulls = true);
1169                },
1170                (false, true) => {
1171                    // Current: Nulls last, Wanted: Nulls first
1172                    extend!(length - null_count, length, is_only_nulls = true);
1173                    extend!(0, length - null_count);
1174                },
1175                (false, false) => {
1176                    // Current: Nulls first, Wanted: Nulls first
1177                    extend!(0, null_count, is_only_nulls = true);
1178                    extend!(null_count, length);
1179                },
1180            }
1181        }
1182
1183        // @NOTE: This can theoretically be pushed into the previous operation but it is really
1184        // worth it... probably not...
1185        if let Some(limit) = options.limit {
1186            let limit = limit.min(length);
1187            values.truncate(limit as usize);
1188        }
1189
1190        IdxCa::from_vec(self.name().clone(), values)
1191    }
1192
1193    pub fn arg_sort_multiple(
1194        &self,
1195        by: &[Column],
1196        options: &SortMultipleOptions,
1197    ) -> PolarsResult<IdxCa> {
1198        // @scalar-opt
1199        self.as_materialized_series().arg_sort_multiple(by, options)
1200    }
1201
1202    pub fn arg_unique(&self) -> PolarsResult<IdxCa> {
1203        match self {
1204            Column::Scalar(s) => Ok(IdxCa::new_vec(s.name().clone(), vec![0])),
1205            _ => self.as_materialized_series().arg_unique(),
1206        }
1207    }
1208
1209    pub fn bit_repr(&self) -> Option<BitRepr> {
1210        // @scalar-opt
1211        self.as_materialized_series().bit_repr()
1212    }
1213
1214    pub fn into_frame(self) -> DataFrame {
1215        // SAFETY: A single-column dataframe cannot have length mismatches or duplicate names
1216        unsafe { DataFrame::new_no_checks(self.len(), vec![self]) }
1217    }
1218
1219    pub fn extend(&mut self, other: &Column) -> PolarsResult<&mut Self> {
1220        // @scalar-opt
1221        self.into_materialized_series()
1222            .extend(other.as_materialized_series())?;
1223        Ok(self)
1224    }
1225
1226    pub fn rechunk(&self) -> Column {
1227        match self {
1228            Column::Series(s) => s.rechunk().into(),
1229            Column::Partitioned(s) => {
1230                if let Some(s) = s.lazy_as_materialized_series() {
1231                    // This should always hold for partitioned.
1232                    debug_assert_eq!(s.n_chunks(), 1)
1233                }
1234                self.clone()
1235            },
1236            Column::Scalar(s) => {
1237                if s.lazy_as_materialized_series()
1238                    .filter(|x| x.n_chunks() > 1)
1239                    .is_some()
1240                {
1241                    Column::Scalar(ScalarColumn::new(
1242                        s.name().clone(),
1243                        s.scalar().clone(),
1244                        s.len(),
1245                    ))
1246                } else {
1247                    self.clone()
1248                }
1249            },
1250        }
1251    }
1252
1253    pub fn explode(&self, skip_empty: bool) -> PolarsResult<Column> {
1254        self.as_materialized_series()
1255            .explode(skip_empty)
1256            .map(Column::from)
1257    }
1258    pub fn implode(&self) -> PolarsResult<ListChunked> {
1259        self.as_materialized_series().implode()
1260    }
1261
1262    pub fn fill_null(&self, strategy: FillNullStrategy) -> PolarsResult<Self> {
1263        // @scalar-opt
1264        self.as_materialized_series()
1265            .fill_null(strategy)
1266            .map(Column::from)
1267    }
1268
1269    pub fn divide(&self, rhs: &Column) -> PolarsResult<Self> {
1270        // @scalar-opt
1271        self.as_materialized_series()
1272            .divide(rhs.as_materialized_series())
1273            .map(Column::from)
1274    }
1275
1276    pub fn shift(&self, periods: i64) -> Column {
1277        // @scalar-opt
1278        self.as_materialized_series().shift(periods).into()
1279    }
1280
1281    #[cfg(feature = "zip_with")]
1282    pub fn zip_with(&self, mask: &BooleanChunked, other: &Self) -> PolarsResult<Self> {
1283        // @scalar-opt
1284        self.as_materialized_series()
1285            .zip_with(mask, other.as_materialized_series())
1286            .map(Self::from)
1287    }
1288
1289    #[cfg(feature = "zip_with")]
1290    pub fn zip_with_same_type(
1291        &self,
1292        mask: &ChunkedArray<BooleanType>,
1293        other: &Column,
1294    ) -> PolarsResult<Column> {
1295        // @scalar-opt
1296        self.as_materialized_series()
1297            .zip_with_same_type(mask, other.as_materialized_series())
1298            .map(Column::from)
1299    }
1300
1301    pub fn drop_nulls(&self) -> Column {
1302        match self {
1303            Column::Series(s) => s.drop_nulls().into_column(),
1304            // @partition-opt
1305            Column::Partitioned(s) => s.as_materialized_series().drop_nulls().into_column(),
1306            Column::Scalar(s) => s.drop_nulls().into_column(),
1307        }
1308    }
1309
1310    /// Packs every element into a list.
1311    pub fn as_list(&self) -> ListChunked {
1312        // @scalar-opt
1313        // @partition-opt
1314        self.as_materialized_series().as_list()
1315    }
1316
1317    pub fn is_sorted_flag(&self) -> IsSorted {
1318        match self {
1319            Column::Series(s) => s.is_sorted_flag(),
1320            Column::Partitioned(s) => s.partitions().is_sorted_flag(),
1321            Column::Scalar(_) => IsSorted::Ascending,
1322        }
1323    }
1324
1325    pub fn unique(&self) -> PolarsResult<Column> {
1326        match self {
1327            Column::Series(s) => s.unique().map(Column::from),
1328            // @partition-opt
1329            Column::Partitioned(s) => s.as_materialized_series().unique().map(Column::from),
1330            Column::Scalar(s) => {
1331                _ = s.as_single_value_series().unique()?;
1332                if s.is_empty() {
1333                    return Ok(s.clone().into_column());
1334                }
1335
1336                Ok(s.resize(1).into_column())
1337            },
1338        }
1339    }
1340    pub fn unique_stable(&self) -> PolarsResult<Column> {
1341        match self {
1342            Column::Series(s) => s.unique_stable().map(Column::from),
1343            // @partition-opt
1344            Column::Partitioned(s) => s.as_materialized_series().unique_stable().map(Column::from),
1345            Column::Scalar(s) => {
1346                _ = s.as_single_value_series().unique_stable()?;
1347                if s.is_empty() {
1348                    return Ok(s.clone().into_column());
1349                }
1350
1351                Ok(s.resize(1).into_column())
1352            },
1353        }
1354    }
1355
1356    pub fn reshape_list(&self, dimensions: &[ReshapeDimension]) -> PolarsResult<Self> {
1357        // @scalar-opt
1358        self.as_materialized_series()
1359            .reshape_list(dimensions)
1360            .map(Self::from)
1361    }
1362
1363    #[cfg(feature = "dtype-array")]
1364    pub fn reshape_array(&self, dimensions: &[ReshapeDimension]) -> PolarsResult<Self> {
1365        // @scalar-opt
1366        self.as_materialized_series()
1367            .reshape_array(dimensions)
1368            .map(Self::from)
1369    }
1370
1371    pub fn sort(&self, sort_options: SortOptions) -> PolarsResult<Self> {
1372        // @scalar-opt
1373        self.as_materialized_series()
1374            .sort(sort_options)
1375            .map(Self::from)
1376    }
1377
1378    pub fn filter(&self, filter: &BooleanChunked) -> PolarsResult<Self> {
1379        match self {
1380            Column::Series(s) => s.filter(filter).map(Column::from),
1381            Column::Partitioned(s) => s.as_materialized_series().filter(filter).map(Column::from),
1382            Column::Scalar(s) => {
1383                if s.is_empty() {
1384                    return Ok(s.clone().into_column());
1385                }
1386
1387                // Broadcasting
1388                if filter.len() == 1 {
1389                    return match filter.get(0) {
1390                        Some(true) => Ok(s.clone().into_column()),
1391                        _ => Ok(s.resize(0).into_column()),
1392                    };
1393                }
1394
1395                Ok(s.resize(filter.sum().unwrap() as usize).into_column())
1396            },
1397        }
1398    }
1399
1400    #[cfg(feature = "random")]
1401    pub fn shuffle(&self, seed: Option<u64>) -> Self {
1402        // @scalar-opt
1403        self.as_materialized_series().shuffle(seed).into()
1404    }
1405
1406    #[cfg(feature = "random")]
1407    pub fn sample_frac(
1408        &self,
1409        frac: f64,
1410        with_replacement: bool,
1411        shuffle: bool,
1412        seed: Option<u64>,
1413    ) -> PolarsResult<Self> {
1414        self.as_materialized_series()
1415            .sample_frac(frac, with_replacement, shuffle, seed)
1416            .map(Self::from)
1417    }
1418
1419    #[cfg(feature = "random")]
1420    pub fn sample_n(
1421        &self,
1422        n: usize,
1423        with_replacement: bool,
1424        shuffle: bool,
1425        seed: Option<u64>,
1426    ) -> PolarsResult<Self> {
1427        self.as_materialized_series()
1428            .sample_n(n, with_replacement, shuffle, seed)
1429            .map(Self::from)
1430    }
1431
1432    pub fn gather_every(&self, n: usize, offset: usize) -> PolarsResult<Column> {
1433        polars_ensure!(n > 0, InvalidOperation: "gather_every(n): n should be positive");
1434        if self.len().saturating_sub(offset) == 0 {
1435            return Ok(self.clear());
1436        }
1437
1438        match self {
1439            Column::Series(s) => Ok(s.gather_every(n, offset)?.into()),
1440            Column::Partitioned(s) => {
1441                Ok(s.as_materialized_series().gather_every(n, offset)?.into())
1442            },
1443            Column::Scalar(s) => {
1444                let total = s.len() - offset;
1445                Ok(s.resize(1 + (total - 1) / n).into())
1446            },
1447        }
1448    }
1449
1450    pub fn extend_constant(&self, value: AnyValue, n: usize) -> PolarsResult<Self> {
1451        if self.is_empty() {
1452            return Ok(Self::new_scalar(
1453                self.name().clone(),
1454                Scalar::new(self.dtype().clone(), value.into_static()),
1455                n,
1456            ));
1457        }
1458
1459        match self {
1460            Column::Series(s) => s.extend_constant(value, n).map(Column::from),
1461            Column::Partitioned(s) => s.extend_constant(value, n).map(Column::from),
1462            Column::Scalar(s) => {
1463                if s.scalar().as_any_value() == value {
1464                    Ok(s.resize(s.len() + n).into())
1465                } else {
1466                    s.as_materialized_series()
1467                        .extend_constant(value, n)
1468                        .map(Column::from)
1469                }
1470            },
1471        }
1472    }
1473
1474    pub fn is_finite(&self) -> PolarsResult<BooleanChunked> {
1475        self.try_map_unary_elementwise_to_bool(|s| s.is_finite())
1476    }
1477    pub fn is_infinite(&self) -> PolarsResult<BooleanChunked> {
1478        self.try_map_unary_elementwise_to_bool(|s| s.is_infinite())
1479    }
1480    pub fn is_nan(&self) -> PolarsResult<BooleanChunked> {
1481        self.try_map_unary_elementwise_to_bool(|s| s.is_nan())
1482    }
1483    pub fn is_not_nan(&self) -> PolarsResult<BooleanChunked> {
1484        self.try_map_unary_elementwise_to_bool(|s| s.is_not_nan())
1485    }
1486
1487    pub fn wrapping_trunc_div_scalar<T>(&self, rhs: T) -> Self
1488    where
1489        T: Num + NumCast,
1490    {
1491        // @scalar-opt
1492        self.as_materialized_series()
1493            .wrapping_trunc_div_scalar(rhs)
1494            .into()
1495    }
1496
1497    pub fn product(&self) -> PolarsResult<Scalar> {
1498        // @scalar-opt
1499        self.as_materialized_series().product()
1500    }
1501
1502    pub fn phys_iter(&self) -> SeriesPhysIter<'_> {
1503        // @scalar-opt
1504        self.as_materialized_series().phys_iter()
1505    }
1506
1507    #[inline]
1508    pub fn get(&self, index: usize) -> PolarsResult<AnyValue<'_>> {
1509        polars_ensure!(index < self.len(), oob = index, self.len());
1510
1511        // SAFETY: Bounds check done just before.
1512        Ok(unsafe { self.get_unchecked(index) })
1513    }
1514    /// # Safety
1515    ///
1516    /// Does not perform bounds check on `index`
1517    #[inline(always)]
1518    pub unsafe fn get_unchecked(&self, index: usize) -> AnyValue<'_> {
1519        debug_assert!(index < self.len());
1520
1521        match self {
1522            Column::Series(s) => unsafe { s.get_unchecked(index) },
1523            Column::Partitioned(s) => unsafe { s.get_unchecked(index) },
1524            Column::Scalar(s) => s.scalar().as_any_value(),
1525        }
1526    }
1527
1528    #[cfg(feature = "object")]
1529    pub fn get_object(
1530        &self,
1531        index: usize,
1532    ) -> Option<&dyn crate::chunked_array::object::PolarsObjectSafe> {
1533        self.as_materialized_series().get_object(index)
1534    }
1535
1536    pub fn bitand(&self, rhs: &Self) -> PolarsResult<Self> {
1537        self.try_apply_broadcasting_binary_elementwise(rhs, |l, r| l & r)
1538    }
1539    pub fn bitor(&self, rhs: &Self) -> PolarsResult<Self> {
1540        self.try_apply_broadcasting_binary_elementwise(rhs, |l, r| l | r)
1541    }
1542    pub fn bitxor(&self, rhs: &Self) -> PolarsResult<Self> {
1543        self.try_apply_broadcasting_binary_elementwise(rhs, |l, r| l ^ r)
1544    }
1545
1546    pub fn try_add_owned(self, other: Self) -> PolarsResult<Self> {
1547        match (self, other) {
1548            (Column::Series(lhs), Column::Series(rhs)) => {
1549                lhs.take().try_add_owned(rhs.take()).map(Column::from)
1550            },
1551            (lhs, rhs) => lhs + rhs,
1552        }
1553    }
1554    pub fn try_sub_owned(self, other: Self) -> PolarsResult<Self> {
1555        match (self, other) {
1556            (Column::Series(lhs), Column::Series(rhs)) => {
1557                lhs.take().try_sub_owned(rhs.take()).map(Column::from)
1558            },
1559            (lhs, rhs) => lhs - rhs,
1560        }
1561    }
1562    pub fn try_mul_owned(self, other: Self) -> PolarsResult<Self> {
1563        match (self, other) {
1564            (Column::Series(lhs), Column::Series(rhs)) => {
1565                lhs.take().try_mul_owned(rhs.take()).map(Column::from)
1566            },
1567            (lhs, rhs) => lhs * rhs,
1568        }
1569    }
1570
1571    pub(crate) fn str_value(&self, index: usize) -> PolarsResult<Cow<'_, str>> {
1572        Ok(self.get(index)?.str_value())
1573    }
1574
1575    pub fn min_reduce(&self) -> PolarsResult<Scalar> {
1576        match self {
1577            Column::Series(s) => s.min_reduce(),
1578            Column::Partitioned(s) => s.min_reduce(),
1579            Column::Scalar(s) => {
1580                // We don't really want to deal with handling the full semantics here so we just
1581                // cast to a single value series. This is a tiny bit wasteful, but probably fine.
1582                s.as_single_value_series().min_reduce()
1583            },
1584        }
1585    }
1586    pub fn max_reduce(&self) -> PolarsResult<Scalar> {
1587        match self {
1588            Column::Series(s) => s.max_reduce(),
1589            Column::Partitioned(s) => s.max_reduce(),
1590            Column::Scalar(s) => {
1591                // We don't really want to deal with handling the full semantics here so we just
1592                // cast to a single value series. This is a tiny bit wasteful, but probably fine.
1593                s.as_single_value_series().max_reduce()
1594            },
1595        }
1596    }
1597    pub fn median_reduce(&self) -> PolarsResult<Scalar> {
1598        match self {
1599            Column::Series(s) => s.median_reduce(),
1600            Column::Partitioned(s) => s.as_materialized_series().median_reduce(),
1601            Column::Scalar(s) => {
1602                // We don't really want to deal with handling the full semantics here so we just
1603                // cast to a single value series. This is a tiny bit wasteful, but probably fine.
1604                s.as_single_value_series().median_reduce()
1605            },
1606        }
1607    }
1608    pub fn mean_reduce(&self) -> PolarsResult<Scalar> {
1609        match self {
1610            Column::Series(s) => s.mean_reduce(),
1611            Column::Partitioned(s) => s.as_materialized_series().mean_reduce(),
1612            Column::Scalar(s) => {
1613                // We don't really want to deal with handling the full semantics here so we just
1614                // cast to a single value series. This is a tiny bit wasteful, but probably fine.
1615                s.as_single_value_series().mean_reduce()
1616            },
1617        }
1618    }
1619    pub fn std_reduce(&self, ddof: u8) -> PolarsResult<Scalar> {
1620        match self {
1621            Column::Series(s) => s.std_reduce(ddof),
1622            Column::Partitioned(s) => s.as_materialized_series().std_reduce(ddof),
1623            Column::Scalar(s) => {
1624                // We don't really want to deal with handling the full semantics here so we just
1625                // cast to a small series. This is a tiny bit wasteful, but probably fine.
1626                let n = s.len().min(ddof as usize + 1);
1627                s.as_n_values_series(n).std_reduce(ddof)
1628            },
1629        }
1630    }
1631    pub fn var_reduce(&self, ddof: u8) -> PolarsResult<Scalar> {
1632        match self {
1633            Column::Series(s) => s.var_reduce(ddof),
1634            Column::Partitioned(s) => s.as_materialized_series().var_reduce(ddof),
1635            Column::Scalar(s) => {
1636                // We don't really want to deal with handling the full semantics here so we just
1637                // cast to a small series. This is a tiny bit wasteful, but probably fine.
1638                let n = s.len().min(ddof as usize + 1);
1639                s.as_n_values_series(n).var_reduce(ddof)
1640            },
1641        }
1642    }
1643    pub fn sum_reduce(&self) -> PolarsResult<Scalar> {
1644        // @partition-opt
1645        // @scalar-opt
1646        self.as_materialized_series().sum_reduce()
1647    }
1648    pub fn and_reduce(&self) -> PolarsResult<Scalar> {
1649        match self {
1650            Column::Series(s) => s.and_reduce(),
1651            Column::Partitioned(s) => s.and_reduce(),
1652            Column::Scalar(s) => {
1653                // We don't really want to deal with handling the full semantics here so we just
1654                // cast to a single value series. This is a tiny bit wasteful, but probably fine.
1655                s.as_single_value_series().and_reduce()
1656            },
1657        }
1658    }
1659    pub fn or_reduce(&self) -> PolarsResult<Scalar> {
1660        match self {
1661            Column::Series(s) => s.or_reduce(),
1662            Column::Partitioned(s) => s.or_reduce(),
1663            Column::Scalar(s) => {
1664                // We don't really want to deal with handling the full semantics here so we just
1665                // cast to a single value series. This is a tiny bit wasteful, but probably fine.
1666                s.as_single_value_series().or_reduce()
1667            },
1668        }
1669    }
1670    pub fn xor_reduce(&self) -> PolarsResult<Scalar> {
1671        match self {
1672            Column::Series(s) => s.xor_reduce(),
1673            // @partition-opt
1674            Column::Partitioned(s) => s.as_materialized_series().xor_reduce(),
1675            Column::Scalar(s) => {
1676                // We don't really want to deal with handling the full semantics here so we just
1677                // cast to a single value series. This is a tiny bit wasteful, but probably fine.
1678                //
1679                // We have to deal with the fact that xor is 0 if there is an even number of
1680                // elements and the value if there is an odd number of elements. If there are zero
1681                // elements the result should be `null`.
1682                s.as_n_values_series(2 - s.len() % 2).xor_reduce()
1683            },
1684        }
1685    }
1686    pub fn n_unique(&self) -> PolarsResult<usize> {
1687        match self {
1688            Column::Series(s) => s.n_unique(),
1689            Column::Partitioned(s) => s.partitions().n_unique(),
1690            Column::Scalar(s) => s.as_single_value_series().n_unique(),
1691        }
1692    }
1693    pub fn quantile_reduce(&self, quantile: f64, method: QuantileMethod) -> PolarsResult<Scalar> {
1694        self.as_materialized_series()
1695            .quantile_reduce(quantile, method)
1696    }
1697
1698    pub(crate) fn estimated_size(&self) -> usize {
1699        // @scalar-opt
1700        self.as_materialized_series().estimated_size()
1701    }
1702
1703    pub fn sort_with(&self, options: SortOptions) -> PolarsResult<Self> {
1704        match self {
1705            Column::Series(s) => s.sort_with(options).map(Self::from),
1706            // @partition-opt
1707            Column::Partitioned(s) => s
1708                .as_materialized_series()
1709                .sort_with(options)
1710                .map(Self::from),
1711            Column::Scalar(s) => {
1712                // This makes this function throw the same errors as Series::sort_with
1713                _ = s.as_single_value_series().sort_with(options)?;
1714
1715                Ok(self.clone())
1716            },
1717        }
1718    }
1719
1720    pub fn map_unary_elementwise_to_bool(
1721        &self,
1722        f: impl Fn(&Series) -> BooleanChunked,
1723    ) -> BooleanChunked {
1724        self.try_map_unary_elementwise_to_bool(|s| Ok(f(s)))
1725            .unwrap()
1726    }
1727    pub fn try_map_unary_elementwise_to_bool(
1728        &self,
1729        f: impl Fn(&Series) -> PolarsResult<BooleanChunked>,
1730    ) -> PolarsResult<BooleanChunked> {
1731        match self {
1732            Column::Series(s) => f(s),
1733            Column::Partitioned(s) => f(s.as_materialized_series()),
1734            Column::Scalar(s) => Ok(f(&s.as_single_value_series())?.new_from_index(0, s.len())),
1735        }
1736    }
1737
1738    pub fn apply_unary_elementwise(&self, f: impl Fn(&Series) -> Series) -> Column {
1739        self.try_apply_unary_elementwise(|s| Ok(f(s))).unwrap()
1740    }
1741    pub fn try_apply_unary_elementwise(
1742        &self,
1743        f: impl Fn(&Series) -> PolarsResult<Series>,
1744    ) -> PolarsResult<Column> {
1745        match self {
1746            Column::Series(s) => f(s).map(Column::from),
1747            Column::Partitioned(s) => s.try_apply_unary_elementwise(f).map(Self::from),
1748            Column::Scalar(s) => Ok(ScalarColumn::from_single_value_series(
1749                f(&s.as_single_value_series())?,
1750                s.len(),
1751            )
1752            .into()),
1753        }
1754    }
1755
1756    pub fn apply_broadcasting_binary_elementwise(
1757        &self,
1758        other: &Self,
1759        op: impl Fn(&Series, &Series) -> Series,
1760    ) -> PolarsResult<Column> {
1761        self.try_apply_broadcasting_binary_elementwise(other, |lhs, rhs| Ok(op(lhs, rhs)))
1762    }
1763    pub fn try_apply_broadcasting_binary_elementwise(
1764        &self,
1765        other: &Self,
1766        op: impl Fn(&Series, &Series) -> PolarsResult<Series>,
1767    ) -> PolarsResult<Column> {
1768        fn output_length(a: &Column, b: &Column) -> PolarsResult<usize> {
1769            match (a.len(), b.len()) {
1770                // broadcasting
1771                (1, o) | (o, 1) => Ok(o),
1772                // equal
1773                (a, b) if a == b => Ok(a),
1774                // unequal
1775                (a, b) => {
1776                    polars_bail!(InvalidOperation: "cannot do a binary operation on columns of different lengths: got {} and {}", a, b)
1777                },
1778            }
1779        }
1780
1781        // Here we rely on the underlying broadcast operations.
1782        let length = output_length(self, other)?;
1783        match (self, other) {
1784            (Column::Series(lhs), Column::Series(rhs)) => op(lhs, rhs).map(Column::from),
1785            (Column::Series(lhs), Column::Scalar(rhs)) => {
1786                op(lhs, &rhs.as_single_value_series()).map(Column::from)
1787            },
1788            (Column::Scalar(lhs), Column::Series(rhs)) => {
1789                op(&lhs.as_single_value_series(), rhs).map(Column::from)
1790            },
1791            (Column::Scalar(lhs), Column::Scalar(rhs)) => {
1792                let lhs = lhs.as_single_value_series();
1793                let rhs = rhs.as_single_value_series();
1794
1795                Ok(ScalarColumn::from_single_value_series(op(&lhs, &rhs)?, length).into_column())
1796            },
1797            // @partition-opt
1798            (lhs, rhs) => {
1799                op(lhs.as_materialized_series(), rhs.as_materialized_series()).map(Column::from)
1800            },
1801        }
1802    }
1803
1804    pub fn apply_binary_elementwise(
1805        &self,
1806        other: &Self,
1807        f: impl Fn(&Series, &Series) -> Series,
1808        f_lb: impl Fn(&Scalar, &Series) -> Series,
1809        f_rb: impl Fn(&Series, &Scalar) -> Series,
1810    ) -> Column {
1811        self.try_apply_binary_elementwise(
1812            other,
1813            |lhs, rhs| Ok(f(lhs, rhs)),
1814            |lhs, rhs| Ok(f_lb(lhs, rhs)),
1815            |lhs, rhs| Ok(f_rb(lhs, rhs)),
1816        )
1817        .unwrap()
1818    }
1819    pub fn try_apply_binary_elementwise(
1820        &self,
1821        other: &Self,
1822        f: impl Fn(&Series, &Series) -> PolarsResult<Series>,
1823        f_lb: impl Fn(&Scalar, &Series) -> PolarsResult<Series>,
1824        f_rb: impl Fn(&Series, &Scalar) -> PolarsResult<Series>,
1825    ) -> PolarsResult<Column> {
1826        debug_assert_eq!(self.len(), other.len());
1827
1828        match (self, other) {
1829            (Column::Series(lhs), Column::Series(rhs)) => f(lhs, rhs).map(Column::from),
1830            (Column::Series(lhs), Column::Scalar(rhs)) => f_rb(lhs, rhs.scalar()).map(Column::from),
1831            (Column::Scalar(lhs), Column::Series(rhs)) => f_lb(lhs.scalar(), rhs).map(Column::from),
1832            (Column::Scalar(lhs), Column::Scalar(rhs)) => {
1833                let lhs = lhs.as_single_value_series();
1834                let rhs = rhs.as_single_value_series();
1835
1836                Ok(
1837                    ScalarColumn::from_single_value_series(f(&lhs, &rhs)?, self.len())
1838                        .into_column(),
1839                )
1840            },
1841            // @partition-opt
1842            (lhs, rhs) => {
1843                f(lhs.as_materialized_series(), rhs.as_materialized_series()).map(Column::from)
1844            },
1845        }
1846    }
1847
1848    #[cfg(feature = "approx_unique")]
1849    pub fn approx_n_unique(&self) -> PolarsResult<IdxSize> {
1850        match self {
1851            Column::Series(s) => s.approx_n_unique(),
1852            // @partition-opt
1853            Column::Partitioned(s) => s.as_materialized_series().approx_n_unique(),
1854            Column::Scalar(s) => {
1855                // @NOTE: We do this for the error handling.
1856                s.as_single_value_series().approx_n_unique()?;
1857                Ok(1)
1858            },
1859        }
1860    }
1861
1862    pub fn n_chunks(&self) -> usize {
1863        match self {
1864            Column::Series(s) => s.n_chunks(),
1865            Column::Scalar(s) => s.lazy_as_materialized_series().map_or(1, |x| x.n_chunks()),
1866            Column::Partitioned(s) => {
1867                if let Some(s) = s.lazy_as_materialized_series() {
1868                    // This should always hold for partitioned.
1869                    debug_assert_eq!(s.n_chunks(), 1)
1870                }
1871                1
1872            },
1873        }
1874    }
1875
1876    #[expect(clippy::wrong_self_convention)]
1877    pub(crate) fn into_total_ord_inner<'a>(&'a self) -> Box<dyn TotalOrdInner + 'a> {
1878        // @scalar-opt
1879        self.as_materialized_series().into_total_ord_inner()
1880    }
1881    #[expect(unused, clippy::wrong_self_convention)]
1882    pub(crate) fn into_total_eq_inner<'a>(&'a self) -> Box<dyn TotalEqInner + 'a> {
1883        // @scalar-opt
1884        self.as_materialized_series().into_total_eq_inner()
1885    }
1886
1887    pub fn rechunk_to_arrow(self, compat_level: CompatLevel) -> Box<dyn Array> {
1888        // Rechunk to one chunk if necessary
1889        let mut series = self.take_materialized_series();
1890        if series.n_chunks() > 1 {
1891            series = series.rechunk();
1892        }
1893        series.to_arrow(0, compat_level)
1894    }
1895
1896    pub fn trim_lists_to_normalized_offsets(&self) -> Option<Column> {
1897        self.as_materialized_series()
1898            .trim_lists_to_normalized_offsets()
1899            .map(Column::from)
1900    }
1901
1902    pub fn propagate_nulls(&self) -> Option<Column> {
1903        self.as_materialized_series()
1904            .propagate_nulls()
1905            .map(Column::from)
1906    }
1907
1908    pub fn deposit(&self, validity: &Bitmap) -> Column {
1909        self.as_materialized_series()
1910            .deposit(validity)
1911            .into_column()
1912    }
1913
1914    pub fn rechunk_validity(&self) -> Option<Bitmap> {
1915        // @scalar-opt
1916        self.as_materialized_series().rechunk_validity()
1917    }
1918}
1919
1920impl Default for Column {
1921    fn default() -> Self {
1922        Self::new_scalar(
1923            PlSmallStr::EMPTY,
1924            Scalar::new(DataType::Int64, AnyValue::Null),
1925            0,
1926        )
1927    }
1928}
1929
1930impl PartialEq for Column {
1931    fn eq(&self, other: &Self) -> bool {
1932        // @scalar-opt
1933        self.as_materialized_series()
1934            .eq(other.as_materialized_series())
1935    }
1936}
1937
1938impl From<Series> for Column {
1939    #[inline]
1940    fn from(series: Series) -> Self {
1941        // We instantiate a Scalar Column if the Series is length is 1. This makes it possible for
1942        // future operations to be faster.
1943        if series.len() == 1 {
1944            return Self::Scalar(ScalarColumn::unit_scalar_from_series(series));
1945        }
1946
1947        Self::Series(SeriesColumn::new(series))
1948    }
1949}
1950
1951impl<T: IntoSeries> IntoColumn for T {
1952    #[inline]
1953    fn into_column(self) -> Column {
1954        self.into_series().into()
1955    }
1956}
1957
1958impl IntoColumn for Column {
1959    #[inline(always)]
1960    fn into_column(self) -> Column {
1961        self
1962    }
1963}
1964
1965/// We don't want to serialize the scalar columns. So this helps pretend that columns are always
1966/// initialized without implementing From<Column> for Series.
1967///
1968/// Those casts should be explicit.
1969#[derive(Clone)]
1970#[cfg_attr(feature = "serde", derive(serde::Serialize))]
1971#[cfg_attr(feature = "serde", serde(into = "Series"))]
1972struct _SerdeSeries(Series);
1973
1974impl From<Column> for _SerdeSeries {
1975    #[inline]
1976    fn from(value: Column) -> Self {
1977        Self(value.take_materialized_series())
1978    }
1979}
1980
1981impl From<_SerdeSeries> for Series {
1982    #[inline]
1983    fn from(value: _SerdeSeries) -> Self {
1984        value.0
1985    }
1986}