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 pub fn count(&self) -> PolarsResult<DataFrame> {
352 let (mut cols, agg_cols) = self.prepare_agg()?;
353
354 for agg_col in agg_cols {
355 let new_name = fmt_group_by_column(
356 agg_col.name().as_str(),
357 GroupByMethod::Count {
358 include_nulls: true,
359 },
360 );
361 let mut ca = self.groups.group_count();
362 ca.rename(new_name);
363 cols.push(ca.into_column());
364 }
365 DataFrame::new_infer_height(cols)
366 }
367
368 pub fn groups(&self) -> PolarsResult<DataFrame> {
394 let mut cols = self.keys();
395 let mut column = self.groups.as_list_chunked();
396 let new_name = fmt_group_by_column("", GroupByMethod::Groups);
397 column.rename(new_name);
398 cols.push(column.into_column());
399 DataFrame::new_infer_height(cols)
400 }
401
402 fn prepare_apply(&self) -> PolarsResult<DataFrame> {
403 if let Some(agg) = &self.selected_agg {
404 if agg.is_empty() {
405 Ok(self.df.clone())
406 } else {
407 let mut new_cols = Vec::with_capacity(self.selected_keys.len() + agg.len());
408 new_cols.extend_from_slice(&self.selected_keys);
409 let cols = self.df.select_to_vec(agg.as_slice())?;
410 new_cols.extend(cols);
411 Ok(unsafe { DataFrame::new_unchecked(self.df.height(), new_cols) })
412 }
413 } else {
414 Ok(self.df.clone())
415 }
416 }
417
418 pub fn apply<F>(&self, f: F) -> PolarsResult<DataFrame>
420 where
421 F: FnMut(DataFrame) -> PolarsResult<DataFrame> + Send + Sync,
422 {
423 self.apply_sliced(None, f, None)
424 }
425
426 pub fn apply_sliced<F>(
427 &self,
428 slice: Option<(i64, usize)>,
429 mut f: F,
430 schema: Option<&SchemaRef>,
431 ) -> PolarsResult<DataFrame>
432 where
433 F: FnMut(DataFrame) -> PolarsResult<DataFrame> + Send + Sync,
434 {
435 if self.df.height() == 0 {
436 if let Some(schema) = schema {
438 return Ok(DataFrame::empty_with_arc_schema(schema.clone()));
439 }
440
441 polars_bail!(ComputeError: "cannot group_by + apply on empty 'DataFrame'");
442 }
443
444 let df = self.prepare_apply()?;
445 let max_height = if let Some((offset, len)) = slice {
446 offset.try_into().unwrap_or(usize::MAX).saturating_add(len)
447 } else {
448 usize::MAX
449 };
450 let mut height = 0;
451 let mut dfs = Vec::with_capacity(self.get_groups().len());
452 for g in self.get_groups().iter() {
453 let sub_df = unsafe { take_df(&df, g) };
455 let df = f(sub_df)?;
456 height += df.height();
457 dfs.push(df);
458
459 if height >= max_height {
462 break;
463 }
464 }
465
466 let mut df = accumulate_dataframes_vertical(dfs)?;
467 if let Some((offset, len)) = slice {
468 df = df.slice(offset, len);
469 }
470 Ok(df)
471 }
472
473 pub fn sliced(mut self, slice: Option<(i64, usize)>) -> Self {
474 match slice {
475 None => self,
476 Some((offset, length)) => {
477 self.groups = self.groups.slice(offset, length);
478 self.selected_keys = self.keys_sliced(slice);
479 self
480 },
481 }
482 }
483}
484
485unsafe fn take_df(df: &DataFrame, g: GroupsIndicator) -> DataFrame {
486 match g {
487 GroupsIndicator::Idx(idx) => df.take_slice_unchecked(idx.1),
488 GroupsIndicator::Slice([first, len]) => df.slice(first as i64, len as usize),
489 }
490}
491
492#[derive(Copy, Clone, Debug)]
493pub enum GroupByMethod {
494 Min,
495 NanMin,
496 Max,
497 NanMax,
498 Median,
499 Mean,
500 First,
501 FirstNonNull,
502 Last,
503 LastNonNull,
504 Item { allow_empty: bool },
505 Sum,
506 Groups,
507 NUnique,
508 Quantile(f64, QuantileMethod),
509 Count { include_nulls: bool },
510 Implode { maintain_order: bool },
511 Std(u8),
512 Var(u8),
513 ArgMin,
514 ArgMax,
515}
516
517impl Display for GroupByMethod {
518 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
519 use GroupByMethod::*;
520 let s = match self {
521 Min => "min",
522 NanMin => "nan_min",
523 Max => "max",
524 NanMax => "nan_max",
525 Median => "median",
526 Mean => "mean",
527 First => "first",
528 FirstNonNull => "first_non_null",
529 Last => "last",
530 LastNonNull => "last_non_null",
531 Item { .. } => "item",
532 Sum => "sum",
533 Groups => "groups",
534 NUnique => "n_unique",
535 Quantile(_, _) => "quantile",
536 Count { .. } => "count",
537 Implode { .. } => "implode",
538 Std(_) => "std",
539 Var(_) => "var",
540 ArgMin => "arg_min",
541 ArgMax => "arg_max",
542 };
543 write!(f, "{s}")
544 }
545}
546
547pub fn fmt_group_by_column(name: &str, method: GroupByMethod) -> PlSmallStr {
549 use GroupByMethod::*;
550 match method {
551 Min => format_pl_smallstr!("{name}_min"),
552 Max => format_pl_smallstr!("{name}_max"),
553 NanMin => format_pl_smallstr!("{name}_nan_min"),
554 NanMax => format_pl_smallstr!("{name}_nan_max"),
555 Median => format_pl_smallstr!("{name}_median"),
556 Mean => format_pl_smallstr!("{name}_mean"),
557 First => format_pl_smallstr!("{name}_first"),
558 FirstNonNull => format_pl_smallstr!("{name}_first_non_null"),
559 Last => format_pl_smallstr!("{name}_last"),
560 LastNonNull => format_pl_smallstr!("{name}_last_non_null"),
561 Item { .. } => format_pl_smallstr!("{name}_item"),
562 Sum => format_pl_smallstr!("{name}_sum"),
563 Groups => PlSmallStr::from_static("groups"),
564 NUnique => format_pl_smallstr!("{name}_n_unique"),
565 Count { .. } => format_pl_smallstr!("{name}_count"),
566 Implode { .. } => format_pl_smallstr!("{name}_agg_list"),
567 Quantile(quantile, _interpol) => format_pl_smallstr!("{name}_quantile_{quantile:.2}"),
568 Std(_) => format_pl_smallstr!("{name}_agg_std"),
569 Var(_) => format_pl_smallstr!("{name}_agg_var"),
570 ArgMin => format_pl_smallstr!("{name}_arg_min"),
571 ArgMax => format_pl_smallstr!("{name}_arg_max"),
572 }
573}