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