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