1#![allow(unsafe_op_in_unsafe_fn)]
2use std::borrow::Cow;
4
5use either::Either;
6use polars_arrow::datatypes::ArrowSchemaRef;
7use polars_row::ArrayRef;
8use polars_utils::UnitVec;
9use polars_utils::itertools::Itertools;
10use rayon::prelude::*;
11
12use crate::chunked_array::flags::StatisticsFlags;
13#[cfg(feature = "algorithm_group_by")]
14use crate::chunked_array::ops::unique::is_unique_helper;
15use crate::prelude::gather::check_bounds_ca;
16use crate::prelude::*;
17#[cfg(feature = "row_hash")]
18use crate::utils::split_df;
19use crate::utils::{Container, NoNull, try_get_supertype};
20use crate::{HEAD_DEFAULT_LENGTH, TAIL_DEFAULT_LENGTH};
21
22#[cfg(feature = "dataframe_arithmetic")]
23mod arithmetic;
24pub mod builder;
25mod chunks;
26pub use chunks::chunk_df_for_writing;
27pub mod column;
28mod dataframe;
29mod filter;
30mod projection;
31pub use dataframe::DataFrame;
32use filter::filter_zero_width;
33use projection::{AmortizedColumnSelector, LINEAR_SEARCH_LIMIT};
34
35pub mod explode;
36mod from;
37#[cfg(feature = "algorithm_group_by")]
38pub mod group_by;
39pub(crate) mod horizontal;
40#[cfg(any(feature = "rows", feature = "object"))]
41pub mod row;
42mod top_k;
43mod upstream_traits;
44pub(crate) mod validation;
45
46use polars_arrow::record_batch::{RecordBatch, RecordBatchT};
47use polars_utils::pl_str::PlSmallStr;
48use polars_utils::slice::slice_offsets;
49#[cfg(feature = "serde")]
50use serde::{Deserialize, Serialize};
51use strum_macros::IntoStaticStr;
52
53#[cfg(feature = "row_hash")]
54use crate::hashing::_df_rows_to_hashes_threaded_vertical;
55use crate::prelude::sort::arg_sort;
56use crate::runtime::RAYON;
57use crate::series::IsSorted;
58
59#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Hash, IntoStaticStr)]
60#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
61#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
62#[strum(serialize_all = "snake_case")]
63pub enum UniqueKeepStrategy {
64 First,
66 Last,
68 None,
70 #[default]
73 Any,
74}
75
76#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Hash, IntoStaticStr)]
77#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
78#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
79#[strum(serialize_all = "snake_case")]
80pub enum PivotColumnNaming {
82 Combine,
84 #[default]
87 Auto,
88}
89
90impl DataFrame {
91 pub fn materialized_column_iter(&self) -> impl ExactSizeIterator<Item = &Series> {
92 self.columns().iter().map(Column::as_materialized_series)
93 }
94
95 pub fn estimated_size(&self, expanded: bool) -> usize {
111 self.columns()
112 .iter()
113 .map(|c| c.estimated_size(expanded))
114 .sum()
115 }
116
117 pub fn try_apply_columns(
118 &self,
119 func: impl Fn(&Column) -> PolarsResult<Column> + Send + Sync,
120 ) -> PolarsResult<Vec<Column>> {
121 return inner(self, &func);
122
123 fn inner(
124 slf: &DataFrame,
125 func: &(dyn Fn(&Column) -> PolarsResult<Column> + Send + Sync),
126 ) -> PolarsResult<Vec<Column>> {
127 slf.columns().iter().map(func).collect()
128 }
129 }
130
131 pub fn apply_columns(&self, func: impl Fn(&Column) -> Column + Send + Sync) -> Vec<Column> {
132 return inner(self, &func);
133
134 fn inner(slf: &DataFrame, func: &(dyn Fn(&Column) -> Column + Send + Sync)) -> Vec<Column> {
135 slf.columns().iter().map(func).collect()
136 }
137 }
138
139 pub fn try_apply_columns_par(
140 &self,
141 func: impl Fn(&Column) -> PolarsResult<Column> + Send + Sync,
142 ) -> PolarsResult<Vec<Column>> {
143 return inner(self, &func);
144
145 fn inner(
146 slf: &DataFrame,
147 func: &(dyn Fn(&Column) -> PolarsResult<Column> + Send + Sync),
148 ) -> PolarsResult<Vec<Column>> {
149 RAYON.install(|| slf.columns().par_iter().map(func).collect())
150 }
151 }
152
153 pub fn apply_columns_par(&self, func: impl Fn(&Column) -> Column + Send + Sync) -> Vec<Column> {
154 return inner(self, &func);
155
156 fn inner(slf: &DataFrame, func: &(dyn Fn(&Column) -> Column + Send + Sync)) -> Vec<Column> {
157 RAYON.install(|| slf.columns().par_iter().map(func).collect())
158 }
159 }
160
161 pub(crate) fn reserve_chunks(&mut self, additional: usize) {
163 for s in unsafe { self.columns_mut_retain_schema() } {
164 if let Column::Series(s) = s {
165 unsafe { s.chunks_mut().reserve(additional) }
168 }
169 }
170 }
171 pub fn new_from_index(&self, index: usize, height: usize) -> Self {
172 let new_cols = self.apply_columns(|c| c.new_from_index(index, height));
173
174 unsafe { Self::_new_unchecked_impl(height, new_cols).with_schema_from(self) }
175 }
176
177 pub fn full_null(schema: &Schema, height: usize) -> Self {
179 let columns = schema
180 .iter_fields()
181 .map(|f| Column::full_null(f.name().clone(), height, f.dtype()))
182 .collect();
183
184 unsafe { DataFrame::_new_unchecked_impl(height, columns) }
185 }
186
187 pub fn ensure_matches_schema(&mut self, schema: &Schema) -> PolarsResult<()> {
190 let mut did_cast = false;
191 let cached_schema = self.cached_schema().cloned();
192
193 for (col, (name, dt)) in unsafe { self.columns_mut() }.iter_mut().zip(schema.iter()) {
194 polars_ensure!(
195 col.name() == name,
196 SchemaMismatch: "column name mismatch: expected {:?}, found {:?}",
197 name,
198 col.name()
199 );
200
201 let needs_cast = col.dtype().matches_schema_type(dt)?;
202
203 if needs_cast {
204 *col = col.cast(dt)?;
205 did_cast = true;
206 }
207 }
208
209 if !did_cast {
210 unsafe { self.set_opt_schema(cached_schema) };
211 }
212
213 Ok(())
214 }
215
216 pub fn with_row_index(&self, name: PlSmallStr, offset: Option<IdxSize>) -> PolarsResult<Self> {
251 let mut new_columns = Vec::with_capacity(self.width() + 1);
252 let offset = offset.unwrap_or(0);
253
254 if self.get_column_index(&name).is_some() {
255 polars_bail!(duplicate = name)
256 }
257
258 let col = Column::new_row_index(name, offset, self.height())?;
259 new_columns.push(col);
260 new_columns.extend_from_slice(self.columns());
261
262 Ok(unsafe { DataFrame::new_unchecked(self.height(), new_columns) })
263 }
264
265 pub unsafe fn with_row_index_mut(
273 &mut self,
274 name: PlSmallStr,
275 offset: Option<IdxSize>,
276 ) -> &mut Self {
277 debug_assert!(
278 self.get_column_index(&name).is_none(),
279 "with_row_index_mut(): column with name {} already exists",
280 name
281 );
282
283 let offset = offset.unwrap_or(0);
284 let col = Column::new_row_index(name, offset, self.height()).unwrap();
285
286 unsafe { self.columns_mut() }.insert(0, col);
287 self
288 }
289
290 pub fn shrink_to_fit(&mut self) {
292 for s in unsafe { self.columns_mut_retain_schema() } {
294 s.shrink_to_fit();
295 }
296 }
297
298 pub fn rechunk_mut_par(&mut self) -> &mut Self {
301 if self.columns().iter().any(|c| c.n_chunks() > 1) {
302 RAYON.install(|| {
303 unsafe { self.columns_mut_retain_schema() }
304 .par_iter_mut()
305 .for_each(|c| *c = c.rechunk());
306 })
307 }
308
309 self
310 }
311
312 pub fn rechunk_mut(&mut self) -> &mut Self {
314 let columns = unsafe { self.columns_mut() };
316
317 for col in columns.iter_mut().filter(|c| c.n_chunks() > 1) {
318 *col = col.rechunk();
319 }
320
321 self
322 }
323
324 pub fn should_rechunk(&self) -> bool {
326 if !self
332 .columns()
333 .iter()
334 .filter_map(Column::as_series)
335 .map(|s| s.n_chunks())
336 .all_equal()
337 {
338 return true;
339 }
340
341 let mut chunk_lengths = self
342 .columns()
343 .iter()
344 .filter_map(Column::as_series)
345 .map(|s| s.chunk_lengths());
346 match chunk_lengths.next() {
347 None => false,
348 Some(first_column_chunk_lengths) => {
349 if first_column_chunk_lengths.size_hint().0 == 1 {
351 return chunk_lengths.any(|cl| cl.size_hint().0 != 1);
352 }
353 let height = self.height();
356 let n_chunks = first_column_chunk_lengths.size_hint().0;
357 if n_chunks > height && !(height == 0 && n_chunks == 1) {
358 return true;
359 }
360 let v: Vec<_> = first_column_chunk_lengths.collect();
362 for cl in chunk_lengths {
363 if cl.enumerate().any(|(idx, el)| Some(&el) != v.get(idx)) {
364 return true;
365 }
366 }
367 false
368 },
369 }
370 }
371
372 pub fn align_chunks_par(&mut self) -> &mut Self {
374 if self.should_rechunk() {
375 self.rechunk_mut_par()
376 } else {
377 self
378 }
379 }
380
381 pub fn align_chunks(&mut self) -> &mut Self {
383 if self.should_rechunk() {
384 self.rechunk_mut()
385 } else {
386 self
387 }
388 }
389
390 pub fn get_column_names(&self) -> Vec<&PlSmallStr> {
401 self.columns().iter().map(|s| s.name()).collect()
402 }
403
404 pub fn get_column_names_owned(&self) -> Vec<PlSmallStr> {
406 self.columns().iter().map(|s| s.name().clone()).collect()
407 }
408
409 pub fn set_column_names<T>(&mut self, new_names: &[T]) -> PolarsResult<()>
421 where
422 T: AsRef<str>,
423 {
424 polars_ensure!(
425 new_names.len() == self.width(),
426 ShapeMismatch: "{} column names provided for a DataFrame of width {}",
427 new_names.len(), self.width()
428 );
429
430 validation::ensure_names_unique(new_names)?;
431
432 *unsafe { self.columns_mut() } = std::mem::take(unsafe { self.columns_mut() })
433 .into_iter()
434 .zip(new_names)
435 .map(|(c, name)| c.with_name(PlSmallStr::from_str(name.as_ref())))
436 .collect();
437
438 Ok(())
439 }
440
441 pub fn dtypes(&self) -> Vec<DataType> {
454 self.columns().iter().map(|s| s.dtype().clone()).collect()
455 }
456
457 pub fn first_col_n_chunks(&self) -> usize {
459 match self.columns().iter().find_map(|col| col.as_series()) {
460 None if self.width() == 0 => 0,
461 None => 1,
462 Some(s) => s.n_chunks(),
463 }
464 }
465
466 pub(crate) fn first_col_chunk_lengths(&self) -> impl Iterator<Item = usize> + '_ {
468 match self.columns().iter().find_map(|col| col.as_series()) {
469 None => Either::Right((self.width() > 0).then_some(self.height()).into_iter()),
470 Some(s) => Either::Left(s.chunk_lengths()),
471 }
472 }
473
474 pub fn max_n_chunks(&self) -> usize {
476 self.columns()
477 .iter()
478 .map(|s| s.as_series().map(|s| s.n_chunks()).unwrap_or(1))
479 .max()
480 .unwrap_or(0)
481 }
482
483 pub fn fields(&self) -> Vec<Field> {
499 self.columns()
500 .iter()
501 .map(|s| s.field().into_owned())
502 .collect()
503 }
504
505 pub fn hstack(&self, columns: &[Column]) -> PolarsResult<Self> {
539 let mut new_cols = Vec::with_capacity(self.width() + columns.len());
540
541 new_cols.extend(self.columns().iter().cloned());
542 new_cols.extend_from_slice(columns);
543
544 DataFrame::new(self.height(), new_cols)
545 }
546 pub fn vstack(&self, other: &DataFrame) -> PolarsResult<Self> {
587 let mut df = self.clone();
588 df.vstack_mut(other)?;
589 Ok(df)
590 }
591
592 pub fn vstack_mut(&mut self, other: &DataFrame) -> PolarsResult<&mut Self> {
633 if self.width() != other.width() {
634 polars_ensure!(
635 self.shape() == (0, 0),
636 ShapeMismatch:
637 "unable to append to a DataFrame of shape {:?} with a DataFrame of width {}",
638 self.shape(), other.width(),
639 );
640
641 self.clone_from(other);
642
643 return Ok(self);
644 }
645
646 let new_height = usize::checked_add(self.height(), other.height()).unwrap();
647
648 unsafe { self.columns_mut_retain_schema() }
649 .iter_mut()
650 .zip(other.columns())
651 .try_for_each::<_, PolarsResult<_>>(|(left, right)| {
652 ensure_can_extend(&*left, right)?;
653 left.append(right)
654 .with_context(|| format!("failed to vstack column '{}'", right.name()))?;
655 Ok(())
656 })?;
657
658 unsafe { self.set_height(new_height) };
659
660 Ok(self)
661 }
662
663 pub fn vstack_mut_owned(&mut self, other: DataFrame) -> PolarsResult<&mut Self> {
664 if self.width() != other.width() {
665 polars_ensure!(
666 self.shape() == (0, 0),
667 ShapeMismatch:
668 "unable to append to a DataFrame of width {} with a DataFrame of width {}",
669 self.width(), other.width(),
670 );
671
672 *self = other;
673
674 return Ok(self);
675 }
676
677 let new_height = usize::checked_add(self.height(), other.height()).unwrap();
678
679 unsafe { self.columns_mut_retain_schema() }
680 .iter_mut()
681 .zip(other.into_columns())
682 .try_for_each::<_, PolarsResult<_>>(|(left, right)| {
683 ensure_can_extend(&*left, &right)?;
684 let right_name = right.name().clone();
685 left.append_owned(right)
686 .with_context(|| format!("failed to vstack column '{right_name}'"))?;
687 Ok(())
688 })?;
689
690 unsafe { self.set_height(new_height) };
691
692 Ok(self)
693 }
694
695 pub fn vstack_mut_unchecked(&mut self, other: &DataFrame) -> &mut Self {
702 let new_height = usize::checked_add(self.height(), other.height()).unwrap();
703
704 unsafe { self.columns_mut_retain_schema() }
705 .iter_mut()
706 .zip(other.columns())
707 .for_each(|(left, right)| {
708 left.append(right)
709 .with_context(|| format!("failed to vstack column '{}'", right.name()))
710 .expect("should not fail");
711 });
712
713 unsafe { self.set_height(new_height) };
714
715 self
716 }
717
718 pub fn vstack_mut_owned_unchecked(&mut self, other: DataFrame) -> &mut Self {
725 let new_height = usize::checked_add(self.height(), other.height()).unwrap();
726
727 unsafe { self.columns_mut_retain_schema() }
728 .iter_mut()
729 .zip(other.into_columns())
730 .for_each(|(left, right)| {
731 left.append_owned(right).expect("should not fail");
732 });
733
734 unsafe { self.set_height(new_height) };
735
736 self
737 }
738
739 pub fn extend(&mut self, other: &DataFrame) -> PolarsResult<()> {
754 polars_ensure!(
755 self.width() == other.width(),
756 ShapeMismatch:
757 "unable to extend a DataFrame of width {} with a DataFrame of width {}",
758 self.width(), other.width(),
759 );
760
761 let new_height = usize::checked_add(self.height(), other.height()).unwrap();
762
763 unsafe { self.columns_mut_retain_schema() }
764 .iter_mut()
765 .zip(other.columns())
766 .try_for_each::<_, PolarsResult<_>>(|(left, right)| {
767 ensure_can_extend(&*left, right)?;
768 left.extend(right)
769 .with_context(|| format!("failed to extend column '{}'", right.name()))?;
770 Ok(())
771 })?;
772
773 unsafe { self.set_height(new_height) };
774
775 Ok(())
776 }
777
778 pub fn drop_in_place(&mut self, name: &str) -> PolarsResult<Column> {
795 let idx = self.try_get_column_index(name)?;
796 Ok(unsafe { self.columns_mut() }.remove(idx))
797 }
798
799 pub fn drop_nulls<S>(&self, subset: Option<&[S]>) -> PolarsResult<Self>
828 where
829 for<'a> &'a S: AsRef<str>,
830 {
831 if let Some(v) = subset {
832 let v = self.select_to_vec(v)?;
833 self._drop_nulls_impl(v.as_slice())
834 } else {
835 self._drop_nulls_impl(self.columns())
836 }
837 }
838
839 fn _drop_nulls_impl(&self, subset: &[Column]) -> PolarsResult<Self> {
840 if subset.iter().all(|s| !s.has_nulls()) {
842 return Ok(self.clone());
843 }
844
845 let mut iter = subset.iter();
846
847 let mask = iter
848 .next()
849 .ok_or_else(|| polars_err!(NoData: "no data to drop nulls from"))?;
850 let mut mask = mask.is_not_null();
851
852 for c in iter {
853 mask = mask & c.is_not_null();
854 }
855 self.filter(&mask)
856 }
857
858 pub fn drop(&self, name: &str) -> PolarsResult<Self> {
873 let idx = self.try_get_column_index(name)?;
874 let mut new_cols = Vec::with_capacity(self.width() - 1);
875
876 self.columns().iter().enumerate().for_each(|(i, s)| {
877 if i != idx {
878 new_cols.push(s.clone())
879 }
880 });
881
882 Ok(unsafe { DataFrame::_new_unchecked_impl(self.height(), new_cols) })
883 }
884
885 pub fn drop_many<I, S>(&self, names: I) -> Self
887 where
888 I: IntoIterator<Item = S>,
889 S: Into<PlSmallStr>,
890 {
891 let names: PlHashSet<PlSmallStr> = names.into_iter().map(|s| s.into()).collect();
892 self.drop_many_amortized(&names)
893 }
894
895 pub fn drop_many_amortized(&self, names: &PlHashSet<PlSmallStr>) -> DataFrame {
897 if names.is_empty() {
898 return self.clone();
899 }
900 let mut new_cols = Vec::with_capacity(self.width().saturating_sub(names.len()));
901 self.columns().iter().for_each(|s| {
902 if !names.contains(s.name()) {
903 new_cols.push(s.clone())
904 }
905 });
906
907 unsafe { DataFrame::new_unchecked(self.height(), new_cols) }
908 }
909
910 fn insert_column_no_namecheck(
913 &mut self,
914 index: usize,
915 column: Column,
916 ) -> PolarsResult<&mut Self> {
917 polars_ensure!(
918 column.len() == self.height(),
919 ShapeMismatch:
920 "unable to add a column of length {} to a DataFrame of height {}",
921 column.len(), self.height(),
922 );
923
924 unsafe { self.columns_mut() }.insert(index, column);
925 Ok(self)
926 }
927
928 pub fn insert_column(&mut self, index: usize, column: Column) -> PolarsResult<&mut Self> {
930 let name = column.name();
931
932 polars_ensure!(
933 self.get_column_index(name).is_none(),
934 Duplicate:
935 "column with name {:?} is already present in the DataFrame", name
936 );
937
938 self.insert_column_no_namecheck(index, column)
939 }
940
941 pub fn with_column(&mut self, mut column: Column) -> PolarsResult<&mut Self> {
944 column.broadcast_in_place_to(self.height())?;
945
946 if let Some(i) = self.get_column_index(column.name()) {
947 *unsafe { self.columns_mut() }.get_mut(i).unwrap() = column
948 } else {
949 unsafe { self.columns_mut() }.push(column)
950 };
951
952 Ok(self)
953 }
954
955 pub unsafe fn push_column_unchecked(&mut self, column: Column) -> &mut Self {
961 unsafe { self.columns_mut() }.push(column);
962 self
963 }
964
965 pub fn with_columns_mut(
968 &mut self,
969 columns: impl IntoIterator<Item = Column>,
970 output_schema: &Schema,
971 ) -> PolarsResult<()> {
972 let columns = columns.into_iter();
973
974 unsafe {
975 self.columns_mut_retain_schema()
976 .reserve(columns.size_hint().0)
977 }
978
979 for c in columns {
980 self.with_column_and_schema_mut(c, output_schema)?;
981 }
982
983 Ok(())
984 }
985
986 fn with_column_and_schema_mut(
987 &mut self,
988 mut column: Column,
989 output_schema: &Schema,
990 ) -> PolarsResult<&mut Self> {
991 column.broadcast_in_place_to(self.height())?;
992
993 let i = output_schema
994 .index_of(column.name())
995 .or_else(|| self.get_column_index(column.name()))
996 .unwrap_or(self.width());
997
998 if i < self.width() {
999 *unsafe { self.columns_mut() }.get_mut(i).unwrap() = column
1000 } else if i == self.width() {
1001 unsafe { self.columns_mut() }.push(column)
1002 } else {
1003 panic!("{:?}, {}", output_schema, column.name());
1005 }
1006
1007 Ok(self)
1008 }
1009
1010 pub fn get(&self, idx: usize) -> Option<Vec<AnyValue<'_>>> {
1021 (idx < self.height()).then(|| self.columns().iter().map(|c| c.get(idx).unwrap()).collect())
1022 }
1023
1024 pub fn select_at_idx(&self, idx: usize) -> Option<&Column> {
1040 self.columns().get(idx)
1041 }
1042
1043 pub fn get_column_index(&self, name: &str) -> Option<usize> {
1061 if let Some(schema) = self.cached_schema() {
1062 schema.index_of(name)
1063 } else if self.width() <= LINEAR_SEARCH_LIMIT {
1064 self.columns().iter().position(|s| s.name() == name)
1065 } else {
1066 self.schema().index_of(name)
1067 }
1068 }
1069
1070 pub fn try_get_column_index(&self, name: &str) -> PolarsResult<usize> {
1072 self.get_column_index(name)
1073 .ok_or_else(|| polars_err!(col_not_found = name))
1074 }
1075
1076 pub fn column(&self, name: &str) -> PolarsResult<&Column> {
1090 let idx = self.try_get_column_index(name)?;
1091 Ok(self.select_at_idx(idx).unwrap())
1092 }
1093
1094 pub fn select<I, S>(&self, names: I) -> PolarsResult<Self>
1105 where
1106 I: IntoIterator<Item = S>,
1107 S: AsRef<str>,
1108 {
1109 DataFrame::new(self.height(), self.select_to_vec(names)?)
1110 }
1111
1112 pub unsafe fn select_unchecked<I, S>(&self, names: I) -> PolarsResult<Self>
1117 where
1118 I: IntoIterator<Item = S>,
1119 S: AsRef<str>,
1120 {
1121 Ok(unsafe { DataFrame::new_unchecked(self.height(), self.select_to_vec(names)?) })
1122 }
1123
1124 pub fn select_to_vec(
1142 &self,
1143 selection: impl IntoIterator<Item = impl AsRef<str>>,
1144 ) -> PolarsResult<Vec<Column>> {
1145 AmortizedColumnSelector::new(self).select_multiple(selection)
1146 }
1147
1148 pub fn filter(&self, mask: &BooleanChunked) -> PolarsResult<Self> {
1160 if self.width() == 0 {
1161 filter_zero_width(self.height(), mask)
1162 } else if mask.len() == 1 && self.len() >= 1 {
1163 if mask.all() && mask.null_count() == 0 {
1164 Ok(self.clone())
1165 } else {
1166 Ok(self.clear())
1167 }
1168 } else {
1169 let all_chunks_aligned =
1172 !self.should_rechunk() && self.first_col_chunk_lengths().eq(mask.chunk_lengths());
1173
1174 let mask = if all_chunks_aligned {
1175 Cow::Borrowed(mask)
1176 } else {
1177 mask.rechunk()
1178 };
1179
1180 let new_columns: Vec<Column> =
1181 self.try_apply_columns_par(|s| s.filter(mask.as_ref()))?;
1182 let out = unsafe {
1183 DataFrame::new_unchecked(new_columns[0].len(), new_columns).with_schema_from(self)
1184 };
1185
1186 Ok(out)
1187 }
1188 }
1189
1190 pub fn filter_seq(&self, mask: &BooleanChunked) -> PolarsResult<Self> {
1192 if self.width() == 0 {
1193 filter_zero_width(self.height(), mask)
1194 } else if mask.len() == 1 && mask.null_count() == 0 && self.len() >= 1 {
1195 if mask.all() && mask.null_count() == 0 {
1196 Ok(self.clone())
1197 } else {
1198 Ok(self.clear())
1199 }
1200 } else {
1201 let all_chunks_aligned =
1202 !self.should_rechunk() && self.first_col_chunk_lengths().eq(mask.chunk_lengths());
1203
1204 let mask = if all_chunks_aligned {
1205 Cow::Borrowed(mask)
1206 } else {
1207 mask.rechunk()
1208 };
1209
1210 let new_columns: Vec<Column> = self.try_apply_columns(|s| s.filter(mask.as_ref()))?;
1211 let out = unsafe {
1212 DataFrame::new_unchecked(new_columns[0].len(), new_columns).with_schema_from(self)
1213 };
1214
1215 Ok(out)
1216 }
1217 }
1218
1219 pub fn take(&self, indices: &IdxCa) -> PolarsResult<Self> {
1231 check_bounds_ca(indices, self.height().try_into().unwrap_or(IdxSize::MAX))?;
1232
1233 let new_cols = self.apply_columns_par(|c| {
1234 assert_eq!(c.len(), self.height());
1235 unsafe { c.take_unchecked(indices) }
1236 });
1237
1238 Ok(unsafe { DataFrame::new_unchecked(indices.len(), new_cols).with_schema_from(self) })
1239 }
1240
1241 pub unsafe fn take_unchecked(&self, idx: &IdxCa) -> Self {
1244 self.take_unchecked_impl(idx, true)
1245 }
1246
1247 #[cfg(feature = "algorithm_group_by")]
1250 pub unsafe fn gather_group_unchecked(&self, group: &GroupsIndicator) -> Self {
1251 match group {
1252 GroupsIndicator::Idx((_, indices)) => unsafe {
1253 self.take_slice_unchecked_impl(indices.as_slice(), false)
1254 },
1255 GroupsIndicator::Slice([offset, len]) => self.slice(*offset as i64, *len as usize),
1256 }
1257 }
1258
1259 pub unsafe fn take_unchecked_impl(&self, idx: &IdxCa, allow_threads: bool) -> Self {
1262 let cols = if allow_threads && RAYON.current_num_threads() > 1 {
1263 RAYON.install(|| {
1264 if RAYON.current_num_threads() > self.width() {
1265 let stride = usize::max(idx.len().div_ceil(RAYON.current_num_threads()), 256);
1266 if self.height() / stride >= 2 {
1267 self.apply_columns_par(|c| {
1268 let c = if c.dtype().is_nested() {
1271 &c.rechunk()
1272 } else {
1273 c
1274 };
1275
1276 (0..idx.len().div_ceil(stride))
1277 .into_par_iter()
1278 .map(|i| c.take_unchecked(&idx.slice((i * stride) as i64, stride)))
1279 .reduce(
1280 || Column::new_empty(c.name().clone(), c.dtype()),
1281 |mut a, b| {
1282 a.append_owned(b).unwrap();
1283 a
1284 },
1285 )
1286 })
1287 } else {
1288 self.apply_columns_par(|c| c.take_unchecked(idx))
1289 }
1290 } else {
1291 self.apply_columns_par(|c| c.take_unchecked(idx))
1292 }
1293 })
1294 } else {
1295 self.apply_columns(|s| s.take_unchecked(idx))
1296 };
1297
1298 unsafe { DataFrame::new_unchecked(idx.len(), cols).with_schema_from(self) }
1299 }
1300
1301 pub unsafe fn take_slice_unchecked(&self, idx: &[IdxSize]) -> Self {
1304 self.take_slice_unchecked_impl(idx, true)
1305 }
1306
1307 pub unsafe fn take_slice_unchecked_impl(&self, idx: &[IdxSize], allow_threads: bool) -> Self {
1310 let cols = if allow_threads && RAYON.current_num_threads() > 1 {
1311 RAYON.install(|| {
1312 if RAYON.current_num_threads() > self.width() {
1313 let stride = usize::max(idx.len().div_ceil(RAYON.current_num_threads()), 256);
1314 if self.height() / stride >= 2 {
1315 self.apply_columns_par(|c| {
1316 let c = if c.dtype().is_nested() {
1319 &c.rechunk()
1320 } else {
1321 c
1322 };
1323
1324 (0..idx.len().div_ceil(stride))
1325 .into_par_iter()
1326 .map(|i| {
1327 let idx = &idx[i * stride..];
1328 let idx = &idx[..idx.len().min(stride)];
1329 c.take_slice_unchecked(idx)
1330 })
1331 .reduce(
1332 || Column::new_empty(c.name().clone(), c.dtype()),
1333 |mut a, b| {
1334 a.append_owned(b).unwrap();
1335 a
1336 },
1337 )
1338 })
1339 } else {
1340 self.apply_columns_par(|s| s.take_slice_unchecked(idx))
1341 }
1342 } else {
1343 self.apply_columns_par(|s| s.take_slice_unchecked(idx))
1344 }
1345 })
1346 } else {
1347 self.apply_columns(|s| s.take_slice_unchecked(idx))
1348 };
1349 unsafe { DataFrame::new_unchecked(idx.len(), cols).with_schema_from(self) }
1350 }
1351
1352 pub fn rename(&mut self, column: &str, name: PlSmallStr) -> PolarsResult<&mut Self> {
1367 if column == name.as_str() {
1368 return Ok(self);
1369 }
1370 polars_ensure!(
1371 !self.schema().contains(&name),
1372 Duplicate: "column rename attempted with already existing name \"{name}\""
1373 );
1374
1375 self.get_column_index(column)
1376 .and_then(|idx| unsafe { self.columns_mut() }.get_mut(idx))
1377 .ok_or_else(|| polars_err!(col_not_found = column))
1378 .map(|c| c.rename(name))?;
1379
1380 Ok(self)
1381 }
1382
1383 pub fn rename_many<'a>(
1384 mut self,
1385 renames: impl Iterator<Item = (&'a str, PlSmallStr)>,
1386 ) -> PolarsResult<Self> {
1387 let schema = self.schema().clone();
1388
1389 for (from, to) in renames {
1390 if from == to.as_str() {
1391 continue;
1392 }
1393
1394 let idx = schema
1395 .index_of(from)
1396 .ok_or_else(|| polars_err!(col_not_found = from))?;
1397
1398 unsafe { self.columns_mut() }
1399 .get_mut(idx)
1400 .unwrap()
1401 .rename(to);
1402 }
1403
1404 let schema = Schema::from_iter_check_duplicates(
1406 self.columns()
1407 .iter()
1408 .map(|c| c.name().clone())
1409 .zip_eq(schema.iter_values().cloned()),
1410 )?;
1411
1412 unsafe { self.set_schema(Arc::new(schema)) };
1413
1414 Ok(self)
1415 }
1416
1417 pub fn sort_in_place(
1421 &mut self,
1422 by: impl IntoIterator<Item = impl AsRef<str>>,
1423 sort_options: SortMultipleOptions,
1424 ) -> PolarsResult<&mut Self> {
1425 let by: Vec<_> = by.into_iter().collect();
1426 if by.len() > 1 {
1429 self.rechunk_mut_par();
1430 }
1431 let by_column = self.select_to_vec(by)?;
1432
1433 let mut out = self.clone().sort_impl(by_column, sort_options, None)?;
1434 unsafe { out.set_schema_from(self) };
1435
1436 *self = out;
1437
1438 Ok(self)
1439 }
1440
1441 #[doc(hidden)]
1442 pub fn sort_impl(
1444 self,
1445 mut by_column: Vec<Column>,
1446 sort_options: SortMultipleOptions,
1447 slice: Option<(i64, usize)>,
1448 ) -> PolarsResult<Self> {
1449 if by_column.is_empty() {
1450 return if let Some((offset, len)) = slice {
1452 Ok(self.slice(offset, len))
1453 } else {
1454 Ok(self)
1455 };
1456 }
1457
1458 for column in &by_column {
1459 if column.dtype().is_object() {
1460 polars_bail!(
1461 InvalidOperation: "column '{}' has a dtype of '{}', which does not support sorting", column.name(), column.dtype()
1462 )
1463 }
1464 }
1465
1466 let first_descending = sort_options.descending[0];
1471 let first_by_column = by_column[0].name().to_string();
1472
1473 let set_sorted = |df: &mut DataFrame| {
1474 let _ = df.apply(&first_by_column, |s| {
1477 let mut s = s.clone();
1478 if first_descending {
1479 s.set_sorted_flag(IsSorted::Descending)
1480 } else {
1481 s.set_sorted_flag(IsSorted::Ascending)
1482 }
1483 s
1484 });
1485 };
1486
1487 if self.shape_has_zero() {
1488 let mut out = self;
1489 set_sorted(&mut out);
1490 return Ok(out);
1491 }
1492
1493 if let Some((0, k)) = slice {
1494 if k < self.height() {
1495 return self.bottom_k_impl(k, by_column, sort_options);
1496 }
1497 }
1498 #[cfg(feature = "dtype-categorical")]
1502 let is_not_categorical_enum =
1503 !(matches!(by_column[0].dtype(), DataType::Categorical(_, _))
1504 || matches!(by_column[0].dtype(), DataType::Enum(_, _)));
1505
1506 #[cfg(not(feature = "dtype-categorical"))]
1507 #[allow(non_upper_case_globals)]
1508 const is_not_categorical_enum: bool = true;
1509
1510 if by_column.len() == 1 && is_not_categorical_enum {
1511 let required_sorting = if sort_options.descending[0] {
1512 IsSorted::Descending
1513 } else {
1514 IsSorted::Ascending
1515 };
1516 let no_sorting_required = (by_column[0].is_sorted_flag() == required_sorting)
1519 && ((by_column[0].null_count() == 0)
1520 || by_column[0].get(by_column[0].len() - 1).unwrap().is_null()
1521 == sort_options.nulls_last[0]);
1522
1523 if no_sorting_required {
1524 return if let Some((offset, len)) = slice {
1525 Ok(self.slice(offset, len))
1526 } else {
1527 Ok(self)
1528 };
1529 }
1530 }
1531
1532 let has_nested = by_column.iter().any(|s| s.dtype().is_nested());
1533 let allow_threads = sort_options.multithreaded;
1534
1535 let mut df = self;
1538 if df.columns().iter().any(|c| c.n_chunks() > 1) {
1539 let frame_key_idxs: Vec<Option<usize>> = by_column
1542 .iter()
1543 .map(|c| {
1544 let s = c.as_series()?;
1545 let idx = df.get_column_index(s.name())?;
1546 let ds = df.columns()[idx].as_series()?;
1547 Arc::ptr_eq(&ds.0, &s.0).then_some(idx)
1548 })
1549 .collect();
1550 df.rechunk_mut_par();
1551 for (c, idx) in by_column.iter_mut().zip(frame_key_idxs) {
1552 if let Some(idx) = idx {
1553 *c = df.columns()[idx].clone();
1554 }
1555 }
1556 }
1557 let df = &df;
1558 let mut take = match (by_column.len(), has_nested) {
1559 (1, false) => {
1560 let s = &by_column[0];
1561 let options = SortOptions {
1562 descending: sort_options.descending[0],
1563 nulls_last: sort_options.nulls_last[0],
1564 multithreaded: sort_options.multithreaded,
1565 maintain_order: sort_options.maintain_order,
1566 limit: sort_options.limit,
1567 };
1568 if df.width() == 1 && df.try_get_column_index(s.name().as_str()).is_ok() {
1572 let mut out = s.sort_with(options)?;
1573 if let Some((offset, len)) = slice {
1574 out = out.slice(offset, len);
1575 }
1576 return Ok(out.into_frame());
1577 }
1578 s.arg_sort(options)
1579 },
1580 _ => arg_sort(&by_column, sort_options)?,
1581 };
1582
1583 if let Some((offset, len)) = slice {
1584 take = take.slice(offset, len);
1585 }
1586
1587 let mut df = unsafe { df.take_unchecked_impl(&take, allow_threads) };
1590 set_sorted(&mut df);
1591 Ok(df)
1592 }
1593
1594 pub fn _to_metadata(&self) -> DataFrame {
1599 let num_columns = self.width();
1600
1601 let mut column_names =
1602 StringChunkedBuilder::new(PlSmallStr::from_static("column_name"), num_columns);
1603 let mut repr_ca = StringChunkedBuilder::new(PlSmallStr::from_static("repr"), num_columns);
1604 let mut sorted_asc_ca =
1605 BooleanChunkedBuilder::new(PlSmallStr::from_static("sorted_asc"), num_columns);
1606 let mut sorted_dsc_ca =
1607 BooleanChunkedBuilder::new(PlSmallStr::from_static("sorted_dsc"), num_columns);
1608 let mut fast_explode_list_ca =
1609 BooleanChunkedBuilder::new(PlSmallStr::from_static("fast_explode_list"), num_columns);
1610 let mut materialized_at_ca =
1611 StringChunkedBuilder::new(PlSmallStr::from_static("materialized_at"), num_columns);
1612
1613 for col in self.columns() {
1614 let flags = col.get_flags();
1615
1616 let (repr, materialized_at) = match col {
1617 Column::Series(s) => ("series", s.materialized_at()),
1618 Column::Scalar(_) => ("scalar", None),
1619 };
1620 let sorted_asc = flags.contains(StatisticsFlags::IS_SORTED_ASC);
1621 let sorted_dsc = flags.contains(StatisticsFlags::IS_SORTED_DSC);
1622 let fast_explode_list = flags.contains(StatisticsFlags::CAN_FAST_EXPLODE_LIST);
1623
1624 column_names.append_value(col.name().clone());
1625 repr_ca.append_value(repr);
1626 sorted_asc_ca.append_value(sorted_asc);
1627 sorted_dsc_ca.append_value(sorted_dsc);
1628 fast_explode_list_ca.append_value(fast_explode_list);
1629 materialized_at_ca.append_option(materialized_at.map(|v| format!("{v:#?}")));
1630 }
1631
1632 unsafe {
1633 DataFrame::new_unchecked(
1634 self.width(),
1635 vec![
1636 column_names.finish().into_column(),
1637 repr_ca.finish().into_column(),
1638 sorted_asc_ca.finish().into_column(),
1639 sorted_dsc_ca.finish().into_column(),
1640 fast_explode_list_ca.finish().into_column(),
1641 materialized_at_ca.finish().into_column(),
1642 ],
1643 )
1644 }
1645 }
1646 pub fn sort(
1684 &self,
1685 by: impl IntoIterator<Item = impl AsRef<str>>,
1686 sort_options: SortMultipleOptions,
1687 ) -> PolarsResult<Self> {
1688 let mut df = self.clone();
1689 df.sort_in_place(by, sort_options)?;
1690 Ok(df)
1691 }
1692
1693 pub fn replace(&mut self, column: &str, new_col: Column) -> PolarsResult<&mut Self> {
1708 self.apply(column, |_| new_col)
1709 }
1710
1711 pub fn replace_column(&mut self, index: usize, new_column: Column) -> PolarsResult<&mut Self> {
1726 polars_ensure!(
1727 index < self.width(),
1728 ShapeMismatch:
1729 "unable to replace at index {}, the DataFrame has only {} columns",
1730 index, self.width(),
1731 );
1732
1733 polars_ensure!(
1734 new_column.len() == self.height(),
1735 ShapeMismatch:
1736 "unable to replace a column, series length {} doesn't match the DataFrame height {}",
1737 new_column.len(), self.height(),
1738 );
1739
1740 unsafe { *self.columns_mut().get_mut(index).unwrap() = new_column };
1741
1742 Ok(self)
1743 }
1744
1745 pub fn apply<F, C>(&mut self, name: &str, f: F) -> PolarsResult<&mut Self>
1786 where
1787 F: FnOnce(&Column) -> C,
1788 C: IntoColumn,
1789 {
1790 let idx = self.try_get_column_index(name)?;
1791 self.apply_at_idx(idx, f)?;
1792 Ok(self)
1793 }
1794
1795 pub fn apply_at_idx<F, C>(&mut self, idx: usize, f: F) -> PolarsResult<&mut Self>
1826 where
1827 F: FnOnce(&Column) -> C,
1828 C: IntoColumn,
1829 {
1830 let df_height = self.height();
1831 let width = self.width();
1832
1833 let cached_schema = self.cached_schema().cloned();
1834
1835 let col = unsafe { self.columns_mut() }.get_mut(idx).ok_or_else(|| {
1836 polars_err!(
1837 ComputeError: "invalid column index: {} for a DataFrame with {} columns",
1838 idx, width
1839 )
1840 })?;
1841
1842 let new_col = f(col)
1843 .into_column()
1844 .with_name(col.name().clone())
1845 .broadcast_owned_to(df_height)?;
1846 let col_before = std::mem::replace(col, new_col);
1847
1848 if col.dtype() == col_before.dtype() {
1849 unsafe { self.set_opt_schema(cached_schema) };
1850 }
1851
1852 Ok(self)
1853 }
1854
1855 pub fn try_apply_at_idx<F, C>(&mut self, idx: usize, f: F) -> PolarsResult<&mut Self>
1896 where
1897 F: FnOnce(&Column) -> PolarsResult<C>,
1898 C: IntoColumn,
1899 {
1900 let df_height = self.height();
1901 let width = self.width();
1902
1903 let cached_schema = self.cached_schema().cloned();
1904
1905 let col = unsafe { self.columns_mut() }.get_mut(idx).ok_or_else(|| {
1906 polars_err!(
1907 ComputeError: "invalid column index: {} for a DataFrame with {} columns",
1908 idx, width
1909 )
1910 })?;
1911
1912 let mut new_col = f(col).map(|c| c.into_column())?;
1913
1914 polars_ensure!(
1915 new_col.len() == df_height,
1916 ShapeMismatch:
1917 "try_apply_at_idx: resulting Series has length {} while the DataFrame has height {}",
1918 new_col.len(), df_height
1919 );
1920
1921 new_col = new_col.with_name(col.name().clone());
1923 let col_before = std::mem::replace(col, new_col);
1924
1925 if col.dtype() == col_before.dtype() {
1926 unsafe { self.set_opt_schema(cached_schema) };
1927 }
1928
1929 Ok(self)
1930 }
1931
1932 pub fn try_apply<F, C>(&mut self, column: &str, f: F) -> PolarsResult<&mut Self>
1975 where
1976 F: FnOnce(&Series) -> PolarsResult<C>,
1977 C: IntoColumn,
1978 {
1979 let idx = self.try_get_column_index(column)?;
1980 self.try_apply_at_idx(idx, |c| f(c.as_materialized_series()))
1981 }
1982
1983 #[must_use]
2013 pub fn slice(&self, offset: i64, length: usize) -> Self {
2014 if offset == 0 && length == self.height() {
2015 return self.clone();
2016 }
2017
2018 if length == 0 {
2019 return self.clear();
2020 }
2021
2022 let cols = self.apply_columns(|s| s.slice(offset, length));
2023
2024 let height = if let Some(fst) = cols.first() {
2025 fst.len()
2026 } else {
2027 let (_, length) = slice_offsets(offset, length, self.height());
2028 length
2029 };
2030
2031 unsafe { DataFrame::_new_unchecked_impl(height, cols).with_schema_from(self) }
2032 }
2033
2034 pub fn split_at(&self, offset: i64) -> (Self, Self) {
2036 let (a, b) = self.columns().iter().map(|s| s.split_at(offset)).unzip();
2037
2038 let (idx, _) = slice_offsets(offset, 0, self.height());
2039
2040 let a = unsafe { DataFrame::new_unchecked(idx, a).with_schema_from(self) };
2041 let b = unsafe { DataFrame::new_unchecked(self.height() - idx, b).with_schema_from(self) };
2042 (a, b)
2043 }
2044
2045 #[must_use]
2046 pub fn clear(&self) -> Self {
2047 let cols = self.columns().iter().map(|s| s.clear()).collect::<Vec<_>>();
2048 unsafe { DataFrame::_new_unchecked_impl(0, cols).with_schema_from(self) }
2049 }
2050
2051 #[must_use]
2085 pub fn head(&self, length: Option<usize>) -> Self {
2086 let new_height = usize::min(self.height(), length.unwrap_or(HEAD_DEFAULT_LENGTH));
2087 let new_cols = self.apply_columns(|c| c.head(Some(new_height)));
2088
2089 unsafe { DataFrame::new_unchecked(new_height, new_cols).with_schema_from(self) }
2090 }
2091
2092 #[must_use]
2123 pub fn tail(&self, length: Option<usize>) -> Self {
2124 let new_height = usize::min(self.height(), length.unwrap_or(TAIL_DEFAULT_LENGTH));
2125 let new_cols = self.apply_columns(|c| c.tail(Some(new_height)));
2126
2127 unsafe { DataFrame::new_unchecked(new_height, new_cols).with_schema_from(self) }
2128 }
2129
2130 pub fn iter_chunks(
2140 &self,
2141 compat_level: CompatLevel,
2142 parallel: bool,
2143 ) -> impl Iterator<Item = RecordBatch> + '_ {
2144 debug_assert!(!self.should_rechunk(), "expected equal chunks");
2145
2146 if self.width() == 0 {
2147 return RecordBatchIterWrap::new_zero_width(self.height());
2148 }
2149
2150 let must_convert = compat_level.0 == 0;
2153 let parallel = parallel
2154 && must_convert
2155 && self.width() > 1
2156 && self
2157 .columns()
2158 .iter()
2159 .any(|s| matches!(s.dtype(), DataType::String | DataType::Binary));
2160
2161 RecordBatchIterWrap::Batches(RecordBatchIter {
2162 df: self,
2163 schema: Arc::new(
2164 self.columns()
2165 .iter()
2166 .map(|c| c.field().to_arrow(compat_level))
2167 .collect(),
2168 ),
2169 cursor: ChunkCursor::new(self),
2170 compat_level,
2171 parallel,
2172 })
2173 }
2174
2175 pub fn iter_chunks_physical(&self) -> impl Iterator<Item = RecordBatch> + '_ {
2185 debug_assert!(!self.should_rechunk());
2186
2187 if self.width() == 0 {
2188 return RecordBatchIterWrap::new_zero_width(self.height());
2189 }
2190
2191 RecordBatchIterWrap::PhysicalBatches(PhysRecordBatchIter {
2192 df: self,
2193 schema: Arc::new(
2194 self.columns()
2195 .iter()
2196 .map(|c| c.field().to_arrow(CompatLevel::newest()))
2197 .collect(),
2198 ),
2199 cursor: ChunkCursor::new(self),
2200 })
2201 }
2202
2203 #[must_use]
2205 pub fn reverse(&self) -> Self {
2206 let new_cols = self.apply_columns(Column::reverse);
2207 unsafe { DataFrame::new_unchecked(self.height(), new_cols).with_schema_from(self) }
2208 }
2209
2210 #[must_use]
2215 pub fn shift(&self, periods: i64) -> Self {
2216 let col = self.apply_columns_par(|s| s.shift(periods));
2217 unsafe { DataFrame::new_unchecked(self.height(), col).with_schema_from(self) }
2218 }
2219
2220 pub fn fill_null(&self, strategy: FillNullStrategy) -> PolarsResult<Self> {
2229 let col = self.try_apply_columns_par(|s| s.fill_null(strategy))?;
2230
2231 Ok(unsafe { DataFrame::new_unchecked(self.height(), col) })
2232 }
2233
2234 #[cfg(feature = "algorithm_group_by")]
2268 pub fn unique_stable(
2269 &self,
2270 subset: Option<&[String]>,
2271 keep: UniqueKeepStrategy,
2272 slice: Option<(i64, usize)>,
2273 ) -> PolarsResult<DataFrame> {
2274 self.unique_impl(
2275 true,
2276 subset.map(|v| v.iter().map(|x| PlSmallStr::from_str(x.as_str())).collect()),
2277 keep,
2278 slice,
2279 )
2280 }
2281
2282 #[cfg(feature = "algorithm_group_by")]
2284 pub fn unique<I, S>(
2285 &self,
2286 subset: Option<&[String]>,
2287 keep: UniqueKeepStrategy,
2288 slice: Option<(i64, usize)>,
2289 ) -> PolarsResult<DataFrame> {
2290 self.unique_impl(
2291 false,
2292 subset.map(|v| v.iter().map(|x| PlSmallStr::from_str(x.as_str())).collect()),
2293 keep,
2294 slice,
2295 )
2296 }
2297
2298 #[cfg(feature = "algorithm_group_by")]
2299 pub fn unique_impl(
2300 &self,
2301 maintain_order: bool,
2302 subset: Option<Vec<PlSmallStr>>,
2303 keep: UniqueKeepStrategy,
2304 slice: Option<(i64, usize)>,
2305 ) -> PolarsResult<Self> {
2306 if self.width() == 0 {
2307 let height = usize::min(self.height(), 1);
2308 return Ok(DataFrame::empty_with_height(height));
2309 }
2310
2311 let names = subset.unwrap_or_else(|| self.get_column_names_owned());
2312 let mut df = self.clone();
2313 df.rechunk_mut_par();
2315
2316 let columns = match (keep, maintain_order) {
2317 (UniqueKeepStrategy::First | UniqueKeepStrategy::Any, true) => {
2318 let gb = df.group_by_stable(names)?;
2319 let groups = gb.get_groups();
2320 let (offset, len) = slice.unwrap_or((0, groups.len()));
2321 let groups = groups.slice(offset, len);
2322 df.apply_columns_par(|s| unsafe { s.agg_first(&groups) })
2323 },
2324 (UniqueKeepStrategy::Last, true) => {
2325 let gb = df.group_by_stable(names)?;
2328 let groups = gb.get_groups();
2329
2330 let last_idx: NoNull<IdxCa> = groups
2331 .iter()
2332 .map(|g| match g {
2333 GroupsIndicator::Idx((_first, idx)) => idx[idx.len() - 1],
2334 GroupsIndicator::Slice([first, len]) => first + len - 1,
2335 })
2336 .collect();
2337
2338 let mut last_idx = last_idx.into_inner().sort(false);
2339
2340 if let Some((offset, len)) = slice {
2341 last_idx = last_idx.slice(offset, len);
2342 }
2343
2344 let last_idx = NoNull::new(last_idx);
2345 let out = unsafe { df.take_unchecked(&last_idx) };
2346 return Ok(out);
2347 },
2348 (UniqueKeepStrategy::First | UniqueKeepStrategy::Any, false) => {
2349 let gb = df.group_by(names)?;
2350 let groups = gb.get_groups();
2351 let (offset, len) = slice.unwrap_or((0, groups.len()));
2352 let groups = groups.slice(offset, len);
2353 df.apply_columns_par(|s| unsafe { s.agg_first(&groups) })
2354 },
2355 (UniqueKeepStrategy::Last, false) => {
2356 let gb = df.group_by(names)?;
2357 let groups = gb.get_groups();
2358 let (offset, len) = slice.unwrap_or((0, groups.len()));
2359 let groups = groups.slice(offset, len);
2360 df.apply_columns_par(|s| unsafe { s.agg_last(&groups) })
2361 },
2362 (UniqueKeepStrategy::None, _) => {
2363 let df_part = df.select(names)?;
2364 let mask = df_part.is_unique()?;
2365 let mut filtered = df.filter(&mask)?;
2366
2367 if let Some((offset, len)) = slice {
2368 filtered = filtered.slice(offset, len);
2369 }
2370 return Ok(filtered);
2371 },
2372 };
2373 Ok(unsafe { DataFrame::new_unchecked_infer_height(columns).with_schema_from(self) })
2374 }
2375
2376 #[cfg(feature = "algorithm_group_by")]
2390 pub fn is_unique(&self) -> PolarsResult<BooleanChunked> {
2391 let gb = self.group_by(self.get_column_names_owned())?;
2392 let groups = gb.get_groups();
2393 Ok(is_unique_helper(
2394 groups,
2395 self.height() as IdxSize,
2396 true,
2397 false,
2398 ))
2399 }
2400
2401 #[cfg(feature = "algorithm_group_by")]
2415 pub fn is_duplicated(&self) -> PolarsResult<BooleanChunked> {
2416 let gb = self.group_by(self.get_column_names_owned())?;
2417 let groups = gb.get_groups();
2418 Ok(is_unique_helper(
2419 groups,
2420 self.height() as IdxSize,
2421 false,
2422 true,
2423 ))
2424 }
2425
2426 #[must_use]
2428 pub fn null_count(&self) -> Self {
2429 let cols =
2430 self.apply_columns(|c| Column::new(c.name().clone(), [c.null_count() as IdxSize]));
2431 unsafe { Self::new_unchecked(1, cols) }
2432 }
2433
2434 #[cfg(feature = "row_hash")]
2436 pub fn hash_rows(
2437 &mut self,
2438 hasher_builder: Option<PlSeedableRandomStateQuality>,
2439 ) -> PolarsResult<UInt64Chunked> {
2440 let dfs = split_df(self, RAYON.current_num_threads(), false);
2441 let (cas, _) = _df_rows_to_hashes_threaded_vertical(&dfs, hasher_builder)?;
2442
2443 let mut iter = cas.into_iter();
2444 let mut acc_ca = iter.next().unwrap();
2445 for ca in iter {
2446 acc_ca.append(&ca)?;
2447 }
2448 Ok(acc_ca.rechunk().into_owned())
2449 }
2450
2451 pub fn get_supertype(&self) -> Option<PolarsResult<DataType>> {
2453 self.columns()
2454 .iter()
2455 .map(|s| Ok(s.dtype().clone()))
2456 .reduce(|acc, b| try_get_supertype(&acc?, &b.unwrap()))
2457 }
2458
2459 #[doc(hidden)]
2464 pub unsafe fn _take_unchecked_slice(&self, idx: &[IdxSize], allow_threads: bool) -> Self {
2465 self._take_unchecked_slice_sorted(idx, allow_threads, IsSorted::Not)
2466 }
2467
2468 #[doc(hidden)]
2475 pub unsafe fn _take_unchecked_slice_sorted(
2476 &self,
2477 idx: &[IdxSize],
2478 allow_threads: bool,
2479 sorted: IsSorted,
2480 ) -> Self {
2481 #[cfg(debug_assertions)]
2482 {
2483 if idx.len() > 2 {
2484 use crate::series::IsSorted;
2485
2486 match sorted {
2487 IsSorted::Ascending => {
2488 assert!(idx[0] <= idx[idx.len() - 1]);
2489 },
2490 IsSorted::Descending => {
2491 assert!(idx[0] >= idx[idx.len() - 1]);
2492 },
2493 _ => {},
2494 }
2495 }
2496 }
2497 let mut ca = IdxCa::mmap_slice(PlSmallStr::EMPTY, idx);
2498 ca.set_sorted_flag(sorted);
2499 self.take_unchecked_impl(&ca, allow_threads)
2500 }
2501 #[cfg(all(feature = "partition_by", feature = "algorithm_group_by"))]
2502 #[doc(hidden)]
2503 pub fn _partition_by_impl(
2504 &self,
2505 cols: &[PlSmallStr],
2506 stable: bool,
2507 include_key: bool,
2508 parallel: bool,
2509 ) -> PolarsResult<Vec<DataFrame>> {
2510 let selected_keys = self.select_to_vec(cols.iter().cloned())?;
2511 let groups = self.group_by_with_series(selected_keys, parallel, stable)?;
2512 let groups = groups.into_groups();
2513
2514 let df = if include_key {
2516 self.clone()
2517 } else {
2518 self.drop_many(cols.iter().cloned())
2519 };
2520
2521 if parallel {
2522 RAYON.install(|| {
2525 match groups.as_ref() {
2526 GroupsType::Idx(idx) => {
2527 let mut df = df.clone();
2529 df.rechunk_mut_par();
2530 Ok(idx
2531 .into_par_iter()
2532 .map(|(_, group)| {
2533 unsafe {
2535 df._take_unchecked_slice_sorted(
2536 group,
2537 false,
2538 IsSorted::Ascending,
2539 )
2540 }
2541 })
2542 .collect())
2543 },
2544 GroupsType::Slice { groups, .. } => Ok(groups
2545 .into_par_iter()
2546 .map(|[first, len]| df.slice(*first as i64, *len as usize))
2547 .collect()),
2548 }
2549 })
2550 } else {
2551 match groups.as_ref() {
2552 GroupsType::Idx(idx) => {
2553 let mut df = df;
2555 df.rechunk_mut();
2556 Ok(idx
2557 .into_iter()
2558 .map(|(_, group)| {
2559 unsafe {
2561 df._take_unchecked_slice_sorted(group, false, IsSorted::Ascending)
2562 }
2563 })
2564 .collect())
2565 },
2566 GroupsType::Slice { groups, .. } => Ok(groups
2567 .iter()
2568 .map(|[first, len]| df.slice(*first as i64, *len as usize))
2569 .collect()),
2570 }
2571 }
2572 }
2573
2574 #[cfg(feature = "partition_by")]
2576 pub fn partition_by<I, S>(&self, cols: I, include_key: bool) -> PolarsResult<Vec<DataFrame>>
2577 where
2578 I: IntoIterator<Item = S>,
2579 S: Into<PlSmallStr>,
2580 {
2581 let cols: UnitVec<PlSmallStr> = cols.into_iter().map(Into::into).collect();
2582 self._partition_by_impl(cols.as_slice(), false, include_key, true)
2583 }
2584
2585 #[cfg(feature = "partition_by")]
2588 pub fn partition_by_stable<I, S>(
2589 &self,
2590 cols: I,
2591 include_key: bool,
2592 ) -> PolarsResult<Vec<DataFrame>>
2593 where
2594 I: IntoIterator<Item = S>,
2595 S: Into<PlSmallStr>,
2596 {
2597 let cols: UnitVec<PlSmallStr> = cols.into_iter().map(Into::into).collect();
2598 self._partition_by_impl(cols.as_slice(), true, include_key, true)
2599 }
2600
2601 #[cfg(feature = "dtype-struct")]
2604 pub fn unnest(
2605 &self,
2606 cols: impl IntoIterator<Item = impl Into<PlSmallStr>>,
2607 separator: Option<&str>,
2608 ) -> PolarsResult<DataFrame> {
2609 self.unnest_impl(cols.into_iter().map(Into::into).collect(), separator)
2610 }
2611
2612 #[cfg(feature = "dtype-struct")]
2613 fn unnest_impl(
2614 &self,
2615 cols: PlHashSet<PlSmallStr>,
2616 separator: Option<&str>,
2617 ) -> PolarsResult<DataFrame> {
2618 let mut new_cols = Vec::with_capacity(std::cmp::min(self.width() * 2, self.width() + 128));
2619 let mut count = 0;
2620 for s in self.columns() {
2621 if cols.contains(s.name()) {
2622 let ca = s.struct_()?.clone();
2623 new_cols.extend(ca.fields_as_series().into_iter().map(|mut f| {
2624 if let Some(separator) = &separator {
2625 f.rename(polars_utils::format_pl_smallstr!(
2626 "{}{}{}",
2627 s.name(),
2628 separator,
2629 f.name()
2630 ));
2631 }
2632 Column::from(f)
2633 }));
2634 count += 1;
2635 } else {
2636 new_cols.push(s.clone())
2637 }
2638 }
2639 if count != cols.len() {
2640 let schema = self.schema();
2643 for col in cols {
2644 let _ = schema
2645 .get(col.as_str())
2646 .ok_or_else(|| polars_err!(col_not_found = col))?;
2647 }
2648 }
2649
2650 DataFrame::new(self.height(), new_cols)
2651 }
2652
2653 pub fn append_record_batch(&mut self, rb: RecordBatchT<ArrayRef>) -> PolarsResult<()> {
2654 let df = DataFrame::from(rb);
2657 polars_ensure!(
2658 self.schema() == df.schema(),
2659 SchemaMismatch: "cannot append record batch with different schema\n\n
2660 Got {:?}\nexpected: {:?}", df.schema(), self.schema(),
2661 );
2662 self.vstack_mut_owned_unchecked(df);
2663 Ok(())
2664 }
2665}
2666
2667struct ChunkCursor<'a> {
2669 chunks: &'a [ArrayRef],
2671 height: usize,
2672 idx: usize,
2673}
2674
2675impl<'a> ChunkCursor<'a> {
2676 fn new(df: &'a DataFrame) -> Self {
2677 let chunks = match df.columns().iter().find_map(Column::as_series) {
2678 Some(s) => s.chunks().as_slice(),
2679 None => &[],
2680 };
2681
2682 Self {
2683 chunks,
2684 height: df.height(),
2685 idx: 0,
2686 }
2687 }
2688
2689 fn n_chunks(&self) -> usize {
2690 usize::max(1, self.chunks.len())
2691 }
2692
2693 fn next(&mut self) -> Option<(usize, usize)> {
2695 let idx = self.idx;
2696 if idx >= self.n_chunks() {
2697 return None;
2698 }
2699
2700 self.idx += 1;
2701 Some((idx, self.chunks.get(idx).map_or(self.height, |c| c.len())))
2702 }
2703
2704 fn size_hint(&self) -> (usize, Option<usize>) {
2705 let n = self.n_chunks() - self.idx;
2706 (n, Some(n))
2707 }
2708}
2709
2710pub struct RecordBatchIter<'a> {
2711 df: &'a DataFrame,
2712 schema: ArrowSchemaRef,
2713 cursor: ChunkCursor<'a>,
2714 compat_level: CompatLevel,
2715 parallel: bool,
2716}
2717
2718impl Iterator for RecordBatchIter<'_> {
2719 type Item = RecordBatch;
2720
2721 fn next(&mut self) -> Option<Self::Item> {
2722 let (idx, length) = self.cursor.next()?;
2723
2724 let to_arrow = |c: &Column| match c.as_series() {
2725 Some(s) => s.to_arrow(idx, self.compat_level),
2726 None => c.slice(0, length).rechunk_to_arrow(self.compat_level),
2727 };
2728
2729 let batch_cols: Vec<ArrayRef> = if self.parallel {
2730 let iter = self.df.columns().par_iter().map(to_arrow);
2731 RAYON.install(|| iter.collect())
2732 } else {
2733 self.df.columns().iter().map(to_arrow).collect()
2734 };
2735
2736 Some(RecordBatch::new(length, self.schema.clone(), batch_cols))
2737 }
2738
2739 fn size_hint(&self) -> (usize, Option<usize>) {
2740 self.cursor.size_hint()
2741 }
2742}
2743
2744pub struct PhysRecordBatchIter<'a> {
2745 df: &'a DataFrame,
2746 schema: ArrowSchemaRef,
2747 cursor: ChunkCursor<'a>,
2748}
2749
2750impl Iterator for PhysRecordBatchIter<'_> {
2751 type Item = RecordBatch;
2752
2753 fn next(&mut self) -> Option<Self::Item> {
2754 let (idx, length) = self.cursor.next()?;
2755
2756 let arrs = self
2757 .df
2758 .columns()
2759 .iter()
2760 .map(|c| match c.as_series() {
2761 Some(s) => s.chunks()[idx].clone(),
2762 None => c
2763 .slice(0, length)
2764 .take_materialized_series()
2765 .rechunk()
2766 .chunks()[0]
2767 .clone(),
2768 })
2769 .collect::<Vec<_>>();
2770
2771 Some(RecordBatch::new(length, self.schema.clone(), arrs))
2772 }
2773
2774 fn size_hint(&self) -> (usize, Option<usize>) {
2775 self.cursor.size_hint()
2776 }
2777}
2778
2779pub enum RecordBatchIterWrap<'a> {
2780 ZeroWidth {
2781 remaining_height: usize,
2782 chunk_size: usize,
2783 },
2784 Batches(RecordBatchIter<'a>),
2785 PhysicalBatches(PhysRecordBatchIter<'a>),
2786}
2787
2788impl<'a> RecordBatchIterWrap<'a> {
2789 fn new_zero_width(height: usize) -> Self {
2790 Self::ZeroWidth {
2791 remaining_height: height,
2792 chunk_size: polars_config::config().ideal_morsel_size() as usize,
2793 }
2794 }
2795}
2796
2797impl Iterator for RecordBatchIterWrap<'_> {
2798 type Item = RecordBatch;
2799
2800 fn next(&mut self) -> Option<Self::Item> {
2801 match self {
2802 Self::ZeroWidth {
2803 remaining_height,
2804 chunk_size,
2805 } => {
2806 let n = usize::min(*remaining_height, *chunk_size);
2807 *remaining_height -= n;
2808
2809 (n > 0).then(|| RecordBatch::new(n, ArrowSchemaRef::default(), vec![]))
2810 },
2811 Self::Batches(v) => v.next(),
2812 Self::PhysicalBatches(v) => v.next(),
2813 }
2814 }
2815
2816 fn size_hint(&self) -> (usize, Option<usize>) {
2817 match self {
2818 Self::ZeroWidth {
2819 remaining_height,
2820 chunk_size,
2821 } => {
2822 let n = remaining_height.div_ceil(*chunk_size);
2823 (n, Some(n))
2824 },
2825 Self::Batches(v) => v.size_hint(),
2826 Self::PhysicalBatches(v) => v.size_hint(),
2827 }
2828 }
2829}
2830
2831fn ensure_can_extend(left: &Column, right: &Column) -> PolarsResult<()> {
2833 polars_ensure!(
2834 left.name() == right.name(),
2835 ShapeMismatch: "unable to vstack, column names don't match: {:?} and {:?}",
2836 left.name(), right.name(),
2837 );
2838 Ok(())
2839}
2840
2841#[cfg(test)]
2842mod test {
2843 use super::*;
2844
2845 fn create_frame() -> DataFrame {
2846 let s0 = Column::new("days".into(), [0, 1, 2].as_ref());
2847 let s1 = Column::new("temp".into(), [22.1, 19.9, 7.].as_ref());
2848 DataFrame::new_infer_height(vec![s0, s1]).unwrap()
2849 }
2850
2851 #[test]
2852 fn sort_in_place_keeps_the_chunks_of_a_frame_sorted_by_one_key() {
2853 let mut df = df!("a" => [1, 2], "b" => [1, 1]).unwrap();
2854 df.vstack_mut(&df!("a" => [3, 4], "b" => [1, 1]).unwrap())
2855 .unwrap();
2856 df.apply("a", |c| {
2857 let mut c = c.clone();
2858 c.set_sorted_flag(IsSorted::Ascending);
2859 c
2860 })
2861 .unwrap();
2862 assert_eq!(df.first_col_n_chunks(), 2);
2863
2864 df.sort_in_place(["a"], SortMultipleOptions::default())
2865 .unwrap();
2866 assert_eq!(df.first_col_n_chunks(), 2);
2867
2868 df.sort_in_place(["a", "b"], SortMultipleOptions::default())
2869 .unwrap();
2870 assert_eq!(df.first_col_n_chunks(), 1);
2871 let a = df.column("a").unwrap().as_materialized_series();
2872 assert_eq!(
2873 a.i32().unwrap().to_vec(),
2874 [Some(1), Some(2), Some(3), Some(4)]
2875 );
2876 }
2877
2878 #[test]
2879 #[cfg_attr(miri, ignore)]
2880 fn test_recordbatch_iterator() {
2881 let df = df!(
2882 "foo" => [1, 2, 3, 4, 5]
2883 )
2884 .unwrap();
2885 let mut iter = df.iter_chunks(CompatLevel::newest(), false);
2886 assert_eq!(5, iter.next().unwrap().len());
2887 assert!(iter.next().is_none());
2888 }
2889
2890 #[test]
2891 #[cfg_attr(miri, ignore)]
2892 fn test_select() {
2893 let df = create_frame();
2894 assert_eq!(
2895 df.column("days")
2896 .unwrap()
2897 .as_series()
2898 .unwrap()
2899 .equal(1)
2900 .unwrap()
2901 .sum(),
2902 Some(1)
2903 );
2904 }
2905
2906 #[test]
2907 #[cfg_attr(miri, ignore)]
2908 fn test_filter_broadcast_on_string_col() {
2909 let col_name = "some_col";
2910 let v = vec!["test".to_string()];
2911 let s0 = Column::new(PlSmallStr::from_str(col_name), v);
2912 let mut df = DataFrame::new_infer_height(vec![s0]).unwrap();
2913
2914 df = df
2915 .filter(
2916 &df.column(col_name)
2917 .unwrap()
2918 .as_materialized_series()
2919 .equal("")
2920 .unwrap(),
2921 )
2922 .unwrap();
2923 assert_eq!(
2924 df.column(col_name)
2925 .unwrap()
2926 .as_materialized_series()
2927 .n_chunks(),
2928 1
2929 );
2930 }
2931
2932 #[test]
2933 #[cfg_attr(miri, ignore)]
2934 fn test_filter_broadcast_on_list_col() {
2935 let s1 = Series::new(PlSmallStr::EMPTY, [true, false, true]);
2936 let ll: ListChunked = [&s1].iter().copied().collect();
2937
2938 let mask = BooleanChunked::from_slice(PlSmallStr::EMPTY, &[false]);
2939 let new = ll.filter(&mask).unwrap();
2940
2941 assert_eq!(new.chunks.len(), 1);
2942 assert_eq!(new.len(), 0);
2943 }
2944
2945 #[test]
2946 fn slice() {
2947 let df = create_frame();
2948 let sliced_df = df.slice(0, 2);
2949 assert_eq!(sliced_df.shape(), (2, 2));
2950 }
2951
2952 #[test]
2953 fn rechunk_false() {
2954 let df = create_frame();
2955 assert!(!df.should_rechunk())
2956 }
2957
2958 #[test]
2959 fn rechunk_true() -> PolarsResult<()> {
2960 let mut base = df!(
2961 "a" => [1, 2, 3],
2962 "b" => [1, 2, 3]
2963 )?;
2964
2965 let mut s = Series::new("foo".into(), 0..2);
2967 let s2 = Series::new("bar".into(), 0..1);
2968 s.append(&s2)?;
2969
2970 let out = base.with_column(s.into_column())?;
2972
2973 assert!(out.should_rechunk());
2975 Ok(())
2976 }
2977
2978 #[test]
2979 fn test_duplicate_column() {
2980 let mut df = df! {
2981 "foo" => [1, 2, 3]
2982 }
2983 .unwrap();
2984 assert!(
2986 df.with_column(Column::new("foo".into(), &[1, 2, 3]))
2987 .is_ok()
2988 );
2989 assert!(
2990 df.with_column(Column::new("bar".into(), &[1, 2, 3]))
2991 .is_ok()
2992 );
2993 assert!(df.column("bar").is_ok())
2994 }
2995
2996 #[test]
2997 #[cfg_attr(miri, ignore)]
2998 fn distinct() {
2999 let df = df! {
3000 "flt" => [1., 1., 2., 2., 3., 3.],
3001 "int" => [1, 1, 2, 2, 3, 3, ],
3002 "str" => ["a", "a", "b", "b", "c", "c"]
3003 }
3004 .unwrap();
3005 let df = df
3006 .unique_stable(None, UniqueKeepStrategy::First, None)
3007 .unwrap()
3008 .sort(["flt"], SortMultipleOptions::default())
3009 .unwrap();
3010 let valid = df! {
3011 "flt" => [1., 2., 3.],
3012 "int" => [1, 2, 3],
3013 "str" => ["a", "b", "c"]
3014 }
3015 .unwrap();
3016 assert!(df.equals(&valid));
3017 }
3018
3019 #[test]
3020 fn test_vstack() {
3021 let mut df = df! {
3023 "flt" => [1., 1., 2., 2., 3., 3.],
3024 "int" => [1, 1, 2, 2, 3, 3, ],
3025 "str" => ["a", "a", "b", "b", "c", "c"]
3026 }
3027 .unwrap();
3028
3029 df.vstack_mut(&df.slice(0, 3)).unwrap();
3030 assert_eq!(df.first_col_n_chunks(), 2)
3031 }
3032
3033 #[test]
3034 fn test_chunk_alignment_with_scalar_column() {
3035 let mut df = df! { "int" => [0, 1] }.unwrap();
3036 df.vstack_mut(&df! { "int" => [2] }.unwrap()).unwrap();
3037 df.insert_column(
3038 0,
3039 Column::new_scalar("str".into(), Scalar::from(PlSmallStr::from_static("a")), 3),
3040 )
3041 .unwrap();
3042
3043 for materialize in [false, true] {
3045 if materialize {
3046 df.columns()[0].as_materialized_series();
3047 }
3048 assert!(!df.should_rechunk());
3049
3050 let batch_lengths = |batches: Vec<RecordBatch>| {
3051 batches
3052 .iter()
3053 .map(|b| {
3054 assert!(b.arrays().iter().all(|arr| arr.len() == b.len()));
3055 b.len()
3056 })
3057 .collect::<Vec<_>>()
3058 };
3059 let batches = df.iter_chunks(CompatLevel::newest(), false).collect();
3060 assert_eq!(batch_lengths(batches), [2, 1]);
3061 let batches = df.iter_chunks_physical().collect();
3062 assert_eq!(batch_lengths(batches), [2, 1]);
3063 }
3064 }
3065
3066 #[test]
3067 fn test_vstack_scalar_chunk_alignment() {
3068 let mut df = df! {
3070 "int" => [0],
3071 "str" => ["a"],
3072 }
3073 .unwrap();
3074 df.vstack_mut(&df! { "int" => [1], "str" => ["a"] }.unwrap())
3075 .unwrap();
3076
3077 assert_eq!(df.column("int").unwrap().n_chunks(), 2);
3078 assert!(df.column("str").unwrap().as_scalar_column().is_some());
3079 assert!(!df.should_rechunk());
3080
3081 let batches = df
3082 .iter_chunks(CompatLevel::newest(), false)
3083 .collect::<Vec<_>>();
3084 assert_eq!(batches.iter().map(|b| b.len()).collect::<Vec<_>>(), [1, 1]);
3085 }
3086
3087 #[test]
3088 fn test_vstack_on_empty_dataframe() {
3089 let mut df = DataFrame::empty();
3090
3091 let df_data = df! {
3092 "flt" => [1., 1., 2., 2., 3., 3.],
3093 "int" => [1, 1, 2, 2, 3, 3, ],
3094 "str" => ["a", "a", "b", "b", "c", "c"]
3095 }
3096 .unwrap();
3097
3098 df.vstack_mut(&df_data).unwrap();
3099 assert_eq!(df.height(), 6)
3100 }
3101
3102 #[test]
3103 fn test_unique_keep_none_with_slice() {
3104 let df = df! {
3105 "x" => [1, 2, 3, 2, 1]
3106 }
3107 .unwrap();
3108 let out = df
3109 .unique_stable(
3110 Some(&["x".to_string()][..]),
3111 UniqueKeepStrategy::None,
3112 Some((0, 2)),
3113 )
3114 .unwrap();
3115 let expected = df! {
3116 "x" => [3]
3117 }
3118 .unwrap();
3119 assert!(out.equals(&expected));
3120 }
3121
3122 #[test]
3123 #[cfg(feature = "dtype-i8")]
3124 fn test_apply_result_schema() {
3125 let mut df = df! {
3126 "x" => [1, 2, 3, 2, 1]
3127 }
3128 .unwrap();
3129
3130 let schema_before = df.schema().clone();
3131 df.apply("x", |f| f.cast(&DataType::Int8).unwrap()).unwrap();
3132 assert_ne!(&schema_before, df.schema());
3133 }
3134}