1use crate::aes::Aesthetic;
16use crate::data::{DataFrame, Value};
17use crate::scale::ScaleSet;
18
19use super::Stat;
20
21#[derive(Clone, Debug, Default)]
23pub struct StatPartition {
24 pub levels: Vec<String>,
26 pub sort: bool,
28 pub downward: bool,
31}
32
33#[derive(Default)]
34struct Node {
35 name: String,
36 value: f64,
37 children: indexmap::IndexMap<String, Node>,
38}
39
40impl Stat for StatPartition {
41 fn compute_group(&self, data: &DataFrame, _scales: &ScaleSet) -> DataFrame {
42 let cols: Vec<&[Value]> = self.levels.iter().filter_map(|l| data.column(l)).collect();
43 if cols.is_empty() || cols.len() != self.levels.len() {
44 return DataFrame::new();
45 }
46 let weight = data.column("weight");
47 let mut root = Node::default();
48 for i in 0..data.nrows() {
49 let w = match weight {
50 Some(c) => match c[i].as_f64() {
51 Some(w) if w.is_finite() && w > 0.0 => w,
52 _ => continue,
53 },
54 None => 1.0,
55 };
56 root.value += w;
57 let mut node = &mut root;
58 for col in &cols {
59 let key = col[i].key_str().into_owned();
60 if col[i].is_na() || key.is_empty() {
61 break;
62 }
63 node = node.children.entry(key.clone()).or_insert_with(|| Node {
64 name: key,
65 ..Default::default()
66 });
67 node.value += w;
68 }
69 }
70 if root.value <= 0.0 {
71 return DataFrame::new();
72 }
73
74 struct Row {
75 x: (f64, f64),
76 depth: usize,
77 node: String,
78 path: String,
79 root: String,
80 value: f64,
81 }
82 fn walk(
83 node: &Node,
84 (x0, span): (f64, f64),
85 depth: usize,
86 path: &str,
87 root: &str,
88 sort: bool,
89 out: &mut Vec<Row>,
90 ) {
91 let mut kids: Vec<&Node> = node.children.values().collect();
92 if sort {
93 kids.sort_by(|a, b| b.value.total_cmp(&a.value));
94 }
95 let mut x = x0;
96 for k in kids {
97 let w = if node.value > 0.0 {
98 span * k.value / node.value
99 } else {
100 0.0
101 };
102 let p = if path.is_empty() {
103 k.name.clone()
104 } else {
105 format!("{path} / {}", k.name)
106 };
107 let r = if depth == 0 { k.name.as_str() } else { root };
108 out.push(Row {
109 x: (x, x + w),
110 depth,
111 node: k.name.clone(),
112 path: p.clone(),
113 root: r.to_string(),
114 value: k.value,
115 });
116 walk(k, (x, w), depth + 1, &p, r, sort, out);
117 x += w;
118 }
119 }
120 let mut rows = Vec::new();
121 walk(&root, (0.0, 1.0), 0, "", "", self.sort, &mut rows);
122
123 let total = root.value;
124 let f = |g: &dyn Fn(&Row) -> f64| rows.iter().map(|r| Value::Float(g(r))).collect();
125 let s = |g: &dyn Fn(&Row) -> String| rows.iter().map(|r| Value::Str(g(r))).collect();
126 let mut out = DataFrame::new();
127 out.add_column("xmin".into(), f(&|r| r.x.0));
128 out.add_column("xmax".into(), f(&|r| r.x.1));
129 let band = |r: &Row| {
130 let (lo, hi) = (r.depth as f64, r.depth as f64 + 1.0);
131 if self.downward {
132 (-hi, -lo)
133 } else {
134 (lo, hi)
135 }
136 };
137 out.add_column("ymin".into(), f(&|r| band(r).0));
138 out.add_column("ymax".into(), f(&|r| band(r).1));
139 out.add_column("depth".into(), f(&|r| r.depth as f64));
140 out.add_column("node".into(), s(&|r| r.node.clone()));
141 out.add_column("path".into(), s(&|r| r.path.clone()));
142 out.add_column("root".into(), s(&|r| r.root.clone()));
143 out.add_column("value".into(), f(&|r| r.value));
144 out.add_column("share".into(), f(&|r| r.value / total));
145 out
146 }
147
148 fn required_aes(&self) -> Vec<Aesthetic> {
149 Vec::new()
150 }
151
152 fn panelwise(&self) -> bool {
154 true
155 }
156
157 fn name(&self) -> &str {
158 "partition"
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn nested_spans_sum_to_parents() {
168 let mut d = DataFrame::new();
169 let s = |v: &[&str]| v.iter().map(|x| Value::Str(x.to_string())).collect();
170 d.add_column("a".into(), s(&["X", "X", "X", "Y"]));
171 d.add_column("b".into(), s(&["p", "p", "q", ""]));
172 d.add_column(
173 "weight".into(),
174 [1.0, 2.0, 3.0, 4.0]
175 .iter()
176 .map(|&v| Value::Float(v))
177 .collect(),
178 );
179 let out = StatPartition {
180 levels: vec!["a".into(), "b".into()],
181 sort: false,
182 downward: false,
183 }
184 .compute_group(&d, &ScaleSet::new());
185 assert_eq!(out.nrows(), 4);
187 let paths: Vec<String> = out
188 .column("path")
189 .unwrap()
190 .iter()
191 .map(|v| v.key_str().into_owned())
192 .collect();
193 assert_eq!(paths, ["X", "X / p", "X / q", "Y"]);
194 let f = |c: &str| -> Vec<f64> {
195 out.column(c)
196 .unwrap()
197 .iter()
198 .map(|v| v.as_f64().unwrap())
199 .collect()
200 };
201 assert_eq!(f("value"), vec![6.0, 3.0, 3.0, 4.0]);
202 assert_eq!(f("xmin"), vec![0.0, 0.0, 0.3, 0.6]);
203 assert_eq!(f("xmax"), vec![0.6, 0.3, 0.6, 1.0]);
204 assert_eq!(f("ymin"), vec![0.0, 1.0, 1.0, 0.0]);
205 assert_eq!(out.column("root").unwrap()[2], Value::Str("X".into()));
206 }
207}