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_with_layout, encode_rows_with_layout_par, get_optimal_row_encoding_layout,
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 opts = vec![RowEncodingOptions::new_unsorted(); by.len()];
89 let layout = get_optimal_row_encoding_layout(&by, &[], &opts);
90 let rows = if multithreaded {
91 encode_rows_with_layout_par(&by, &layout, false)?
92 } else {
93 encode_rows_with_layout(&by, &layout, false)?
94 };
95 rows.group_tuples(multithreaded, sorted)
96 }
97 };
98 Ok(GroupBy::new(self, by, groups?.into_sliceable(), None))
99 }
100
101 pub fn group_by<I, S>(&self, by: I) -> PolarsResult<GroupBy<'_>>
114 where
115 I: IntoIterator<Item = S>,
116 S: AsRef<str>,
117 {
118 let selected_keys = self.select_to_vec(by)?;
119 self.group_by_with_series(selected_keys, true, false)
120 }
121
122 pub fn group_by_stable<I, S>(&self, by: I) -> PolarsResult<GroupBy<'_>>
125 where
126 I: IntoIterator<Item = S>,
127 S: AsRef<str>,
128 {
129 let selected_keys = self.select_to_vec(by)?;
130 self.group_by_with_series(selected_keys, true, true)
131 }
132}
133
134#[derive(Debug, Clone)]
184pub struct GroupBy<'a> {
185 pub df: &'a DataFrame,
186 pub(crate) selected_keys: Vec<Column>,
187 groups: GroupPositions,
189 pub(crate) selected_agg: Option<Vec<PlSmallStr>>,
191}
192
193impl<'a> GroupBy<'a> {
194 pub fn new(
195 df: &'a DataFrame,
196 by: Vec<Column>,
197 groups: GroupPositions,
198 selected_agg: Option<Vec<PlSmallStr>>,
199 ) -> Self {
200 GroupBy {
201 df,
202 selected_keys: by,
203 groups,
204 selected_agg,
205 }
206 }
207
208 #[must_use]
214 pub fn select<I: IntoIterator<Item = S>, S: Into<PlSmallStr>>(mut self, selection: I) -> Self {
215 self.selected_agg = Some(selection.into_iter().map(|s| s.into()).collect());
216 self
217 }
218
219 pub fn get_groups(&self) -> &GroupPositions {
224 &self.groups
225 }
226
227 pub fn into_groups(self) -> GroupPositions {
228 self.groups
229 }
230
231 pub fn keys_sliced(&self, slice: Option<(i64, usize)>) -> Vec<Column> {
232 #[allow(unused_assignments)]
233 let mut groups_owned = None;
235
236 let groups = if let Some((offset, len)) = slice {
237 groups_owned = Some(self.groups.slice(offset, len));
238 groups_owned.as_deref().unwrap()
239 } else {
240 &self.groups
241 };
242 RAYON.install(|| {
243 self.selected_keys
244 .par_iter()
245 .map(Column::as_materialized_series)
246 .map(|s| {
247 match groups {
248 GroupsType::Idx(groups) => {
249 let mut out = unsafe { s.take_slice_unchecked(groups.first()) };
251 if groups.sorted_by_first_idx {
252 out.set_sorted_flag(s.is_sorted_flag());
253 };
254 out
255 },
256 GroupsType::Slice {
257 groups,
258 overlapping,
259 monotonic: _,
260 } => {
261 if *overlapping && !groups.is_empty() {
262 let offset = groups[0][0];
264 let [upper_offset, upper_len] = groups[groups.len() - 1];
265 return s.slice(
266 offset as i64,
267 ((upper_offset + upper_len) - offset) as usize,
268 );
269 }
270
271 let indices = groups
272 .iter()
273 .map(|&[first, _len]| first)
274 .collect_ca(PlSmallStr::EMPTY);
275 let mut out = unsafe { s.take_unchecked(&indices) };
277 out.set_sorted_flag(s.is_sorted_flag());
279 out
280 },
281 }
282 })
283 .map(Column::from)
284 .collect()
285 })
286 }
287
288 pub fn keys(&self) -> Vec<Column> {
289 self.keys_sliced(None)
290 }
291
292 fn prepare_agg(&self) -> PolarsResult<(Vec<Column>, Vec<Column>)> {
293 let keys = self.keys();
294
295 let agg_col = match &self.selected_agg {
296 Some(selection) => self.df.select_to_vec(selection),
297 None => {
298 let by: Vec<_> = self.selected_keys.iter().map(|s| s.name()).collect();
299 let selection = self
300 .df
301 .columns()
302 .iter()
303 .map(|s| s.name())
304 .filter(|a| !by.contains(a))
305 .cloned()
306 .collect::<Vec<_>>();
307
308 self.df.select_to_vec(selection.as_slice())
309 },
310 }?;
311
312 Ok((keys, agg_col))
313 }
314
315 pub fn count(&self) -> PolarsResult<DataFrame> {
341 let (mut cols, agg_cols) = self.prepare_agg()?;
342
343 for agg_col in agg_cols {
344 let new_name = fmt_group_by_column(
345 agg_col.name().as_str(),
346 GroupByMethod::Count {
347 include_nulls: true,
348 },
349 );
350 let mut ca = self.groups.group_count();
351 ca.rename(new_name);
352 cols.push(ca.into_column());
353 }
354 DataFrame::new_infer_height(cols)
355 }
356
357 pub fn groups(&self) -> PolarsResult<DataFrame> {
383 let mut cols = self.keys();
384 let mut column = self.groups.as_list_chunked();
385 let new_name = fmt_group_by_column("", GroupByMethod::Groups);
386 column.rename(new_name);
387 cols.push(column.into_column());
388 DataFrame::new_infer_height(cols)
389 }
390
391 fn prepare_apply(&self) -> PolarsResult<DataFrame> {
392 if let Some(agg) = &self.selected_agg {
393 if agg.is_empty() {
394 Ok(self.df.clone())
395 } else {
396 let mut new_cols = Vec::with_capacity(self.selected_keys.len() + agg.len());
397 new_cols.extend_from_slice(&self.selected_keys);
398 let cols = self.df.select_to_vec(agg.as_slice())?;
399 new_cols.extend(cols);
400 Ok(unsafe { DataFrame::new_unchecked(self.df.height(), new_cols) })
401 }
402 } else {
403 Ok(self.df.clone())
404 }
405 }
406
407 pub fn apply<F>(&self, f: F) -> PolarsResult<DataFrame>
409 where
410 F: FnMut(DataFrame) -> PolarsResult<DataFrame> + Send + Sync,
411 {
412 self.apply_sliced(None, f, None)
413 }
414
415 pub fn apply_sliced<F>(
416 &self,
417 slice: Option<(i64, usize)>,
418 mut f: F,
419 schema: Option<&SchemaRef>,
420 ) -> PolarsResult<DataFrame>
421 where
422 F: FnMut(DataFrame) -> PolarsResult<DataFrame> + Send + Sync,
423 {
424 if self.df.height() == 0 || self.get_groups().is_empty() {
426 if let Some(schema) = schema {
427 return Ok(DataFrame::empty_with_arc_schema(schema.clone()));
428 }
429
430 polars_bail!(ComputeError: "cannot group_by + apply on empty 'DataFrame'");
431 }
432
433 let df = self.prepare_apply()?;
434 let max_height = if let Some((offset, len)) = slice {
435 offset.try_into().unwrap_or(usize::MAX).saturating_add(len)
436 } else {
437 usize::MAX
438 };
439 let mut height = 0;
440 let mut dfs = Vec::with_capacity(self.get_groups().len());
441 for g in self.get_groups().iter() {
442 let sub_df = unsafe { take_df(&df, g) };
444 let df = f(sub_df)?;
445 height += df.height();
446 dfs.push(df);
447
448 if height >= max_height {
451 break;
452 }
453 }
454
455 let mut df = accumulate_dataframes_vertical(dfs)?;
456 if let Some((offset, len)) = slice {
457 df = df.slice(offset, len);
458 }
459 Ok(df)
460 }
461
462 pub fn sliced(mut self, slice: Option<(i64, usize)>) -> Self {
463 match slice {
464 None => self,
465 Some((offset, length)) => {
466 self.groups = self.groups.slice(offset, length);
467 self.selected_keys = self.keys_sliced(slice);
468 self
469 },
470 }
471 }
472}
473
474unsafe fn take_df(df: &DataFrame, g: GroupsIndicator) -> DataFrame {
475 match g {
476 GroupsIndicator::Idx(idx) => df.take_slice_unchecked(idx.1),
477 GroupsIndicator::Slice([first, len]) => df.slice(first as i64, len as usize),
478 }
479}
480
481#[derive(Copy, Clone, Debug)]
482pub enum GroupByMethod {
483 Min,
484 NanMin,
485 Max,
486 NanMax,
487 Median,
488 Mean,
489 First,
490 FirstNonNull,
491 Last,
492 LastNonNull,
493 Item { allow_empty: bool },
494 Sum { null_on_empty: bool },
495 Groups,
496 NUnique,
497 Quantile(f64, QuantileMethod),
498 Count { include_nulls: bool },
499 Implode { maintain_order: bool },
500 Std(u8),
501 Var(u8),
502 ArgMin,
503 ArgMax,
504}
505
506impl Display for GroupByMethod {
507 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
508 use GroupByMethod::*;
509 let s = match self {
510 Min => "min",
511 NanMin => "nan_min",
512 Max => "max",
513 NanMax => "nan_max",
514 Median => "median",
515 Mean => "mean",
516 First => "first",
517 FirstNonNull => "first_non_null",
518 Last => "last",
519 LastNonNull => "last_non_null",
520 Item { .. } => "item",
521 Sum { .. } => "sum",
522 Groups => "groups",
523 NUnique => "n_unique",
524 Quantile(_, _) => "quantile",
525 Count { .. } => "count",
526 Implode { .. } => "implode",
527 Std(_) => "std",
528 Var(_) => "var",
529 ArgMin => "arg_min",
530 ArgMax => "arg_max",
531 };
532 write!(f, "{s}")
533 }
534}
535
536pub fn fmt_group_by_column(name: &str, method: GroupByMethod) -> PlSmallStr {
538 use GroupByMethod::*;
539 match method {
540 Min => format_pl_smallstr!("{name}_min"),
541 Max => format_pl_smallstr!("{name}_max"),
542 NanMin => format_pl_smallstr!("{name}_nan_min"),
543 NanMax => format_pl_smallstr!("{name}_nan_max"),
544 Median => format_pl_smallstr!("{name}_median"),
545 Mean => format_pl_smallstr!("{name}_mean"),
546 First => format_pl_smallstr!("{name}_first"),
547 FirstNonNull => format_pl_smallstr!("{name}_first_non_null"),
548 Last => format_pl_smallstr!("{name}_last"),
549 LastNonNull => format_pl_smallstr!("{name}_last_non_null"),
550 Item { .. } => format_pl_smallstr!("{name}_item"),
551 Sum { .. } => format_pl_smallstr!("{name}_sum"),
552 Groups => PlSmallStr::from_static("groups"),
553 NUnique => format_pl_smallstr!("{name}_n_unique"),
554 Count { .. } => format_pl_smallstr!("{name}_count"),
555 Implode { .. } => format_pl_smallstr!("{name}_agg_list"),
556 Quantile(quantile, _interpol) => format_pl_smallstr!("{name}_quantile_{quantile:.2}"),
557 Std(_) => format_pl_smallstr!("{name}_agg_std"),
558 Var(_) => format_pl_smallstr!("{name}_agg_var"),
559 ArgMin => format_pl_smallstr!("{name}_arg_min"),
560 ArgMax => format_pl_smallstr!("{name}_arg_max"),
561 }
562}