use std::collections::HashMap;
use super::super::core::OptimizedDataFrame;
use super::types::{GroupBy, GroupKey, GroupKeyValue};
use crate::column::Column;
use crate::error::{Error, Result};
pub const NA_GROUP_KEY_MARKER: &str = "<NA>";
fn push_escaped(part: &str, out: &mut String) {
for ch in part.chars() {
if ch == '\\' || ch == '_' {
out.push('\\');
}
out.push(ch);
}
}
fn render_group_key(key: &[GroupKeyValue<'_>]) -> String {
if key.len() == 1 {
return key[0]
.to_value_string()
.unwrap_or_else(|| NA_GROUP_KEY_MARKER.to_string());
}
let mut out = String::new();
for (idx, part) in key.iter().enumerate() {
if idx > 0 {
out.push('_');
}
match part.to_value_string() {
Some(value) => push_escaped(&value, &mut out),
None => out.push_str("\\N"),
}
}
out
}
impl OptimizedDataFrame {
fn resolve_group_columns(&self, names: &[String]) -> Result<Vec<&Column>> {
let mut columns = Vec::with_capacity(names.len());
for name in names {
let idx = *self
.column_indices
.get(name)
.ok_or_else(|| Error::ColumnNotFound(name.clone()))?;
let column = self
.columns
.get(idx)
.ok_or_else(|| Error::ColumnNotFound(name.clone()))?;
columns.push(column);
}
Ok(columns)
}
fn build_groups<'a>(
&'a self,
key_columns: &[&'a Column],
rows: impl Iterator<Item = usize>,
dropna: bool,
) -> HashMap<GroupKey<'a>, Vec<usize>> {
let mut groups: HashMap<GroupKey<'a>, Vec<usize>> = HashMap::new();
let mut key_buf: GroupKey<'a> = Vec::with_capacity(key_columns.len());
'rows: for row_idx in rows {
key_buf.clear();
for column in key_columns {
let part = GroupKeyValue::from_column(column, row_idx);
if dropna && part.is_null() {
continue 'rows;
}
key_buf.push(part);
}
if groups.contains_key(key_buf.as_slice()) {
if let Some(indices) = groups.get_mut(key_buf.as_slice()) {
indices.push(row_idx);
}
} else {
groups.insert(key_buf.clone(), vec![row_idx]);
}
}
groups
}
pub fn group_by<I, S>(&self, columns: I) -> Result<GroupBy<'_>>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.group_by_with_options(columns, true)
}
pub fn group_by_with_options<I, S>(
&self,
columns: I,
as_multi_index: bool,
) -> Result<GroupBy<'_>>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.group_by_with_config(columns, as_multi_index, true)
}
pub fn group_by_with_config<I, S>(
&self,
columns: I,
as_multi_index: bool,
dropna: bool,
) -> Result<GroupBy<'_>>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let group_by_columns: Vec<String> = columns
.into_iter()
.map(|s| s.as_ref().to_string())
.collect();
let key_columns = self.resolve_group_columns(&group_by_columns)?;
let groups = self.build_groups(&key_columns, 0..self.row_count, dropna);
let create_multi_index = as_multi_index && group_by_columns.len() > 1;
Ok(GroupBy {
df: self,
group_by_columns,
groups,
create_multi_index,
dropna,
})
}
pub fn par_groupby(&self, group_by_columns: &[&str]) -> Result<HashMap<String, Self>> {
self.par_groupby_with_config(group_by_columns, true)
}
pub fn par_groupby_with_config(
&self,
group_by_columns: &[&str],
dropna: bool,
) -> Result<HashMap<String, Self>> {
use rayon::prelude::*;
const PARALLEL_ROW_THRESHOLD: usize = 50_000;
const PARALLEL_GROUP_THRESHOLD: usize = 100;
let names: Vec<String> = group_by_columns.iter().map(|s| (*s).to_string()).collect();
let key_columns = self.resolve_group_columns(&names)?;
let groups: HashMap<GroupKey<'_>, Vec<usize>> = if self.row_count < PARALLEL_ROW_THRESHOLD {
self.build_groups(&key_columns, 0..self.row_count, dropna)
} else {
let chunk_size = (self.row_count / rayon::current_num_threads()).max(1000);
let local_maps: Vec<HashMap<GroupKey<'_>, Vec<usize>>> = (0..self.row_count)
.collect::<Vec<_>>()
.par_chunks(chunk_size)
.map(|chunk| self.build_groups(&key_columns, chunk.iter().copied(), dropna))
.collect();
let mut merged: HashMap<GroupKey<'_>, Vec<usize>> = HashMap::new();
for local_map in local_maps {
for (key, indices) in local_map {
merged.entry(key).or_default().extend(indices);
}
}
merged
};
if groups.len() < PARALLEL_GROUP_THRESHOLD || self.row_count < PARALLEL_ROW_THRESHOLD {
let mut result = HashMap::with_capacity(groups.len());
for (key, indices) in &groups {
let group_df = self.filter_by_indices(indices)?;
result.insert(render_group_key(key), group_df);
}
Ok(result)
} else {
let group_items: Vec<(&GroupKey<'_>, &Vec<usize>)> = groups.iter().collect();
let built: Vec<(String, Self)> = group_items
.into_par_iter()
.map(|(key, indices)| {
let group_df = self.filter_by_indices(indices)?;
Ok((render_group_key(key), group_df))
})
.collect::<Result<Vec<_>>>()?;
Ok(built.into_iter().collect())
}
}
}