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