use crate::error::{Error, Result};
use crate::na::NA;
use crate::series::NASeries;
use crate::DataFrame;
use crate::Series;
use rayon::prelude::*;
const DATAFRAME_PARALLEL_THRESHOLD: usize = 8192;
const SCALAR_PARALLEL_THRESHOLD: usize = 100_000;
impl<T> Series<T>
where
T: Clone + Send + Sync + 'static + std::fmt::Debug,
{
pub fn par_map<F, R>(&self, f: F) -> Series<R>
where
F: Fn(&T) -> R + Send + Sync,
R: Clone + Send + Sync + 'static + std::fmt::Debug,
{
let source = self.values();
let new_values: Vec<R> = if source.len() < SCALAR_PARALLEL_THRESHOLD {
source.iter().map(&f).collect()
} else {
source.par_iter().map(&f).collect()
};
Series::new(new_values, self.name().cloned())
.expect("parallel map produces valid series from valid input")
}
pub fn par_filter<F>(&self, f: F) -> Series<T>
where
F: Fn(&T) -> bool + Send + Sync,
{
let source = self.values();
let filtered_values: Vec<T> = if source.len() < SCALAR_PARALLEL_THRESHOLD {
source.iter().filter(|v| f(v)).cloned().collect()
} else {
source.par_iter().filter(|v| f(v)).cloned().collect()
};
Series::new(filtered_values, self.name().cloned())
.expect("parallel filter produces valid series from valid input")
}
}
impl<T> NASeries<T>
where
T: Clone + Send + Sync + 'static + std::fmt::Debug,
{
pub fn par_map<F, R>(&self, f: F) -> NASeries<R>
where
F: Fn(&T) -> R + Send + Sync,
R: Clone + Send + Sync + 'static + std::fmt::Debug,
{
let map_one = |v: &NA<T>| match v {
NA::Value(val) => NA::Value(f(val)),
NA::NA => NA::NA,
};
let source = self.values();
let new_values: Vec<NA<R>> = if source.len() < SCALAR_PARALLEL_THRESHOLD {
source.iter().map(map_one).collect()
} else {
source.par_iter().map(map_one).collect()
};
NASeries::new(new_values, self.name().cloned())
.expect("parallel map produces valid NA series from valid input")
}
pub fn par_filter<F>(&self, f: F) -> NASeries<T>
where
F: Fn(&T) -> bool + Send + Sync,
{
let keep_one = |v: &&NA<T>| match v {
NA::Value(val) => f(val),
NA::NA => false,
};
let source = self.values();
let filtered_values: Vec<NA<T>> = if source.len() < SCALAR_PARALLEL_THRESHOLD {
source.iter().filter(keep_one).cloned().collect()
} else {
source.par_iter().filter(keep_one).cloned().collect()
};
NASeries::new(filtered_values, self.name().cloned())
.expect("parallel filter produces valid NA series from valid input")
}
}
impl DataFrame {
pub fn par_apply<F>(&self, f: F) -> Result<DataFrame>
where
F: Fn(&str, usize, &str) -> String + Send + Sync,
{
let mut result = DataFrame::new();
let column_names = self.column_names().to_vec();
let n_rows = self.row_count();
let parallel = n_rows >= DATAFRAME_PARALLEL_THRESHOLD;
for col_name in &column_names {
let values = self.get_column_string_values(col_name)?;
let map_one = |i: usize| {
let val = if i < values.len() { &values[i] } else { "" };
f(col_name, i, val)
};
let new_values: Vec<String> = if parallel {
(0..n_rows).into_par_iter().map(map_one).collect()
} else {
(0..n_rows).map(map_one).collect()
};
let new_series = Series::new(new_values, Some(col_name.to_string()))?;
result.add_column(col_name.to_string(), new_series)?;
}
Ok(result)
}
pub fn par_filter_rows<F>(&self, f: F) -> Result<DataFrame>
where
F: Fn(usize) -> bool + Send + Sync,
{
let n_rows = self.row_count();
let row_indices: Vec<usize> = if n_rows < DATAFRAME_PARALLEL_THRESHOLD {
(0..n_rows).filter(|&i| f(i)).collect()
} else {
(0..n_rows).into_par_iter().filter(|&i| f(i)).collect()
};
self.gather_rows_typed(&row_indices)
}
pub fn par_groupby<K>(&self, key_func: K) -> Result<HashMap<String, DataFrame>>
where
K: Fn(usize) -> String + Send + Sync,
{
let n_rows = self.row_count();
let keyed: Vec<(String, usize)> = if n_rows < DATAFRAME_PARALLEL_THRESHOLD {
(0..n_rows).map(|i| (key_func(i), i)).collect()
} else {
(0..n_rows)
.into_par_iter()
.map(|i| (key_func(i), i))
.collect()
};
let mut groups: HashMap<String, Vec<usize>> = HashMap::new();
for (key, idx) in keyed {
groups.entry(key).or_insert_with(Vec::new).push(idx);
}
let mut result = HashMap::new();
for (key, indices) in groups {
let group_df = self.gather_rows_typed(&indices)?;
result.insert(key, group_df);
}
Ok(result)
}
fn gather_rows_typed(&self, indices: &[usize]) -> Result<DataFrame> {
let parallel = indices.len() >= DATAFRAME_PARALLEL_THRESHOLD;
let mut result = DataFrame::new();
for col_name in self.column_names() {
macro_rules! try_gather {
($ty:ty) => {
if let Ok(series) = self.get_column::<$ty>(col_name) {
let values = series.values();
let fetch = |&i: &usize| -> Result<$ty> {
values
.get(i)
.cloned()
.ok_or_else(|| Error::IndexOutOfBounds {
index: i,
size: values.len(),
})
};
let gathered: Vec<$ty> = if parallel {
indices
.par_iter()
.map(fetch)
.collect::<Result<Vec<$ty>>>()?
} else {
indices.iter().map(fetch).collect::<Result<Vec<$ty>>>()?
};
let new_series = Series::new(gathered, Some(col_name.to_string()))?;
result.add_column(col_name.to_string(), new_series)?;
continue;
}
};
}
try_gather!(String);
try_gather!(i32);
try_gather!(i64);
try_gather!(f32);
try_gather!(f64);
try_gather!(bool);
try_gather!(i8);
try_gather!(i16);
try_gather!(i128);
try_gather!(isize);
try_gather!(u8);
try_gather!(u16);
try_gather!(u32);
try_gather!(u64);
try_gather!(u128);
try_gather!(usize);
try_gather!(chrono::NaiveDate);
try_gather!(chrono::NaiveDateTime);
try_gather!(chrono::DateTime<chrono::Utc>);
return Err(Error::InvalidValue(format!(
"Column '{}' has an element type that is not supported for parallel row \
selection (supported: String, integers, floats, bool, NaiveDate, \
NaiveDateTime, DateTime<Utc>)",
col_name
)));
}
Ok(result)
}
}
pub struct ParallelUtils;
impl ParallelUtils {
pub fn par_sort<T>(mut values: Vec<T>) -> Vec<T>
where
T: Ord + Send,
{
if values.len() < SCALAR_PARALLEL_THRESHOLD {
values.sort();
} else {
values.par_sort();
}
values
}
pub fn par_sum<T>(values: &[T]) -> T
where
T: Send + Sync + std::iter::Sum + Copy,
{
if values.len() < SCALAR_PARALLEL_THRESHOLD {
values.iter().copied().sum()
} else {
values.par_iter().copied().sum()
}
}
pub fn par_mean<T>(values: &[T]) -> Option<f64>
where
T: Send + Sync + Copy + Into<f64>,
{
if values.is_empty() {
return None;
}
let sum: f64 = if values.len() < SCALAR_PARALLEL_THRESHOLD {
values.iter().map(|&v| v.into()).sum()
} else {
values.par_iter().map(|&v| v.into()).sum()
};
Some(sum / values.len() as f64)
}
pub fn par_min<T>(values: &[T]) -> Option<T>
where
T: Send + Sync + Copy + Ord,
{
if values.len() < SCALAR_PARALLEL_THRESHOLD {
values.iter().min().copied()
} else {
values.par_iter().min().copied()
}
}
pub fn par_max<T>(values: &[T]) -> Option<T>
where
T: Send + Sync + Copy + Ord,
{
if values.len() < SCALAR_PARALLEL_THRESHOLD {
values.iter().max().copied()
} else {
values.par_iter().max().copied()
}
}
}
use std::collections::HashMap;