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