1use std::fmt::{Debug, Display, Formatter};
2use std::hash::Hash;
3
4use num_traits::NumCast;
5use polars_compute::rolling::QuantileMethod;
6use polars_utils::broadcast::broadcast_len;
7use polars_utils::format_pl_smallstr;
8use polars_utils::hashing::DirtyHash;
9use rayon::prelude::*;
10
11use self::hashing::*;
12use crate::prelude::*;
13use crate::runtime::RAYON;
14use crate::utils::{_set_partition_size, accumulate_dataframes_vertical};
15
16pub mod aggregations;
17pub(crate) mod hashing;
18mod into_groups;
19mod position;
20
21pub use into_groups::*;
22pub use position::*;
23
24use crate::chunked_array::ops::row_encode::{
25 encode_rows_unordered, encode_rows_vertical_par_unordered,
26};
27
28impl DataFrame {
29 pub fn group_by_with_series(
30 &self,
31 mut by: Vec<Column>,
32 multithreaded: bool,
33 sorted: bool,
34 ) -> PolarsResult<GroupBy<'_>> {
35 polars_ensure!(
36 !by.is_empty(),
37 ComputeError: "at least one key is required in a group_by operation"
38 );
39
40 let common_height = if self.width() > 0 {
44 self.height()
45 } else {
46 broadcast_len(by.iter()).context("group_by key")?
47 };
48 for by_key in by.iter_mut() {
49 by_key
50 .broadcast_in_place_to(common_height)
51 .context("group_by keys should have the same length as the DataFrame")?;
52 }
53
54 let groups = if by.len() == 1 {
55 let column = &by[0];
56 column
57 .as_materialized_series()
58 .group_tuples(multithreaded, sorted)
59 } else if by.iter().any(|s| s.dtype().is_object()) {
60 #[cfg(feature = "object")]
61 {
62 let mut df = DataFrame::new(self.height(), by.clone()).unwrap();
63 let n = df.height();
64 let rows = df.to_av_rows();
65 let iter = (0..n).map(|i| rows.get(i));
66 Ok(group_by(iter, sorted))
67 }
68 #[cfg(not(feature = "object"))]
69 {
70 unreachable!()
71 }
72 } else {
73 let by = by
75 .iter()
76 .filter(|s| !s.dtype().is_null())
77 .cloned()
78 .collect::<Vec<_>>();
79 if by.is_empty() {
80 let groups = if self.height() == 0 {
81 vec![]
82 } else {
83 vec![[0, self.height() as IdxSize]]
84 };
85
86 Ok(GroupsType::new_slice(groups, false, true))
87 } else {
88 let rows = if multithreaded {
89 encode_rows_vertical_par_unordered(&by)
90 } else {
91 encode_rows_unordered(&by)
92 }?
93 .into_series();
94 rows.group_tuples(multithreaded, sorted)
95 }
96 };
97 Ok(GroupBy::new(self, by, groups?.into_sliceable(), None))
98 }
99
100 pub fn group_by<I, S>(&self, by: I) -> PolarsResult<GroupBy<'_>>
113 where
114 I: IntoIterator<Item = S>,
115 S: AsRef<str>,
116 {
117 let selected_keys = self.select_to_vec(by)?;
118 self.group_by_with_series(selected_keys, true, false)
119 }
120
121 pub fn group_by_stable<I, S>(&self, by: I) -> PolarsResult<GroupBy<'_>>
124 where
125 I: IntoIterator<Item = S>,
126 S: AsRef<str>,
127 {
128 let selected_keys = self.select_to_vec(by)?;
129 self.group_by_with_series(selected_keys, true, true)
130 }
131}
132
133#[derive(Debug, Clone)]
183pub struct GroupBy<'a> {
184 pub df: &'a DataFrame,
185 pub(crate) selected_keys: Vec<Column>,
186 groups: GroupPositions,
188 pub(crate) selected_agg: Option<Vec<PlSmallStr>>,
190}
191
192impl<'a> GroupBy<'a> {
193 pub fn new(
194 df: &'a DataFrame,
195 by: Vec<Column>,
196 groups: GroupPositions,
197 selected_agg: Option<Vec<PlSmallStr>>,
198 ) -> Self {
199 GroupBy {
200 df,
201 selected_keys: by,
202 groups,
203 selected_agg,
204 }
205 }
206
207 #[must_use]
213 pub fn select<I: IntoIterator<Item = S>, S: Into<PlSmallStr>>(mut self, selection: I) -> Self {
214 self.selected_agg = Some(selection.into_iter().map(|s| s.into()).collect());
215 self
216 }
217
218 pub fn get_groups(&self) -> &GroupPositions {
223 &self.groups
224 }
225
226 pub unsafe fn get_groups_mut(&mut self) -> &mut GroupPositions {
235 &mut self.groups
236 }
237
238 pub fn into_groups(self) -> GroupPositions {
239 self.groups
240 }
241
242 pub fn keys_sliced(&self, slice: Option<(i64, usize)>) -> Vec<Column> {
243 #[allow(unused_assignments)]
244 let mut groups_owned = None;
246
247 let groups = if let Some((offset, len)) = slice {
248 groups_owned = Some(self.groups.slice(offset, len));
249 groups_owned.as_deref().unwrap()
250 } else {
251 &self.groups
252 };
253 RAYON.install(|| {
254 self.selected_keys
255 .par_iter()
256 .map(Column::as_materialized_series)
257 .map(|s| {
258 match groups {
259 GroupsType::Idx(groups) => {
260 let mut out = unsafe { s.take_slice_unchecked(groups.first()) };
262 if groups.sorted_by_first_idx {
263 out.set_sorted_flag(s.is_sorted_flag());
264 };
265 out
266 },
267 GroupsType::Slice {
268 groups,
269 overlapping,
270 monotonic: _,
271 } => {
272 if *overlapping && !groups.is_empty() {
273 let offset = groups[0][0];
275 let [upper_offset, upper_len] = groups[groups.len() - 1];
276 return s.slice(
277 offset as i64,
278 ((upper_offset + upper_len) - offset) as usize,
279 );
280 }
281
282 let indices = groups
283 .iter()
284 .map(|&[first, _len]| first)
285 .collect_ca(PlSmallStr::EMPTY);
286 let mut out = unsafe { s.take_unchecked(&indices) };
288 out.set_sorted_flag(s.is_sorted_flag());
290 out
291 },
292 }
293 })
294 .map(Column::from)
295 .collect()
296 })
297 }
298
299 pub fn keys(&self) -> Vec<Column> {
300 self.keys_sliced(None)
301 }
302
303 fn prepare_agg(&self) -> PolarsResult<(Vec<Column>, Vec<Column>)> {
304 let keys = self.keys();
305
306 let agg_col = match &self.selected_agg {
307 Some(selection) => self.df.select_to_vec(selection),
308 None => {
309 let by: Vec<_> = self.selected_keys.iter().map(|s| s.name()).collect();
310 let selection = self
311 .df
312 .columns()
313 .iter()
314 .map(|s| s.name())
315 .filter(|a| !by.contains(a))
316 .cloned()
317 .collect::<Vec<_>>();
318
319 self.df.select_to_vec(selection.as_slice())
320 },
321 }?;
322
323 Ok((keys, agg_col))
324 }
325
326 #[deprecated(since = "0.24.1", note = "use polars.lazy aggregations")]
352 pub fn mean(&self) -> PolarsResult<DataFrame> {
353 let (mut cols, agg_cols) = self.prepare_agg()?;
354
355 for agg_col in agg_cols {
356 let new_name = fmt_group_by_column(agg_col.name().as_str(), GroupByMethod::Mean);
357 let mut agg = unsafe { agg_col.agg_mean(&self.groups) };
358 agg.rename(new_name);
359 cols.push(agg);
360 }
361
362 DataFrame::new_infer_height(cols)
363 }
364
365 #[deprecated(since = "0.24.1", note = "use polars.lazy aggregations")]
391 pub fn sum(&self) -> PolarsResult<DataFrame> {
392 let (mut cols, agg_cols) = self.prepare_agg()?;
393
394 for agg_col in agg_cols {
395 let new_name = fmt_group_by_column(agg_col.name().as_str(), GroupByMethod::Sum);
396 let mut agg = unsafe { agg_col.agg_sum(&self.groups) };
397 agg.rename(new_name);
398 cols.push(agg);
399 }
400 DataFrame::new_infer_height(cols)
401 }
402
403 #[deprecated(since = "0.24.1", note = "use polars.lazy aggregations")]
429 pub fn min(&self) -> PolarsResult<DataFrame> {
430 let (mut cols, agg_cols) = self.prepare_agg()?;
431 for agg_col in agg_cols {
432 let new_name = fmt_group_by_column(agg_col.name().as_str(), GroupByMethod::Min);
433 let mut agg = unsafe { agg_col.agg_min(&self.groups) };
434 agg.rename(new_name);
435 cols.push(agg);
436 }
437 DataFrame::new_infer_height(cols)
438 }
439
440 #[deprecated(since = "0.24.1", note = "use polars.lazy aggregations")]
466 pub fn max(&self) -> PolarsResult<DataFrame> {
467 let (mut cols, agg_cols) = self.prepare_agg()?;
468 for agg_col in agg_cols {
469 let new_name = fmt_group_by_column(agg_col.name().as_str(), GroupByMethod::Max);
470 let mut agg = unsafe { agg_col.agg_max(&self.groups) };
471 agg.rename(new_name);
472 cols.push(agg);
473 }
474 DataFrame::new_infer_height(cols)
475 }
476
477 #[deprecated(since = "0.24.1", note = "use polars.lazy aggregations")]
503 pub fn first(&self) -> PolarsResult<DataFrame> {
504 let (mut cols, agg_cols) = self.prepare_agg()?;
505 for agg_col in agg_cols {
506 let new_name = fmt_group_by_column(agg_col.name().as_str(), GroupByMethod::First);
507 let mut agg = unsafe { agg_col.agg_first(&self.groups) };
508 agg.rename(new_name);
509 cols.push(agg);
510 }
511 DataFrame::new_infer_height(cols)
512 }
513
514 #[deprecated(since = "0.24.1", note = "use polars.lazy aggregations")]
540 pub fn last(&self) -> PolarsResult<DataFrame> {
541 let (mut cols, agg_cols) = self.prepare_agg()?;
542 for agg_col in agg_cols {
543 let new_name = fmt_group_by_column(agg_col.name().as_str(), GroupByMethod::Last);
544 let mut agg = unsafe { agg_col.agg_last(&self.groups) };
545 agg.rename(new_name);
546 cols.push(agg);
547 }
548 DataFrame::new_infer_height(cols)
549 }
550
551 #[deprecated(since = "0.24.1", note = "use polars.lazy aggregations")]
577 pub fn n_unique(&self) -> PolarsResult<DataFrame> {
578 let (mut cols, agg_cols) = self.prepare_agg()?;
579 for agg_col in agg_cols {
580 let new_name = fmt_group_by_column(agg_col.name().as_str(), GroupByMethod::NUnique);
581 let mut agg = unsafe { agg_col.agg_n_unique(&self.groups) };
582 agg.rename(new_name);
583 cols.push(agg);
584 }
585 DataFrame::new_infer_height(cols)
586 }
587
588 #[deprecated(since = "0.24.1", note = "use polars.lazy aggregations")]
600 pub fn quantile(&self, quantile: f64, method: QuantileMethod) -> PolarsResult<DataFrame> {
601 polars_ensure!(
602 (0.0..=1.0).contains(&quantile),
603 ComputeError: "`quantile` should be within 0.0 and 1.0"
604 );
605 let (mut cols, agg_cols) = self.prepare_agg()?;
606 for agg_col in agg_cols {
607 let new_name = fmt_group_by_column(
608 agg_col.name().as_str(),
609 GroupByMethod::Quantile(quantile, method),
610 );
611 let mut agg = unsafe { agg_col.agg_quantile(&self.groups, quantile, method) };
612 agg.rename(new_name);
613 cols.push(agg);
614 }
615 DataFrame::new_infer_height(cols)
616 }
617
618 #[deprecated(since = "0.24.1", note = "use polars.lazy aggregations")]
629 pub fn median(&self) -> PolarsResult<DataFrame> {
630 let (mut cols, agg_cols) = self.prepare_agg()?;
631 for agg_col in agg_cols {
632 let new_name = fmt_group_by_column(agg_col.name().as_str(), GroupByMethod::Median);
633 let mut agg = unsafe { agg_col.agg_median(&self.groups) };
634 agg.rename(new_name);
635 cols.push(agg);
636 }
637 DataFrame::new_infer_height(cols)
638 }
639
640 #[deprecated(since = "0.24.1", note = "use polars.lazy aggregations")]
642 pub fn var(&self, ddof: u8) -> PolarsResult<DataFrame> {
643 let (mut cols, agg_cols) = self.prepare_agg()?;
644 for agg_col in agg_cols {
645 let new_name = fmt_group_by_column(agg_col.name().as_str(), GroupByMethod::Var(ddof));
646 let mut agg = unsafe { agg_col.agg_var(&self.groups, ddof) };
647 agg.rename(new_name);
648 cols.push(agg);
649 }
650 DataFrame::new_infer_height(cols)
651 }
652
653 #[deprecated(since = "0.24.1", note = "use polars.lazy aggregations")]
655 pub fn std(&self, ddof: u8) -> PolarsResult<DataFrame> {
656 let (mut cols, agg_cols) = self.prepare_agg()?;
657 for agg_col in agg_cols {
658 let new_name = fmt_group_by_column(agg_col.name().as_str(), GroupByMethod::Std(ddof));
659 let mut agg = unsafe { agg_col.agg_std(&self.groups, ddof) };
660 agg.rename(new_name);
661 cols.push(agg);
662 }
663 DataFrame::new_infer_height(cols)
664 }
665
666 pub fn count(&self) -> PolarsResult<DataFrame> {
692 let (mut cols, agg_cols) = self.prepare_agg()?;
693
694 for agg_col in agg_cols {
695 let new_name = fmt_group_by_column(
696 agg_col.name().as_str(),
697 GroupByMethod::Count {
698 include_nulls: true,
699 },
700 );
701 let mut ca = self.groups.group_count();
702 ca.rename(new_name);
703 cols.push(ca.into_column());
704 }
705 DataFrame::new_infer_height(cols)
706 }
707
708 pub fn groups(&self) -> PolarsResult<DataFrame> {
734 let mut cols = self.keys();
735 let mut column = self.groups.as_list_chunked();
736 let new_name = fmt_group_by_column("", GroupByMethod::Groups);
737 column.rename(new_name);
738 cols.push(column.into_column());
739 DataFrame::new_infer_height(cols)
740 }
741
742 fn prepare_apply(&self) -> PolarsResult<DataFrame> {
743 if let Some(agg) = &self.selected_agg {
744 if agg.is_empty() {
745 Ok(self.df.clone())
746 } else {
747 let mut new_cols = Vec::with_capacity(self.selected_keys.len() + agg.len());
748 new_cols.extend_from_slice(&self.selected_keys);
749 let cols = self.df.select_to_vec(agg.as_slice())?;
750 new_cols.extend(cols);
751 Ok(unsafe { DataFrame::new_unchecked(self.df.height(), new_cols) })
752 }
753 } else {
754 Ok(self.df.clone())
755 }
756 }
757
758 #[deprecated(since = "0.24.1", note = "use polars.lazy aggregations")]
760 pub fn par_apply<F>(&self, f: F) -> PolarsResult<DataFrame>
761 where
762 F: Fn(DataFrame) -> PolarsResult<DataFrame> + Send + Sync,
763 {
764 polars_ensure!(self.df.height() > 0, ComputeError: "cannot group_by + apply on empty 'DataFrame'");
765 let df = self.prepare_apply()?;
766 let dfs = self
767 .get_groups()
768 .par_iter()
769 .map(|g| {
770 let sub_df = unsafe { take_df(&df, g) };
773 f(sub_df)
774 })
775 .collect::<PolarsResult<Vec<_>>>()?;
776
777 let mut df = accumulate_dataframes_vertical(dfs)?;
778 df.rechunk_mut_par();
779 Ok(df)
780 }
781
782 pub fn apply<F>(&self, f: F) -> PolarsResult<DataFrame>
784 where
785 F: FnMut(DataFrame) -> PolarsResult<DataFrame> + Send + Sync,
786 {
787 self.apply_sliced(None, f, None)
788 }
789
790 pub fn apply_sliced<F>(
791 &self,
792 slice: Option<(i64, usize)>,
793 mut f: F,
794 schema: Option<&SchemaRef>,
795 ) -> PolarsResult<DataFrame>
796 where
797 F: FnMut(DataFrame) -> PolarsResult<DataFrame> + Send + Sync,
798 {
799 if self.df.height() == 0 {
800 if let Some(schema) = schema {
802 return Ok(DataFrame::empty_with_arc_schema(schema.clone()));
803 }
804
805 polars_bail!(ComputeError: "cannot group_by + apply on empty 'DataFrame'");
806 }
807
808 let df = self.prepare_apply()?;
809 let max_height = if let Some((offset, len)) = slice {
810 offset.try_into().unwrap_or(usize::MAX).saturating_add(len)
811 } else {
812 usize::MAX
813 };
814 let mut height = 0;
815 let mut dfs = Vec::with_capacity(self.get_groups().len());
816 for g in self.get_groups().iter() {
817 let sub_df = unsafe { take_df(&df, g) };
819 let df = f(sub_df)?;
820 height += df.height();
821 dfs.push(df);
822
823 if height >= max_height {
826 break;
827 }
828 }
829
830 let mut df = accumulate_dataframes_vertical(dfs)?;
831 if let Some((offset, len)) = slice {
832 df = df.slice(offset, len);
833 }
834 Ok(df)
835 }
836
837 pub fn sliced(mut self, slice: Option<(i64, usize)>) -> Self {
838 match slice {
839 None => self,
840 Some((offset, length)) => {
841 self.groups = self.groups.slice(offset, length);
842 self.selected_keys = self.keys_sliced(slice);
843 self
844 },
845 }
846 }
847}
848
849unsafe fn take_df(df: &DataFrame, g: GroupsIndicator) -> DataFrame {
850 match g {
851 GroupsIndicator::Idx(idx) => df.take_slice_unchecked(idx.1),
852 GroupsIndicator::Slice([first, len]) => df.slice(first as i64, len as usize),
853 }
854}
855
856#[derive(Copy, Clone, Debug)]
857pub enum GroupByMethod {
858 Min,
859 NanMin,
860 Max,
861 NanMax,
862 Median,
863 Mean,
864 First,
865 FirstNonNull,
866 Last,
867 LastNonNull,
868 Item { allow_empty: bool },
869 Sum,
870 Groups,
871 NUnique,
872 Quantile(f64, QuantileMethod),
873 Count { include_nulls: bool },
874 Implode { maintain_order: bool },
875 Std(u8),
876 Var(u8),
877 ArgMin,
878 ArgMax,
879}
880
881impl Display for GroupByMethod {
882 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
883 use GroupByMethod::*;
884 let s = match self {
885 Min => "min",
886 NanMin => "nan_min",
887 Max => "max",
888 NanMax => "nan_max",
889 Median => "median",
890 Mean => "mean",
891 First => "first",
892 FirstNonNull => "first_non_null",
893 Last => "last",
894 LastNonNull => "last_non_null",
895 Item { .. } => "item",
896 Sum => "sum",
897 Groups => "groups",
898 NUnique => "n_unique",
899 Quantile(_, _) => "quantile",
900 Count { .. } => "count",
901 Implode { .. } => "implode",
902 Std(_) => "std",
903 Var(_) => "var",
904 ArgMin => "arg_min",
905 ArgMax => "arg_max",
906 };
907 write!(f, "{s}")
908 }
909}
910
911pub fn fmt_group_by_column(name: &str, method: GroupByMethod) -> PlSmallStr {
913 use GroupByMethod::*;
914 match method {
915 Min => format_pl_smallstr!("{name}_min"),
916 Max => format_pl_smallstr!("{name}_max"),
917 NanMin => format_pl_smallstr!("{name}_nan_min"),
918 NanMax => format_pl_smallstr!("{name}_nan_max"),
919 Median => format_pl_smallstr!("{name}_median"),
920 Mean => format_pl_smallstr!("{name}_mean"),
921 First => format_pl_smallstr!("{name}_first"),
922 FirstNonNull => format_pl_smallstr!("{name}_first_non_null"),
923 Last => format_pl_smallstr!("{name}_last"),
924 LastNonNull => format_pl_smallstr!("{name}_last_non_null"),
925 Item { .. } => format_pl_smallstr!("{name}_item"),
926 Sum => format_pl_smallstr!("{name}_sum"),
927 Groups => PlSmallStr::from_static("groups"),
928 NUnique => format_pl_smallstr!("{name}_n_unique"),
929 Count { .. } => format_pl_smallstr!("{name}_count"),
930 Implode { .. } => format_pl_smallstr!("{name}_agg_list"),
931 Quantile(quantile, _interpol) => format_pl_smallstr!("{name}_quantile_{quantile:.2}"),
932 Std(_) => format_pl_smallstr!("{name}_agg_std"),
933 Var(_) => format_pl_smallstr!("{name}_agg_var"),
934 ArgMin => format_pl_smallstr!("{name}_arg_min"),
935 ArgMax => format_pl_smallstr!("{name}_arg_max"),
936 }
937}
938
939#[cfg(test)]
940mod test {
941 use num_traits::FloatConst;
942
943 use crate::prelude::*;
944
945 #[test]
946 #[cfg(feature = "dtype-date")]
947 #[cfg_attr(miri, ignore)]
948 fn test_group_by() -> PolarsResult<()> {
949 let s0 = Column::new(
950 PlSmallStr::from_static("date"),
951 &[
952 "2020-08-21",
953 "2020-08-21",
954 "2020-08-22",
955 "2020-08-23",
956 "2020-08-22",
957 ],
958 );
959 let s1 = Column::new(PlSmallStr::from_static("temp"), [20, 10, 7, 9, 1]);
960 let s2 = Column::new(PlSmallStr::from_static("rain"), [0.2, 0.1, 0.3, 0.1, 0.01]);
961 let df = DataFrame::new_infer_height(vec![s0, s1, s2]).unwrap();
962
963 let out = df.group_by_stable(["date"])?.select(["temp"]).count()?;
964 assert_eq!(
965 out.column("temp_count")?,
966 &Column::new(PlSmallStr::from_static("temp_count"), [2 as IdxSize, 2, 1])
967 );
968
969 #[allow(deprecated)]
971 let out = df
973 .group_by_stable(["date"])?
974 .select(["temp", "rain"])
975 .mean()?;
976 assert_eq!(
977 out.column("temp_mean")?,
978 &Column::new(PlSmallStr::from_static("temp_mean"), [15.0f64, 4.0, 9.0])
979 );
980
981 #[allow(deprecated)]
983 let out = df
985 .group_by_stable(["date", "temp"])?
986 .select(["rain"])
987 .mean()?;
988 assert!(out.column("rain_mean").is_ok());
989
990 #[allow(deprecated)]
992 let out = df.group_by_stable(["date"])?.select(["temp"]).sum()?;
993 assert_eq!(
994 out.column("temp_sum")?,
995 &Column::new(PlSmallStr::from_static("temp_sum"), [30, 8, 9])
996 );
997
998 #[allow(deprecated)]
1000 let gb = df.group_by(["date"]).unwrap().n_unique().unwrap();
1002 assert_eq!(gb.width(), 3);
1004 Ok(())
1005 }
1006
1007 #[test]
1008 #[cfg_attr(miri, ignore)]
1009 fn test_static_group_by_by_12_columns() {
1010 let s0 = Column::new("G1".into(), ["A", "A", "B", "B", "C"].as_ref());
1012 let s1 = Column::new("N".into(), [1, 2, 2, 4, 2].as_ref());
1013 let s2 = Column::new("G2".into(), ["k", "l", "m", "m", "l"].as_ref());
1014 let s3 = Column::new("G3".into(), ["a", "b", "c", "c", "d"].as_ref());
1015 let s4 = Column::new("G4".into(), ["1", "2", "3", "3", "4"].as_ref());
1016 let s5 = Column::new("G5".into(), ["X", "Y", "Z", "Z", "W"].as_ref());
1017 let s6 = Column::new("G6".into(), [false, true, true, true, false].as_ref());
1018 let s7 = Column::new("G7".into(), ["r", "x", "q", "q", "o"].as_ref());
1019 let s8 = Column::new("G8".into(), ["R", "X", "Q", "Q", "O"].as_ref());
1020 let s9 = Column::new("G9".into(), [1, 2, 3, 3, 4].as_ref());
1021 let s10 = Column::new("G10".into(), [".", "!", "?", "?", "/"].as_ref());
1022 let s11 = Column::new("G11".into(), ["(", ")", "@", "@", "$"].as_ref());
1023 let s12 = Column::new("G12".into(), ["-", "_", ";", ";", ","].as_ref());
1024
1025 let df = DataFrame::new_infer_height(vec![
1026 s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12,
1027 ])
1028 .unwrap();
1029
1030 #[allow(deprecated)]
1032 let adf = df
1033 .group_by([
1034 "G1", "G2", "G3", "G4", "G5", "G6", "G7", "G8", "G9", "G10", "G11", "G12",
1035 ])
1036 .unwrap()
1037 .select(["N"])
1038 .sum()
1039 .unwrap();
1040
1041 assert_eq!(
1042 Vec::from(&adf.column("N_sum").unwrap().i32().unwrap().sort(false)),
1043 &[Some(1), Some(2), Some(2), Some(6)]
1044 );
1045 }
1046
1047 #[test]
1048 #[cfg_attr(miri, ignore)]
1049 fn test_dynamic_group_by_by_13_columns() {
1050 let series_content = ["A", "A", "B", "B", "C"];
1052
1053 let series_names = [
1055 "G1", "G2", "G3", "G4", "G5", "G6", "G7", "G8", "G9", "G10", "G11", "G12", "G13",
1056 ];
1057
1058 let mut columns = Vec::with_capacity(14);
1060
1061 for series_name in series_names {
1063 let group_columns = Column::new(series_name.into(), series_content.as_ref());
1064 columns.push(group_columns);
1065 }
1066
1067 let agg_series = Column::new("N".into(), [1, 2, 3, 3, 4].as_ref());
1069 columns.push(agg_series);
1070
1071 let df = DataFrame::new_infer_height(columns).unwrap();
1073
1074 #[allow(deprecated)]
1076 let adf = df
1078 .group_by(series_names)
1079 .unwrap()
1080 .select(["N"])
1081 .sum()
1082 .unwrap();
1083
1084 for series_name in &series_names {
1087 assert_eq!(
1088 Vec::from(&adf.column(series_name).unwrap().str().unwrap().sort(false)),
1089 &[Some("A"), Some("B"), Some("C")]
1090 );
1091 }
1092
1093 assert_eq!(
1095 Vec::from(&adf.column("N_sum").unwrap().i32().unwrap().sort(false)),
1096 &[Some(3), Some(4), Some(6)]
1097 );
1098 }
1099
1100 #[test]
1101 #[cfg_attr(miri, ignore)]
1102 fn test_group_by_floats() {
1103 let df = df! {"flt" => [1., 1., 2., 2., 3.],
1104 "val" => [1, 1, 1, 1, 1]
1105 }
1106 .unwrap();
1107 #[allow(deprecated)]
1109 let res = df.group_by(["flt"]).unwrap().sum().unwrap();
1110 let res = res.sort(["flt"], SortMultipleOptions::default()).unwrap();
1111 assert_eq!(
1112 Vec::from(res.column("val_sum").unwrap().i32().unwrap()),
1113 &[Some(2), Some(2), Some(1)]
1114 );
1115 }
1116
1117 #[test]
1118 #[cfg_attr(miri, ignore)]
1119 #[cfg(feature = "dtype-categorical")]
1120 fn test_group_by_categorical() {
1121 let mut df = df! {"foo" => ["a", "a", "b", "b", "c"],
1122 "ham" => ["a", "a", "b", "b", "c"],
1123 "bar" => [1, 1, 1, 1, 1]
1124 }
1125 .unwrap();
1126
1127 df.apply("foo", |s| {
1128 s.cast(&DataType::from_categories(Categories::global()))
1129 .unwrap()
1130 })
1131 .unwrap();
1132
1133 #[allow(deprecated)]
1135 let res = df
1137 .group_by_stable(["foo", "ham"])
1138 .unwrap()
1139 .select(["bar"])
1140 .sum()
1141 .unwrap();
1142
1143 assert_eq!(
1144 Vec::from(
1145 res.column("bar_sum")
1146 .unwrap()
1147 .as_materialized_series()
1148 .i32()
1149 .unwrap()
1150 ),
1151 &[Some(2), Some(2), Some(1)]
1152 );
1153 }
1154
1155 #[test]
1156 #[cfg_attr(miri, ignore)]
1157 fn test_group_by_null_handling() -> PolarsResult<()> {
1158 let df = df!(
1159 "a" => ["a", "a", "a", "b", "b"],
1160 "b" => [Some(1), Some(2), None, None, Some(1)]
1161 )?;
1162 #[allow(deprecated)]
1164 let out = df.group_by_stable(["a"])?.mean()?;
1165
1166 assert_eq!(
1167 Vec::from(out.column("b_mean")?.as_materialized_series().f64()?),
1168 &[Some(1.5), Some(1.0)]
1169 );
1170 Ok(())
1171 }
1172
1173 #[test]
1174 #[cfg_attr(miri, ignore)]
1175 fn test_group_by_var() -> PolarsResult<()> {
1176 let df = df![
1178 "g" => ["foo", "foo", "bar"],
1179 "flt" => [1.0, 2.0, 3.0],
1180 "int" => [1, 2, 3]
1181 ]?;
1182
1183 #[allow(deprecated)]
1185 let out = df.group_by_stable(["g"])?.select(["int"]).var(1)?;
1186
1187 assert_eq!(out.column("int_agg_var")?.f64()?.get(0), Some(0.5));
1188 #[allow(deprecated)]
1190 let out = df.group_by_stable(["g"])?.select(["int"]).std(1)?;
1191 let val = out.column("int_agg_std")?.f64()?.get(0).unwrap();
1192 let expected = f64::FRAC_1_SQRT_2();
1193 assert!((val - expected).abs() < 0.000001);
1194 Ok(())
1195 }
1196
1197 #[test]
1198 #[cfg_attr(miri, ignore)]
1199 #[cfg(feature = "dtype-categorical")]
1200 fn test_group_by_null_group() -> PolarsResult<()> {
1201 let mut df = df![
1203 "g" => [Some("foo"), Some("foo"), Some("bar"), None, None],
1204 "flt" => [1.0, 2.0, 3.0, 1.0, 1.0],
1205 "int" => [1, 2, 3, 1, 1]
1206 ]?;
1207
1208 df.try_apply("g", |s| {
1209 s.cast(&DataType::from_categories(Categories::global()))
1210 })?;
1211
1212 #[allow(deprecated)]
1214 let _ = df.group_by(["g"])?.sum()?;
1215 Ok(())
1216 }
1217}