1use crate::aes::Aesthetic;
5use crate::data::{DataFrame, Value};
6use crate::scale::ScaleSet;
7
8use super::Stat;
9
10#[derive(Clone, Debug)]
17pub struct StatPareto {
18 pub sort: bool,
21}
22
23impl Default for StatPareto {
24 fn default() -> Self {
25 StatPareto { sort: true }
26 }
27}
28
29impl Stat for StatPareto {
30 fn compute_group(&self, data: &DataFrame, _scales: &ScaleSet) -> DataFrame {
31 let (Some(x_col), Some(y_col)) = (data.column("x"), data.column("y")) else {
32 return DataFrame::new();
33 };
34 let mut cats: indexmap::IndexMap<String, (Value, f64)> = indexmap::IndexMap::new();
35 for (x, y) in x_col.iter().zip(y_col) {
36 let Some(v) = y.as_f64().filter(|v| v.is_finite()) else {
37 continue;
38 };
39 cats.entry(x.key_str().into_owned())
40 .or_insert_with(|| (x.clone(), 0.0))
41 .1 += v;
42 }
43 let mut rows: Vec<(Value, f64)> = cats.into_values().collect();
44 if self.sort {
45 rows.sort_by(|a, b| b.1.total_cmp(&a.1));
46 }
47 let total: f64 = rows.iter().map(|r| r.1).sum();
48 let mut cum = 0.0;
49 let (mut xs, mut ys, mut cums, mut pcts) = (vec![], vec![], vec![], vec![]);
50 for (x, v) in rows {
51 cum += v;
52 xs.push(x);
53 ys.push(Value::Float(v));
54 cums.push(Value::Float(cum));
55 pcts.push(if total != 0.0 {
56 Value::Float(cum / total * 100.0)
57 } else {
58 Value::Na
59 });
60 }
61 let n = xs.len();
62 let mut out = DataFrame::new();
63 out.add_column("x".into(), xs);
64 out.add_column("y".into(), ys);
65 out.add_column("cum".into(), cums.clone());
66 out.add_column("cum_pct".into(), pcts);
67 out.add_column("ymin".into(), vec![Value::Float(0.0); n]);
68 out.add_column("ymax".into(), cums);
69 out
70 }
71
72 fn required_aes(&self) -> Vec<Aesthetic> {
73 vec![Aesthetic::X, Aesthetic::Y]
74 }
75
76 fn panelwise(&self) -> bool {
78 true
79 }
80
81 fn name(&self) -> &str {
82 "pareto"
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn sums_sorts_and_accumulates() {
92 let mut d = DataFrame::new();
93 d.add_column(
94 "x".into(),
95 ["a", "b", "c", "b"]
96 .iter()
97 .map(|s| Value::Str(s.to_string()))
98 .collect(),
99 );
100 d.add_column(
101 "y".into(),
102 [10.0, 20.0, 50.0, 20.0]
103 .iter()
104 .map(|v| Value::Float(*v))
105 .collect(),
106 );
107 let out = StatPareto::default().compute_group(&d, &ScaleSet::new());
108 let keys: Vec<String> = out
109 .column("x")
110 .unwrap()
111 .iter()
112 .map(|v| v.key_str().into_owned())
113 .collect();
114 assert_eq!(keys, ["c", "b", "a"]);
115 let f = |c: &str| -> Vec<f64> {
116 out.column(c)
117 .unwrap()
118 .iter()
119 .map(|v| v.as_f64().unwrap())
120 .collect()
121 };
122 assert_eq!(f("y"), vec![50.0, 40.0, 10.0]);
123 assert_eq!(f("cum"), vec![50.0, 90.0, 100.0]);
124 assert_eq!(f("cum_pct"), vec![50.0, 90.0, 100.0]);
125 }
126}