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