Skip to main content

polars_core/frame/
mod.rs

1#![allow(unsafe_op_in_unsafe_fn)]
2//! DataFrame module.
3use std::borrow::Cow;
4
5use either::Either;
6use polars_arrow::datatypes::ArrowSchemaRef;
7use polars_row::ArrayRef;
8use polars_utils::UnitVec;
9use polars_utils::itertools::Itertools;
10use rayon::prelude::*;
11
12use crate::chunked_array::flags::StatisticsFlags;
13#[cfg(feature = "algorithm_group_by")]
14use crate::chunked_array::ops::unique::is_unique_helper;
15use crate::prelude::gather::check_bounds_ca;
16use crate::prelude::*;
17#[cfg(feature = "row_hash")]
18use crate::utils::split_df;
19use crate::utils::{Container, NoNull, slice_offsets, try_get_supertype};
20use crate::{HEAD_DEFAULT_LENGTH, TAIL_DEFAULT_LENGTH};
21
22#[cfg(feature = "dataframe_arithmetic")]
23mod arithmetic;
24pub mod builder;
25mod chunks;
26pub use chunks::chunk_df_for_writing;
27pub mod column;
28mod dataframe;
29mod filter;
30mod projection;
31pub use dataframe::DataFrame;
32use filter::filter_zero_width;
33use projection::{AmortizedColumnSelector, LINEAR_SEARCH_LIMIT};
34
35pub mod explode;
36mod from;
37#[cfg(feature = "algorithm_group_by")]
38pub mod group_by;
39pub(crate) mod horizontal;
40#[cfg(any(feature = "rows", feature = "object"))]
41pub mod row;
42mod top_k;
43mod upstream_traits;
44pub(crate) mod validation;
45
46use polars_arrow::record_batch::{RecordBatch, RecordBatchT};
47use polars_utils::pl_str::PlSmallStr;
48#[cfg(feature = "serde")]
49use serde::{Deserialize, Serialize};
50use strum_macros::IntoStaticStr;
51
52#[cfg(feature = "row_hash")]
53use crate::hashing::_df_rows_to_hashes_threaded_vertical;
54use crate::prelude::sort::arg_sort;
55use crate::runtime::RAYON;
56use crate::series::IsSorted;
57
58#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Hash, IntoStaticStr)]
59#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
60#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
61#[strum(serialize_all = "snake_case")]
62pub enum UniqueKeepStrategy {
63    /// Keep the first unique row.
64    First,
65    /// Keep the last unique row.
66    Last,
67    /// Keep None of the unique rows.
68    None,
69    /// Keep any of the unique rows
70    /// This allows more optimizations
71    #[default]
72    Any,
73}
74
75#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Hash, IntoStaticStr)]
76#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
77#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
78#[strum(serialize_all = "snake_case")]
79/// Naming strategy for the results of a pivot.
80pub enum PivotColumnNaming {
81    /// Always combine the values and on-column names.
82    Combine,
83    /// Prefix the values column name only if there is more than one values
84    /// column.
85    #[default]
86    Auto,
87}
88
89impl DataFrame {
90    pub fn materialized_column_iter(&self) -> impl ExactSizeIterator<Item = &Series> {
91        self.columns().iter().map(Column::as_materialized_series)
92    }
93
94    /// Returns an estimation of the total (heap) allocated size of the `DataFrame` in bytes.
95    ///
96    /// # Implementation
97    /// This estimation is the sum of the size of its buffers, validity, including nested arrays.
98    /// Multiple arrays may share buffers and bitmaps. Therefore, the size of 2 arrays is not the
99    /// sum of the sizes computed from this function. In particular, [`StructArray`]'s size is an upper bound.
100    ///
101    /// When an array is sliced, its allocated size remains constant because the buffer unchanged.
102    /// However, this function will yield a smaller number. This is because this function returns
103    /// the visible size of the buffer, not its total capacity.
104    ///
105    /// FFI buffers are included in this estimation.
106    ///
107    /// If `expanded` is true, scalar columns are counted as if they were materialized. Otherwise,
108    /// they are counted by the memory they currently use.
109    pub fn estimated_size(&self, expanded: bool) -> usize {
110        self.columns()
111            .iter()
112            .map(|c| c.estimated_size(expanded))
113            .sum()
114    }
115
116    pub fn try_apply_columns(
117        &self,
118        func: impl Fn(&Column) -> PolarsResult<Column> + Send + Sync,
119    ) -> PolarsResult<Vec<Column>> {
120        return inner(self, &func);
121
122        fn inner(
123            slf: &DataFrame,
124            func: &(dyn Fn(&Column) -> PolarsResult<Column> + Send + Sync),
125        ) -> PolarsResult<Vec<Column>> {
126            slf.columns().iter().map(func).collect()
127        }
128    }
129
130    pub fn apply_columns(&self, func: impl Fn(&Column) -> Column + Send + Sync) -> Vec<Column> {
131        return inner(self, &func);
132
133        fn inner(slf: &DataFrame, func: &(dyn Fn(&Column) -> Column + Send + Sync)) -> Vec<Column> {
134            slf.columns().iter().map(func).collect()
135        }
136    }
137
138    pub fn try_apply_columns_par(
139        &self,
140        func: impl Fn(&Column) -> PolarsResult<Column> + Send + Sync,
141    ) -> PolarsResult<Vec<Column>> {
142        return inner(self, &func);
143
144        fn inner(
145            slf: &DataFrame,
146            func: &(dyn Fn(&Column) -> PolarsResult<Column> + Send + Sync),
147        ) -> PolarsResult<Vec<Column>> {
148            RAYON.install(|| slf.columns().par_iter().map(func).collect())
149        }
150    }
151
152    pub fn apply_columns_par(&self, func: impl Fn(&Column) -> Column + Send + Sync) -> Vec<Column> {
153        return inner(self, &func);
154
155        fn inner(slf: &DataFrame, func: &(dyn Fn(&Column) -> Column + Send + Sync)) -> Vec<Column> {
156            RAYON.install(|| slf.columns().par_iter().map(func).collect())
157        }
158    }
159
160    /// Reserve additional slots into the chunks of the series.
161    pub(crate) fn reserve_chunks(&mut self, additional: usize) {
162        for s in unsafe { self.columns_mut_retain_schema() } {
163            if let Column::Series(s) = s {
164                // SAFETY:
165                // do not modify the data, simply resize.
166                unsafe { s.chunks_mut().reserve(additional) }
167            }
168        }
169    }
170    pub fn new_from_index(&self, index: usize, height: usize) -> Self {
171        let new_cols = self.apply_columns(|c| c.new_from_index(index, height));
172
173        unsafe { Self::_new_unchecked_impl(height, new_cols).with_schema_from(self) }
174    }
175
176    /// Create a new `DataFrame` with the given schema, only containing nulls.
177    pub fn full_null(schema: &Schema, height: usize) -> Self {
178        let columns = schema
179            .iter_fields()
180            .map(|f| Column::full_null(f.name().clone(), height, f.dtype()))
181            .collect();
182
183        unsafe { DataFrame::_new_unchecked_impl(height, columns) }
184    }
185
186    /// Ensure this DataFrame matches the given schema. Casts null columns to
187    /// the expected schema if necessary (but nothing else).
188    pub fn ensure_matches_schema(&mut self, schema: &Schema) -> PolarsResult<()> {
189        let mut did_cast = false;
190        let cached_schema = self.cached_schema().cloned();
191
192        for (col, (name, dt)) in unsafe { self.columns_mut() }.iter_mut().zip(schema.iter()) {
193            polars_ensure!(
194                col.name() == name,
195                SchemaMismatch: "column name mismatch: expected {:?}, found {:?}",
196                name,
197                col.name()
198            );
199
200            let needs_cast = col.dtype().matches_schema_type(dt)?;
201
202            if needs_cast {
203                *col = col.cast(dt)?;
204                did_cast = true;
205            }
206        }
207
208        if !did_cast {
209            unsafe { self.set_opt_schema(cached_schema) };
210        }
211
212        Ok(())
213    }
214
215    /// Add a new column at index 0 that counts the rows.
216    ///
217    /// # Example
218    ///
219    /// ```
220    /// # use polars_core::prelude::*;
221    /// let df1: DataFrame = df!("Name" => ["James", "Mary", "John", "Patricia"])?;
222    /// assert_eq!(df1.shape(), (4, 1));
223    ///
224    /// let df2: DataFrame = df1.with_row_index("Id".into(), None)?;
225    /// assert_eq!(df2.shape(), (4, 2));
226    /// println!("{}", df2);
227    ///
228    /// # Ok::<(), PolarsError>(())
229    /// ```
230    ///
231    /// Output:
232    ///
233    /// ```text
234    ///  shape: (4, 2)
235    ///  +-----+----------+
236    ///  | Id  | Name     |
237    ///  | --- | ---      |
238    ///  | u32 | str      |
239    ///  +=====+==========+
240    ///  | 0   | James    |
241    ///  +-----+----------+
242    ///  | 1   | Mary     |
243    ///  +-----+----------+
244    ///  | 2   | John     |
245    ///  +-----+----------+
246    ///  | 3   | Patricia |
247    ///  +-----+----------+
248    /// ```
249    pub fn with_row_index(&self, name: PlSmallStr, offset: Option<IdxSize>) -> PolarsResult<Self> {
250        let mut new_columns = Vec::with_capacity(self.width() + 1);
251        let offset = offset.unwrap_or(0);
252
253        if self.get_column_index(&name).is_some() {
254            polars_bail!(duplicate = name)
255        }
256
257        let col = Column::new_row_index(name, offset, self.height())?;
258        new_columns.push(col);
259        new_columns.extend_from_slice(self.columns());
260
261        Ok(unsafe { DataFrame::new_unchecked(self.height(), new_columns) })
262    }
263
264    /// Add a row index column in place.
265    ///
266    /// # Safety
267    /// The caller should ensure the DataFrame does not already contain a column with the given name.
268    ///
269    /// # Panics
270    /// Panics if the resulting column would reach or overflow IdxSize::MAX.
271    pub unsafe fn with_row_index_mut(
272        &mut self,
273        name: PlSmallStr,
274        offset: Option<IdxSize>,
275    ) -> &mut Self {
276        debug_assert!(
277            self.get_column_index(&name).is_none(),
278            "with_row_index_mut(): column with name {} already exists",
279            name
280        );
281
282        let offset = offset.unwrap_or(0);
283        let col = Column::new_row_index(name, offset, self.height()).unwrap();
284
285        unsafe { self.columns_mut() }.insert(0, col);
286        self
287    }
288
289    /// Shrink the capacity of this DataFrame to fit its length.
290    pub fn shrink_to_fit(&mut self) {
291        // Don't parallelize this. Memory overhead
292        for s in unsafe { self.columns_mut_retain_schema() } {
293            s.shrink_to_fit();
294        }
295    }
296
297    /// Aggregate all the chunks in the DataFrame to a single chunk in parallel.
298    /// This may lead to more peak memory consumption.
299    pub fn rechunk_mut_par(&mut self) -> &mut Self {
300        if self.columns().iter().any(|c| c.n_chunks() > 1) {
301            RAYON.install(|| {
302                unsafe { self.columns_mut_retain_schema() }
303                    .par_iter_mut()
304                    .for_each(|c| *c = c.rechunk());
305            })
306        }
307
308        self
309    }
310
311    /// Rechunks all columns to only have a single chunk.
312    pub fn rechunk_mut(&mut self) -> &mut Self {
313        // SAFETY: We never adjust the length or names of the columns.
314        let columns = unsafe { self.columns_mut() };
315
316        for col in columns.iter_mut().filter(|c| c.n_chunks() > 1) {
317            *col = col.rechunk();
318        }
319
320        self
321    }
322
323    /// Returns true if the chunks of the columns do not align and re-chunking should be done
324    pub fn should_rechunk(&self) -> bool {
325        // Scalar columns have no chunks of their own and align with any layout, even when they
326        // are materialized.
327        //
328        // Fast check. It is also needed for correctness, as code below doesn't check if the number
329        // of chunks is equal.
330        if !self
331            .columns()
332            .iter()
333            .filter_map(Column::as_series)
334            .map(|s| s.n_chunks())
335            .all_equal()
336        {
337            return true;
338        }
339
340        let mut chunk_lengths = self
341            .columns()
342            .iter()
343            .filter_map(Column::as_series)
344            .map(|s| s.chunk_lengths());
345        match chunk_lengths.next() {
346            None => false,
347            Some(first_column_chunk_lengths) => {
348                // Fast Path for single Chunk Series
349                if first_column_chunk_lengths.size_hint().0 == 1 {
350                    return chunk_lengths.any(|cl| cl.size_hint().0 != 1);
351                }
352                // Always rechunk if we have more chunks than rows.
353                // except when we have an empty df containing a single chunk
354                let height = self.height();
355                let n_chunks = first_column_chunk_lengths.size_hint().0;
356                if n_chunks > height && !(height == 0 && n_chunks == 1) {
357                    return true;
358                }
359                // Slow Path for multi Chunk series
360                let v: Vec<_> = first_column_chunk_lengths.collect();
361                for cl in chunk_lengths {
362                    if cl.enumerate().any(|(idx, el)| Some(&el) != v.get(idx)) {
363                        return true;
364                    }
365                }
366                false
367            },
368        }
369    }
370
371    /// Ensure all the chunks in the [`DataFrame`] are aligned.
372    pub fn align_chunks_par(&mut self) -> &mut Self {
373        if self.should_rechunk() {
374            self.rechunk_mut_par()
375        } else {
376            self
377        }
378    }
379
380    /// Ensure all the chunks in the [`DataFrame`] are aligned.
381    pub fn align_chunks(&mut self) -> &mut Self {
382        if self.should_rechunk() {
383            self.rechunk_mut()
384        } else {
385            self
386        }
387    }
388
389    /// # Example
390    ///
391    /// ```rust
392    /// # use polars_core::prelude::*;
393    /// let df: DataFrame = df!("Language" => ["Rust", "Python"],
394    ///                         "Designer" => ["Graydon Hoare", "Guido van Rossum"])?;
395    ///
396    /// assert_eq!(df.get_column_names(), &["Language", "Designer"]);
397    /// # Ok::<(), PolarsError>(())
398    /// ```
399    pub fn get_column_names(&self) -> Vec<&PlSmallStr> {
400        self.columns().iter().map(|s| s.name()).collect()
401    }
402
403    /// Get the [`Vec<PlSmallStr>`] representing the column names.
404    pub fn get_column_names_owned(&self) -> Vec<PlSmallStr> {
405        self.columns().iter().map(|s| s.name().clone()).collect()
406    }
407
408    /// Set the column names.
409    /// # Example
410    ///
411    /// ```rust
412    /// # use polars_core::prelude::*;
413    /// let mut df: DataFrame = df!("Mathematical set" => ["ℕ", "ℤ", "𝔻", "ℚ", "ℝ", "ℂ"])?;
414    /// df.set_column_names(&["Set"])?;
415    ///
416    /// assert_eq!(df.get_column_names(), &["Set"]);
417    /// # Ok::<(), PolarsError>(())
418    /// ```
419    pub fn set_column_names<T>(&mut self, new_names: &[T]) -> PolarsResult<()>
420    where
421        T: AsRef<str>,
422    {
423        polars_ensure!(
424            new_names.len() == self.width(),
425            ShapeMismatch: "{} column names provided for a DataFrame of width {}",
426            new_names.len(), self.width()
427        );
428
429        validation::ensure_names_unique(new_names)?;
430
431        *unsafe { self.columns_mut() } = std::mem::take(unsafe { self.columns_mut() })
432            .into_iter()
433            .zip(new_names)
434            .map(|(c, name)| c.with_name(PlSmallStr::from_str(name.as_ref())))
435            .collect();
436
437        Ok(())
438    }
439
440    /// Get the data types of the columns in the [`DataFrame`].
441    ///
442    /// # Example
443    ///
444    /// ```rust
445    /// # use polars_core::prelude::*;
446    /// let venus_air: DataFrame = df!("Element" => ["Carbon dioxide", "Nitrogen"],
447    ///                                "Fraction" => [0.965, 0.035])?;
448    ///
449    /// assert_eq!(venus_air.dtypes(), &[DataType::String, DataType::Float64]);
450    /// # Ok::<(), PolarsError>(())
451    /// ```
452    pub fn dtypes(&self) -> Vec<DataType> {
453        self.columns().iter().map(|s| s.dtype().clone()).collect()
454    }
455
456    /// The number of chunks for the first column.
457    pub fn first_col_n_chunks(&self) -> usize {
458        match self.columns().iter().find_map(|col| col.as_series()) {
459            None if self.width() == 0 => 0,
460            None => 1,
461            Some(s) => s.n_chunks(),
462        }
463    }
464
465    /// The chunk lengths of the first column that has chunks, see [`Self::first_col_n_chunks`].
466    pub(crate) fn first_col_chunk_lengths(&self) -> impl Iterator<Item = usize> + '_ {
467        match self.columns().iter().find_map(|col| col.as_series()) {
468            None => Either::Right((self.width() > 0).then_some(self.height()).into_iter()),
469            Some(s) => Either::Left(s.chunk_lengths()),
470        }
471    }
472
473    /// The highest number of chunks for any column.
474    pub fn max_n_chunks(&self) -> usize {
475        self.columns()
476            .iter()
477            .map(|s| s.as_series().map(|s| s.n_chunks()).unwrap_or(1))
478            .max()
479            .unwrap_or(0)
480    }
481
482    /// Generate the schema fields of the [`DataFrame`].
483    ///
484    /// # Example
485    ///
486    /// ```rust
487    /// # use polars_core::prelude::*;
488    /// let earth: DataFrame = df!("Surface type" => ["Water", "Land"],
489    ///                            "Fraction" => [0.708, 0.292])?;
490    ///
491    /// let f1: Field = Field::new("Surface type".into(), DataType::String);
492    /// let f2: Field = Field::new("Fraction".into(), DataType::Float64);
493    ///
494    /// assert_eq!(earth.fields(), &[f1, f2]);
495    /// # Ok::<(), PolarsError>(())
496    /// ```
497    pub fn fields(&self) -> Vec<Field> {
498        self.columns()
499            .iter()
500            .map(|s| s.field().into_owned())
501            .collect()
502    }
503
504    /// Add multiple [`Series`] to a [`DataFrame`].
505    /// The added `Series` are required to have the same length.
506    ///
507    /// # Example
508    ///
509    /// ```rust
510    /// # use polars_core::prelude::*;
511    /// let df1: DataFrame = df!("Element" => ["Copper", "Silver", "Gold"])?;
512    /// let s1 = Column::new("Proton".into(), [29, 47, 79]);
513    /// let s2 = Column::new("Electron".into(), [29, 47, 79]);
514    ///
515    /// let df2: DataFrame = df1.hstack(&[s1, s2])?;
516    /// assert_eq!(df2.shape(), (3, 3));
517    /// println!("{}", df2);
518    /// # Ok::<(), PolarsError>(())
519    /// ```
520    ///
521    /// Output:
522    ///
523    /// ```text
524    /// shape: (3, 3)
525    /// +---------+--------+----------+
526    /// | Element | Proton | Electron |
527    /// | ---     | ---    | ---      |
528    /// | str     | i32    | i32      |
529    /// +=========+========+==========+
530    /// | Copper  | 29     | 29       |
531    /// +---------+--------+----------+
532    /// | Silver  | 47     | 47       |
533    /// +---------+--------+----------+
534    /// | Gold    | 79     | 79       |
535    /// +---------+--------+----------+
536    /// ```
537    pub fn hstack(&self, columns: &[Column]) -> PolarsResult<Self> {
538        let mut new_cols = Vec::with_capacity(self.width() + columns.len());
539
540        new_cols.extend(self.columns().iter().cloned());
541        new_cols.extend_from_slice(columns);
542
543        DataFrame::new(self.height(), new_cols)
544    }
545    /// Concatenate a [`DataFrame`] to this [`DataFrame`] and return as newly allocated [`DataFrame`].
546    ///
547    /// If many `vstack` operations are done, it is recommended to call [`DataFrame::align_chunks_par`].
548    ///
549    /// # Example
550    ///
551    /// ```rust
552    /// # use polars_core::prelude::*;
553    /// let df1: DataFrame = df!("Element" => ["Copper", "Silver", "Gold"],
554    ///                          "Melting Point (K)" => [1357.77, 1234.93, 1337.33])?;
555    /// let df2: DataFrame = df!("Element" => ["Platinum", "Palladium"],
556    ///                          "Melting Point (K)" => [2041.4, 1828.05])?;
557    ///
558    /// let df3: DataFrame = df1.vstack(&df2)?;
559    ///
560    /// assert_eq!(df3.shape(), (5, 2));
561    /// println!("{}", df3);
562    /// # Ok::<(), PolarsError>(())
563    /// ```
564    ///
565    /// Output:
566    ///
567    /// ```text
568    /// shape: (5, 2)
569    /// +-----------+-------------------+
570    /// | Element   | Melting Point (K) |
571    /// | ---       | ---               |
572    /// | str       | f64               |
573    /// +===========+===================+
574    /// | Copper    | 1357.77           |
575    /// +-----------+-------------------+
576    /// | Silver    | 1234.93           |
577    /// +-----------+-------------------+
578    /// | Gold      | 1337.33           |
579    /// +-----------+-------------------+
580    /// | Platinum  | 2041.4            |
581    /// +-----------+-------------------+
582    /// | Palladium | 1828.05           |
583    /// +-----------+-------------------+
584    /// ```
585    pub fn vstack(&self, other: &DataFrame) -> PolarsResult<Self> {
586        let mut df = self.clone();
587        df.vstack_mut(other)?;
588        Ok(df)
589    }
590
591    /// Concatenate a [`DataFrame`] to this [`DataFrame`]
592    ///
593    /// If many `vstack` operations are done, it is recommended to call [`DataFrame::align_chunks_par`].
594    ///
595    /// # Example
596    ///
597    /// ```rust
598    /// # use polars_core::prelude::*;
599    /// let mut df1: DataFrame = df!("Element" => ["Copper", "Silver", "Gold"],
600    ///                          "Melting Point (K)" => [1357.77, 1234.93, 1337.33])?;
601    /// let df2: DataFrame = df!("Element" => ["Platinum", "Palladium"],
602    ///                          "Melting Point (K)" => [2041.4, 1828.05])?;
603    ///
604    /// df1.vstack_mut(&df2)?;
605    ///
606    /// assert_eq!(df1.shape(), (5, 2));
607    /// println!("{}", df1);
608    /// # Ok::<(), PolarsError>(())
609    /// ```
610    ///
611    /// Output:
612    ///
613    /// ```text
614    /// shape: (5, 2)
615    /// +-----------+-------------------+
616    /// | Element   | Melting Point (K) |
617    /// | ---       | ---               |
618    /// | str       | f64               |
619    /// +===========+===================+
620    /// | Copper    | 1357.77           |
621    /// +-----------+-------------------+
622    /// | Silver    | 1234.93           |
623    /// +-----------+-------------------+
624    /// | Gold      | 1337.33           |
625    /// +-----------+-------------------+
626    /// | Platinum  | 2041.4            |
627    /// +-----------+-------------------+
628    /// | Palladium | 1828.05           |
629    /// +-----------+-------------------+
630    /// ```
631    pub fn vstack_mut(&mut self, other: &DataFrame) -> PolarsResult<&mut Self> {
632        if self.width() != other.width() {
633            polars_ensure!(
634                self.shape() == (0, 0),
635                ShapeMismatch:
636                "unable to append to a DataFrame of shape {:?} with a DataFrame of width {}",
637                self.shape(), other.width(),
638            );
639
640            self.clone_from(other);
641
642            return Ok(self);
643        }
644
645        let new_height = usize::checked_add(self.height(), other.height()).unwrap();
646
647        unsafe { self.columns_mut_retain_schema() }
648            .iter_mut()
649            .zip(other.columns())
650            .try_for_each::<_, PolarsResult<_>>(|(left, right)| {
651                ensure_can_extend(&*left, right)?;
652                left.append(right)
653                    .with_context(|| format!("failed to vstack column '{}'", right.name()))?;
654                Ok(())
655            })?;
656
657        unsafe { self.set_height(new_height) };
658
659        Ok(self)
660    }
661
662    pub fn vstack_mut_owned(&mut self, other: DataFrame) -> PolarsResult<&mut Self> {
663        if self.width() != other.width() {
664            polars_ensure!(
665                self.shape() == (0, 0),
666                ShapeMismatch:
667                "unable to append to a DataFrame of width {} with a DataFrame of width {}",
668                self.width(), other.width(),
669            );
670
671            *self = other;
672
673            return Ok(self);
674        }
675
676        let new_height = usize::checked_add(self.height(), other.height()).unwrap();
677
678        unsafe { self.columns_mut_retain_schema() }
679            .iter_mut()
680            .zip(other.into_columns())
681            .try_for_each::<_, PolarsResult<_>>(|(left, right)| {
682                ensure_can_extend(&*left, &right)?;
683                let right_name = right.name().clone();
684                left.append_owned(right)
685                    .with_context(|| format!("failed to vstack column '{right_name}'"))?;
686                Ok(())
687            })?;
688
689        unsafe { self.set_height(new_height) };
690
691        Ok(self)
692    }
693
694    /// Concatenate a [`DataFrame`] to this [`DataFrame`]
695    ///
696    /// If many `vstack` operations are done, it is recommended to call [`DataFrame::align_chunks_par`].
697    ///
698    /// # Panics
699    /// Panics if the schema's don't match.
700    pub fn vstack_mut_unchecked(&mut self, other: &DataFrame) -> &mut Self {
701        let new_height = usize::checked_add(self.height(), other.height()).unwrap();
702
703        unsafe { self.columns_mut_retain_schema() }
704            .iter_mut()
705            .zip(other.columns())
706            .for_each(|(left, right)| {
707                left.append(right)
708                    .with_context(|| format!("failed to vstack column '{}'", right.name()))
709                    .expect("should not fail");
710            });
711
712        unsafe { self.set_height(new_height) };
713
714        self
715    }
716
717    /// Concatenate a [`DataFrame`] to this [`DataFrame`]
718    ///
719    /// If many `vstack` operations are done, it is recommended to call [`DataFrame::align_chunks_par`].
720    ///
721    /// # Panics
722    /// Panics if the schema's don't match.
723    pub fn vstack_mut_owned_unchecked(&mut self, other: DataFrame) -> &mut Self {
724        let new_height = usize::checked_add(self.height(), other.height()).unwrap();
725
726        unsafe { self.columns_mut_retain_schema() }
727            .iter_mut()
728            .zip(other.into_columns())
729            .for_each(|(left, right)| {
730                left.append_owned(right).expect("should not fail");
731            });
732
733        unsafe { self.set_height(new_height) };
734
735        self
736    }
737
738    /// Extend the memory backed by this [`DataFrame`] with the values from `other`.
739    ///
740    /// Different from [`vstack`](Self::vstack) which adds the chunks from `other` to the chunks of this [`DataFrame`]
741    /// `extend` appends the data from `other` to the underlying memory locations and thus may cause a reallocation.
742    ///
743    /// If this does not cause a reallocation, the resulting data structure will not have any extra chunks
744    /// and thus will yield faster queries.
745    ///
746    /// Prefer `extend` over `vstack` when you want to do a query after a single append. For instance during
747    /// online operations where you add `n` rows and rerun a query.
748    ///
749    /// Prefer `vstack` over `extend` when you want to append many times before doing a query. For instance
750    /// when you read in multiple files and when to store them in a single `DataFrame`. In the latter case, finish the sequence
751    /// of `append` operations with a [`rechunk`](Self::align_chunks_par).
752    pub fn extend(&mut self, other: &DataFrame) -> PolarsResult<()> {
753        polars_ensure!(
754            self.width() == other.width(),
755            ShapeMismatch:
756            "unable to extend a DataFrame of width {} with a DataFrame of width {}",
757            self.width(), other.width(),
758        );
759
760        let new_height = usize::checked_add(self.height(), other.height()).unwrap();
761
762        unsafe { self.columns_mut_retain_schema() }
763            .iter_mut()
764            .zip(other.columns())
765            .try_for_each::<_, PolarsResult<_>>(|(left, right)| {
766                ensure_can_extend(&*left, right)?;
767                left.extend(right)
768                    .with_context(|| format!("failed to extend column '{}'", right.name()))?;
769                Ok(())
770            })?;
771
772        unsafe { self.set_height(new_height) };
773
774        Ok(())
775    }
776
777    /// Remove a column by name and return the column removed.
778    ///
779    /// # Example
780    ///
781    /// ```rust
782    /// # use polars_core::prelude::*;
783    /// let mut df: DataFrame = df!("Animal" => ["Tiger", "Lion", "Great auk"],
784    ///                             "IUCN" => ["Endangered", "Vulnerable", "Extinct"])?;
785    ///
786    /// let s1: PolarsResult<Column> = df.drop_in_place("Average weight");
787    /// assert!(s1.is_err());
788    ///
789    /// let s2: Column = df.drop_in_place("Animal")?;
790    /// assert_eq!(s2, Column::new("Animal".into(), &["Tiger", "Lion", "Great auk"]));
791    /// # Ok::<(), PolarsError>(())
792    /// ```
793    pub fn drop_in_place(&mut self, name: &str) -> PolarsResult<Column> {
794        let idx = self.try_get_column_index(name)?;
795        Ok(unsafe { self.columns_mut() }.remove(idx))
796    }
797
798    /// Return a new [`DataFrame`] where all null values are dropped.
799    ///
800    /// # Example
801    ///
802    /// ```no_run
803    /// # use polars_core::prelude::*;
804    /// let df1: DataFrame = df!("Country" => ["Malta", "Liechtenstein", "North Korea"],
805    ///                         "Tax revenue (% GDP)" => [Some(32.7), None, None])?;
806    /// assert_eq!(df1.shape(), (3, 2));
807    ///
808    /// let df2: DataFrame = df1.drop_nulls::<String>(None)?;
809    /// assert_eq!(df2.shape(), (1, 2));
810    /// println!("{}", df2);
811    /// # Ok::<(), PolarsError>(())
812    /// ```
813    ///
814    /// Output:
815    ///
816    /// ```text
817    /// shape: (1, 2)
818    /// +---------+---------------------+
819    /// | Country | Tax revenue (% GDP) |
820    /// | ---     | ---                 |
821    /// | str     | f64                 |
822    /// +=========+=====================+
823    /// | Malta   | 32.7                |
824    /// +---------+---------------------+
825    /// ```
826    pub fn drop_nulls<S>(&self, subset: Option<&[S]>) -> PolarsResult<Self>
827    where
828        for<'a> &'a S: AsRef<str>,
829    {
830        if let Some(v) = subset {
831            let v = self.select_to_vec(v)?;
832            self._drop_nulls_impl(v.as_slice())
833        } else {
834            self._drop_nulls_impl(self.columns())
835        }
836    }
837
838    fn _drop_nulls_impl(&self, subset: &[Column]) -> PolarsResult<Self> {
839        // fast path for no nulls in df
840        if subset.iter().all(|s| !s.has_nulls()) {
841            return Ok(self.clone());
842        }
843
844        let mut iter = subset.iter();
845
846        let mask = iter
847            .next()
848            .ok_or_else(|| polars_err!(NoData: "no data to drop nulls from"))?;
849        let mut mask = mask.is_not_null();
850
851        for c in iter {
852            mask = mask & c.is_not_null();
853        }
854        self.filter(&mask)
855    }
856
857    /// Drop a column by name.
858    /// This is a pure method and will return a new [`DataFrame`] instead of modifying
859    /// the current one in place.
860    ///
861    /// # Example
862    ///
863    /// ```rust
864    /// # use polars_core::prelude::*;
865    /// let df1: DataFrame = df!("Ray type" => ["α", "β", "X", "γ"])?;
866    /// let df2: DataFrame = df1.drop("Ray type")?;
867    ///
868    /// assert_eq!(df2.width(), 0);
869    /// # Ok::<(), PolarsError>(())
870    /// ```
871    pub fn drop(&self, name: &str) -> PolarsResult<Self> {
872        let idx = self.try_get_column_index(name)?;
873        let mut new_cols = Vec::with_capacity(self.width() - 1);
874
875        self.columns().iter().enumerate().for_each(|(i, s)| {
876            if i != idx {
877                new_cols.push(s.clone())
878            }
879        });
880
881        Ok(unsafe { DataFrame::_new_unchecked_impl(self.height(), new_cols) })
882    }
883
884    /// Drop columns that are in `names`.
885    pub fn drop_many<I, S>(&self, names: I) -> Self
886    where
887        I: IntoIterator<Item = S>,
888        S: Into<PlSmallStr>,
889    {
890        let names: PlHashSet<PlSmallStr> = names.into_iter().map(|s| s.into()).collect();
891        self.drop_many_amortized(&names)
892    }
893
894    /// Drop columns that are in `names` without allocating a [`HashSet`](std::collections::HashSet).
895    pub fn drop_many_amortized(&self, names: &PlHashSet<PlSmallStr>) -> DataFrame {
896        if names.is_empty() {
897            return self.clone();
898        }
899        let mut new_cols = Vec::with_capacity(self.width().saturating_sub(names.len()));
900        self.columns().iter().for_each(|s| {
901            if !names.contains(s.name()) {
902                new_cols.push(s.clone())
903            }
904        });
905
906        unsafe { DataFrame::new_unchecked(self.height(), new_cols) }
907    }
908
909    /// Insert a new column at a given index without checking for duplicates.
910    /// This can leave the [`DataFrame`] at an invalid state
911    fn insert_column_no_namecheck(
912        &mut self,
913        index: usize,
914        column: Column,
915    ) -> PolarsResult<&mut Self> {
916        polars_ensure!(
917            column.len() == self.height(),
918            ShapeMismatch:
919            "unable to add a column of length {} to a DataFrame of height {}",
920            column.len(), self.height(),
921        );
922
923        unsafe { self.columns_mut() }.insert(index, column);
924        Ok(self)
925    }
926
927    /// Insert a new column at a given index.
928    pub fn insert_column(&mut self, index: usize, column: Column) -> PolarsResult<&mut Self> {
929        let name = column.name();
930
931        polars_ensure!(
932            self.get_column_index(name).is_none(),
933            Duplicate:
934            "column with name {:?} is already present in the DataFrame", name
935        );
936
937        self.insert_column_no_namecheck(index, column)
938    }
939
940    /// Add a new column to this [`DataFrame`] or replace an existing one. Broadcasts unit-length
941    /// columns.
942    pub fn with_column(&mut self, mut column: Column) -> PolarsResult<&mut Self> {
943        column.broadcast_in_place_to(self.height())?;
944
945        if let Some(i) = self.get_column_index(column.name()) {
946            *unsafe { self.columns_mut() }.get_mut(i).unwrap() = column
947        } else {
948            unsafe { self.columns_mut() }.push(column)
949        };
950
951        Ok(self)
952    }
953
954    /// Adds a column to the [`DataFrame`] without doing any checks
955    /// on length or duplicates.
956    ///
957    /// # Safety
958    /// The caller must ensure `column.len() == self.height()` .
959    pub unsafe fn push_column_unchecked(&mut self, column: Column) -> &mut Self {
960        unsafe { self.columns_mut() }.push(column);
961        self
962    }
963
964    /// Add or replace columns to this [`DataFrame`] or replace an existing one.
965    /// Broadcasts unit-length columns, and uses an existing schema to amortize lookups.
966    pub fn with_columns_mut(
967        &mut self,
968        columns: impl IntoIterator<Item = Column>,
969        output_schema: &Schema,
970    ) -> PolarsResult<()> {
971        let columns = columns.into_iter();
972
973        unsafe {
974            self.columns_mut_retain_schema()
975                .reserve(columns.size_hint().0)
976        }
977
978        for c in columns {
979            self.with_column_and_schema_mut(c, output_schema)?;
980        }
981
982        Ok(())
983    }
984
985    fn with_column_and_schema_mut(
986        &mut self,
987        mut column: Column,
988        output_schema: &Schema,
989    ) -> PolarsResult<&mut Self> {
990        column.broadcast_in_place_to(self.height())?;
991
992        let i = output_schema
993            .index_of(column.name())
994            .or_else(|| self.get_column_index(column.name()))
995            .unwrap_or(self.width());
996
997        if i < self.width() {
998            *unsafe { self.columns_mut() }.get_mut(i).unwrap() = column
999        } else if i == self.width() {
1000            unsafe { self.columns_mut() }.push(column)
1001        } else {
1002            // Unordered column insertion is not handled.
1003            panic!("{:?}, {}", output_schema, column.name());
1004        }
1005
1006        Ok(self)
1007    }
1008
1009    /// Get a row in the [`DataFrame`]. Beware this is slow.
1010    ///
1011    /// # Example
1012    ///
1013    /// ```
1014    /// # use polars_core::prelude::*;
1015    /// fn example(df: &mut DataFrame, idx: usize) -> Option<Vec<AnyValue>> {
1016    ///     df.get(idx)
1017    /// }
1018    /// ```
1019    pub fn get(&self, idx: usize) -> Option<Vec<AnyValue<'_>>> {
1020        (idx < self.height()).then(|| self.columns().iter().map(|c| c.get(idx).unwrap()).collect())
1021    }
1022
1023    /// Select a [`Series`] by index.
1024    ///
1025    /// # Example
1026    ///
1027    /// ```rust
1028    /// # use polars_core::prelude::*;
1029    /// let df: DataFrame = df!("Star" => ["Sun", "Betelgeuse", "Sirius A", "Sirius B"],
1030    ///                         "Absolute magnitude" => [4.83, -5.85, 1.42, 11.18])?;
1031    ///
1032    /// let s1: Option<&Column> = df.select_at_idx(0);
1033    /// let s2 = Column::new("Star".into(), ["Sun", "Betelgeuse", "Sirius A", "Sirius B"]);
1034    ///
1035    /// assert_eq!(s1, Some(&s2));
1036    /// # Ok::<(), PolarsError>(())
1037    /// ```
1038    pub fn select_at_idx(&self, idx: usize) -> Option<&Column> {
1039        self.columns().get(idx)
1040    }
1041
1042    /// Get column index of a [`Series`] by name.
1043    /// # Example
1044    ///
1045    /// ```rust
1046    /// # use polars_core::prelude::*;
1047    /// let df: DataFrame = df!("Name" => ["Player 1", "Player 2", "Player 3"],
1048    ///                         "Health" => [100, 200, 500],
1049    ///                         "Mana" => [250, 100, 0],
1050    ///                         "Strength" => [30, 150, 300])?;
1051    ///
1052    /// assert_eq!(df.get_column_index("Name"), Some(0));
1053    /// assert_eq!(df.get_column_index("Health"), Some(1));
1054    /// assert_eq!(df.get_column_index("Mana"), Some(2));
1055    /// assert_eq!(df.get_column_index("Strength"), Some(3));
1056    /// assert_eq!(df.get_column_index("Haste"), None);
1057    /// # Ok::<(), PolarsError>(())
1058    /// ```
1059    pub fn get_column_index(&self, name: &str) -> Option<usize> {
1060        if let Some(schema) = self.cached_schema() {
1061            schema.index_of(name)
1062        } else if self.width() <= LINEAR_SEARCH_LIMIT {
1063            self.columns().iter().position(|s| s.name() == name)
1064        } else {
1065            self.schema().index_of(name)
1066        }
1067    }
1068
1069    /// Get column index of a [`Series`] by name.
1070    pub fn try_get_column_index(&self, name: &str) -> PolarsResult<usize> {
1071        self.get_column_index(name)
1072            .ok_or_else(|| polars_err!(col_not_found = name))
1073    }
1074
1075    /// Select a single column by name.
1076    ///
1077    /// # Example
1078    ///
1079    /// ```rust
1080    /// # use polars_core::prelude::*;
1081    /// let s1 = Column::new("Password".into(), ["123456", "[]B$u$g$s$B#u#n#n#y[]{}"]);
1082    /// let s2 = Column::new("Robustness".into(), ["Weak", "Strong"]);
1083    /// let df: DataFrame = DataFrame::new_infer_height(vec![s1.clone(), s2])?;
1084    ///
1085    /// assert_eq!(df.column("Password")?, &s1);
1086    /// # Ok::<(), PolarsError>(())
1087    /// ```
1088    pub fn column(&self, name: &str) -> PolarsResult<&Column> {
1089        let idx = self.try_get_column_index(name)?;
1090        Ok(self.select_at_idx(idx).unwrap())
1091    }
1092
1093    /// Select column(s) from this [`DataFrame`] and return a new [`DataFrame`].
1094    ///
1095    /// # Examples
1096    ///
1097    /// ```
1098    /// # use polars_core::prelude::*;
1099    /// fn example(df: &DataFrame) -> PolarsResult<DataFrame> {
1100    ///     df.select(["foo", "bar"])
1101    /// }
1102    /// ```
1103    pub fn select<I, S>(&self, names: I) -> PolarsResult<Self>
1104    where
1105        I: IntoIterator<Item = S>,
1106        S: AsRef<str>,
1107    {
1108        DataFrame::new(self.height(), self.select_to_vec(names)?)
1109    }
1110
1111    /// Does not check for duplicates.
1112    ///
1113    /// # Safety
1114    /// `names` must not contain duplicates.
1115    pub unsafe fn select_unchecked<I, S>(&self, names: I) -> PolarsResult<Self>
1116    where
1117        I: IntoIterator<Item = S>,
1118        S: AsRef<str>,
1119    {
1120        Ok(unsafe { DataFrame::new_unchecked(self.height(), self.select_to_vec(names)?) })
1121    }
1122
1123    /// Select column(s) from this [`DataFrame`] and return them into a [`Vec`].
1124    ///
1125    /// This does not error on duplicate selections.
1126    ///
1127    /// # Example
1128    ///
1129    /// ```rust
1130    /// # use polars_core::prelude::*;
1131    /// let df: DataFrame = df!("Name" => ["Methane", "Ethane", "Propane"],
1132    ///                         "Carbon" => [1, 2, 3],
1133    ///                         "Hydrogen" => [4, 6, 8])?;
1134    /// let sv: Vec<Column> = df.select_to_vec(["Carbon", "Hydrogen"])?;
1135    ///
1136    /// assert_eq!(df["Carbon"], sv[0]);
1137    /// assert_eq!(df["Hydrogen"], sv[1]);
1138    /// # Ok::<(), PolarsError>(())
1139    /// ```
1140    pub fn select_to_vec(
1141        &self,
1142        selection: impl IntoIterator<Item = impl AsRef<str>>,
1143    ) -> PolarsResult<Vec<Column>> {
1144        AmortizedColumnSelector::new(self).select_multiple(selection)
1145    }
1146
1147    /// Take the [`DataFrame`] rows by a boolean mask.
1148    ///
1149    /// # Example
1150    ///
1151    /// ```
1152    /// # use polars_core::prelude::*;
1153    /// fn example(df: &DataFrame) -> PolarsResult<DataFrame> {
1154    ///     let mask = df.column("sepal_width")?.is_not_null();
1155    ///     df.filter(&mask)
1156    /// }
1157    /// ```
1158    pub fn filter(&self, mask: &BooleanChunked) -> PolarsResult<Self> {
1159        if self.width() == 0 {
1160            filter_zero_width(self.height(), mask)
1161        } else if mask.len() == 1 && self.len() >= 1 {
1162            if mask.all() && mask.null_count() == 0 {
1163                Ok(self.clone())
1164            } else {
1165                Ok(self.clear())
1166            }
1167        } else {
1168            // Rechunk when not all chunks are aligned. This avoid O(n*m) overhead,
1169            // where n = number of chunks, and m = number of columns.
1170            let all_chunks_aligned =
1171                !self.should_rechunk() && self.first_col_chunk_lengths().eq(mask.chunk_lengths());
1172
1173            let mask = if all_chunks_aligned {
1174                Cow::Borrowed(mask)
1175            } else {
1176                mask.rechunk()
1177            };
1178
1179            let new_columns: Vec<Column> =
1180                self.try_apply_columns_par(|s| s.filter(mask.as_ref()))?;
1181            let out = unsafe {
1182                DataFrame::new_unchecked(new_columns[0].len(), new_columns).with_schema_from(self)
1183            };
1184
1185            Ok(out)
1186        }
1187    }
1188
1189    /// Same as `filter` but does not parallelize.
1190    pub fn filter_seq(&self, mask: &BooleanChunked) -> PolarsResult<Self> {
1191        if self.width() == 0 {
1192            filter_zero_width(self.height(), mask)
1193        } else if mask.len() == 1 && mask.null_count() == 0 && self.len() >= 1 {
1194            if mask.all() && mask.null_count() == 0 {
1195                Ok(self.clone())
1196            } else {
1197                Ok(self.clear())
1198            }
1199        } else {
1200            let all_chunks_aligned =
1201                !self.should_rechunk() && self.first_col_chunk_lengths().eq(mask.chunk_lengths());
1202
1203            let mask = if all_chunks_aligned {
1204                Cow::Borrowed(mask)
1205            } else {
1206                mask.rechunk()
1207            };
1208
1209            let new_columns: Vec<Column> = self.try_apply_columns(|s| s.filter(mask.as_ref()))?;
1210            let out = unsafe {
1211                DataFrame::new_unchecked(new_columns[0].len(), new_columns).with_schema_from(self)
1212            };
1213
1214            Ok(out)
1215        }
1216    }
1217
1218    /// Gather [`DataFrame`] rows by index values.
1219    ///
1220    /// # Example
1221    ///
1222    /// ```
1223    /// # use polars_core::prelude::*;
1224    /// fn example(df: &DataFrame) -> PolarsResult<DataFrame> {
1225    ///     let idx = IdxCa::new("idx".into(), [0, 1, 9]);
1226    ///     df.take(&idx)
1227    /// }
1228    /// ```
1229    pub fn take(&self, indices: &IdxCa) -> PolarsResult<Self> {
1230        check_bounds_ca(indices, self.height().try_into().unwrap_or(IdxSize::MAX))?;
1231
1232        let new_cols = self.apply_columns_par(|c| {
1233            assert_eq!(c.len(), self.height());
1234            unsafe { c.take_unchecked(indices) }
1235        });
1236
1237        Ok(unsafe { DataFrame::new_unchecked(indices.len(), new_cols).with_schema_from(self) })
1238    }
1239
1240    /// # Safety
1241    /// The indices must be in-bounds.
1242    pub unsafe fn take_unchecked(&self, idx: &IdxCa) -> Self {
1243        self.take_unchecked_impl(idx, true)
1244    }
1245
1246    /// # Safety
1247    /// The indices must be in-bounds.
1248    #[cfg(feature = "algorithm_group_by")]
1249    pub unsafe fn gather_group_unchecked(&self, group: &GroupsIndicator) -> Self {
1250        match group {
1251            GroupsIndicator::Idx((_, indices)) => unsafe {
1252                self.take_slice_unchecked_impl(indices.as_slice(), false)
1253            },
1254            GroupsIndicator::Slice([offset, len]) => self.slice(*offset as i64, *len as usize),
1255        }
1256    }
1257
1258    /// # Safety
1259    /// The indices must be in-bounds.
1260    pub unsafe fn take_unchecked_impl(&self, idx: &IdxCa, allow_threads: bool) -> Self {
1261        let cols = if allow_threads && RAYON.current_num_threads() > 1 {
1262            RAYON.install(|| {
1263                if RAYON.current_num_threads() > self.width() {
1264                    let stride = usize::max(idx.len().div_ceil(RAYON.current_num_threads()), 256);
1265                    if self.height() / stride >= 2 {
1266                        self.apply_columns_par(|c| {
1267                            // Nested types initiate a rechunk in their take_unchecked implementation.
1268                            // If we do not rechunk, it will result in rechunk storms downstream.
1269                            let c = if c.dtype().is_nested() {
1270                                &c.rechunk()
1271                            } else {
1272                                c
1273                            };
1274
1275                            (0..idx.len().div_ceil(stride))
1276                                .into_par_iter()
1277                                .map(|i| c.take_unchecked(&idx.slice((i * stride) as i64, stride)))
1278                                .reduce(
1279                                    || Column::new_empty(c.name().clone(), c.dtype()),
1280                                    |mut a, b| {
1281                                        a.append_owned(b).unwrap();
1282                                        a
1283                                    },
1284                                )
1285                        })
1286                    } else {
1287                        self.apply_columns_par(|c| c.take_unchecked(idx))
1288                    }
1289                } else {
1290                    self.apply_columns_par(|c| c.take_unchecked(idx))
1291                }
1292            })
1293        } else {
1294            self.apply_columns(|s| s.take_unchecked(idx))
1295        };
1296
1297        unsafe { DataFrame::new_unchecked(idx.len(), cols).with_schema_from(self) }
1298    }
1299
1300    /// # Safety
1301    /// The indices must be in-bounds.
1302    pub unsafe fn take_slice_unchecked(&self, idx: &[IdxSize]) -> Self {
1303        self.take_slice_unchecked_impl(idx, true)
1304    }
1305
1306    /// # Safety
1307    /// The indices must be in-bounds.
1308    pub unsafe fn take_slice_unchecked_impl(&self, idx: &[IdxSize], allow_threads: bool) -> Self {
1309        let cols = if allow_threads && RAYON.current_num_threads() > 1 {
1310            RAYON.install(|| {
1311                if RAYON.current_num_threads() > self.width() {
1312                    let stride = usize::max(idx.len().div_ceil(RAYON.current_num_threads()), 256);
1313                    if self.height() / stride >= 2 {
1314                        self.apply_columns_par(|c| {
1315                            // Nested types initiate a rechunk in their take_unchecked implementation.
1316                            // If we do not rechunk, it will result in rechunk storms downstream.
1317                            let c = if c.dtype().is_nested() {
1318                                &c.rechunk()
1319                            } else {
1320                                c
1321                            };
1322
1323                            (0..idx.len().div_ceil(stride))
1324                                .into_par_iter()
1325                                .map(|i| {
1326                                    let idx = &idx[i * stride..];
1327                                    let idx = &idx[..idx.len().min(stride)];
1328                                    c.take_slice_unchecked(idx)
1329                                })
1330                                .reduce(
1331                                    || Column::new_empty(c.name().clone(), c.dtype()),
1332                                    |mut a, b| {
1333                                        a.append_owned(b).unwrap();
1334                                        a
1335                                    },
1336                                )
1337                        })
1338                    } else {
1339                        self.apply_columns_par(|s| s.take_slice_unchecked(idx))
1340                    }
1341                } else {
1342                    self.apply_columns_par(|s| s.take_slice_unchecked(idx))
1343                }
1344            })
1345        } else {
1346            self.apply_columns(|s| s.take_slice_unchecked(idx))
1347        };
1348        unsafe { DataFrame::new_unchecked(idx.len(), cols).with_schema_from(self) }
1349    }
1350
1351    /// Rename a column in the [`DataFrame`].
1352    ///
1353    /// Should not be called in a loop as that can lead to quadratic behavior.
1354    ///
1355    /// # Example
1356    ///
1357    /// ```
1358    /// # use polars_core::prelude::*;
1359    /// fn example(df: &mut DataFrame) -> PolarsResult<&mut DataFrame> {
1360    ///     let original_name = "foo";
1361    ///     let new_name = "bar";
1362    ///     df.rename(original_name, new_name.into())
1363    /// }
1364    /// ```
1365    pub fn rename(&mut self, column: &str, name: PlSmallStr) -> PolarsResult<&mut Self> {
1366        if column == name.as_str() {
1367            return Ok(self);
1368        }
1369        polars_ensure!(
1370            !self.schema().contains(&name),
1371            Duplicate: "column rename attempted with already existing name \"{name}\""
1372        );
1373
1374        self.get_column_index(column)
1375            .and_then(|idx| unsafe { self.columns_mut() }.get_mut(idx))
1376            .ok_or_else(|| polars_err!(col_not_found = column))
1377            .map(|c| c.rename(name))?;
1378
1379        Ok(self)
1380    }
1381
1382    pub fn rename_many<'a>(
1383        mut self,
1384        renames: impl Iterator<Item = (&'a str, PlSmallStr)>,
1385    ) -> PolarsResult<Self> {
1386        let schema = self.schema().clone();
1387
1388        for (from, to) in renames {
1389            if from == to.as_str() {
1390                continue;
1391            }
1392
1393            let idx = schema
1394                .index_of(from)
1395                .ok_or_else(|| polars_err!(col_not_found = from))?;
1396
1397            unsafe { self.columns_mut() }
1398                .get_mut(idx)
1399                .unwrap()
1400                .rename(to);
1401        }
1402
1403        // Check for duplicates.
1404        let schema = Schema::from_iter_check_duplicates(
1405            self.columns()
1406                .iter()
1407                .map(|c| c.name().clone())
1408                .zip_eq(schema.iter_values().cloned()),
1409        )?;
1410
1411        unsafe { self.set_schema(Arc::new(schema)) };
1412
1413        Ok(self)
1414    }
1415
1416    /// Sort [`DataFrame`] in place.
1417    ///
1418    /// See [`DataFrame::sort`] for more instruction.
1419    pub fn sort_in_place(
1420        &mut self,
1421        by: impl IntoIterator<Item = impl AsRef<str>>,
1422        sort_options: SortMultipleOptions,
1423    ) -> PolarsResult<&mut Self> {
1424        let by: Vec<_> = by.into_iter().collect();
1425        // Several keys are sorted through a row encoding of single chunks; one
1426        // key may skip the sort by its sorted flag.
1427        if by.len() > 1 {
1428            self.rechunk_mut_par();
1429        }
1430        let by_column = self.select_to_vec(by)?;
1431
1432        let mut out = self.sort_impl(by_column, sort_options, None)?;
1433        unsafe { out.set_schema_from(self) };
1434
1435        *self = out;
1436
1437        Ok(self)
1438    }
1439
1440    #[doc(hidden)]
1441    /// This is the dispatch of Self::sort, and exists to reduce compile bloat by monomorphization.
1442    pub fn sort_impl(
1443        &self,
1444        by_column: Vec<Column>,
1445        sort_options: SortMultipleOptions,
1446        slice: Option<(i64, usize)>,
1447    ) -> PolarsResult<Self> {
1448        if by_column.is_empty() {
1449            // If no columns selected, any order (including original order) is correct.
1450            return if let Some((offset, len)) = slice {
1451                Ok(self.slice(offset, len))
1452            } else {
1453                Ok(self.clone())
1454            };
1455        }
1456
1457        for column in &by_column {
1458            if column.dtype().is_object() {
1459                polars_bail!(
1460                    InvalidOperation: "column '{}' has a dtype of '{}', which does not support sorting", column.name(), column.dtype()
1461                )
1462            }
1463        }
1464
1465        // note that the by_column argument also contains evaluated expression from
1466        // polars-lazy that may not even be present in this dataframe. therefore
1467        // when we try to set the first columns as sorted, we ignore the error as
1468        // expressions are not present (they are renamed to _POLARS_SORT_COLUMN_i.
1469        let first_descending = sort_options.descending[0];
1470        let first_by_column = by_column[0].name().to_string();
1471
1472        let set_sorted = |df: &mut DataFrame| {
1473            // Mark the first sort column as sorted; if the column does not exist it
1474            // is ok, because we sorted by an expression not present in the dataframe
1475            let _ = df.apply(&first_by_column, |s| {
1476                let mut s = s.clone();
1477                if first_descending {
1478                    s.set_sorted_flag(IsSorted::Descending)
1479                } else {
1480                    s.set_sorted_flag(IsSorted::Ascending)
1481                }
1482                s
1483            });
1484        };
1485
1486        if self.shape_has_zero() {
1487            let mut out = self.clone();
1488            set_sorted(&mut out);
1489            return Ok(out);
1490        }
1491
1492        if let Some((0, k)) = slice {
1493            if k < self.height() {
1494                return self.bottom_k_impl(k, by_column, sort_options);
1495            }
1496        }
1497        // Check if the required column is already sorted; if so we can exit early
1498        // We can do so when there is only one column to sort by, for multiple columns
1499        // it will be complicated to do so
1500        #[cfg(feature = "dtype-categorical")]
1501        let is_not_categorical_enum =
1502            !(matches!(by_column[0].dtype(), DataType::Categorical(_, _))
1503                || matches!(by_column[0].dtype(), DataType::Enum(_, _)));
1504
1505        #[cfg(not(feature = "dtype-categorical"))]
1506        #[allow(non_upper_case_globals)]
1507        const is_not_categorical_enum: bool = true;
1508
1509        if by_column.len() == 1 && is_not_categorical_enum {
1510            let required_sorting = if sort_options.descending[0] {
1511                IsSorted::Descending
1512            } else {
1513                IsSorted::Ascending
1514            };
1515            // If null count is 0 then nulls_last doesnt matter
1516            // Safe to get value at last position since the dataframe is not empty (taken care above)
1517            let no_sorting_required = (by_column[0].is_sorted_flag() == required_sorting)
1518                && ((by_column[0].null_count() == 0)
1519                    || by_column[0].get(by_column[0].len() - 1).unwrap().is_null()
1520                        == sort_options.nulls_last[0]);
1521
1522            if no_sorting_required {
1523                return if let Some((offset, len)) = slice {
1524                    Ok(self.slice(offset, len))
1525                } else {
1526                    Ok(self.clone())
1527                };
1528            }
1529        }
1530
1531        let has_nested = by_column.iter().any(|s| s.dtype().is_nested());
1532        let allow_threads = sort_options.multithreaded;
1533
1534        // a lot of indirection in both sorting and take
1535        let mut df = self.clone();
1536        let df = df.rechunk_mut_par();
1537        let mut take = match (by_column.len(), has_nested) {
1538            (1, false) => {
1539                let s = &by_column[0];
1540                let options = SortOptions {
1541                    descending: sort_options.descending[0],
1542                    nulls_last: sort_options.nulls_last[0],
1543                    multithreaded: sort_options.multithreaded,
1544                    maintain_order: sort_options.maintain_order,
1545                    limit: sort_options.limit,
1546                };
1547                // fast path for a frame with a single series
1548                // no need to compute the sort indices and then take by these indices
1549                // simply sort and return as frame
1550                if df.width() == 1 && df.try_get_column_index(s.name().as_str()).is_ok() {
1551                    let mut out = s.sort_with(options)?;
1552                    if let Some((offset, len)) = slice {
1553                        out = out.slice(offset, len);
1554                    }
1555                    return Ok(out.into_frame());
1556                }
1557                s.arg_sort(options)
1558            },
1559            _ => arg_sort(&by_column, sort_options)?,
1560        };
1561
1562        if let Some((offset, len)) = slice {
1563            take = take.slice(offset, len);
1564        }
1565
1566        // SAFETY:
1567        // the created indices are in bounds
1568        let mut df = unsafe { df.take_unchecked_impl(&take, allow_threads) };
1569        set_sorted(&mut df);
1570        Ok(df)
1571    }
1572
1573    /// Create a `DataFrame` that has fields for all the known runtime metadata for each column.
1574    ///
1575    /// This dataframe does not necessarily have a specified schema and may be changed at any
1576    /// point. It is primarily used for debugging.
1577    pub fn _to_metadata(&self) -> DataFrame {
1578        let num_columns = self.width();
1579
1580        let mut column_names =
1581            StringChunkedBuilder::new(PlSmallStr::from_static("column_name"), num_columns);
1582        let mut repr_ca = StringChunkedBuilder::new(PlSmallStr::from_static("repr"), num_columns);
1583        let mut sorted_asc_ca =
1584            BooleanChunkedBuilder::new(PlSmallStr::from_static("sorted_asc"), num_columns);
1585        let mut sorted_dsc_ca =
1586            BooleanChunkedBuilder::new(PlSmallStr::from_static("sorted_dsc"), num_columns);
1587        let mut fast_explode_list_ca =
1588            BooleanChunkedBuilder::new(PlSmallStr::from_static("fast_explode_list"), num_columns);
1589        let mut materialized_at_ca =
1590            StringChunkedBuilder::new(PlSmallStr::from_static("materialized_at"), num_columns);
1591
1592        for col in self.columns() {
1593            let flags = col.get_flags();
1594
1595            let (repr, materialized_at) = match col {
1596                Column::Series(s) => ("series", s.materialized_at()),
1597                Column::Scalar(_) => ("scalar", None),
1598            };
1599            let sorted_asc = flags.contains(StatisticsFlags::IS_SORTED_ASC);
1600            let sorted_dsc = flags.contains(StatisticsFlags::IS_SORTED_DSC);
1601            let fast_explode_list = flags.contains(StatisticsFlags::CAN_FAST_EXPLODE_LIST);
1602
1603            column_names.append_value(col.name().clone());
1604            repr_ca.append_value(repr);
1605            sorted_asc_ca.append_value(sorted_asc);
1606            sorted_dsc_ca.append_value(sorted_dsc);
1607            fast_explode_list_ca.append_value(fast_explode_list);
1608            materialized_at_ca.append_option(materialized_at.map(|v| format!("{v:#?}")));
1609        }
1610
1611        unsafe {
1612            DataFrame::new_unchecked(
1613                self.width(),
1614                vec![
1615                    column_names.finish().into_column(),
1616                    repr_ca.finish().into_column(),
1617                    sorted_asc_ca.finish().into_column(),
1618                    sorted_dsc_ca.finish().into_column(),
1619                    fast_explode_list_ca.finish().into_column(),
1620                    materialized_at_ca.finish().into_column(),
1621                ],
1622            )
1623        }
1624    }
1625    /// Return a sorted clone of this [`DataFrame`].
1626    ///
1627    /// In many cases the output chunks will be continuous in memory but this is not guaranteed
1628    /// # Example
1629    ///
1630    /// Sort by a single column with default options:
1631    /// ```
1632    /// # use polars_core::prelude::*;
1633    /// fn sort_by_sepal_width(df: &DataFrame) -> PolarsResult<DataFrame> {
1634    ///     df.sort(["sepal_width"], Default::default())
1635    /// }
1636    /// ```
1637    /// Sort by a single column with specific order:
1638    /// ```
1639    /// # use polars_core::prelude::*;
1640    /// fn sort_with_specific_order(df: &DataFrame, descending: bool) -> PolarsResult<DataFrame> {
1641    ///     df.sort(
1642    ///         ["sepal_width"],
1643    ///         SortMultipleOptions::new()
1644    ///             .with_order_descending(descending)
1645    ///     )
1646    /// }
1647    /// ```
1648    /// Sort by multiple columns with specifying order for each column:
1649    /// ```
1650    /// # use polars_core::prelude::*;
1651    /// fn sort_by_multiple_columns_with_specific_order(df: &DataFrame) -> PolarsResult<DataFrame> {
1652    ///     df.sort(
1653    ///         ["sepal_width", "sepal_length"],
1654    ///         SortMultipleOptions::new()
1655    ///             .with_order_descending_multi([false, true])
1656    ///     )
1657    /// }
1658    /// ```
1659    /// See [`SortMultipleOptions`] for more options.
1660    ///
1661    /// Also see [`DataFrame::sort_in_place`].
1662    pub fn sort(
1663        &self,
1664        by: impl IntoIterator<Item = impl AsRef<str>>,
1665        sort_options: SortMultipleOptions,
1666    ) -> PolarsResult<Self> {
1667        let mut df = self.clone();
1668        df.sort_in_place(by, sort_options)?;
1669        Ok(df)
1670    }
1671
1672    /// Replace a column with a [`Column`].
1673    ///
1674    /// # Example
1675    ///
1676    /// ```rust
1677    /// # use polars_core::prelude::*;
1678    /// let mut df: DataFrame = df!("Country" => ["United States", "China"],
1679    ///                         "Area (km²)" => [9_833_520, 9_596_961])?;
1680    /// let s: Column = Column::new("Country".into(), ["USA", "PRC"]);
1681    ///
1682    /// assert!(df.replace("Nation", s.clone()).is_err());
1683    /// assert!(df.replace("Country", s).is_ok());
1684    /// # Ok::<(), PolarsError>(())
1685    /// ```
1686    pub fn replace(&mut self, column: &str, new_col: Column) -> PolarsResult<&mut Self> {
1687        self.apply(column, |_| new_col)
1688    }
1689
1690    /// Replace column at index `idx` with a [`Series`].
1691    ///
1692    /// # Example
1693    ///
1694    /// ```ignored
1695    /// # use polars_core::prelude::*;
1696    /// let s0 = Series::new("foo".into(), ["ham", "spam", "egg"]);
1697    /// let s1 = Series::new("ascii".into(), [70, 79, 79]);
1698    /// let mut df = DataFrame::new_infer_height(vec![s0, s1])?;
1699    ///
1700    /// // Add 32 to get lowercase ascii values
1701    /// df.replace_column(1, df.select_at_idx(1).unwrap() + 32);
1702    /// # Ok::<(), PolarsError>(())
1703    /// ```
1704    pub fn replace_column(&mut self, index: usize, new_column: Column) -> PolarsResult<&mut Self> {
1705        polars_ensure!(
1706            index < self.width(),
1707            ShapeMismatch:
1708            "unable to replace at index {}, the DataFrame has only {} columns",
1709            index, self.width(),
1710        );
1711
1712        polars_ensure!(
1713            new_column.len() == self.height(),
1714            ShapeMismatch:
1715            "unable to replace a column, series length {} doesn't match the DataFrame height {}",
1716            new_column.len(), self.height(),
1717        );
1718
1719        unsafe { *self.columns_mut().get_mut(index).unwrap() = new_column };
1720
1721        Ok(self)
1722    }
1723
1724    /// Apply a closure to a column. This is the recommended way to do in place modification.
1725    ///
1726    /// # Example
1727    ///
1728    /// ```rust
1729    /// # use polars_core::prelude::*;
1730    /// let s0 = Column::new("foo".into(), ["ham", "spam", "egg"]);
1731    /// let s1 = Column::new("names".into(), ["Jean", "Claude", "van"]);
1732    /// let mut df = DataFrame::new_infer_height(vec![s0, s1])?;
1733    ///
1734    /// fn str_to_len(str_val: &Column) -> Column {
1735    ///     str_val.str()
1736    ///         .unwrap()
1737    ///         .iter()
1738    ///         .map(|opt_name: Option<&str>| {
1739    ///             opt_name.map(|name: &str| name.len() as u32)
1740    ///          })
1741    ///         .collect::<UInt32Chunked>()
1742    ///         .into_column()
1743    /// }
1744    ///
1745    /// // Replace the names column by the length of the names.
1746    /// df.apply("names", str_to_len);
1747    /// # Ok::<(), PolarsError>(())
1748    /// ```
1749    /// Results in:
1750    ///
1751    /// ```text
1752    /// +--------+-------+
1753    /// | foo    |       |
1754    /// | ---    | names |
1755    /// | str    | u32   |
1756    /// +========+=======+
1757    /// | "ham"  | 4     |
1758    /// +--------+-------+
1759    /// | "spam" | 6     |
1760    /// +--------+-------+
1761    /// | "egg"  | 3     |
1762    /// +--------+-------+
1763    /// ```
1764    pub fn apply<F, C>(&mut self, name: &str, f: F) -> PolarsResult<&mut Self>
1765    where
1766        F: FnOnce(&Column) -> C,
1767        C: IntoColumn,
1768    {
1769        let idx = self.try_get_column_index(name)?;
1770        self.apply_at_idx(idx, f)?;
1771        Ok(self)
1772    }
1773
1774    /// Apply a closure to a column at index `idx`. This is the recommended way to do in place
1775    /// modification.
1776    ///
1777    /// # Example
1778    ///
1779    /// ```rust
1780    /// # use polars_core::prelude::*;
1781    /// let s0 = Column::new("foo".into(), ["ham", "spam", "egg"]);
1782    /// let s1 = Column::new("ascii".into(), [70, 79, 79]);
1783    /// let mut df = DataFrame::new_infer_height(vec![s0, s1])?;
1784    ///
1785    /// // Add 32 to get lowercase ascii values
1786    /// df.apply_at_idx(1, |s| s + 32);
1787    /// # Ok::<(), PolarsError>(())
1788    /// ```
1789    /// Results in:
1790    ///
1791    /// ```text
1792    /// +--------+-------+
1793    /// | foo    | ascii |
1794    /// | ---    | ---   |
1795    /// | str    | i32   |
1796    /// +========+=======+
1797    /// | "ham"  | 102   |
1798    /// +--------+-------+
1799    /// | "spam" | 111   |
1800    /// +--------+-------+
1801    /// | "egg"  | 111   |
1802    /// +--------+-------+
1803    /// ```
1804    pub fn apply_at_idx<F, C>(&mut self, idx: usize, f: F) -> PolarsResult<&mut Self>
1805    where
1806        F: FnOnce(&Column) -> C,
1807        C: IntoColumn,
1808    {
1809        let df_height = self.height();
1810        let width = self.width();
1811
1812        let cached_schema = self.cached_schema().cloned();
1813
1814        let col = unsafe { self.columns_mut() }.get_mut(idx).ok_or_else(|| {
1815            polars_err!(
1816                ComputeError: "invalid column index: {} for a DataFrame with {} columns",
1817                idx, width
1818            )
1819        })?;
1820
1821        let new_col = f(col)
1822            .into_column()
1823            .with_name(col.name().clone())
1824            .broadcast_owned_to(df_height)?;
1825        let col_before = std::mem::replace(col, new_col);
1826
1827        if col.dtype() == col_before.dtype() {
1828            unsafe { self.set_opt_schema(cached_schema) };
1829        }
1830
1831        Ok(self)
1832    }
1833
1834    /// Apply a closure that may fail to a column at index `idx`. This is the recommended way to do in place
1835    /// modification.
1836    ///
1837    /// # Example
1838    ///
1839    /// This is the idiomatic way to replace some values a column of a `DataFrame` given range of indexes.
1840    ///
1841    /// ```rust
1842    /// # use polars_core::prelude::*;
1843    /// let s0 = Column::new("foo".into(), ["ham", "spam", "egg", "bacon", "quack"]);
1844    /// let s1 = Column::new("values".into(), [1, 2, 3, 4, 5]);
1845    /// let mut df = DataFrame::new_infer_height(vec![s0, s1])?;
1846    ///
1847    /// let idx = vec![0, 1, 4];
1848    ///
1849    /// df.try_apply("foo", |c| {
1850    ///     c.str()?
1851    ///     .scatter_with(idx, |opt_val| opt_val.map(|string| format!("{}-is-modified", string)))
1852    /// });
1853    /// # Ok::<(), PolarsError>(())
1854    /// ```
1855    /// Results in:
1856    ///
1857    /// ```text
1858    /// +---------------------+--------+
1859    /// | foo                 | values |
1860    /// | ---                 | ---    |
1861    /// | str                 | i32    |
1862    /// +=====================+========+
1863    /// | "ham-is-modified"   | 1      |
1864    /// +---------------------+--------+
1865    /// | "spam-is-modified"  | 2      |
1866    /// +---------------------+--------+
1867    /// | "egg"               | 3      |
1868    /// +---------------------+--------+
1869    /// | "bacon"             | 4      |
1870    /// +---------------------+--------+
1871    /// | "quack-is-modified" | 5      |
1872    /// +---------------------+--------+
1873    /// ```
1874    pub fn try_apply_at_idx<F, C>(&mut self, idx: usize, f: F) -> PolarsResult<&mut Self>
1875    where
1876        F: FnOnce(&Column) -> PolarsResult<C>,
1877        C: IntoColumn,
1878    {
1879        let df_height = self.height();
1880        let width = self.width();
1881
1882        let cached_schema = self.cached_schema().cloned();
1883
1884        let col = unsafe { self.columns_mut() }.get_mut(idx).ok_or_else(|| {
1885            polars_err!(
1886                ComputeError: "invalid column index: {} for a DataFrame with {} columns",
1887                idx, width
1888            )
1889        })?;
1890
1891        let mut new_col = f(col).map(|c| c.into_column())?;
1892
1893        polars_ensure!(
1894            new_col.len() == df_height,
1895            ShapeMismatch:
1896            "try_apply_at_idx: resulting Series has length {} while the DataFrame has height {}",
1897            new_col.len(), df_height
1898        );
1899
1900        // make sure the name remains the same after applying the closure
1901        new_col = new_col.with_name(col.name().clone());
1902        let col_before = std::mem::replace(col, new_col);
1903
1904        if col.dtype() == col_before.dtype() {
1905            unsafe { self.set_opt_schema(cached_schema) };
1906        }
1907
1908        Ok(self)
1909    }
1910
1911    /// Apply a closure that may fail to a column. This is the recommended way to do in place
1912    /// modification.
1913    ///
1914    /// # Example
1915    ///
1916    /// This is the idiomatic way to replace some values a column of a `DataFrame` given a boolean mask.
1917    ///
1918    /// ```rust
1919    /// # use polars_core::prelude::*;
1920    /// let s0 = Column::new("foo".into(), ["ham", "spam", "egg", "bacon", "quack"]);
1921    /// let s1 = Column::new("values".into(), [1, 2, 3, 4, 5]);
1922    /// let mut df = DataFrame::new_infer_height(vec![s0, s1])?;
1923    ///
1924    /// // create a mask
1925    /// let values = df.column("values")?.as_materialized_series();
1926    /// let mask = values.lt_eq(1)? | values.gt_eq(5_i32)?;
1927    ///
1928    /// df.try_apply("foo", |c| {
1929    ///     c.str()?
1930    ///     .set(&mask, Some("not_within_bounds"))
1931    /// });
1932    /// # Ok::<(), PolarsError>(())
1933    /// ```
1934    /// Results in:
1935    ///
1936    /// ```text
1937    /// +---------------------+--------+
1938    /// | foo                 | values |
1939    /// | ---                 | ---    |
1940    /// | str                 | i32    |
1941    /// +=====================+========+
1942    /// | "not_within_bounds" | 1      |
1943    /// +---------------------+--------+
1944    /// | "spam"              | 2      |
1945    /// +---------------------+--------+
1946    /// | "egg"               | 3      |
1947    /// +---------------------+--------+
1948    /// | "bacon"             | 4      |
1949    /// +---------------------+--------+
1950    /// | "not_within_bounds" | 5      |
1951    /// +---------------------+--------+
1952    /// ```
1953    pub fn try_apply<F, C>(&mut self, column: &str, f: F) -> PolarsResult<&mut Self>
1954    where
1955        F: FnOnce(&Series) -> PolarsResult<C>,
1956        C: IntoColumn,
1957    {
1958        let idx = self.try_get_column_index(column)?;
1959        self.try_apply_at_idx(idx, |c| f(c.as_materialized_series()))
1960    }
1961
1962    /// Slice the [`DataFrame`] along the rows.
1963    ///
1964    /// # Example
1965    ///
1966    /// ```rust
1967    /// # use polars_core::prelude::*;
1968    /// let df: DataFrame = df!("Fruit" => ["Apple", "Grape", "Grape", "Fig", "Fig"],
1969    ///                         "Color" => ["Green", "Red", "White", "White", "Red"])?;
1970    /// let sl: DataFrame = df.slice(2, 3);
1971    ///
1972    /// assert_eq!(sl.shape(), (3, 2));
1973    /// println!("{}", sl);
1974    /// # Ok::<(), PolarsError>(())
1975    /// ```
1976    /// Output:
1977    /// ```text
1978    /// shape: (3, 2)
1979    /// +-------+-------+
1980    /// | Fruit | Color |
1981    /// | ---   | ---   |
1982    /// | str   | str   |
1983    /// +=======+=======+
1984    /// | Grape | White |
1985    /// +-------+-------+
1986    /// | Fig   | White |
1987    /// +-------+-------+
1988    /// | Fig   | Red   |
1989    /// +-------+-------+
1990    /// ```
1991    #[must_use]
1992    pub fn slice(&self, offset: i64, length: usize) -> Self {
1993        if offset == 0 && length == self.height() {
1994            return self.clone();
1995        }
1996
1997        if length == 0 {
1998            return self.clear();
1999        }
2000
2001        let cols = self.apply_columns(|s| s.slice(offset, length));
2002
2003        let height = if let Some(fst) = cols.first() {
2004            fst.len()
2005        } else {
2006            let (_, length) = slice_offsets(offset, length, self.height());
2007            length
2008        };
2009
2010        unsafe { DataFrame::_new_unchecked_impl(height, cols).with_schema_from(self) }
2011    }
2012
2013    /// Split [`DataFrame`] at the given `offset`.
2014    pub fn split_at(&self, offset: i64) -> (Self, Self) {
2015        let (a, b) = self.columns().iter().map(|s| s.split_at(offset)).unzip();
2016
2017        let (idx, _) = slice_offsets(offset, 0, self.height());
2018
2019        let a = unsafe { DataFrame::new_unchecked(idx, a).with_schema_from(self) };
2020        let b = unsafe { DataFrame::new_unchecked(self.height() - idx, b).with_schema_from(self) };
2021        (a, b)
2022    }
2023
2024    #[must_use]
2025    pub fn clear(&self) -> Self {
2026        let cols = self.columns().iter().map(|s| s.clear()).collect::<Vec<_>>();
2027        unsafe { DataFrame::_new_unchecked_impl(0, cols).with_schema_from(self) }
2028    }
2029
2030    /// Get the head of the [`DataFrame`].
2031    ///
2032    /// # Example
2033    ///
2034    /// ```rust
2035    /// # use polars_core::prelude::*;
2036    /// let countries: DataFrame =
2037    ///     df!("Rank by GDP (2021)" => [1, 2, 3, 4, 5],
2038    ///         "Continent" => ["North America", "Asia", "Asia", "Europe", "Europe"],
2039    ///         "Country" => ["United States", "China", "Japan", "Germany", "United Kingdom"],
2040    ///         "Capital" => ["Washington", "Beijing", "Tokyo", "Berlin", "London"])?;
2041    /// assert_eq!(countries.shape(), (5, 4));
2042    ///
2043    /// println!("{}", countries.head(Some(3)));
2044    /// # Ok::<(), PolarsError>(())
2045    /// ```
2046    ///
2047    /// Output:
2048    ///
2049    /// ```text
2050    /// shape: (3, 4)
2051    /// +--------------------+---------------+---------------+------------+
2052    /// | Rank by GDP (2021) | Continent     | Country       | Capital    |
2053    /// | ---                | ---           | ---           | ---        |
2054    /// | i32                | str           | str           | str        |
2055    /// +====================+===============+===============+============+
2056    /// | 1                  | North America | United States | Washington |
2057    /// +--------------------+---------------+---------------+------------+
2058    /// | 2                  | Asia          | China         | Beijing    |
2059    /// +--------------------+---------------+---------------+------------+
2060    /// | 3                  | Asia          | Japan         | Tokyo      |
2061    /// +--------------------+---------------+---------------+------------+
2062    /// ```
2063    #[must_use]
2064    pub fn head(&self, length: Option<usize>) -> Self {
2065        let new_height = usize::min(self.height(), length.unwrap_or(HEAD_DEFAULT_LENGTH));
2066        let new_cols = self.apply_columns(|c| c.head(Some(new_height)));
2067
2068        unsafe { DataFrame::new_unchecked(new_height, new_cols).with_schema_from(self) }
2069    }
2070
2071    /// Get the tail of the [`DataFrame`].
2072    ///
2073    /// # Example
2074    ///
2075    /// ```rust
2076    /// # use polars_core::prelude::*;
2077    /// let countries: DataFrame =
2078    ///     df!("Rank (2021)" => [105, 106, 107, 108, 109],
2079    ///         "Apple Price (€/kg)" => [0.75, 0.70, 0.70, 0.65, 0.52],
2080    ///         "Country" => ["Kosovo", "Moldova", "North Macedonia", "Syria", "Turkey"])?;
2081    /// assert_eq!(countries.shape(), (5, 3));
2082    ///
2083    /// println!("{}", countries.tail(Some(2)));
2084    /// # Ok::<(), PolarsError>(())
2085    /// ```
2086    ///
2087    /// Output:
2088    ///
2089    /// ```text
2090    /// shape: (2, 3)
2091    /// +-------------+--------------------+---------+
2092    /// | Rank (2021) | Apple Price (€/kg) | Country |
2093    /// | ---         | ---                | ---     |
2094    /// | i32         | f64                | str     |
2095    /// +=============+====================+=========+
2096    /// | 108         | 0.65               | Syria   |
2097    /// +-------------+--------------------+---------+
2098    /// | 109         | 0.52               | Turkey  |
2099    /// +-------------+--------------------+---------+
2100    /// ```
2101    #[must_use]
2102    pub fn tail(&self, length: Option<usize>) -> Self {
2103        let new_height = usize::min(self.height(), length.unwrap_or(TAIL_DEFAULT_LENGTH));
2104        let new_cols = self.apply_columns(|c| c.tail(Some(new_height)));
2105
2106        unsafe { DataFrame::new_unchecked(new_height, new_cols).with_schema_from(self) }
2107    }
2108
2109    /// Iterator over the rows in this [`DataFrame`] as Arrow RecordBatches.
2110    ///
2111    /// # Panics
2112    ///
2113    /// Panics if the [`DataFrame`] that is passed is not rechunked.
2114    ///
2115    /// This responsibility is left to the caller as we don't want to take mutable references here,
2116    /// but we also don't want to rechunk here, as this operation is costly and would benefit the caller
2117    /// as well.
2118    pub fn iter_chunks(
2119        &self,
2120        compat_level: CompatLevel,
2121        parallel: bool,
2122    ) -> impl Iterator<Item = RecordBatch> + '_ {
2123        debug_assert!(!self.should_rechunk(), "expected equal chunks");
2124
2125        if self.width() == 0 {
2126            return RecordBatchIterWrap::new_zero_width(self.height());
2127        }
2128
2129        // If any of the columns is binview and we don't convert `compat_level` we allow parallelism
2130        // as we must allocate arrow strings/binaries.
2131        let must_convert = compat_level.0 == 0;
2132        let parallel = parallel
2133            && must_convert
2134            && self.width() > 1
2135            && self
2136                .columns()
2137                .iter()
2138                .any(|s| matches!(s.dtype(), DataType::String | DataType::Binary));
2139
2140        RecordBatchIterWrap::Batches(RecordBatchIter {
2141            df: self,
2142            schema: Arc::new(
2143                self.columns()
2144                    .iter()
2145                    .map(|c| c.field().to_arrow(compat_level))
2146                    .collect(),
2147            ),
2148            cursor: ChunkCursor::new(self),
2149            compat_level,
2150            parallel,
2151        })
2152    }
2153
2154    /// Iterator over the rows in this [`DataFrame`] as Arrow RecordBatches as physical values.
2155    ///
2156    /// # Panics
2157    ///
2158    /// Panics if the [`DataFrame`] that is passed is not rechunked.
2159    ///
2160    /// This responsibility is left to the caller as we don't want to take mutable references here,
2161    /// but we also don't want to rechunk here, as this operation is costly and would benefit the caller
2162    /// as well.
2163    pub fn iter_chunks_physical(&self) -> impl Iterator<Item = RecordBatch> + '_ {
2164        debug_assert!(!self.should_rechunk());
2165
2166        if self.width() == 0 {
2167            return RecordBatchIterWrap::new_zero_width(self.height());
2168        }
2169
2170        RecordBatchIterWrap::PhysicalBatches(PhysRecordBatchIter {
2171            df: self,
2172            schema: Arc::new(
2173                self.columns()
2174                    .iter()
2175                    .map(|c| c.field().to_arrow(CompatLevel::newest()))
2176                    .collect(),
2177            ),
2178            cursor: ChunkCursor::new(self),
2179        })
2180    }
2181
2182    /// Get a [`DataFrame`] with all the columns in reversed order.
2183    #[must_use]
2184    pub fn reverse(&self) -> Self {
2185        let new_cols = self.apply_columns(Column::reverse);
2186        unsafe { DataFrame::new_unchecked(self.height(), new_cols).with_schema_from(self) }
2187    }
2188
2189    /// Shift the values by a given period and fill the parts that will be empty due to this operation
2190    /// with `Nones`.
2191    ///
2192    /// See the method on [Series](crate::series::SeriesTrait::shift) for more info on the `shift` operation.
2193    #[must_use]
2194    pub fn shift(&self, periods: i64) -> Self {
2195        let col = self.apply_columns_par(|s| s.shift(periods));
2196        unsafe { DataFrame::new_unchecked(self.height(), col).with_schema_from(self) }
2197    }
2198
2199    /// Replace None values with one of the following strategies:
2200    /// * Forward fill (replace None with the previous value)
2201    /// * Backward fill (replace None with the next value)
2202    /// * Mean fill (replace None with the mean of the whole array)
2203    /// * Min fill (replace None with the minimum of the whole array)
2204    /// * Max fill (replace None with the maximum of the whole array)
2205    ///
2206    /// See the method on [Series](crate::series::Series::fill_null) for more info on the `fill_null` operation.
2207    pub fn fill_null(&self, strategy: FillNullStrategy) -> PolarsResult<Self> {
2208        let col = self.try_apply_columns_par(|s| s.fill_null(strategy))?;
2209
2210        Ok(unsafe { DataFrame::new_unchecked(self.height(), col) })
2211    }
2212
2213    /// Drop duplicate rows from a [`DataFrame`].
2214    /// *This fails when there is a column of type List in DataFrame*
2215    ///
2216    /// Stable means that the order is maintained. This has a higher cost than an unstable distinct.
2217    ///
2218    /// # Example
2219    ///
2220    /// ```no_run
2221    /// # use polars_core::prelude::*;
2222    /// let df = df! {
2223    ///               "flt" => [1., 1., 2., 2., 3., 3.],
2224    ///               "int" => [1, 1, 2, 2, 3, 3, ],
2225    ///               "str" => ["a", "a", "b", "b", "c", "c"]
2226    ///           }?;
2227    ///
2228    /// println!("{}", df.unique_stable(None, UniqueKeepStrategy::First, None)?);
2229    /// # Ok::<(), PolarsError>(())
2230    /// ```
2231    /// Returns
2232    ///
2233    /// ```text
2234    /// +-----+-----+-----+
2235    /// | flt | int | str |
2236    /// | --- | --- | --- |
2237    /// | f64 | i32 | str |
2238    /// +=====+=====+=====+
2239    /// | 1   | 1   | "a" |
2240    /// +-----+-----+-----+
2241    /// | 2   | 2   | "b" |
2242    /// +-----+-----+-----+
2243    /// | 3   | 3   | "c" |
2244    /// +-----+-----+-----+
2245    /// ```
2246    #[cfg(feature = "algorithm_group_by")]
2247    pub fn unique_stable(
2248        &self,
2249        subset: Option<&[String]>,
2250        keep: UniqueKeepStrategy,
2251        slice: Option<(i64, usize)>,
2252    ) -> PolarsResult<DataFrame> {
2253        self.unique_impl(
2254            true,
2255            subset.map(|v| v.iter().map(|x| PlSmallStr::from_str(x.as_str())).collect()),
2256            keep,
2257            slice,
2258        )
2259    }
2260
2261    /// Unstable distinct. See [`DataFrame::unique_stable`].
2262    #[cfg(feature = "algorithm_group_by")]
2263    pub fn unique<I, S>(
2264        &self,
2265        subset: Option<&[String]>,
2266        keep: UniqueKeepStrategy,
2267        slice: Option<(i64, usize)>,
2268    ) -> PolarsResult<DataFrame> {
2269        self.unique_impl(
2270            false,
2271            subset.map(|v| v.iter().map(|x| PlSmallStr::from_str(x.as_str())).collect()),
2272            keep,
2273            slice,
2274        )
2275    }
2276
2277    #[cfg(feature = "algorithm_group_by")]
2278    pub fn unique_impl(
2279        &self,
2280        maintain_order: bool,
2281        subset: Option<Vec<PlSmallStr>>,
2282        keep: UniqueKeepStrategy,
2283        slice: Option<(i64, usize)>,
2284    ) -> PolarsResult<Self> {
2285        if self.width() == 0 {
2286            let height = usize::min(self.height(), 1);
2287            return Ok(DataFrame::empty_with_height(height));
2288        }
2289
2290        let names = subset.unwrap_or_else(|| self.get_column_names_owned());
2291        let mut df = self.clone();
2292        // take on multiple chunks is terrible
2293        df.rechunk_mut_par();
2294
2295        let columns = match (keep, maintain_order) {
2296            (UniqueKeepStrategy::First | UniqueKeepStrategy::Any, true) => {
2297                let gb = df.group_by_stable(names)?;
2298                let groups = gb.get_groups();
2299                let (offset, len) = slice.unwrap_or((0, groups.len()));
2300                let groups = groups.slice(offset, len);
2301                df.apply_columns_par(|s| unsafe { s.agg_first(&groups) })
2302            },
2303            (UniqueKeepStrategy::Last, true) => {
2304                // maintain order by last values, so the sorted groups are not correct as they
2305                // are sorted by the first value
2306                let gb = df.group_by_stable(names)?;
2307                let groups = gb.get_groups();
2308
2309                let last_idx: NoNull<IdxCa> = groups
2310                    .iter()
2311                    .map(|g| match g {
2312                        GroupsIndicator::Idx((_first, idx)) => idx[idx.len() - 1],
2313                        GroupsIndicator::Slice([first, len]) => first + len - 1,
2314                    })
2315                    .collect();
2316
2317                let mut last_idx = last_idx.into_inner().sort(false);
2318
2319                if let Some((offset, len)) = slice {
2320                    last_idx = last_idx.slice(offset, len);
2321                }
2322
2323                let last_idx = NoNull::new(last_idx);
2324                let out = unsafe { df.take_unchecked(&last_idx) };
2325                return Ok(out);
2326            },
2327            (UniqueKeepStrategy::First | UniqueKeepStrategy::Any, false) => {
2328                let gb = df.group_by(names)?;
2329                let groups = gb.get_groups();
2330                let (offset, len) = slice.unwrap_or((0, groups.len()));
2331                let groups = groups.slice(offset, len);
2332                df.apply_columns_par(|s| unsafe { s.agg_first(&groups) })
2333            },
2334            (UniqueKeepStrategy::Last, false) => {
2335                let gb = df.group_by(names)?;
2336                let groups = gb.get_groups();
2337                let (offset, len) = slice.unwrap_or((0, groups.len()));
2338                let groups = groups.slice(offset, len);
2339                df.apply_columns_par(|s| unsafe { s.agg_last(&groups) })
2340            },
2341            (UniqueKeepStrategy::None, _) => {
2342                let df_part = df.select(names)?;
2343                let mask = df_part.is_unique()?;
2344                let mut filtered = df.filter(&mask)?;
2345
2346                if let Some((offset, len)) = slice {
2347                    filtered = filtered.slice(offset, len);
2348                }
2349                return Ok(filtered);
2350            },
2351        };
2352        Ok(unsafe { DataFrame::new_unchecked_infer_height(columns).with_schema_from(self) })
2353    }
2354
2355    /// Get a mask of all the unique rows in the [`DataFrame`].
2356    ///
2357    /// # Example
2358    ///
2359    /// ```no_run
2360    /// # use polars_core::prelude::*;
2361    /// let df: DataFrame = df!("Company" => ["Apple", "Microsoft"],
2362    ///                         "ISIN" => ["US0378331005", "US5949181045"])?;
2363    /// let ca: ChunkedArray<BooleanType> = df.is_unique()?;
2364    ///
2365    /// assert!(ca.all());
2366    /// # Ok::<(), PolarsError>(())
2367    /// ```
2368    #[cfg(feature = "algorithm_group_by")]
2369    pub fn is_unique(&self) -> PolarsResult<BooleanChunked> {
2370        let gb = self.group_by(self.get_column_names_owned())?;
2371        let groups = gb.get_groups();
2372        Ok(is_unique_helper(
2373            groups,
2374            self.height() as IdxSize,
2375            true,
2376            false,
2377        ))
2378    }
2379
2380    /// Get a mask of all the duplicated rows in the [`DataFrame`].
2381    ///
2382    /// # Example
2383    ///
2384    /// ```no_run
2385    /// # use polars_core::prelude::*;
2386    /// let df: DataFrame = df!("Company" => ["Alphabet", "Alphabet"],
2387    ///                         "ISIN" => ["US02079K3059", "US02079K1079"])?;
2388    /// let ca: ChunkedArray<BooleanType> = df.is_duplicated()?;
2389    ///
2390    /// assert!(!ca.all());
2391    /// # Ok::<(), PolarsError>(())
2392    /// ```
2393    #[cfg(feature = "algorithm_group_by")]
2394    pub fn is_duplicated(&self) -> PolarsResult<BooleanChunked> {
2395        let gb = self.group_by(self.get_column_names_owned())?;
2396        let groups = gb.get_groups();
2397        Ok(is_unique_helper(
2398            groups,
2399            self.height() as IdxSize,
2400            false,
2401            true,
2402        ))
2403    }
2404
2405    /// Create a new [`DataFrame`] that shows the null counts per column.
2406    #[must_use]
2407    pub fn null_count(&self) -> Self {
2408        let cols =
2409            self.apply_columns(|c| Column::new(c.name().clone(), [c.null_count() as IdxSize]));
2410        unsafe { Self::new_unchecked(1, cols) }
2411    }
2412
2413    /// Hash and combine the row values
2414    #[cfg(feature = "row_hash")]
2415    pub fn hash_rows(
2416        &mut self,
2417        hasher_builder: Option<PlSeedableRandomStateQuality>,
2418    ) -> PolarsResult<UInt64Chunked> {
2419        let dfs = split_df(self, RAYON.current_num_threads(), false);
2420        let (cas, _) = _df_rows_to_hashes_threaded_vertical(&dfs, hasher_builder)?;
2421
2422        let mut iter = cas.into_iter();
2423        let mut acc_ca = iter.next().unwrap();
2424        for ca in iter {
2425            acc_ca.append(&ca)?;
2426        }
2427        Ok(acc_ca.rechunk().into_owned())
2428    }
2429
2430    /// Get the supertype of the columns in this DataFrame
2431    pub fn get_supertype(&self) -> Option<PolarsResult<DataType>> {
2432        self.columns()
2433            .iter()
2434            .map(|s| Ok(s.dtype().clone()))
2435            .reduce(|acc, b| try_get_supertype(&acc?, &b.unwrap()))
2436    }
2437
2438    /// Take by index values given by the slice `idx`.
2439    /// # Warning
2440    /// Be careful with allowing threads when calling this in a large hot loop
2441    /// every thread split may be on rayon stack and lead to SO
2442    #[doc(hidden)]
2443    pub unsafe fn _take_unchecked_slice(&self, idx: &[IdxSize], allow_threads: bool) -> Self {
2444        self._take_unchecked_slice_sorted(idx, allow_threads, IsSorted::Not)
2445    }
2446
2447    /// Take by index values given by the slice `idx`. Use this over `_take_unchecked_slice`
2448    /// if the index value in `idx` are sorted. This will maintain sorted flags.
2449    ///
2450    /// # Warning
2451    /// Be careful with allowing threads when calling this in a large hot loop
2452    /// every thread split may be on rayon stack and lead to SO
2453    #[doc(hidden)]
2454    pub unsafe fn _take_unchecked_slice_sorted(
2455        &self,
2456        idx: &[IdxSize],
2457        allow_threads: bool,
2458        sorted: IsSorted,
2459    ) -> Self {
2460        #[cfg(debug_assertions)]
2461        {
2462            if idx.len() > 2 {
2463                use crate::series::IsSorted;
2464
2465                match sorted {
2466                    IsSorted::Ascending => {
2467                        assert!(idx[0] <= idx[idx.len() - 1]);
2468                    },
2469                    IsSorted::Descending => {
2470                        assert!(idx[0] >= idx[idx.len() - 1]);
2471                    },
2472                    _ => {},
2473                }
2474            }
2475        }
2476        let mut ca = IdxCa::mmap_slice(PlSmallStr::EMPTY, idx);
2477        ca.set_sorted_flag(sorted);
2478        self.take_unchecked_impl(&ca, allow_threads)
2479    }
2480    #[cfg(all(feature = "partition_by", feature = "algorithm_group_by"))]
2481    #[doc(hidden)]
2482    pub fn _partition_by_impl(
2483        &self,
2484        cols: &[PlSmallStr],
2485        stable: bool,
2486        include_key: bool,
2487        parallel: bool,
2488    ) -> PolarsResult<Vec<DataFrame>> {
2489        let selected_keys = self.select_to_vec(cols.iter().cloned())?;
2490        let groups = self.group_by_with_series(selected_keys, parallel, stable)?;
2491        let groups = groups.into_groups();
2492
2493        // drop key columns prior to calculation if requested
2494        let df = if include_key {
2495            self.clone()
2496        } else {
2497            self.drop_many(cols.iter().cloned())
2498        };
2499
2500        if parallel {
2501            // don't parallelize this
2502            // there is a lot of parallelization in take and this may easily SO
2503            RAYON.install(|| {
2504                match groups.as_ref() {
2505                    GroupsType::Idx(idx) => {
2506                        // Rechunk as the gather may rechunk for every group #17562.
2507                        let mut df = df.clone();
2508                        df.rechunk_mut_par();
2509                        Ok(idx
2510                            .into_par_iter()
2511                            .map(|(_, group)| {
2512                                // groups are in bounds
2513                                unsafe {
2514                                    df._take_unchecked_slice_sorted(
2515                                        group,
2516                                        false,
2517                                        IsSorted::Ascending,
2518                                    )
2519                                }
2520                            })
2521                            .collect())
2522                    },
2523                    GroupsType::Slice { groups, .. } => Ok(groups
2524                        .into_par_iter()
2525                        .map(|[first, len]| df.slice(*first as i64, *len as usize))
2526                        .collect()),
2527                }
2528            })
2529        } else {
2530            match groups.as_ref() {
2531                GroupsType::Idx(idx) => {
2532                    // Rechunk as the gather may rechunk for every group #17562.
2533                    let mut df = df;
2534                    df.rechunk_mut();
2535                    Ok(idx
2536                        .into_iter()
2537                        .map(|(_, group)| {
2538                            // groups are in bounds
2539                            unsafe {
2540                                df._take_unchecked_slice_sorted(group, false, IsSorted::Ascending)
2541                            }
2542                        })
2543                        .collect())
2544                },
2545                GroupsType::Slice { groups, .. } => Ok(groups
2546                    .iter()
2547                    .map(|[first, len]| df.slice(*first as i64, *len as usize))
2548                    .collect()),
2549            }
2550        }
2551    }
2552
2553    /// Split into multiple DataFrames partitioned by groups
2554    #[cfg(feature = "partition_by")]
2555    pub fn partition_by<I, S>(&self, cols: I, include_key: bool) -> PolarsResult<Vec<DataFrame>>
2556    where
2557        I: IntoIterator<Item = S>,
2558        S: Into<PlSmallStr>,
2559    {
2560        let cols: UnitVec<PlSmallStr> = cols.into_iter().map(Into::into).collect();
2561        self._partition_by_impl(cols.as_slice(), false, include_key, true)
2562    }
2563
2564    /// Split into multiple DataFrames partitioned by groups
2565    /// Order of the groups are maintained.
2566    #[cfg(feature = "partition_by")]
2567    pub fn partition_by_stable<I, S>(
2568        &self,
2569        cols: I,
2570        include_key: bool,
2571    ) -> PolarsResult<Vec<DataFrame>>
2572    where
2573        I: IntoIterator<Item = S>,
2574        S: Into<PlSmallStr>,
2575    {
2576        let cols: UnitVec<PlSmallStr> = cols.into_iter().map(Into::into).collect();
2577        self._partition_by_impl(cols.as_slice(), true, include_key, true)
2578    }
2579
2580    /// Unnest the given `Struct` columns. This means that the fields of the `Struct` type will be
2581    /// inserted as columns.
2582    #[cfg(feature = "dtype-struct")]
2583    pub fn unnest(
2584        &self,
2585        cols: impl IntoIterator<Item = impl Into<PlSmallStr>>,
2586        separator: Option<&str>,
2587    ) -> PolarsResult<DataFrame> {
2588        self.unnest_impl(cols.into_iter().map(Into::into).collect(), separator)
2589    }
2590
2591    #[cfg(feature = "dtype-struct")]
2592    fn unnest_impl(
2593        &self,
2594        cols: PlHashSet<PlSmallStr>,
2595        separator: Option<&str>,
2596    ) -> PolarsResult<DataFrame> {
2597        let mut new_cols = Vec::with_capacity(std::cmp::min(self.width() * 2, self.width() + 128));
2598        let mut count = 0;
2599        for s in self.columns() {
2600            if cols.contains(s.name()) {
2601                let ca = s.struct_()?.clone();
2602                new_cols.extend(ca.fields_as_series().into_iter().map(|mut f| {
2603                    if let Some(separator) = &separator {
2604                        f.rename(polars_utils::format_pl_smallstr!(
2605                            "{}{}{}",
2606                            s.name(),
2607                            separator,
2608                            f.name()
2609                        ));
2610                    }
2611                    Column::from(f)
2612                }));
2613                count += 1;
2614            } else {
2615                new_cols.push(s.clone())
2616            }
2617        }
2618        if count != cols.len() {
2619            // one or more columns not found
2620            // the code below will return an error with the missing name
2621            let schema = self.schema();
2622            for col in cols {
2623                let _ = schema
2624                    .get(col.as_str())
2625                    .ok_or_else(|| polars_err!(col_not_found = col))?;
2626            }
2627        }
2628
2629        DataFrame::new(self.height(), new_cols)
2630    }
2631
2632    pub fn append_record_batch(&mut self, rb: RecordBatchT<ArrayRef>) -> PolarsResult<()> {
2633        // @Optimize: this does a lot of unnecessary allocations. We should probably have a
2634        // append_chunk or something like this. It is just quite difficult to make that safe.
2635        let df = DataFrame::from(rb);
2636        polars_ensure!(
2637            self.schema() == df.schema(),
2638            SchemaMismatch: "cannot append record batch with different schema\n\n
2639        Got {:?}\nexpected: {:?}", df.schema(), self.schema(),
2640        );
2641        self.vstack_mut_owned_unchecked(df);
2642        Ok(())
2643    }
2644}
2645
2646/// Cursor over the chunks a [`DataFrame`] is laid out along.
2647struct ChunkCursor<'a> {
2648    /// The chunks of the first column that has any; empty when no column does.
2649    chunks: &'a [ArrayRef],
2650    height: usize,
2651    idx: usize,
2652}
2653
2654impl<'a> ChunkCursor<'a> {
2655    fn new(df: &'a DataFrame) -> Self {
2656        let chunks = match df.columns().iter().find_map(Column::as_series) {
2657            Some(s) => s.chunks().as_slice(),
2658            None => &[],
2659        };
2660
2661        Self {
2662            chunks,
2663            height: df.height(),
2664            idx: 0,
2665        }
2666    }
2667
2668    fn n_chunks(&self) -> usize {
2669        usize::max(1, self.chunks.len())
2670    }
2671
2672    /// The index and number of rows of the next chunk.
2673    fn next(&mut self) -> Option<(usize, usize)> {
2674        let idx = self.idx;
2675        if idx >= self.n_chunks() {
2676            return None;
2677        }
2678
2679        self.idx += 1;
2680        Some((idx, self.chunks.get(idx).map_or(self.height, |c| c.len())))
2681    }
2682
2683    fn size_hint(&self) -> (usize, Option<usize>) {
2684        let n = self.n_chunks() - self.idx;
2685        (n, Some(n))
2686    }
2687}
2688
2689pub struct RecordBatchIter<'a> {
2690    df: &'a DataFrame,
2691    schema: ArrowSchemaRef,
2692    cursor: ChunkCursor<'a>,
2693    compat_level: CompatLevel,
2694    parallel: bool,
2695}
2696
2697impl Iterator for RecordBatchIter<'_> {
2698    type Item = RecordBatch;
2699
2700    fn next(&mut self) -> Option<Self::Item> {
2701        let (idx, length) = self.cursor.next()?;
2702
2703        let to_arrow = |c: &Column| match c.as_series() {
2704            Some(s) => s.to_arrow(idx, self.compat_level),
2705            None => c.slice(0, length).rechunk_to_arrow(self.compat_level),
2706        };
2707
2708        let batch_cols: Vec<ArrayRef> = if self.parallel {
2709            let iter = self.df.columns().par_iter().map(to_arrow);
2710            RAYON.install(|| iter.collect())
2711        } else {
2712            self.df.columns().iter().map(to_arrow).collect()
2713        };
2714
2715        Some(RecordBatch::new(length, self.schema.clone(), batch_cols))
2716    }
2717
2718    fn size_hint(&self) -> (usize, Option<usize>) {
2719        self.cursor.size_hint()
2720    }
2721}
2722
2723pub struct PhysRecordBatchIter<'a> {
2724    df: &'a DataFrame,
2725    schema: ArrowSchemaRef,
2726    cursor: ChunkCursor<'a>,
2727}
2728
2729impl Iterator for PhysRecordBatchIter<'_> {
2730    type Item = RecordBatch;
2731
2732    fn next(&mut self) -> Option<Self::Item> {
2733        let (idx, length) = self.cursor.next()?;
2734
2735        let arrs = self
2736            .df
2737            .columns()
2738            .iter()
2739            .map(|c| match c.as_series() {
2740                Some(s) => s.chunks()[idx].clone(),
2741                None => c
2742                    .slice(0, length)
2743                    .take_materialized_series()
2744                    .rechunk()
2745                    .chunks()[0]
2746                    .clone(),
2747            })
2748            .collect::<Vec<_>>();
2749
2750        Some(RecordBatch::new(length, self.schema.clone(), arrs))
2751    }
2752
2753    fn size_hint(&self) -> (usize, Option<usize>) {
2754        self.cursor.size_hint()
2755    }
2756}
2757
2758pub enum RecordBatchIterWrap<'a> {
2759    ZeroWidth {
2760        remaining_height: usize,
2761        chunk_size: usize,
2762    },
2763    Batches(RecordBatchIter<'a>),
2764    PhysicalBatches(PhysRecordBatchIter<'a>),
2765}
2766
2767impl<'a> RecordBatchIterWrap<'a> {
2768    fn new_zero_width(height: usize) -> Self {
2769        Self::ZeroWidth {
2770            remaining_height: height,
2771            chunk_size: polars_config::config().ideal_morsel_size() as usize,
2772        }
2773    }
2774}
2775
2776impl Iterator for RecordBatchIterWrap<'_> {
2777    type Item = RecordBatch;
2778
2779    fn next(&mut self) -> Option<Self::Item> {
2780        match self {
2781            Self::ZeroWidth {
2782                remaining_height,
2783                chunk_size,
2784            } => {
2785                let n = usize::min(*remaining_height, *chunk_size);
2786                *remaining_height -= n;
2787
2788                (n > 0).then(|| RecordBatch::new(n, ArrowSchemaRef::default(), vec![]))
2789            },
2790            Self::Batches(v) => v.next(),
2791            Self::PhysicalBatches(v) => v.next(),
2792        }
2793    }
2794
2795    fn size_hint(&self) -> (usize, Option<usize>) {
2796        match self {
2797            Self::ZeroWidth {
2798                remaining_height,
2799                chunk_size,
2800            } => {
2801                let n = remaining_height.div_ceil(*chunk_size);
2802                (n, Some(n))
2803            },
2804            Self::Batches(v) => v.size_hint(),
2805            Self::PhysicalBatches(v) => v.size_hint(),
2806        }
2807    }
2808}
2809
2810// utility to test if we can vstack/extend the columns
2811fn ensure_can_extend(left: &Column, right: &Column) -> PolarsResult<()> {
2812    polars_ensure!(
2813        left.name() == right.name(),
2814        ShapeMismatch: "unable to vstack, column names don't match: {:?} and {:?}",
2815        left.name(), right.name(),
2816    );
2817    Ok(())
2818}
2819
2820#[cfg(test)]
2821mod test {
2822    use super::*;
2823
2824    fn create_frame() -> DataFrame {
2825        let s0 = Column::new("days".into(), [0, 1, 2].as_ref());
2826        let s1 = Column::new("temp".into(), [22.1, 19.9, 7.].as_ref());
2827        DataFrame::new_infer_height(vec![s0, s1]).unwrap()
2828    }
2829
2830    #[test]
2831    fn sort_in_place_keeps_the_chunks_of_a_frame_sorted_by_one_key() {
2832        let mut df = df!("a" => [1, 2], "b" => [1, 1]).unwrap();
2833        df.vstack_mut(&df!("a" => [3, 4], "b" => [1, 1]).unwrap())
2834            .unwrap();
2835        df.apply("a", |c| {
2836            let mut c = c.clone();
2837            c.set_sorted_flag(IsSorted::Ascending);
2838            c
2839        })
2840        .unwrap();
2841        assert_eq!(df.first_col_n_chunks(), 2);
2842
2843        df.sort_in_place(["a"], SortMultipleOptions::default())
2844            .unwrap();
2845        assert_eq!(df.first_col_n_chunks(), 2);
2846
2847        df.sort_in_place(["a", "b"], SortMultipleOptions::default())
2848            .unwrap();
2849        assert_eq!(df.first_col_n_chunks(), 1);
2850        let a = df.column("a").unwrap().as_materialized_series();
2851        assert_eq!(
2852            a.i32().unwrap().to_vec(),
2853            [Some(1), Some(2), Some(3), Some(4)]
2854        );
2855    }
2856
2857    #[test]
2858    #[cfg_attr(miri, ignore)]
2859    fn test_recordbatch_iterator() {
2860        let df = df!(
2861            "foo" => [1, 2, 3, 4, 5]
2862        )
2863        .unwrap();
2864        let mut iter = df.iter_chunks(CompatLevel::newest(), false);
2865        assert_eq!(5, iter.next().unwrap().len());
2866        assert!(iter.next().is_none());
2867    }
2868
2869    #[test]
2870    #[cfg_attr(miri, ignore)]
2871    fn test_select() {
2872        let df = create_frame();
2873        assert_eq!(
2874            df.column("days")
2875                .unwrap()
2876                .as_series()
2877                .unwrap()
2878                .equal(1)
2879                .unwrap()
2880                .sum(),
2881            Some(1)
2882        );
2883    }
2884
2885    #[test]
2886    #[cfg_attr(miri, ignore)]
2887    fn test_filter_broadcast_on_string_col() {
2888        let col_name = "some_col";
2889        let v = vec!["test".to_string()];
2890        let s0 = Column::new(PlSmallStr::from_str(col_name), v);
2891        let mut df = DataFrame::new_infer_height(vec![s0]).unwrap();
2892
2893        df = df
2894            .filter(
2895                &df.column(col_name)
2896                    .unwrap()
2897                    .as_materialized_series()
2898                    .equal("")
2899                    .unwrap(),
2900            )
2901            .unwrap();
2902        assert_eq!(
2903            df.column(col_name)
2904                .unwrap()
2905                .as_materialized_series()
2906                .n_chunks(),
2907            1
2908        );
2909    }
2910
2911    #[test]
2912    #[cfg_attr(miri, ignore)]
2913    fn test_filter_broadcast_on_list_col() {
2914        let s1 = Series::new(PlSmallStr::EMPTY, [true, false, true]);
2915        let ll: ListChunked = [&s1].iter().copied().collect();
2916
2917        let mask = BooleanChunked::from_slice(PlSmallStr::EMPTY, &[false]);
2918        let new = ll.filter(&mask).unwrap();
2919
2920        assert_eq!(new.chunks.len(), 1);
2921        assert_eq!(new.len(), 0);
2922    }
2923
2924    #[test]
2925    fn slice() {
2926        let df = create_frame();
2927        let sliced_df = df.slice(0, 2);
2928        assert_eq!(sliced_df.shape(), (2, 2));
2929    }
2930
2931    #[test]
2932    fn rechunk_false() {
2933        let df = create_frame();
2934        assert!(!df.should_rechunk())
2935    }
2936
2937    #[test]
2938    fn rechunk_true() -> PolarsResult<()> {
2939        let mut base = df!(
2940            "a" => [1, 2, 3],
2941            "b" => [1, 2, 3]
2942        )?;
2943
2944        // Create a series with multiple chunks
2945        let mut s = Series::new("foo".into(), 0..2);
2946        let s2 = Series::new("bar".into(), 0..1);
2947        s.append(&s2)?;
2948
2949        // Append series to frame
2950        let out = base.with_column(s.into_column())?;
2951
2952        // Now we should rechunk
2953        assert!(out.should_rechunk());
2954        Ok(())
2955    }
2956
2957    #[test]
2958    fn test_duplicate_column() {
2959        let mut df = df! {
2960            "foo" => [1, 2, 3]
2961        }
2962        .unwrap();
2963        // check if column is replaced
2964        assert!(
2965            df.with_column(Column::new("foo".into(), &[1, 2, 3]))
2966                .is_ok()
2967        );
2968        assert!(
2969            df.with_column(Column::new("bar".into(), &[1, 2, 3]))
2970                .is_ok()
2971        );
2972        assert!(df.column("bar").is_ok())
2973    }
2974
2975    #[test]
2976    #[cfg_attr(miri, ignore)]
2977    fn distinct() {
2978        let df = df! {
2979            "flt" => [1., 1., 2., 2., 3., 3.],
2980            "int" => [1, 1, 2, 2, 3, 3, ],
2981            "str" => ["a", "a", "b", "b", "c", "c"]
2982        }
2983        .unwrap();
2984        let df = df
2985            .unique_stable(None, UniqueKeepStrategy::First, None)
2986            .unwrap()
2987            .sort(["flt"], SortMultipleOptions::default())
2988            .unwrap();
2989        let valid = df! {
2990            "flt" => [1., 2., 3.],
2991            "int" => [1, 2, 3],
2992            "str" => ["a", "b", "c"]
2993        }
2994        .unwrap();
2995        assert!(df.equals(&valid));
2996    }
2997
2998    #[test]
2999    fn test_vstack() {
3000        // check that it does not accidentally rechunks
3001        let mut df = df! {
3002            "flt" => [1., 1., 2., 2., 3., 3.],
3003            "int" => [1, 1, 2, 2, 3, 3, ],
3004            "str" => ["a", "a", "b", "b", "c", "c"]
3005        }
3006        .unwrap();
3007
3008        df.vstack_mut(&df.slice(0, 3)).unwrap();
3009        assert_eq!(df.first_col_n_chunks(), 2)
3010    }
3011
3012    #[test]
3013    fn test_chunk_alignment_with_scalar_column() {
3014        let mut df = df! { "int" => [0, 1] }.unwrap();
3015        df.vstack_mut(&df! { "int" => [2] }.unwrap()).unwrap();
3016        df.insert_column(
3017            0,
3018            Column::new_scalar("str".into(), Scalar::from(PlSmallStr::from_static("a")), 3),
3019        )
3020        .unwrap();
3021
3022        // A materialized scalar column still follows the layout of the other columns.
3023        for materialize in [false, true] {
3024            if materialize {
3025                df.columns()[0].as_materialized_series();
3026            }
3027            assert!(!df.should_rechunk());
3028
3029            let batch_lengths = |batches: Vec<RecordBatch>| {
3030                batches
3031                    .iter()
3032                    .map(|b| {
3033                        assert!(b.arrays().iter().all(|arr| arr.len() == b.len()));
3034                        b.len()
3035                    })
3036                    .collect::<Vec<_>>()
3037            };
3038            let batches = df.iter_chunks(CompatLevel::newest(), false).collect();
3039            assert_eq!(batch_lengths(batches), [2, 1]);
3040            let batches = df.iter_chunks_physical().collect();
3041            assert_eq!(batch_lengths(batches), [2, 1]);
3042        }
3043    }
3044
3045    #[test]
3046    fn test_vstack_scalar_chunk_alignment() {
3047        // Appending equal scalars keeps the column unmaterialized.
3048        let mut df = df! {
3049            "int" => [0],
3050            "str" => ["a"],
3051        }
3052        .unwrap();
3053        df.vstack_mut(&df! { "int" => [1], "str" => ["a"] }.unwrap())
3054            .unwrap();
3055
3056        assert_eq!(df.column("int").unwrap().n_chunks(), 2);
3057        assert!(df.column("str").unwrap().as_scalar_column().is_some());
3058        assert!(!df.should_rechunk());
3059
3060        let batches = df
3061            .iter_chunks(CompatLevel::newest(), false)
3062            .collect::<Vec<_>>();
3063        assert_eq!(batches.iter().map(|b| b.len()).collect::<Vec<_>>(), [1, 1]);
3064    }
3065
3066    #[test]
3067    fn test_vstack_on_empty_dataframe() {
3068        let mut df = DataFrame::empty();
3069
3070        let df_data = df! {
3071            "flt" => [1., 1., 2., 2., 3., 3.],
3072            "int" => [1, 1, 2, 2, 3, 3, ],
3073            "str" => ["a", "a", "b", "b", "c", "c"]
3074        }
3075        .unwrap();
3076
3077        df.vstack_mut(&df_data).unwrap();
3078        assert_eq!(df.height(), 6)
3079    }
3080
3081    #[test]
3082    fn test_unique_keep_none_with_slice() {
3083        let df = df! {
3084            "x" => [1, 2, 3, 2, 1]
3085        }
3086        .unwrap();
3087        let out = df
3088            .unique_stable(
3089                Some(&["x".to_string()][..]),
3090                UniqueKeepStrategy::None,
3091                Some((0, 2)),
3092            )
3093            .unwrap();
3094        let expected = df! {
3095            "x" => [3]
3096        }
3097        .unwrap();
3098        assert!(out.equals(&expected));
3099    }
3100
3101    #[test]
3102    #[cfg(feature = "dtype-i8")]
3103    fn test_apply_result_schema() {
3104        let mut df = df! {
3105            "x" => [1, 2, 3, 2, 1]
3106        }
3107        .unwrap();
3108
3109        let schema_before = df.schema().clone();
3110        df.apply("x", |f| f.cast(&DataType::Int8).unwrap()).unwrap();
3111        assert_ne!(&schema_before, df.schema());
3112    }
3113}