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, slice_offsets, try_get_supertype};
20use crate::{HEAD_DEFAULT_LENGTH, TAIL_DEFAULT_LENGTH};
21
22#[cfg(feature = "dataframe_arithmetic")]
23mod arithmetic;
24pub mod builder;
25mod chunks;
26pub use chunks::chunk_df_for_writing;
27pub mod column;
28mod dataframe;
29mod filter;
30mod projection;
31pub use dataframe::DataFrame;
32use filter::filter_zero_width;
33use projection::{AmortizedColumnSelector, LINEAR_SEARCH_LIMIT};
34
35pub mod explode;
36mod from;
37#[cfg(feature = "algorithm_group_by")]
38pub mod group_by;
39pub(crate) mod horizontal;
40#[cfg(any(feature = "rows", feature = "object"))]
41pub mod row;
42mod top_k;
43mod upstream_traits;
44pub(crate) mod validation;
45
46use polars_arrow::record_batch::{RecordBatch, RecordBatchT};
47use polars_utils::pl_str::PlSmallStr;
48#[cfg(feature = "serde")]
49use serde::{Deserialize, Serialize};
50use strum_macros::IntoStaticStr;
51
52#[cfg(feature = "row_hash")]
53use crate::hashing::_df_rows_to_hashes_threaded_vertical;
54use crate::prelude::sort::arg_sort;
55use crate::runtime::RAYON;
56use crate::series::IsSorted;
57
58#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Hash, IntoStaticStr)]
59#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
60#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
61#[strum(serialize_all = "snake_case")]
62pub enum UniqueKeepStrategy {
63 First,
65 Last,
67 None,
69 #[default]
72 Any,
73}
74
75#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Hash, IntoStaticStr)]
76#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
77#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
78#[strum(serialize_all = "snake_case")]
79pub enum PivotColumnNaming {
81 Combine,
83 #[default]
86 Auto,
87}
88
89impl DataFrame {
90 pub fn materialized_column_iter(&self) -> impl ExactSizeIterator<Item = &Series> {
91 self.columns().iter().map(Column::as_materialized_series)
92 }
93
94 pub fn estimated_size(&self, expanded: bool) -> usize {
110 self.columns()
111 .iter()
112 .map(|c| c.estimated_size(expanded))
113 .sum()
114 }
115
116 pub fn try_apply_columns(
117 &self,
118 func: impl Fn(&Column) -> PolarsResult<Column> + Send + Sync,
119 ) -> PolarsResult<Vec<Column>> {
120 return inner(self, &func);
121
122 fn inner(
123 slf: &DataFrame,
124 func: &(dyn Fn(&Column) -> PolarsResult<Column> + Send + Sync),
125 ) -> PolarsResult<Vec<Column>> {
126 slf.columns().iter().map(func).collect()
127 }
128 }
129
130 pub fn apply_columns(&self, func: impl Fn(&Column) -> Column + Send + Sync) -> Vec<Column> {
131 return inner(self, &func);
132
133 fn inner(slf: &DataFrame, func: &(dyn Fn(&Column) -> Column + Send + Sync)) -> Vec<Column> {
134 slf.columns().iter().map(func).collect()
135 }
136 }
137
138 pub fn try_apply_columns_par(
139 &self,
140 func: impl Fn(&Column) -> PolarsResult<Column> + Send + Sync,
141 ) -> PolarsResult<Vec<Column>> {
142 return inner(self, &func);
143
144 fn inner(
145 slf: &DataFrame,
146 func: &(dyn Fn(&Column) -> PolarsResult<Column> + Send + Sync),
147 ) -> PolarsResult<Vec<Column>> {
148 RAYON.install(|| slf.columns().par_iter().map(func).collect())
149 }
150 }
151
152 pub fn apply_columns_par(&self, func: impl Fn(&Column) -> Column + Send + Sync) -> Vec<Column> {
153 return inner(self, &func);
154
155 fn inner(slf: &DataFrame, func: &(dyn Fn(&Column) -> Column + Send + Sync)) -> Vec<Column> {
156 RAYON.install(|| slf.columns().par_iter().map(func).collect())
157 }
158 }
159
160 pub(crate) fn reserve_chunks(&mut self, additional: usize) {
162 for s in unsafe { self.columns_mut_retain_schema() } {
163 if let Column::Series(s) = s {
164 unsafe { s.chunks_mut().reserve(additional) }
167 }
168 }
169 }
170 pub fn new_from_index(&self, index: usize, height: usize) -> Self {
171 let new_cols = self.apply_columns(|c| c.new_from_index(index, height));
172
173 unsafe { Self::_new_unchecked_impl(height, new_cols).with_schema_from(self) }
174 }
175
176 pub fn full_null(schema: &Schema, height: usize) -> Self {
178 let columns = schema
179 .iter_fields()
180 .map(|f| Column::full_null(f.name().clone(), height, f.dtype()))
181 .collect();
182
183 unsafe { DataFrame::_new_unchecked_impl(height, columns) }
184 }
185
186 pub fn ensure_matches_schema(&mut self, schema: &Schema) -> PolarsResult<()> {
189 let mut did_cast = false;
190 let cached_schema = self.cached_schema().cloned();
191
192 for (col, (name, dt)) in unsafe { self.columns_mut() }.iter_mut().zip(schema.iter()) {
193 polars_ensure!(
194 col.name() == name,
195 SchemaMismatch: "column name mismatch: expected {:?}, found {:?}",
196 name,
197 col.name()
198 );
199
200 let needs_cast = col.dtype().matches_schema_type(dt)?;
201
202 if needs_cast {
203 *col = col.cast(dt)?;
204 did_cast = true;
205 }
206 }
207
208 if !did_cast {
209 unsafe { self.set_opt_schema(cached_schema) };
210 }
211
212 Ok(())
213 }
214
215 pub fn with_row_index(&self, name: PlSmallStr, offset: Option<IdxSize>) -> PolarsResult<Self> {
250 let mut new_columns = Vec::with_capacity(self.width() + 1);
251 let offset = offset.unwrap_or(0);
252
253 if self.get_column_index(&name).is_some() {
254 polars_bail!(duplicate = name)
255 }
256
257 let col = Column::new_row_index(name, offset, self.height())?;
258 new_columns.push(col);
259 new_columns.extend_from_slice(self.columns());
260
261 Ok(unsafe { DataFrame::new_unchecked(self.height(), new_columns) })
262 }
263
264 pub unsafe fn with_row_index_mut(
272 &mut self,
273 name: PlSmallStr,
274 offset: Option<IdxSize>,
275 ) -> &mut Self {
276 debug_assert!(
277 self.get_column_index(&name).is_none(),
278 "with_row_index_mut(): column with name {} already exists",
279 name
280 );
281
282 let offset = offset.unwrap_or(0);
283 let col = Column::new_row_index(name, offset, self.height()).unwrap();
284
285 unsafe { self.columns_mut() }.insert(0, col);
286 self
287 }
288
289 pub fn shrink_to_fit(&mut self) {
291 for s in unsafe { self.columns_mut_retain_schema() } {
293 s.shrink_to_fit();
294 }
295 }
296
297 pub fn rechunk_mut_par(&mut self) -> &mut Self {
300 if self.columns().iter().any(|c| c.n_chunks() > 1) {
301 RAYON.install(|| {
302 unsafe { self.columns_mut_retain_schema() }
303 .par_iter_mut()
304 .for_each(|c| *c = c.rechunk());
305 })
306 }
307
308 self
309 }
310
311 pub fn rechunk_mut(&mut self) -> &mut Self {
313 let columns = unsafe { self.columns_mut() };
315
316 for col in columns.iter_mut().filter(|c| c.n_chunks() > 1) {
317 *col = col.rechunk();
318 }
319
320 self
321 }
322
323 pub fn should_rechunk(&self) -> bool {
325 if !self
331 .columns()
332 .iter()
333 .filter_map(Column::as_series)
334 .map(|s| s.n_chunks())
335 .all_equal()
336 {
337 return true;
338 }
339
340 let mut chunk_lengths = self
341 .columns()
342 .iter()
343 .filter_map(Column::as_series)
344 .map(|s| s.chunk_lengths());
345 match chunk_lengths.next() {
346 None => false,
347 Some(first_column_chunk_lengths) => {
348 if first_column_chunk_lengths.size_hint().0 == 1 {
350 return chunk_lengths.any(|cl| cl.size_hint().0 != 1);
351 }
352 let height = self.height();
355 let n_chunks = first_column_chunk_lengths.size_hint().0;
356 if n_chunks > height && !(height == 0 && n_chunks == 1) {
357 return true;
358 }
359 let v: Vec<_> = first_column_chunk_lengths.collect();
361 for cl in chunk_lengths {
362 if cl.enumerate().any(|(idx, el)| Some(&el) != v.get(idx)) {
363 return true;
364 }
365 }
366 false
367 },
368 }
369 }
370
371 pub fn align_chunks_par(&mut self) -> &mut Self {
373 if self.should_rechunk() {
374 self.rechunk_mut_par()
375 } else {
376 self
377 }
378 }
379
380 pub fn align_chunks(&mut self) -> &mut Self {
382 if self.should_rechunk() {
383 self.rechunk_mut()
384 } else {
385 self
386 }
387 }
388
389 pub fn get_column_names(&self) -> Vec<&PlSmallStr> {
400 self.columns().iter().map(|s| s.name()).collect()
401 }
402
403 pub fn get_column_names_owned(&self) -> Vec<PlSmallStr> {
405 self.columns().iter().map(|s| s.name().clone()).collect()
406 }
407
408 pub fn set_column_names<T>(&mut self, new_names: &[T]) -> PolarsResult<()>
420 where
421 T: AsRef<str>,
422 {
423 polars_ensure!(
424 new_names.len() == self.width(),
425 ShapeMismatch: "{} column names provided for a DataFrame of width {}",
426 new_names.len(), self.width()
427 );
428
429 validation::ensure_names_unique(new_names)?;
430
431 *unsafe { self.columns_mut() } = std::mem::take(unsafe { self.columns_mut() })
432 .into_iter()
433 .zip(new_names)
434 .map(|(c, name)| c.with_name(PlSmallStr::from_str(name.as_ref())))
435 .collect();
436
437 Ok(())
438 }
439
440 pub fn dtypes(&self) -> Vec<DataType> {
453 self.columns().iter().map(|s| s.dtype().clone()).collect()
454 }
455
456 pub fn first_col_n_chunks(&self) -> usize {
458 match self.columns().iter().find_map(|col| col.as_series()) {
459 None if self.width() == 0 => 0,
460 None => 1,
461 Some(s) => s.n_chunks(),
462 }
463 }
464
465 pub(crate) fn first_col_chunk_lengths(&self) -> impl Iterator<Item = usize> + '_ {
467 match self.columns().iter().find_map(|col| col.as_series()) {
468 None => Either::Right((self.width() > 0).then_some(self.height()).into_iter()),
469 Some(s) => Either::Left(s.chunk_lengths()),
470 }
471 }
472
473 pub fn max_n_chunks(&self) -> usize {
475 self.columns()
476 .iter()
477 .map(|s| s.as_series().map(|s| s.n_chunks()).unwrap_or(1))
478 .max()
479 .unwrap_or(0)
480 }
481
482 pub fn fields(&self) -> Vec<Field> {
498 self.columns()
499 .iter()
500 .map(|s| s.field().into_owned())
501 .collect()
502 }
503
504 pub fn hstack(&self, columns: &[Column]) -> PolarsResult<Self> {
538 let mut new_cols = Vec::with_capacity(self.width() + columns.len());
539
540 new_cols.extend(self.columns().iter().cloned());
541 new_cols.extend_from_slice(columns);
542
543 DataFrame::new(self.height(), new_cols)
544 }
545 pub fn vstack(&self, other: &DataFrame) -> PolarsResult<Self> {
586 let mut df = self.clone();
587 df.vstack_mut(other)?;
588 Ok(df)
589 }
590
591 pub fn vstack_mut(&mut self, other: &DataFrame) -> PolarsResult<&mut Self> {
632 if self.width() != other.width() {
633 polars_ensure!(
634 self.shape() == (0, 0),
635 ShapeMismatch:
636 "unable to append to a DataFrame of shape {:?} with a DataFrame of width {}",
637 self.shape(), other.width(),
638 );
639
640 self.clone_from(other);
641
642 return Ok(self);
643 }
644
645 let new_height = usize::checked_add(self.height(), other.height()).unwrap();
646
647 unsafe { self.columns_mut_retain_schema() }
648 .iter_mut()
649 .zip(other.columns())
650 .try_for_each::<_, PolarsResult<_>>(|(left, right)| {
651 ensure_can_extend(&*left, right)?;
652 left.append(right)
653 .with_context(|| format!("failed to vstack column '{}'", right.name()))?;
654 Ok(())
655 })?;
656
657 unsafe { self.set_height(new_height) };
658
659 Ok(self)
660 }
661
662 pub fn vstack_mut_owned(&mut self, other: DataFrame) -> PolarsResult<&mut Self> {
663 if self.width() != other.width() {
664 polars_ensure!(
665 self.shape() == (0, 0),
666 ShapeMismatch:
667 "unable to append to a DataFrame of width {} with a DataFrame of width {}",
668 self.width(), other.width(),
669 );
670
671 *self = other;
672
673 return Ok(self);
674 }
675
676 let new_height = usize::checked_add(self.height(), other.height()).unwrap();
677
678 unsafe { self.columns_mut_retain_schema() }
679 .iter_mut()
680 .zip(other.into_columns())
681 .try_for_each::<_, PolarsResult<_>>(|(left, right)| {
682 ensure_can_extend(&*left, &right)?;
683 let right_name = right.name().clone();
684 left.append_owned(right)
685 .with_context(|| format!("failed to vstack column '{right_name}'"))?;
686 Ok(())
687 })?;
688
689 unsafe { self.set_height(new_height) };
690
691 Ok(self)
692 }
693
694 pub fn vstack_mut_unchecked(&mut self, other: &DataFrame) -> &mut Self {
701 let new_height = usize::checked_add(self.height(), other.height()).unwrap();
702
703 unsafe { self.columns_mut_retain_schema() }
704 .iter_mut()
705 .zip(other.columns())
706 .for_each(|(left, right)| {
707 left.append(right)
708 .with_context(|| format!("failed to vstack column '{}'", right.name()))
709 .expect("should not fail");
710 });
711
712 unsafe { self.set_height(new_height) };
713
714 self
715 }
716
717 pub fn vstack_mut_owned_unchecked(&mut self, other: DataFrame) -> &mut Self {
724 let new_height = usize::checked_add(self.height(), other.height()).unwrap();
725
726 unsafe { self.columns_mut_retain_schema() }
727 .iter_mut()
728 .zip(other.into_columns())
729 .for_each(|(left, right)| {
730 left.append_owned(right).expect("should not fail");
731 });
732
733 unsafe { self.set_height(new_height) };
734
735 self
736 }
737
738 pub fn extend(&mut self, other: &DataFrame) -> PolarsResult<()> {
753 polars_ensure!(
754 self.width() == other.width(),
755 ShapeMismatch:
756 "unable to extend a DataFrame of width {} with a DataFrame of width {}",
757 self.width(), other.width(),
758 );
759
760 let new_height = usize::checked_add(self.height(), other.height()).unwrap();
761
762 unsafe { self.columns_mut_retain_schema() }
763 .iter_mut()
764 .zip(other.columns())
765 .try_for_each::<_, PolarsResult<_>>(|(left, right)| {
766 ensure_can_extend(&*left, right)?;
767 left.extend(right)
768 .with_context(|| format!("failed to extend column '{}'", right.name()))?;
769 Ok(())
770 })?;
771
772 unsafe { self.set_height(new_height) };
773
774 Ok(())
775 }
776
777 pub fn drop_in_place(&mut self, name: &str) -> PolarsResult<Column> {
794 let idx = self.try_get_column_index(name)?;
795 Ok(unsafe { self.columns_mut() }.remove(idx))
796 }
797
798 pub fn drop_nulls<S>(&self, subset: Option<&[S]>) -> PolarsResult<Self>
827 where
828 for<'a> &'a S: AsRef<str>,
829 {
830 if let Some(v) = subset {
831 let v = self.select_to_vec(v)?;
832 self._drop_nulls_impl(v.as_slice())
833 } else {
834 self._drop_nulls_impl(self.columns())
835 }
836 }
837
838 fn _drop_nulls_impl(&self, subset: &[Column]) -> PolarsResult<Self> {
839 if subset.iter().all(|s| !s.has_nulls()) {
841 return Ok(self.clone());
842 }
843
844 let mut iter = subset.iter();
845
846 let mask = iter
847 .next()
848 .ok_or_else(|| polars_err!(NoData: "no data to drop nulls from"))?;
849 let mut mask = mask.is_not_null();
850
851 for c in iter {
852 mask = mask & c.is_not_null();
853 }
854 self.filter(&mask)
855 }
856
857 pub fn drop(&self, name: &str) -> PolarsResult<Self> {
872 let idx = self.try_get_column_index(name)?;
873 let mut new_cols = Vec::with_capacity(self.width() - 1);
874
875 self.columns().iter().enumerate().for_each(|(i, s)| {
876 if i != idx {
877 new_cols.push(s.clone())
878 }
879 });
880
881 Ok(unsafe { DataFrame::_new_unchecked_impl(self.height(), new_cols) })
882 }
883
884 pub fn drop_many<I, S>(&self, names: I) -> Self
886 where
887 I: IntoIterator<Item = S>,
888 S: Into<PlSmallStr>,
889 {
890 let names: PlHashSet<PlSmallStr> = names.into_iter().map(|s| s.into()).collect();
891 self.drop_many_amortized(&names)
892 }
893
894 pub fn drop_many_amortized(&self, names: &PlHashSet<PlSmallStr>) -> DataFrame {
896 if names.is_empty() {
897 return self.clone();
898 }
899 let mut new_cols = Vec::with_capacity(self.width().saturating_sub(names.len()));
900 self.columns().iter().for_each(|s| {
901 if !names.contains(s.name()) {
902 new_cols.push(s.clone())
903 }
904 });
905
906 unsafe { DataFrame::new_unchecked(self.height(), new_cols) }
907 }
908
909 fn insert_column_no_namecheck(
912 &mut self,
913 index: usize,
914 column: Column,
915 ) -> PolarsResult<&mut Self> {
916 polars_ensure!(
917 column.len() == self.height(),
918 ShapeMismatch:
919 "unable to add a column of length {} to a DataFrame of height {}",
920 column.len(), self.height(),
921 );
922
923 unsafe { self.columns_mut() }.insert(index, column);
924 Ok(self)
925 }
926
927 pub fn insert_column(&mut self, index: usize, column: Column) -> PolarsResult<&mut Self> {
929 let name = column.name();
930
931 polars_ensure!(
932 self.get_column_index(name).is_none(),
933 Duplicate:
934 "column with name {:?} is already present in the DataFrame", name
935 );
936
937 self.insert_column_no_namecheck(index, column)
938 }
939
940 pub fn with_column(&mut self, mut column: Column) -> PolarsResult<&mut Self> {
943 column.broadcast_in_place_to(self.height())?;
944
945 if let Some(i) = self.get_column_index(column.name()) {
946 *unsafe { self.columns_mut() }.get_mut(i).unwrap() = column
947 } else {
948 unsafe { self.columns_mut() }.push(column)
949 };
950
951 Ok(self)
952 }
953
954 pub unsafe fn push_column_unchecked(&mut self, column: Column) -> &mut Self {
960 unsafe { self.columns_mut() }.push(column);
961 self
962 }
963
964 pub fn with_columns_mut(
967 &mut self,
968 columns: impl IntoIterator<Item = Column>,
969 output_schema: &Schema,
970 ) -> PolarsResult<()> {
971 let columns = columns.into_iter();
972
973 unsafe {
974 self.columns_mut_retain_schema()
975 .reserve(columns.size_hint().0)
976 }
977
978 for c in columns {
979 self.with_column_and_schema_mut(c, output_schema)?;
980 }
981
982 Ok(())
983 }
984
985 fn with_column_and_schema_mut(
986 &mut self,
987 mut column: Column,
988 output_schema: &Schema,
989 ) -> PolarsResult<&mut Self> {
990 column.broadcast_in_place_to(self.height())?;
991
992 let i = output_schema
993 .index_of(column.name())
994 .or_else(|| self.get_column_index(column.name()))
995 .unwrap_or(self.width());
996
997 if i < self.width() {
998 *unsafe { self.columns_mut() }.get_mut(i).unwrap() = column
999 } else if i == self.width() {
1000 unsafe { self.columns_mut() }.push(column)
1001 } else {
1002 panic!("{:?}, {}", output_schema, column.name());
1004 }
1005
1006 Ok(self)
1007 }
1008
1009 pub fn get(&self, idx: usize) -> Option<Vec<AnyValue<'_>>> {
1020 (idx < self.height()).then(|| self.columns().iter().map(|c| c.get(idx).unwrap()).collect())
1021 }
1022
1023 pub fn select_at_idx(&self, idx: usize) -> Option<&Column> {
1039 self.columns().get(idx)
1040 }
1041
1042 pub fn get_column_index(&self, name: &str) -> Option<usize> {
1060 if let Some(schema) = self.cached_schema() {
1061 schema.index_of(name)
1062 } else if self.width() <= LINEAR_SEARCH_LIMIT {
1063 self.columns().iter().position(|s| s.name() == name)
1064 } else {
1065 self.schema().index_of(name)
1066 }
1067 }
1068
1069 pub fn try_get_column_index(&self, name: &str) -> PolarsResult<usize> {
1071 self.get_column_index(name)
1072 .ok_or_else(|| polars_err!(col_not_found = name))
1073 }
1074
1075 pub fn column(&self, name: &str) -> PolarsResult<&Column> {
1089 let idx = self.try_get_column_index(name)?;
1090 Ok(self.select_at_idx(idx).unwrap())
1091 }
1092
1093 pub fn select<I, S>(&self, names: I) -> PolarsResult<Self>
1104 where
1105 I: IntoIterator<Item = S>,
1106 S: AsRef<str>,
1107 {
1108 DataFrame::new(self.height(), self.select_to_vec(names)?)
1109 }
1110
1111 pub unsafe fn select_unchecked<I, S>(&self, names: I) -> PolarsResult<Self>
1116 where
1117 I: IntoIterator<Item = S>,
1118 S: AsRef<str>,
1119 {
1120 Ok(unsafe { DataFrame::new_unchecked(self.height(), self.select_to_vec(names)?) })
1121 }
1122
1123 pub fn select_to_vec(
1141 &self,
1142 selection: impl IntoIterator<Item = impl AsRef<str>>,
1143 ) -> PolarsResult<Vec<Column>> {
1144 AmortizedColumnSelector::new(self).select_multiple(selection)
1145 }
1146
1147 pub fn filter(&self, mask: &BooleanChunked) -> PolarsResult<Self> {
1159 if self.width() == 0 {
1160 filter_zero_width(self.height(), mask)
1161 } else if mask.len() == 1 && self.len() >= 1 {
1162 if mask.all() && mask.null_count() == 0 {
1163 Ok(self.clone())
1164 } else {
1165 Ok(self.clear())
1166 }
1167 } else {
1168 let all_chunks_aligned =
1171 !self.should_rechunk() && self.first_col_chunk_lengths().eq(mask.chunk_lengths());
1172
1173 let mask = if all_chunks_aligned {
1174 Cow::Borrowed(mask)
1175 } else {
1176 mask.rechunk()
1177 };
1178
1179 let new_columns: Vec<Column> =
1180 self.try_apply_columns_par(|s| s.filter(mask.as_ref()))?;
1181 let out = unsafe {
1182 DataFrame::new_unchecked(new_columns[0].len(), new_columns).with_schema_from(self)
1183 };
1184
1185 Ok(out)
1186 }
1187 }
1188
1189 pub fn filter_seq(&self, mask: &BooleanChunked) -> PolarsResult<Self> {
1191 if self.width() == 0 {
1192 filter_zero_width(self.height(), mask)
1193 } else if mask.len() == 1 && mask.null_count() == 0 && self.len() >= 1 {
1194 if mask.all() && mask.null_count() == 0 {
1195 Ok(self.clone())
1196 } else {
1197 Ok(self.clear())
1198 }
1199 } else {
1200 let all_chunks_aligned =
1201 !self.should_rechunk() && self.first_col_chunk_lengths().eq(mask.chunk_lengths());
1202
1203 let mask = if all_chunks_aligned {
1204 Cow::Borrowed(mask)
1205 } else {
1206 mask.rechunk()
1207 };
1208
1209 let new_columns: Vec<Column> = self.try_apply_columns(|s| s.filter(mask.as_ref()))?;
1210 let out = unsafe {
1211 DataFrame::new_unchecked(new_columns[0].len(), new_columns).with_schema_from(self)
1212 };
1213
1214 Ok(out)
1215 }
1216 }
1217
1218 pub fn take(&self, indices: &IdxCa) -> PolarsResult<Self> {
1230 check_bounds_ca(indices, self.height().try_into().unwrap_or(IdxSize::MAX))?;
1231
1232 let new_cols = self.apply_columns_par(|c| {
1233 assert_eq!(c.len(), self.height());
1234 unsafe { c.take_unchecked(indices) }
1235 });
1236
1237 Ok(unsafe { DataFrame::new_unchecked(indices.len(), new_cols).with_schema_from(self) })
1238 }
1239
1240 pub unsafe fn take_unchecked(&self, idx: &IdxCa) -> Self {
1243 self.take_unchecked_impl(idx, true)
1244 }
1245
1246 #[cfg(feature = "algorithm_group_by")]
1249 pub unsafe fn gather_group_unchecked(&self, group: &GroupsIndicator) -> Self {
1250 match group {
1251 GroupsIndicator::Idx((_, indices)) => unsafe {
1252 self.take_slice_unchecked_impl(indices.as_slice(), false)
1253 },
1254 GroupsIndicator::Slice([offset, len]) => self.slice(*offset as i64, *len as usize),
1255 }
1256 }
1257
1258 pub unsafe fn take_unchecked_impl(&self, idx: &IdxCa, allow_threads: bool) -> Self {
1261 let cols = if allow_threads && RAYON.current_num_threads() > 1 {
1262 RAYON.install(|| {
1263 if RAYON.current_num_threads() > self.width() {
1264 let stride = usize::max(idx.len().div_ceil(RAYON.current_num_threads()), 256);
1265 if self.height() / stride >= 2 {
1266 self.apply_columns_par(|c| {
1267 let c = if c.dtype().is_nested() {
1270 &c.rechunk()
1271 } else {
1272 c
1273 };
1274
1275 (0..idx.len().div_ceil(stride))
1276 .into_par_iter()
1277 .map(|i| c.take_unchecked(&idx.slice((i * stride) as i64, stride)))
1278 .reduce(
1279 || Column::new_empty(c.name().clone(), c.dtype()),
1280 |mut a, b| {
1281 a.append_owned(b).unwrap();
1282 a
1283 },
1284 )
1285 })
1286 } else {
1287 self.apply_columns_par(|c| c.take_unchecked(idx))
1288 }
1289 } else {
1290 self.apply_columns_par(|c| c.take_unchecked(idx))
1291 }
1292 })
1293 } else {
1294 self.apply_columns(|s| s.take_unchecked(idx))
1295 };
1296
1297 unsafe { DataFrame::new_unchecked(idx.len(), cols).with_schema_from(self) }
1298 }
1299
1300 pub unsafe fn take_slice_unchecked(&self, idx: &[IdxSize]) -> Self {
1303 self.take_slice_unchecked_impl(idx, true)
1304 }
1305
1306 pub unsafe fn take_slice_unchecked_impl(&self, idx: &[IdxSize], allow_threads: bool) -> Self {
1309 let cols = if allow_threads && RAYON.current_num_threads() > 1 {
1310 RAYON.install(|| {
1311 if RAYON.current_num_threads() > self.width() {
1312 let stride = usize::max(idx.len().div_ceil(RAYON.current_num_threads()), 256);
1313 if self.height() / stride >= 2 {
1314 self.apply_columns_par(|c| {
1315 let c = if c.dtype().is_nested() {
1318 &c.rechunk()
1319 } else {
1320 c
1321 };
1322
1323 (0..idx.len().div_ceil(stride))
1324 .into_par_iter()
1325 .map(|i| {
1326 let idx = &idx[i * stride..];
1327 let idx = &idx[..idx.len().min(stride)];
1328 c.take_slice_unchecked(idx)
1329 })
1330 .reduce(
1331 || Column::new_empty(c.name().clone(), c.dtype()),
1332 |mut a, b| {
1333 a.append_owned(b).unwrap();
1334 a
1335 },
1336 )
1337 })
1338 } else {
1339 self.apply_columns_par(|s| s.take_slice_unchecked(idx))
1340 }
1341 } else {
1342 self.apply_columns_par(|s| s.take_slice_unchecked(idx))
1343 }
1344 })
1345 } else {
1346 self.apply_columns(|s| s.take_slice_unchecked(idx))
1347 };
1348 unsafe { DataFrame::new_unchecked(idx.len(), cols).with_schema_from(self) }
1349 }
1350
1351 pub fn rename(&mut self, column: &str, name: PlSmallStr) -> PolarsResult<&mut Self> {
1366 if column == name.as_str() {
1367 return Ok(self);
1368 }
1369 polars_ensure!(
1370 !self.schema().contains(&name),
1371 Duplicate: "column rename attempted with already existing name \"{name}\""
1372 );
1373
1374 self.get_column_index(column)
1375 .and_then(|idx| unsafe { self.columns_mut() }.get_mut(idx))
1376 .ok_or_else(|| polars_err!(col_not_found = column))
1377 .map(|c| c.rename(name))?;
1378
1379 Ok(self)
1380 }
1381
1382 pub fn rename_many<'a>(
1383 mut self,
1384 renames: impl Iterator<Item = (&'a str, PlSmallStr)>,
1385 ) -> PolarsResult<Self> {
1386 let schema = self.schema().clone();
1387
1388 for (from, to) in renames {
1389 if from == to.as_str() {
1390 continue;
1391 }
1392
1393 let idx = schema
1394 .index_of(from)
1395 .ok_or_else(|| polars_err!(col_not_found = from))?;
1396
1397 unsafe { self.columns_mut() }
1398 .get_mut(idx)
1399 .unwrap()
1400 .rename(to);
1401 }
1402
1403 let schema = Schema::from_iter_check_duplicates(
1405 self.columns()
1406 .iter()
1407 .map(|c| c.name().clone())
1408 .zip_eq(schema.iter_values().cloned()),
1409 )?;
1410
1411 unsafe { self.set_schema(Arc::new(schema)) };
1412
1413 Ok(self)
1414 }
1415
1416 pub fn sort_in_place(
1420 &mut self,
1421 by: impl IntoIterator<Item = impl AsRef<str>>,
1422 sort_options: SortMultipleOptions,
1423 ) -> PolarsResult<&mut Self> {
1424 let by: Vec<_> = by.into_iter().collect();
1425 if by.len() > 1 {
1428 self.rechunk_mut_par();
1429 }
1430 let by_column = self.select_to_vec(by)?;
1431
1432 let mut out = self.sort_impl(by_column, sort_options, None)?;
1433 unsafe { out.set_schema_from(self) };
1434
1435 *self = out;
1436
1437 Ok(self)
1438 }
1439
1440 #[doc(hidden)]
1441 pub fn sort_impl(
1443 &self,
1444 by_column: Vec<Column>,
1445 sort_options: SortMultipleOptions,
1446 slice: Option<(i64, usize)>,
1447 ) -> PolarsResult<Self> {
1448 if by_column.is_empty() {
1449 return if let Some((offset, len)) = slice {
1451 Ok(self.slice(offset, len))
1452 } else {
1453 Ok(self.clone())
1454 };
1455 }
1456
1457 for column in &by_column {
1458 if column.dtype().is_object() {
1459 polars_bail!(
1460 InvalidOperation: "column '{}' has a dtype of '{}', which does not support sorting", column.name(), column.dtype()
1461 )
1462 }
1463 }
1464
1465 let first_descending = sort_options.descending[0];
1470 let first_by_column = by_column[0].name().to_string();
1471
1472 let set_sorted = |df: &mut DataFrame| {
1473 let _ = df.apply(&first_by_column, |s| {
1476 let mut s = s.clone();
1477 if first_descending {
1478 s.set_sorted_flag(IsSorted::Descending)
1479 } else {
1480 s.set_sorted_flag(IsSorted::Ascending)
1481 }
1482 s
1483 });
1484 };
1485
1486 if self.shape_has_zero() {
1487 let mut out = self.clone();
1488 set_sorted(&mut out);
1489 return Ok(out);
1490 }
1491
1492 if let Some((0, k)) = slice {
1493 if k < self.height() {
1494 return self.bottom_k_impl(k, by_column, sort_options);
1495 }
1496 }
1497 #[cfg(feature = "dtype-categorical")]
1501 let is_not_categorical_enum =
1502 !(matches!(by_column[0].dtype(), DataType::Categorical(_, _))
1503 || matches!(by_column[0].dtype(), DataType::Enum(_, _)));
1504
1505 #[cfg(not(feature = "dtype-categorical"))]
1506 #[allow(non_upper_case_globals)]
1507 const is_not_categorical_enum: bool = true;
1508
1509 if by_column.len() == 1 && is_not_categorical_enum {
1510 let required_sorting = if sort_options.descending[0] {
1511 IsSorted::Descending
1512 } else {
1513 IsSorted::Ascending
1514 };
1515 let no_sorting_required = (by_column[0].is_sorted_flag() == required_sorting)
1518 && ((by_column[0].null_count() == 0)
1519 || by_column[0].get(by_column[0].len() - 1).unwrap().is_null()
1520 == sort_options.nulls_last[0]);
1521
1522 if no_sorting_required {
1523 return if let Some((offset, len)) = slice {
1524 Ok(self.slice(offset, len))
1525 } else {
1526 Ok(self.clone())
1527 };
1528 }
1529 }
1530
1531 let has_nested = by_column.iter().any(|s| s.dtype().is_nested());
1532 let allow_threads = sort_options.multithreaded;
1533
1534 let mut df = self.clone();
1536 let df = df.rechunk_mut_par();
1537 let mut take = match (by_column.len(), has_nested) {
1538 (1, false) => {
1539 let s = &by_column[0];
1540 let options = SortOptions {
1541 descending: sort_options.descending[0],
1542 nulls_last: sort_options.nulls_last[0],
1543 multithreaded: sort_options.multithreaded,
1544 maintain_order: sort_options.maintain_order,
1545 limit: sort_options.limit,
1546 };
1547 if df.width() == 1 && df.try_get_column_index(s.name().as_str()).is_ok() {
1551 let mut out = s.sort_with(options)?;
1552 if let Some((offset, len)) = slice {
1553 out = out.slice(offset, len);
1554 }
1555 return Ok(out.into_frame());
1556 }
1557 s.arg_sort(options)
1558 },
1559 _ => arg_sort(&by_column, sort_options)?,
1560 };
1561
1562 if let Some((offset, len)) = slice {
1563 take = take.slice(offset, len);
1564 }
1565
1566 let mut df = unsafe { df.take_unchecked_impl(&take, allow_threads) };
1569 set_sorted(&mut df);
1570 Ok(df)
1571 }
1572
1573 pub fn _to_metadata(&self) -> DataFrame {
1578 let num_columns = self.width();
1579
1580 let mut column_names =
1581 StringChunkedBuilder::new(PlSmallStr::from_static("column_name"), num_columns);
1582 let mut repr_ca = StringChunkedBuilder::new(PlSmallStr::from_static("repr"), num_columns);
1583 let mut sorted_asc_ca =
1584 BooleanChunkedBuilder::new(PlSmallStr::from_static("sorted_asc"), num_columns);
1585 let mut sorted_dsc_ca =
1586 BooleanChunkedBuilder::new(PlSmallStr::from_static("sorted_dsc"), num_columns);
1587 let mut fast_explode_list_ca =
1588 BooleanChunkedBuilder::new(PlSmallStr::from_static("fast_explode_list"), num_columns);
1589 let mut materialized_at_ca =
1590 StringChunkedBuilder::new(PlSmallStr::from_static("materialized_at"), num_columns);
1591
1592 for col in self.columns() {
1593 let flags = col.get_flags();
1594
1595 let (repr, materialized_at) = match col {
1596 Column::Series(s) => ("series", s.materialized_at()),
1597 Column::Scalar(_) => ("scalar", None),
1598 };
1599 let sorted_asc = flags.contains(StatisticsFlags::IS_SORTED_ASC);
1600 let sorted_dsc = flags.contains(StatisticsFlags::IS_SORTED_DSC);
1601 let fast_explode_list = flags.contains(StatisticsFlags::CAN_FAST_EXPLODE_LIST);
1602
1603 column_names.append_value(col.name().clone());
1604 repr_ca.append_value(repr);
1605 sorted_asc_ca.append_value(sorted_asc);
1606 sorted_dsc_ca.append_value(sorted_dsc);
1607 fast_explode_list_ca.append_value(fast_explode_list);
1608 materialized_at_ca.append_option(materialized_at.map(|v| format!("{v:#?}")));
1609 }
1610
1611 unsafe {
1612 DataFrame::new_unchecked(
1613 self.width(),
1614 vec![
1615 column_names.finish().into_column(),
1616 repr_ca.finish().into_column(),
1617 sorted_asc_ca.finish().into_column(),
1618 sorted_dsc_ca.finish().into_column(),
1619 fast_explode_list_ca.finish().into_column(),
1620 materialized_at_ca.finish().into_column(),
1621 ],
1622 )
1623 }
1624 }
1625 pub fn sort(
1663 &self,
1664 by: impl IntoIterator<Item = impl AsRef<str>>,
1665 sort_options: SortMultipleOptions,
1666 ) -> PolarsResult<Self> {
1667 let mut df = self.clone();
1668 df.sort_in_place(by, sort_options)?;
1669 Ok(df)
1670 }
1671
1672 pub fn replace(&mut self, column: &str, new_col: Column) -> PolarsResult<&mut Self> {
1687 self.apply(column, |_| new_col)
1688 }
1689
1690 pub fn replace_column(&mut self, index: usize, new_column: Column) -> PolarsResult<&mut Self> {
1705 polars_ensure!(
1706 index < self.width(),
1707 ShapeMismatch:
1708 "unable to replace at index {}, the DataFrame has only {} columns",
1709 index, self.width(),
1710 );
1711
1712 polars_ensure!(
1713 new_column.len() == self.height(),
1714 ShapeMismatch:
1715 "unable to replace a column, series length {} doesn't match the DataFrame height {}",
1716 new_column.len(), self.height(),
1717 );
1718
1719 unsafe { *self.columns_mut().get_mut(index).unwrap() = new_column };
1720
1721 Ok(self)
1722 }
1723
1724 pub fn apply<F, C>(&mut self, name: &str, f: F) -> PolarsResult<&mut Self>
1765 where
1766 F: FnOnce(&Column) -> C,
1767 C: IntoColumn,
1768 {
1769 let idx = self.try_get_column_index(name)?;
1770 self.apply_at_idx(idx, f)?;
1771 Ok(self)
1772 }
1773
1774 pub fn apply_at_idx<F, C>(&mut self, idx: usize, f: F) -> PolarsResult<&mut Self>
1805 where
1806 F: FnOnce(&Column) -> C,
1807 C: IntoColumn,
1808 {
1809 let df_height = self.height();
1810 let width = self.width();
1811
1812 let cached_schema = self.cached_schema().cloned();
1813
1814 let col = unsafe { self.columns_mut() }.get_mut(idx).ok_or_else(|| {
1815 polars_err!(
1816 ComputeError: "invalid column index: {} for a DataFrame with {} columns",
1817 idx, width
1818 )
1819 })?;
1820
1821 let new_col = f(col)
1822 .into_column()
1823 .with_name(col.name().clone())
1824 .broadcast_owned_to(df_height)?;
1825 let col_before = std::mem::replace(col, new_col);
1826
1827 if col.dtype() == col_before.dtype() {
1828 unsafe { self.set_opt_schema(cached_schema) };
1829 }
1830
1831 Ok(self)
1832 }
1833
1834 pub fn try_apply_at_idx<F, C>(&mut self, idx: usize, f: F) -> PolarsResult<&mut Self>
1875 where
1876 F: FnOnce(&Column) -> PolarsResult<C>,
1877 C: IntoColumn,
1878 {
1879 let df_height = self.height();
1880 let width = self.width();
1881
1882 let cached_schema = self.cached_schema().cloned();
1883
1884 let col = unsafe { self.columns_mut() }.get_mut(idx).ok_or_else(|| {
1885 polars_err!(
1886 ComputeError: "invalid column index: {} for a DataFrame with {} columns",
1887 idx, width
1888 )
1889 })?;
1890
1891 let mut new_col = f(col).map(|c| c.into_column())?;
1892
1893 polars_ensure!(
1894 new_col.len() == df_height,
1895 ShapeMismatch:
1896 "try_apply_at_idx: resulting Series has length {} while the DataFrame has height {}",
1897 new_col.len(), df_height
1898 );
1899
1900 new_col = new_col.with_name(col.name().clone());
1902 let col_before = std::mem::replace(col, new_col);
1903
1904 if col.dtype() == col_before.dtype() {
1905 unsafe { self.set_opt_schema(cached_schema) };
1906 }
1907
1908 Ok(self)
1909 }
1910
1911 pub fn try_apply<F, C>(&mut self, column: &str, f: F) -> PolarsResult<&mut Self>
1954 where
1955 F: FnOnce(&Series) -> PolarsResult<C>,
1956 C: IntoColumn,
1957 {
1958 let idx = self.try_get_column_index(column)?;
1959 self.try_apply_at_idx(idx, |c| f(c.as_materialized_series()))
1960 }
1961
1962 #[must_use]
1992 pub fn slice(&self, offset: i64, length: usize) -> Self {
1993 if offset == 0 && length == self.height() {
1994 return self.clone();
1995 }
1996
1997 if length == 0 {
1998 return self.clear();
1999 }
2000
2001 let cols = self.apply_columns(|s| s.slice(offset, length));
2002
2003 let height = if let Some(fst) = cols.first() {
2004 fst.len()
2005 } else {
2006 let (_, length) = slice_offsets(offset, length, self.height());
2007 length
2008 };
2009
2010 unsafe { DataFrame::_new_unchecked_impl(height, cols).with_schema_from(self) }
2011 }
2012
2013 pub fn split_at(&self, offset: i64) -> (Self, Self) {
2015 let (a, b) = self.columns().iter().map(|s| s.split_at(offset)).unzip();
2016
2017 let (idx, _) = slice_offsets(offset, 0, self.height());
2018
2019 let a = unsafe { DataFrame::new_unchecked(idx, a).with_schema_from(self) };
2020 let b = unsafe { DataFrame::new_unchecked(self.height() - idx, b).with_schema_from(self) };
2021 (a, b)
2022 }
2023
2024 #[must_use]
2025 pub fn clear(&self) -> Self {
2026 let cols = self.columns().iter().map(|s| s.clear()).collect::<Vec<_>>();
2027 unsafe { DataFrame::_new_unchecked_impl(0, cols).with_schema_from(self) }
2028 }
2029
2030 #[must_use]
2064 pub fn head(&self, length: Option<usize>) -> Self {
2065 let new_height = usize::min(self.height(), length.unwrap_or(HEAD_DEFAULT_LENGTH));
2066 let new_cols = self.apply_columns(|c| c.head(Some(new_height)));
2067
2068 unsafe { DataFrame::new_unchecked(new_height, new_cols).with_schema_from(self) }
2069 }
2070
2071 #[must_use]
2102 pub fn tail(&self, length: Option<usize>) -> Self {
2103 let new_height = usize::min(self.height(), length.unwrap_or(TAIL_DEFAULT_LENGTH));
2104 let new_cols = self.apply_columns(|c| c.tail(Some(new_height)));
2105
2106 unsafe { DataFrame::new_unchecked(new_height, new_cols).with_schema_from(self) }
2107 }
2108
2109 pub fn iter_chunks(
2119 &self,
2120 compat_level: CompatLevel,
2121 parallel: bool,
2122 ) -> impl Iterator<Item = RecordBatch> + '_ {
2123 debug_assert!(!self.should_rechunk(), "expected equal chunks");
2124
2125 if self.width() == 0 {
2126 return RecordBatchIterWrap::new_zero_width(self.height());
2127 }
2128
2129 let must_convert = compat_level.0 == 0;
2132 let parallel = parallel
2133 && must_convert
2134 && self.width() > 1
2135 && self
2136 .columns()
2137 .iter()
2138 .any(|s| matches!(s.dtype(), DataType::String | DataType::Binary));
2139
2140 RecordBatchIterWrap::Batches(RecordBatchIter {
2141 df: self,
2142 schema: Arc::new(
2143 self.columns()
2144 .iter()
2145 .map(|c| c.field().to_arrow(compat_level))
2146 .collect(),
2147 ),
2148 cursor: ChunkCursor::new(self),
2149 compat_level,
2150 parallel,
2151 })
2152 }
2153
2154 pub fn iter_chunks_physical(&self) -> impl Iterator<Item = RecordBatch> + '_ {
2164 debug_assert!(!self.should_rechunk());
2165
2166 if self.width() == 0 {
2167 return RecordBatchIterWrap::new_zero_width(self.height());
2168 }
2169
2170 RecordBatchIterWrap::PhysicalBatches(PhysRecordBatchIter {
2171 df: self,
2172 schema: Arc::new(
2173 self.columns()
2174 .iter()
2175 .map(|c| c.field().to_arrow(CompatLevel::newest()))
2176 .collect(),
2177 ),
2178 cursor: ChunkCursor::new(self),
2179 })
2180 }
2181
2182 #[must_use]
2184 pub fn reverse(&self) -> Self {
2185 let new_cols = self.apply_columns(Column::reverse);
2186 unsafe { DataFrame::new_unchecked(self.height(), new_cols).with_schema_from(self) }
2187 }
2188
2189 #[must_use]
2194 pub fn shift(&self, periods: i64) -> Self {
2195 let col = self.apply_columns_par(|s| s.shift(periods));
2196 unsafe { DataFrame::new_unchecked(self.height(), col).with_schema_from(self) }
2197 }
2198
2199 pub fn fill_null(&self, strategy: FillNullStrategy) -> PolarsResult<Self> {
2208 let col = self.try_apply_columns_par(|s| s.fill_null(strategy))?;
2209
2210 Ok(unsafe { DataFrame::new_unchecked(self.height(), col) })
2211 }
2212
2213 #[cfg(feature = "algorithm_group_by")]
2247 pub fn unique_stable(
2248 &self,
2249 subset: Option<&[String]>,
2250 keep: UniqueKeepStrategy,
2251 slice: Option<(i64, usize)>,
2252 ) -> PolarsResult<DataFrame> {
2253 self.unique_impl(
2254 true,
2255 subset.map(|v| v.iter().map(|x| PlSmallStr::from_str(x.as_str())).collect()),
2256 keep,
2257 slice,
2258 )
2259 }
2260
2261 #[cfg(feature = "algorithm_group_by")]
2263 pub fn unique<I, S>(
2264 &self,
2265 subset: Option<&[String]>,
2266 keep: UniqueKeepStrategy,
2267 slice: Option<(i64, usize)>,
2268 ) -> PolarsResult<DataFrame> {
2269 self.unique_impl(
2270 false,
2271 subset.map(|v| v.iter().map(|x| PlSmallStr::from_str(x.as_str())).collect()),
2272 keep,
2273 slice,
2274 )
2275 }
2276
2277 #[cfg(feature = "algorithm_group_by")]
2278 pub fn unique_impl(
2279 &self,
2280 maintain_order: bool,
2281 subset: Option<Vec<PlSmallStr>>,
2282 keep: UniqueKeepStrategy,
2283 slice: Option<(i64, usize)>,
2284 ) -> PolarsResult<Self> {
2285 if self.width() == 0 {
2286 let height = usize::min(self.height(), 1);
2287 return Ok(DataFrame::empty_with_height(height));
2288 }
2289
2290 let names = subset.unwrap_or_else(|| self.get_column_names_owned());
2291 let mut df = self.clone();
2292 df.rechunk_mut_par();
2294
2295 let columns = match (keep, maintain_order) {
2296 (UniqueKeepStrategy::First | UniqueKeepStrategy::Any, true) => {
2297 let gb = df.group_by_stable(names)?;
2298 let groups = gb.get_groups();
2299 let (offset, len) = slice.unwrap_or((0, groups.len()));
2300 let groups = groups.slice(offset, len);
2301 df.apply_columns_par(|s| unsafe { s.agg_first(&groups) })
2302 },
2303 (UniqueKeepStrategy::Last, true) => {
2304 let gb = df.group_by_stable(names)?;
2307 let groups = gb.get_groups();
2308
2309 let last_idx: NoNull<IdxCa> = groups
2310 .iter()
2311 .map(|g| match g {
2312 GroupsIndicator::Idx((_first, idx)) => idx[idx.len() - 1],
2313 GroupsIndicator::Slice([first, len]) => first + len - 1,
2314 })
2315 .collect();
2316
2317 let mut last_idx = last_idx.into_inner().sort(false);
2318
2319 if let Some((offset, len)) = slice {
2320 last_idx = last_idx.slice(offset, len);
2321 }
2322
2323 let last_idx = NoNull::new(last_idx);
2324 let out = unsafe { df.take_unchecked(&last_idx) };
2325 return Ok(out);
2326 },
2327 (UniqueKeepStrategy::First | UniqueKeepStrategy::Any, false) => {
2328 let gb = df.group_by(names)?;
2329 let groups = gb.get_groups();
2330 let (offset, len) = slice.unwrap_or((0, groups.len()));
2331 let groups = groups.slice(offset, len);
2332 df.apply_columns_par(|s| unsafe { s.agg_first(&groups) })
2333 },
2334 (UniqueKeepStrategy::Last, false) => {
2335 let gb = df.group_by(names)?;
2336 let groups = gb.get_groups();
2337 let (offset, len) = slice.unwrap_or((0, groups.len()));
2338 let groups = groups.slice(offset, len);
2339 df.apply_columns_par(|s| unsafe { s.agg_last(&groups) })
2340 },
2341 (UniqueKeepStrategy::None, _) => {
2342 let df_part = df.select(names)?;
2343 let mask = df_part.is_unique()?;
2344 let mut filtered = df.filter(&mask)?;
2345
2346 if let Some((offset, len)) = slice {
2347 filtered = filtered.slice(offset, len);
2348 }
2349 return Ok(filtered);
2350 },
2351 };
2352 Ok(unsafe { DataFrame::new_unchecked_infer_height(columns).with_schema_from(self) })
2353 }
2354
2355 #[cfg(feature = "algorithm_group_by")]
2369 pub fn is_unique(&self) -> PolarsResult<BooleanChunked> {
2370 let gb = self.group_by(self.get_column_names_owned())?;
2371 let groups = gb.get_groups();
2372 Ok(is_unique_helper(
2373 groups,
2374 self.height() as IdxSize,
2375 true,
2376 false,
2377 ))
2378 }
2379
2380 #[cfg(feature = "algorithm_group_by")]
2394 pub fn is_duplicated(&self) -> PolarsResult<BooleanChunked> {
2395 let gb = self.group_by(self.get_column_names_owned())?;
2396 let groups = gb.get_groups();
2397 Ok(is_unique_helper(
2398 groups,
2399 self.height() as IdxSize,
2400 false,
2401 true,
2402 ))
2403 }
2404
2405 #[must_use]
2407 pub fn null_count(&self) -> Self {
2408 let cols =
2409 self.apply_columns(|c| Column::new(c.name().clone(), [c.null_count() as IdxSize]));
2410 unsafe { Self::new_unchecked(1, cols) }
2411 }
2412
2413 #[cfg(feature = "row_hash")]
2415 pub fn hash_rows(
2416 &mut self,
2417 hasher_builder: Option<PlSeedableRandomStateQuality>,
2418 ) -> PolarsResult<UInt64Chunked> {
2419 let dfs = split_df(self, RAYON.current_num_threads(), false);
2420 let (cas, _) = _df_rows_to_hashes_threaded_vertical(&dfs, hasher_builder)?;
2421
2422 let mut iter = cas.into_iter();
2423 let mut acc_ca = iter.next().unwrap();
2424 for ca in iter {
2425 acc_ca.append(&ca)?;
2426 }
2427 Ok(acc_ca.rechunk().into_owned())
2428 }
2429
2430 pub fn get_supertype(&self) -> Option<PolarsResult<DataType>> {
2432 self.columns()
2433 .iter()
2434 .map(|s| Ok(s.dtype().clone()))
2435 .reduce(|acc, b| try_get_supertype(&acc?, &b.unwrap()))
2436 }
2437
2438 #[doc(hidden)]
2443 pub unsafe fn _take_unchecked_slice(&self, idx: &[IdxSize], allow_threads: bool) -> Self {
2444 self._take_unchecked_slice_sorted(idx, allow_threads, IsSorted::Not)
2445 }
2446
2447 #[doc(hidden)]
2454 pub unsafe fn _take_unchecked_slice_sorted(
2455 &self,
2456 idx: &[IdxSize],
2457 allow_threads: bool,
2458 sorted: IsSorted,
2459 ) -> Self {
2460 #[cfg(debug_assertions)]
2461 {
2462 if idx.len() > 2 {
2463 use crate::series::IsSorted;
2464
2465 match sorted {
2466 IsSorted::Ascending => {
2467 assert!(idx[0] <= idx[idx.len() - 1]);
2468 },
2469 IsSorted::Descending => {
2470 assert!(idx[0] >= idx[idx.len() - 1]);
2471 },
2472 _ => {},
2473 }
2474 }
2475 }
2476 let mut ca = IdxCa::mmap_slice(PlSmallStr::EMPTY, idx);
2477 ca.set_sorted_flag(sorted);
2478 self.take_unchecked_impl(&ca, allow_threads)
2479 }
2480 #[cfg(all(feature = "partition_by", feature = "algorithm_group_by"))]
2481 #[doc(hidden)]
2482 pub fn _partition_by_impl(
2483 &self,
2484 cols: &[PlSmallStr],
2485 stable: bool,
2486 include_key: bool,
2487 parallel: bool,
2488 ) -> PolarsResult<Vec<DataFrame>> {
2489 let selected_keys = self.select_to_vec(cols.iter().cloned())?;
2490 let groups = self.group_by_with_series(selected_keys, parallel, stable)?;
2491 let groups = groups.into_groups();
2492
2493 let df = if include_key {
2495 self.clone()
2496 } else {
2497 self.drop_many(cols.iter().cloned())
2498 };
2499
2500 if parallel {
2501 RAYON.install(|| {
2504 match groups.as_ref() {
2505 GroupsType::Idx(idx) => {
2506 let mut df = df.clone();
2508 df.rechunk_mut_par();
2509 Ok(idx
2510 .into_par_iter()
2511 .map(|(_, group)| {
2512 unsafe {
2514 df._take_unchecked_slice_sorted(
2515 group,
2516 false,
2517 IsSorted::Ascending,
2518 )
2519 }
2520 })
2521 .collect())
2522 },
2523 GroupsType::Slice { groups, .. } => Ok(groups
2524 .into_par_iter()
2525 .map(|[first, len]| df.slice(*first as i64, *len as usize))
2526 .collect()),
2527 }
2528 })
2529 } else {
2530 match groups.as_ref() {
2531 GroupsType::Idx(idx) => {
2532 let mut df = df;
2534 df.rechunk_mut();
2535 Ok(idx
2536 .into_iter()
2537 .map(|(_, group)| {
2538 unsafe {
2540 df._take_unchecked_slice_sorted(group, false, IsSorted::Ascending)
2541 }
2542 })
2543 .collect())
2544 },
2545 GroupsType::Slice { groups, .. } => Ok(groups
2546 .iter()
2547 .map(|[first, len]| df.slice(*first as i64, *len as usize))
2548 .collect()),
2549 }
2550 }
2551 }
2552
2553 #[cfg(feature = "partition_by")]
2555 pub fn partition_by<I, S>(&self, cols: I, include_key: bool) -> PolarsResult<Vec<DataFrame>>
2556 where
2557 I: IntoIterator<Item = S>,
2558 S: Into<PlSmallStr>,
2559 {
2560 let cols: UnitVec<PlSmallStr> = cols.into_iter().map(Into::into).collect();
2561 self._partition_by_impl(cols.as_slice(), false, include_key, true)
2562 }
2563
2564 #[cfg(feature = "partition_by")]
2567 pub fn partition_by_stable<I, S>(
2568 &self,
2569 cols: I,
2570 include_key: bool,
2571 ) -> PolarsResult<Vec<DataFrame>>
2572 where
2573 I: IntoIterator<Item = S>,
2574 S: Into<PlSmallStr>,
2575 {
2576 let cols: UnitVec<PlSmallStr> = cols.into_iter().map(Into::into).collect();
2577 self._partition_by_impl(cols.as_slice(), true, include_key, true)
2578 }
2579
2580 #[cfg(feature = "dtype-struct")]
2583 pub fn unnest(
2584 &self,
2585 cols: impl IntoIterator<Item = impl Into<PlSmallStr>>,
2586 separator: Option<&str>,
2587 ) -> PolarsResult<DataFrame> {
2588 self.unnest_impl(cols.into_iter().map(Into::into).collect(), separator)
2589 }
2590
2591 #[cfg(feature = "dtype-struct")]
2592 fn unnest_impl(
2593 &self,
2594 cols: PlHashSet<PlSmallStr>,
2595 separator: Option<&str>,
2596 ) -> PolarsResult<DataFrame> {
2597 let mut new_cols = Vec::with_capacity(std::cmp::min(self.width() * 2, self.width() + 128));
2598 let mut count = 0;
2599 for s in self.columns() {
2600 if cols.contains(s.name()) {
2601 let ca = s.struct_()?.clone();
2602 new_cols.extend(ca.fields_as_series().into_iter().map(|mut f| {
2603 if let Some(separator) = &separator {
2604 f.rename(polars_utils::format_pl_smallstr!(
2605 "{}{}{}",
2606 s.name(),
2607 separator,
2608 f.name()
2609 ));
2610 }
2611 Column::from(f)
2612 }));
2613 count += 1;
2614 } else {
2615 new_cols.push(s.clone())
2616 }
2617 }
2618 if count != cols.len() {
2619 let schema = self.schema();
2622 for col in cols {
2623 let _ = schema
2624 .get(col.as_str())
2625 .ok_or_else(|| polars_err!(col_not_found = col))?;
2626 }
2627 }
2628
2629 DataFrame::new(self.height(), new_cols)
2630 }
2631
2632 pub fn append_record_batch(&mut self, rb: RecordBatchT<ArrayRef>) -> PolarsResult<()> {
2633 let df = DataFrame::from(rb);
2636 polars_ensure!(
2637 self.schema() == df.schema(),
2638 SchemaMismatch: "cannot append record batch with different schema\n\n
2639 Got {:?}\nexpected: {:?}", df.schema(), self.schema(),
2640 );
2641 self.vstack_mut_owned_unchecked(df);
2642 Ok(())
2643 }
2644}
2645
2646struct ChunkCursor<'a> {
2648 chunks: &'a [ArrayRef],
2650 height: usize,
2651 idx: usize,
2652}
2653
2654impl<'a> ChunkCursor<'a> {
2655 fn new(df: &'a DataFrame) -> Self {
2656 let chunks = match df.columns().iter().find_map(Column::as_series) {
2657 Some(s) => s.chunks().as_slice(),
2658 None => &[],
2659 };
2660
2661 Self {
2662 chunks,
2663 height: df.height(),
2664 idx: 0,
2665 }
2666 }
2667
2668 fn n_chunks(&self) -> usize {
2669 usize::max(1, self.chunks.len())
2670 }
2671
2672 fn next(&mut self) -> Option<(usize, usize)> {
2674 let idx = self.idx;
2675 if idx >= self.n_chunks() {
2676 return None;
2677 }
2678
2679 self.idx += 1;
2680 Some((idx, self.chunks.get(idx).map_or(self.height, |c| c.len())))
2681 }
2682
2683 fn size_hint(&self) -> (usize, Option<usize>) {
2684 let n = self.n_chunks() - self.idx;
2685 (n, Some(n))
2686 }
2687}
2688
2689pub struct RecordBatchIter<'a> {
2690 df: &'a DataFrame,
2691 schema: ArrowSchemaRef,
2692 cursor: ChunkCursor<'a>,
2693 compat_level: CompatLevel,
2694 parallel: bool,
2695}
2696
2697impl Iterator for RecordBatchIter<'_> {
2698 type Item = RecordBatch;
2699
2700 fn next(&mut self) -> Option<Self::Item> {
2701 let (idx, length) = self.cursor.next()?;
2702
2703 let to_arrow = |c: &Column| match c.as_series() {
2704 Some(s) => s.to_arrow(idx, self.compat_level),
2705 None => c.slice(0, length).rechunk_to_arrow(self.compat_level),
2706 };
2707
2708 let batch_cols: Vec<ArrayRef> = if self.parallel {
2709 let iter = self.df.columns().par_iter().map(to_arrow);
2710 RAYON.install(|| iter.collect())
2711 } else {
2712 self.df.columns().iter().map(to_arrow).collect()
2713 };
2714
2715 Some(RecordBatch::new(length, self.schema.clone(), batch_cols))
2716 }
2717
2718 fn size_hint(&self) -> (usize, Option<usize>) {
2719 self.cursor.size_hint()
2720 }
2721}
2722
2723pub struct PhysRecordBatchIter<'a> {
2724 df: &'a DataFrame,
2725 schema: ArrowSchemaRef,
2726 cursor: ChunkCursor<'a>,
2727}
2728
2729impl Iterator for PhysRecordBatchIter<'_> {
2730 type Item = RecordBatch;
2731
2732 fn next(&mut self) -> Option<Self::Item> {
2733 let (idx, length) = self.cursor.next()?;
2734
2735 let arrs = self
2736 .df
2737 .columns()
2738 .iter()
2739 .map(|c| match c.as_series() {
2740 Some(s) => s.chunks()[idx].clone(),
2741 None => c
2742 .slice(0, length)
2743 .take_materialized_series()
2744 .rechunk()
2745 .chunks()[0]
2746 .clone(),
2747 })
2748 .collect::<Vec<_>>();
2749
2750 Some(RecordBatch::new(length, self.schema.clone(), arrs))
2751 }
2752
2753 fn size_hint(&self) -> (usize, Option<usize>) {
2754 self.cursor.size_hint()
2755 }
2756}
2757
2758pub enum RecordBatchIterWrap<'a> {
2759 ZeroWidth {
2760 remaining_height: usize,
2761 chunk_size: usize,
2762 },
2763 Batches(RecordBatchIter<'a>),
2764 PhysicalBatches(PhysRecordBatchIter<'a>),
2765}
2766
2767impl<'a> RecordBatchIterWrap<'a> {
2768 fn new_zero_width(height: usize) -> Self {
2769 Self::ZeroWidth {
2770 remaining_height: height,
2771 chunk_size: polars_config::config().ideal_morsel_size() as usize,
2772 }
2773 }
2774}
2775
2776impl Iterator for RecordBatchIterWrap<'_> {
2777 type Item = RecordBatch;
2778
2779 fn next(&mut self) -> Option<Self::Item> {
2780 match self {
2781 Self::ZeroWidth {
2782 remaining_height,
2783 chunk_size,
2784 } => {
2785 let n = usize::min(*remaining_height, *chunk_size);
2786 *remaining_height -= n;
2787
2788 (n > 0).then(|| RecordBatch::new(n, ArrowSchemaRef::default(), vec![]))
2789 },
2790 Self::Batches(v) => v.next(),
2791 Self::PhysicalBatches(v) => v.next(),
2792 }
2793 }
2794
2795 fn size_hint(&self) -> (usize, Option<usize>) {
2796 match self {
2797 Self::ZeroWidth {
2798 remaining_height,
2799 chunk_size,
2800 } => {
2801 let n = remaining_height.div_ceil(*chunk_size);
2802 (n, Some(n))
2803 },
2804 Self::Batches(v) => v.size_hint(),
2805 Self::PhysicalBatches(v) => v.size_hint(),
2806 }
2807 }
2808}
2809
2810fn ensure_can_extend(left: &Column, right: &Column) -> PolarsResult<()> {
2812 polars_ensure!(
2813 left.name() == right.name(),
2814 ShapeMismatch: "unable to vstack, column names don't match: {:?} and {:?}",
2815 left.name(), right.name(),
2816 );
2817 Ok(())
2818}
2819
2820#[cfg(test)]
2821mod test {
2822 use super::*;
2823
2824 fn create_frame() -> DataFrame {
2825 let s0 = Column::new("days".into(), [0, 1, 2].as_ref());
2826 let s1 = Column::new("temp".into(), [22.1, 19.9, 7.].as_ref());
2827 DataFrame::new_infer_height(vec![s0, s1]).unwrap()
2828 }
2829
2830 #[test]
2831 fn sort_in_place_keeps_the_chunks_of_a_frame_sorted_by_one_key() {
2832 let mut df = df!("a" => [1, 2], "b" => [1, 1]).unwrap();
2833 df.vstack_mut(&df!("a" => [3, 4], "b" => [1, 1]).unwrap())
2834 .unwrap();
2835 df.apply("a", |c| {
2836 let mut c = c.clone();
2837 c.set_sorted_flag(IsSorted::Ascending);
2838 c
2839 })
2840 .unwrap();
2841 assert_eq!(df.first_col_n_chunks(), 2);
2842
2843 df.sort_in_place(["a"], SortMultipleOptions::default())
2844 .unwrap();
2845 assert_eq!(df.first_col_n_chunks(), 2);
2846
2847 df.sort_in_place(["a", "b"], SortMultipleOptions::default())
2848 .unwrap();
2849 assert_eq!(df.first_col_n_chunks(), 1);
2850 let a = df.column("a").unwrap().as_materialized_series();
2851 assert_eq!(
2852 a.i32().unwrap().to_vec(),
2853 [Some(1), Some(2), Some(3), Some(4)]
2854 );
2855 }
2856
2857 #[test]
2858 #[cfg_attr(miri, ignore)]
2859 fn test_recordbatch_iterator() {
2860 let df = df!(
2861 "foo" => [1, 2, 3, 4, 5]
2862 )
2863 .unwrap();
2864 let mut iter = df.iter_chunks(CompatLevel::newest(), false);
2865 assert_eq!(5, iter.next().unwrap().len());
2866 assert!(iter.next().is_none());
2867 }
2868
2869 #[test]
2870 #[cfg_attr(miri, ignore)]
2871 fn test_select() {
2872 let df = create_frame();
2873 assert_eq!(
2874 df.column("days")
2875 .unwrap()
2876 .as_series()
2877 .unwrap()
2878 .equal(1)
2879 .unwrap()
2880 .sum(),
2881 Some(1)
2882 );
2883 }
2884
2885 #[test]
2886 #[cfg_attr(miri, ignore)]
2887 fn test_filter_broadcast_on_string_col() {
2888 let col_name = "some_col";
2889 let v = vec!["test".to_string()];
2890 let s0 = Column::new(PlSmallStr::from_str(col_name), v);
2891 let mut df = DataFrame::new_infer_height(vec![s0]).unwrap();
2892
2893 df = df
2894 .filter(
2895 &df.column(col_name)
2896 .unwrap()
2897 .as_materialized_series()
2898 .equal("")
2899 .unwrap(),
2900 )
2901 .unwrap();
2902 assert_eq!(
2903 df.column(col_name)
2904 .unwrap()
2905 .as_materialized_series()
2906 .n_chunks(),
2907 1
2908 );
2909 }
2910
2911 #[test]
2912 #[cfg_attr(miri, ignore)]
2913 fn test_filter_broadcast_on_list_col() {
2914 let s1 = Series::new(PlSmallStr::EMPTY, [true, false, true]);
2915 let ll: ListChunked = [&s1].iter().copied().collect();
2916
2917 let mask = BooleanChunked::from_slice(PlSmallStr::EMPTY, &[false]);
2918 let new = ll.filter(&mask).unwrap();
2919
2920 assert_eq!(new.chunks.len(), 1);
2921 assert_eq!(new.len(), 0);
2922 }
2923
2924 #[test]
2925 fn slice() {
2926 let df = create_frame();
2927 let sliced_df = df.slice(0, 2);
2928 assert_eq!(sliced_df.shape(), (2, 2));
2929 }
2930
2931 #[test]
2932 fn rechunk_false() {
2933 let df = create_frame();
2934 assert!(!df.should_rechunk())
2935 }
2936
2937 #[test]
2938 fn rechunk_true() -> PolarsResult<()> {
2939 let mut base = df!(
2940 "a" => [1, 2, 3],
2941 "b" => [1, 2, 3]
2942 )?;
2943
2944 let mut s = Series::new("foo".into(), 0..2);
2946 let s2 = Series::new("bar".into(), 0..1);
2947 s.append(&s2)?;
2948
2949 let out = base.with_column(s.into_column())?;
2951
2952 assert!(out.should_rechunk());
2954 Ok(())
2955 }
2956
2957 #[test]
2958 fn test_duplicate_column() {
2959 let mut df = df! {
2960 "foo" => [1, 2, 3]
2961 }
2962 .unwrap();
2963 assert!(
2965 df.with_column(Column::new("foo".into(), &[1, 2, 3]))
2966 .is_ok()
2967 );
2968 assert!(
2969 df.with_column(Column::new("bar".into(), &[1, 2, 3]))
2970 .is_ok()
2971 );
2972 assert!(df.column("bar").is_ok())
2973 }
2974
2975 #[test]
2976 #[cfg_attr(miri, ignore)]
2977 fn distinct() {
2978 let df = df! {
2979 "flt" => [1., 1., 2., 2., 3., 3.],
2980 "int" => [1, 1, 2, 2, 3, 3, ],
2981 "str" => ["a", "a", "b", "b", "c", "c"]
2982 }
2983 .unwrap();
2984 let df = df
2985 .unique_stable(None, UniqueKeepStrategy::First, None)
2986 .unwrap()
2987 .sort(["flt"], SortMultipleOptions::default())
2988 .unwrap();
2989 let valid = df! {
2990 "flt" => [1., 2., 3.],
2991 "int" => [1, 2, 3],
2992 "str" => ["a", "b", "c"]
2993 }
2994 .unwrap();
2995 assert!(df.equals(&valid));
2996 }
2997
2998 #[test]
2999 fn test_vstack() {
3000 let mut df = df! {
3002 "flt" => [1., 1., 2., 2., 3., 3.],
3003 "int" => [1, 1, 2, 2, 3, 3, ],
3004 "str" => ["a", "a", "b", "b", "c", "c"]
3005 }
3006 .unwrap();
3007
3008 df.vstack_mut(&df.slice(0, 3)).unwrap();
3009 assert_eq!(df.first_col_n_chunks(), 2)
3010 }
3011
3012 #[test]
3013 fn test_chunk_alignment_with_scalar_column() {
3014 let mut df = df! { "int" => [0, 1] }.unwrap();
3015 df.vstack_mut(&df! { "int" => [2] }.unwrap()).unwrap();
3016 df.insert_column(
3017 0,
3018 Column::new_scalar("str".into(), Scalar::from(PlSmallStr::from_static("a")), 3),
3019 )
3020 .unwrap();
3021
3022 for materialize in [false, true] {
3024 if materialize {
3025 df.columns()[0].as_materialized_series();
3026 }
3027 assert!(!df.should_rechunk());
3028
3029 let batch_lengths = |batches: Vec<RecordBatch>| {
3030 batches
3031 .iter()
3032 .map(|b| {
3033 assert!(b.arrays().iter().all(|arr| arr.len() == b.len()));
3034 b.len()
3035 })
3036 .collect::<Vec<_>>()
3037 };
3038 let batches = df.iter_chunks(CompatLevel::newest(), false).collect();
3039 assert_eq!(batch_lengths(batches), [2, 1]);
3040 let batches = df.iter_chunks_physical().collect();
3041 assert_eq!(batch_lengths(batches), [2, 1]);
3042 }
3043 }
3044
3045 #[test]
3046 fn test_vstack_scalar_chunk_alignment() {
3047 let mut df = df! {
3049 "int" => [0],
3050 "str" => ["a"],
3051 }
3052 .unwrap();
3053 df.vstack_mut(&df! { "int" => [1], "str" => ["a"] }.unwrap())
3054 .unwrap();
3055
3056 assert_eq!(df.column("int").unwrap().n_chunks(), 2);
3057 assert!(df.column("str").unwrap().as_scalar_column().is_some());
3058 assert!(!df.should_rechunk());
3059
3060 let batches = df
3061 .iter_chunks(CompatLevel::newest(), false)
3062 .collect::<Vec<_>>();
3063 assert_eq!(batches.iter().map(|b| b.len()).collect::<Vec<_>>(), [1, 1]);
3064 }
3065
3066 #[test]
3067 fn test_vstack_on_empty_dataframe() {
3068 let mut df = DataFrame::empty();
3069
3070 let df_data = df! {
3071 "flt" => [1., 1., 2., 2., 3., 3.],
3072 "int" => [1, 1, 2, 2, 3, 3, ],
3073 "str" => ["a", "a", "b", "b", "c", "c"]
3074 }
3075 .unwrap();
3076
3077 df.vstack_mut(&df_data).unwrap();
3078 assert_eq!(df.height(), 6)
3079 }
3080
3081 #[test]
3082 fn test_unique_keep_none_with_slice() {
3083 let df = df! {
3084 "x" => [1, 2, 3, 2, 1]
3085 }
3086 .unwrap();
3087 let out = df
3088 .unique_stable(
3089 Some(&["x".to_string()][..]),
3090 UniqueKeepStrategy::None,
3091 Some((0, 2)),
3092 )
3093 .unwrap();
3094 let expected = df! {
3095 "x" => [3]
3096 }
3097 .unwrap();
3098 assert!(out.equals(&expected));
3099 }
3100
3101 #[test]
3102 #[cfg(feature = "dtype-i8")]
3103 fn test_apply_result_schema() {
3104 let mut df = df! {
3105 "x" => [1, 2, 3, 2, 1]
3106 }
3107 .unwrap();
3108
3109 let schema_before = df.schema().clone();
3110 df.apply("x", |f| f.cast(&DataType::Int8).unwrap()).unwrap();
3111 assert_ne!(&schema_before, df.schema());
3112 }
3113}