use rayon::prelude::*;
use super::core::OptimizedDataFrame;
use super::select::take_rows;
use crate::column::{BooleanColumn, Column, ColumnTrait, Float64Column, Int64Column, StringColumn};
use crate::error::{Error, Result};
use crate::index::DataFrameIndex;
const ROW_GATHER_PARALLEL_THRESHOLD: usize = 8192;
impl OptimizedDataFrame {
pub fn par_filter(&self, condition_column: &str) -> Result<Self> {
const PARALLEL_THRESHOLD: usize = 100_000;
let column_idx = self
.column_indices
.get(condition_column)
.ok_or_else(|| Error::ColumnNotFound(condition_column.to_string()))?;
let condition = &self.columns[*column_idx];
if let Column::Boolean(bool_col) = condition {
let row_count = bool_col.len();
let indices: Vec<usize> = if row_count < PARALLEL_THRESHOLD {
(0..row_count)
.filter_map(|i| {
if let Ok(Some(true)) = bool_col.get(i) {
Some(i)
} else {
None
}
})
.collect()
} else {
let chunk_size = (row_count / rayon::current_num_threads()).max(1000);
(0..row_count)
.collect::<Vec<_>>()
.par_chunks(chunk_size)
.flat_map(|chunk| {
chunk
.iter()
.filter_map(|&i| {
if let Ok(Some(true)) = bool_col.get(i) {
Some(i)
} else {
None
}
})
.collect::<Vec<_>>()
})
.collect()
};
let indices: Vec<usize> = indices
.into_iter()
.filter(|&i| i < self.row_count)
.collect();
let row_parallel = indices.len() >= ROW_GATHER_PARALLEL_THRESHOLD;
let workload = indices.len().saturating_mul(self.column_names.len());
let col_parallel = !row_parallel
&& self.column_names.len() > 1
&& workload >= ROW_GATHER_PARALLEL_THRESHOLD;
if !row_parallel && !col_parallel {
return take_rows(self, &indices);
}
let mut result = Self::new();
let gather = |name: &String| {
let i = self.column_indices[name];
let column = take_column_row_aware(&self.columns[i], &indices, row_parallel);
(name.clone(), column)
};
let result_columns: Vec<(String, Column)> = if col_parallel {
self.column_names.par_iter().map(gather).collect()
} else {
self.column_names.iter().map(gather).collect()
};
for (name, column) in result_columns {
result.add_column(name, column)?;
}
if !self.column_names.is_empty() {
if let Some(ref index) = self.index {
resubset_index(index, &indices, &mut result)?;
}
}
Ok(result)
} else {
Err(Error::OperationFailed(format!(
"Column '{}' is not of boolean type",
condition_column
)))
}
}
}
fn take_column_row_aware(column: &Column, indices: &[usize], parallel: bool) -> Column {
match column {
Column::Int64(col) => {
let (values, nulls): (Vec<i64>, Vec<bool>) = if parallel {
indices
.par_iter()
.map(|&idx| match col.get(idx) {
Ok(Some(v)) => (v, false),
_ => (0, true),
})
.unzip()
} else {
indices
.iter()
.map(|&idx| match col.get(idx) {
Ok(Some(v)) => (v, false),
_ => (0, true),
})
.unzip()
};
let mut new_col = Int64Column::with_nulls(values, nulls);
if let Some(name) = col.get_name() {
new_col.set_name(name.to_string());
}
Column::Int64(new_col)
}
Column::Float64(col) => {
let (values, nulls): (Vec<f64>, Vec<bool>) = if parallel {
indices
.par_iter()
.map(|&idx| match col.get(idx) {
Ok(Some(v)) => (v, false),
_ => (0.0, true),
})
.unzip()
} else {
indices
.iter()
.map(|&idx| match col.get(idx) {
Ok(Some(v)) => (v, false),
_ => (0.0, true),
})
.unzip()
};
let mut new_col = Float64Column::with_nulls(values, nulls);
if let Some(name) = col.get_name() {
new_col.set_name(name.to_string());
}
Column::Float64(new_col)
}
Column::String(col) => {
let (values, nulls): (Vec<String>, Vec<bool>) = if parallel {
indices
.par_iter()
.map(|&idx| match col.get(idx) {
Ok(Some(v)) => (v.to_string(), false),
_ => (String::new(), true),
})
.unzip()
} else {
indices
.iter()
.map(|&idx| match col.get(idx) {
Ok(Some(v)) => (v.to_string(), false),
_ => (String::new(), true),
})
.unzip()
};
let mut new_col = StringColumn::with_nulls(values, nulls);
if let Some(name) = col.get_name() {
new_col.set_name(name.to_string());
}
Column::String(new_col)
}
Column::Boolean(col) => {
let (values, nulls): (Vec<bool>, Vec<bool>) = if parallel {
indices
.par_iter()
.map(|&idx| match col.get(idx) {
Ok(Some(v)) => (v, false),
_ => (false, true),
})
.unzip()
} else {
indices
.iter()
.map(|&idx| match col.get(idx) {
Ok(Some(v)) => (v, false),
_ => (false, true),
})
.unzip()
};
let mut new_col = BooleanColumn::with_nulls(values, nulls);
if let Some(name) = col.get_name() {
new_col.set_name(name.to_string());
}
Column::Boolean(new_col)
}
}
}
fn resubset_index(
source_index: &DataFrameIndex<String>,
positions: &[usize],
result: &mut OptimizedDataFrame,
) -> Result<()> {
match source_index {
DataFrameIndex::Simple(simple_idx) => {
let values: Vec<String> = positions
.iter()
.map(|&pos| {
simple_idx
.get_value(pos)
.cloned()
.unwrap_or_else(|| pos.to_string())
})
.collect();
match crate::index::Index::with_name(values, simple_idx.name().cloned()) {
Ok(new_index) => result.set_index_from_simple_index(new_index)?,
Err(_) => result.set_default_index()?,
}
}
DataFrameIndex::Multi(multi_idx) => {
let tuples: Option<Vec<Vec<String>>> = positions
.iter()
.map(|&pos| multi_idx.get_tuple(pos))
.collect();
match tuples {
Some(tuples) if !tuples.is_empty() => {
let level_names: Vec<Option<String>> = multi_idx.names().to_vec();
match crate::index::MultiIndex::from_tuples(tuples, Some(level_names)) {
Ok(new_index) => result.set_index_from_multi_index(new_index)?,
Err(_) => result.set_default_index()?,
}
}
_ => result.set_default_index()?,
}
}
}
Ok(())
}