use crate::aes::Aesthetic;
use crate::data::{DataFrame, Value};
use crate::scale::ScaleSet;
use super::Stat;
#[derive(Clone, Debug)]
pub struct StatPareto {
pub sort: bool,
}
impl Default for StatPareto {
fn default() -> Self {
StatPareto { sort: true }
}
}
impl Stat for StatPareto {
fn compute_group(&self, data: &DataFrame, _scales: &ScaleSet) -> DataFrame {
let (Some(x_col), Some(y_col)) = (data.column("x"), data.column("y")) else {
return DataFrame::new();
};
let mut cats: indexmap::IndexMap<String, (Value, f64)> = indexmap::IndexMap::new();
for (x, y) in x_col.iter().zip(y_col) {
let Some(v) = y.as_f64().filter(|v| v.is_finite()) else {
continue;
};
cats.entry(x.key_str().into_owned())
.or_insert_with(|| (x.clone(), 0.0))
.1 += v;
}
let mut rows: Vec<(Value, f64)> = cats.into_values().collect();
if self.sort {
rows.sort_by(|a, b| b.1.total_cmp(&a.1));
}
let total: f64 = rows.iter().map(|r| r.1).sum();
let mut cum = 0.0;
let (mut xs, mut ys, mut cums, mut pcts) = (vec![], vec![], vec![], vec![]);
for (x, v) in rows {
cum += v;
xs.push(x);
ys.push(Value::Float(v));
cums.push(Value::Float(cum));
pcts.push(if total != 0.0 {
Value::Float(cum / total * 100.0)
} else {
Value::Na
});
}
let n = xs.len();
let mut out = DataFrame::new();
out.add_column("x".into(), xs);
out.add_column("y".into(), ys);
out.add_column("cum".into(), cums.clone());
out.add_column("cum_pct".into(), pcts);
out.add_column("ymin".into(), vec![Value::Float(0.0); n]);
out.add_column("ymax".into(), cums);
out
}
fn required_aes(&self) -> Vec<Aesthetic> {
vec![Aesthetic::X, Aesthetic::Y]
}
fn panelwise(&self) -> bool {
true
}
fn name(&self) -> &str {
"pareto"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sums_sorts_and_accumulates() {
let mut d = DataFrame::new();
d.add_column(
"x".into(),
["a", "b", "c", "b"]
.iter()
.map(|s| Value::Str(s.to_string()))
.collect(),
);
d.add_column(
"y".into(),
[10.0, 20.0, 50.0, 20.0]
.iter()
.map(|v| Value::Float(*v))
.collect(),
);
let out = StatPareto::default().compute_group(&d, &ScaleSet::new());
let keys: Vec<String> = out
.column("x")
.unwrap()
.iter()
.map(|v| v.key_str().into_owned())
.collect();
assert_eq!(keys, ["c", "b", "a"]);
let f = |c: &str| -> Vec<f64> {
out.column(c)
.unwrap()
.iter()
.map(|v| v.as_f64().unwrap())
.collect()
};
assert_eq!(f("y"), vec![50.0, 40.0, 10.0]);
assert_eq!(f("cum"), vec![50.0, 90.0, 100.0]);
assert_eq!(f("cum_pct"), vec![50.0, 90.0, 100.0]);
}
}