Skip to main content

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