ferroui_dynamic_data/aggregation/
std_dev_ex.rs1use super::{AggregateChangeSets, AggregateType, StdDev};
14use ferroui_reactive::ObservableExt;
15use ferroui_system::IObservable;
16use std::rc::Rc;
17
18pub trait StdDevValue: Copy + 'static {
20 type Moment: Copy + 'static;
23
24 const ZERO: Self::Moment;
26
27 fn moment(self) -> Self::Moment;
29
30 fn plus(left: Self::Moment, right: Self::Moment) -> Self::Moment;
32
33 fn minus(left: Self::Moment, right: Self::Moment) -> Self::Moment;
35
36 fn times(left: Self::Moment, right: Self::Moment) -> Self::Moment;
38
39 fn deviation(count: i32, sum_of_items: Self::Moment, sum_of_squares: Self::Moment) -> f64;
42
43 fn to_f64(self) -> f64;
45}
46
47fn integral_std_dev(count: i32, sum_of_items: i64, sum_of_squares: i64) -> f64 {
55 let count = i128::from(count);
56 let sum_of_items = i128::from(sum_of_items);
57 let scaled_sum_of_squared_deviations = i128::from(sum_of_squares) * count - sum_of_items * sum_of_items;
58 let divisor = count * (count - 1);
59 let quotient = scaled_sum_of_squared_deviations / divisor;
60 let remainder = scaled_sum_of_squared_deviations % divisor;
61 (quotient as f64 + remainder as f64 / divisor as f64).sqrt()
62}
63
64impl StdDevValue for i32 {
65 type Moment = i64;
66 const ZERO: i64 = 0;
67
68 fn moment(self) -> i64 {
69 i64::from(self)
70 }
71
72 fn plus(left: i64, right: i64) -> i64 {
73 left.wrapping_add(right)
74 }
75
76 fn minus(left: i64, right: i64) -> i64 {
77 left.wrapping_sub(right)
78 }
79
80 fn times(left: i64, right: i64) -> i64 {
81 left.wrapping_mul(right)
82 }
83
84 fn deviation(count: i32, sum_of_items: i64, sum_of_squares: i64) -> f64 {
85 integral_std_dev(count, sum_of_items, sum_of_squares)
86 }
87
88 fn to_f64(self) -> f64 {
89 f64::from(self)
90 }
91}
92
93impl StdDevValue for i64 {
94 type Moment = i64;
95 const ZERO: i64 = 0;
96
97 fn moment(self) -> i64 {
98 self
99 }
100
101 fn plus(left: i64, right: i64) -> i64 {
102 left.wrapping_add(right)
103 }
104
105 fn minus(left: i64, right: i64) -> i64 {
106 left.wrapping_sub(right)
107 }
108
109 fn times(left: i64, right: i64) -> i64 {
110 left.wrapping_mul(right)
111 }
112
113 fn deviation(count: i32, sum_of_items: i64, sum_of_squares: i64) -> f64 {
114 integral_std_dev(count, sum_of_items, sum_of_squares)
115 }
116
117 fn to_f64(self) -> f64 {
118 self as f64
119 }
120}
121
122impl StdDevValue for f64 {
123 type Moment = f64;
124 const ZERO: f64 = 0.0;
125
126 fn moment(self) -> f64 {
127 self
128 }
129
130 fn plus(left: f64, right: f64) -> f64 {
131 left + right
132 }
133
134 fn minus(left: f64, right: f64) -> f64 {
135 left - right
136 }
137
138 fn times(left: f64, right: f64) -> f64 {
139 left * right
140 }
141
142 fn deviation(count: i32, sum_of_items: f64, sum_of_squares: f64) -> f64 {
143 let count = f64::from(count);
144 ((sum_of_squares - (sum_of_items * sum_of_items) / count) / (count - 1.0)).sqrt()
145 }
146
147 fn to_f64(self) -> f64 {
148 self
149 }
150}
151
152impl StdDevValue for f32 {
153 type Moment = f32;
154 const ZERO: f32 = 0.0;
155
156 fn moment(self) -> f32 {
157 self
158 }
159
160 fn plus(left: f32, right: f32) -> f32 {
161 left + right
162 }
163
164 fn minus(left: f32, right: f32) -> f32 {
165 left - right
166 }
167
168 fn times(left: f32, right: f32) -> f32 {
169 left * right
170 }
171
172 fn deviation(count: i32, sum_of_items: f32, sum_of_squares: f32) -> f64 {
174 let variance = (sum_of_squares - (sum_of_items * sum_of_items) / count as f32) / (count - 1) as f32;
175 f64::from(variance).sqrt()
176 }
177
178 fn to_f64(self) -> f64 {
179 f64::from(self)
180 }
181}
182
183pub(crate) fn std_dev<T: 'static, V: StdDevValue>(source: AggregateChangeSets<T>, value_selector: impl Fn(&T) -> V + 'static, fallback_value: V) -> Rc<dyn IObservable<f64>> {
187 let fallback_value = fallback_value.to_f64();
188 source
189 .scan_with(StdDev::new(0, V::ZERO, V::ZERO), move |state: StdDev<V::Moment>, changes| {
190 changes.items().fold(state, |current, aggregate_item| {
191 let item = value_selector(aggregate_item.item()).moment();
192 let square = V::times(item, item);
193 match aggregate_item.aggregate_type() {
194 AggregateType::Add => {
195 StdDev::new(current.count() + 1, V::plus(current.sum_of_items(), item), V::plus(current.sum_of_squares(), square))
196 }
197 AggregateType::Remove => {
198 StdDev::new(current.count() - 1, V::minus(current.sum_of_items(), item), V::minus(current.sum_of_squares(), square))
199 }
200 }
201 })
202 })
203 .select(move |values: StdDev<V::Moment>| {
204 if values.count() < 2 {
205 fallback_value
206 } else {
207 V::deviation(values.count(), values.sum_of_items(), values.sum_of_squares())
208 }
209 })
210}