Skip to main content

polars_core/frame/column/
mod.rs

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