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