use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::Arc;
use crate::column::{Column, ColumnTrait, Float64Column, Int64Column};
use crate::error::{Error, Result};
use crate::optimized::jit::{
parallel_max_f64, parallel_mean_f64_value, parallel_min_f64, parallel_sum_f64, ParallelConfig,
};
use crate::optimized::split_dataframe::core::OptimizedDataFrame;
#[inline]
fn is_null_at(mask: &Option<Arc<[u8]>>, index: usize) -> bool {
match mask {
None => false,
Some(bits) => {
let byte_idx = index / 8;
let bit_idx = index % 8;
byte_idx < bits.len() && (bits[byte_idx] & (1 << bit_idx)) != 0
}
}
}
fn valid_count(mask: &Option<Arc<[u8]>>, len: usize) -> usize {
match mask {
None => len,
Some(_) => (0..len).filter(|&idx| !is_null_at(mask, idx)).count(),
}
}
fn int_values(col: &Int64Column) -> impl Iterator<Item = i64> + '_ {
let mask = &col.null_mask;
col.data()
.iter()
.enumerate()
.filter(move |(idx, _)| !is_null_at(mask, *idx))
.map(|(_, &value)| value)
}
fn float_values(col: &Float64Column) -> Cow<'_, [f64]> {
match &col.null_mask {
None => Cow::Borrowed(col.data()),
Some(_) => Cow::Owned(
col.data()
.iter()
.enumerate()
.filter(|(idx, _)| !is_null_at(&col.null_mask, *idx))
.map(|(_, &value)| value)
.collect(),
),
}
}
fn int_sum(col: &Int64Column) -> (i128, usize) {
let mut sum: i128 = 0;
let mut count: usize = 0;
for value in int_values(col) {
sum += i128::from(value);
count += 1;
}
(sum, count)
}
impl OptimizedDataFrame {
fn numeric_column(&self, column_name: &str) -> Result<&Column> {
let column_idx = *self
.column_indices
.get(column_name)
.ok_or_else(|| Error::ColumnNotFound(column_name.to_string()))?;
self.columns
.get(column_idx)
.ok_or_else(|| Error::ColumnNotFound(column_name.to_string()))
}
pub fn sum(&self, column_name: &str) -> Result<f64> {
self.sum_with_config(column_name, None)
}
pub fn mean(&self, column_name: &str) -> Result<f64> {
self.mean_with_config(column_name, None)
}
pub fn max(&self, column_name: &str) -> Result<f64> {
self.max_with_config(column_name, None)
}
pub fn min(&self, column_name: &str) -> Result<f64> {
self.min_with_config(column_name, None)
}
pub fn count(&self, column_name: &str) -> Result<usize> {
let column = self.numeric_column(column_name)?;
let count = match column {
Column::Int64(col) => valid_count(&col.null_mask, col.len()),
Column::Float64(col) => valid_count(&col.null_mask, col.len()),
Column::String(col) => valid_count(&col.null_mask, col.len()),
Column::Boolean(col) => valid_count(&col.null_mask, col.len()),
};
Ok(count)
}
pub fn aggregate(
&self,
column_names: &[&str],
operation: &str,
) -> Result<HashMap<String, f64>> {
let mut results = HashMap::new();
for &column_name in column_names {
let result = match operation {
"sum" => self.sum(column_name),
"mean" => self.mean(column_name),
"max" => self.max(column_name),
"min" => self.min(column_name),
"count" => self.count(column_name).map(|c| c as f64),
_ => {
return Err(Error::Operation(format!(
"Operation '{}' is not supported",
operation
)))
}
};
if let Ok(value) = result {
results.insert(column_name.to_string(), value);
}
}
if results.is_empty() {
Err(Error::OperationFailed(format!(
"Operation '{}' failed for all columns",
operation
)))
} else {
Ok(results)
}
}
pub fn aggregate_numeric(&self, operation: &str) -> Result<HashMap<String, f64>> {
let numeric_columns: Vec<&str> = self
.column_names
.iter()
.filter(|&name| {
if let Some(idx) = self.column_indices.get(name) {
matches!(self.columns[*idx], Column::Int64(_) | Column::Float64(_))
} else {
false
}
})
.map(|s| s.as_str())
.collect();
if numeric_columns.is_empty() {
return Err(Error::OperationFailed(
"No numeric columns exist".to_string(),
));
}
self.aggregate(&numeric_columns, operation)
}
pub fn sum_with_config(
&self,
column_name: &str,
config: Option<ParallelConfig>,
) -> Result<f64> {
match self.numeric_column(column_name)? {
Column::Int64(col) => {
let (sum, count) = int_sum(col);
if count == 0 {
return Ok(0.0);
}
Ok(sum as f64)
}
Column::Float64(col) => {
let values = float_values(col);
if values.is_empty() {
return Ok(0.0);
}
let sum_func = parallel_sum_f64(config);
Ok(sum_func.execute(values.as_ref()))
}
_ => Err(Error::Type(format!(
"Column '{}' is not a numeric type",
column_name
))),
}
}
pub fn mean_with_config(
&self,
column_name: &str,
config: Option<ParallelConfig>,
) -> Result<f64> {
match self.numeric_column(column_name)? {
Column::Int64(col) => {
let (sum, count) = int_sum(col);
if count == 0 {
return Err(Error::Empty(format!("Column '{}' is empty", column_name)));
}
Ok(sum as f64 / count as f64)
}
Column::Float64(col) => {
let values = float_values(col);
if values.is_empty() {
return Err(Error::Empty(format!("Column '{}' is empty", column_name)));
}
Ok(parallel_mean_f64_value(values.as_ref(), config))
}
_ => Err(Error::Type(format!(
"Column '{}' is not a numeric type",
column_name
))),
}
}
pub fn max_with_config(
&self,
column_name: &str,
config: Option<ParallelConfig>,
) -> Result<f64> {
match self.numeric_column(column_name)? {
Column::Int64(col) => match int_values(col).max() {
Some(value) => Ok(value as f64),
None => Err(Error::Empty(format!("Column '{}' is empty", column_name))),
},
Column::Float64(col) => {
let values = float_values(col);
if values.is_empty() {
return Err(Error::Empty(format!("Column '{}' is empty", column_name)));
}
let max_func = parallel_max_f64(config);
Ok(max_func.execute(values.as_ref()))
}
_ => Err(Error::Type(format!(
"Column '{}' is not a numeric type",
column_name
))),
}
}
pub fn min_with_config(
&self,
column_name: &str,
config: Option<ParallelConfig>,
) -> Result<f64> {
match self.numeric_column(column_name)? {
Column::Int64(col) => match int_values(col).min() {
Some(value) => Ok(value as f64),
None => Err(Error::Empty(format!("Column '{}' is empty", column_name))),
},
Column::Float64(col) => {
let values = float_values(col);
if values.is_empty() {
return Err(Error::Empty(format!("Column '{}' is empty", column_name)));
}
let min_func = parallel_min_f64(config);
Ok(min_func.execute(values.as_ref()))
}
_ => Err(Error::Type(format!(
"Column '{}' is not a numeric type",
column_name
))),
}
}
}