polars_core/frame/column/
mod.rs

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