1use super::result::NormalResult;
2use crate::error::Result;
3
4pub fn analyze_normal_distribution(numbers: &[f64], dataset_name: &str) -> Result<NormalResult> {
6 NormalResult::new(dataset_name.to_string(), numbers)
7}
8
9pub fn test_normality(numbers: &[f64], test_type: NormalityTest) -> Result<NormalityTestResult> {
11 let result = NormalResult::new("normality_test".to_string(), numbers)?;
12
13 match test_type {
14 NormalityTest::ShapiroWilk => Ok(NormalityTestResult {
15 test_name: "Shapiro-Wilk".to_string(),
16 statistic: result.shapiro_wilk_statistic,
17 p_value: result.shapiro_wilk_p_value,
18 critical_value: 0.05,
19 is_normal: result.shapiro_wilk_p_value > 0.05,
20 }),
21 NormalityTest::AndersonDarling => Ok(NormalityTestResult {
22 test_name: "Anderson-Darling".to_string(),
23 statistic: result.anderson_darling_statistic,
24 p_value: result.anderson_darling_p_value,
25 critical_value: 0.05,
26 is_normal: result.anderson_darling_p_value > 0.05,
27 }),
28 NormalityTest::KolmogorovSmirnov => Ok(NormalityTestResult {
29 test_name: "Kolmogorov-Smirnov".to_string(),
30 statistic: result.kolmogorov_smirnov_statistic,
31 p_value: result.kolmogorov_smirnov_p_value,
32 critical_value: 0.05,
33 is_normal: result.kolmogorov_smirnov_p_value > 0.05,
34 }),
35 NormalityTest::All => {
36 let overall_p = (result.shapiro_wilk_p_value
38 + result.anderson_darling_p_value
39 + result.kolmogorov_smirnov_p_value)
40 / 3.0;
41 Ok(NormalityTestResult {
42 test_name: "Combined Test".to_string(),
43 statistic: result.normality_score,
44 p_value: overall_p,
45 critical_value: 0.05,
46 is_normal: overall_p > 0.05,
47 })
48 }
49 }
50}
51
52pub fn detect_outliers(
54 numbers: &[f64],
55 method: OutlierDetectionMethod,
56) -> Result<OutlierDetectionResult> {
57 let result = NormalResult::new("outlier_detection".to_string(), numbers)?;
58
59 match method {
60 OutlierDetectionMethod::ZScore => Ok(OutlierDetectionResult {
61 method_name: "Z-Score".to_string(),
62 outliers: result
63 .outliers_z_score
64 .into_iter()
65 .map(|(idx, val, score)| OutlierInfo {
66 index: idx,
67 value: val,
68 score,
69 is_outlier: score.abs() > 2.5,
70 })
71 .collect(),
72 threshold: 2.5,
73 }),
74 OutlierDetectionMethod::ModifiedZScore => Ok(OutlierDetectionResult {
75 method_name: "Modified Z-Score".to_string(),
76 outliers: result
77 .outliers_modified_z
78 .into_iter()
79 .map(|(idx, val, score)| OutlierInfo {
80 index: idx,
81 value: val,
82 score,
83 is_outlier: score.abs() > 3.5,
84 })
85 .collect(),
86 threshold: 3.5,
87 }),
88 OutlierDetectionMethod::IQR => Ok(OutlierDetectionResult {
89 method_name: "IQR Method".to_string(),
90 outliers: result
91 .outliers_iqr
92 .into_iter()
93 .map(|(idx, val)| OutlierInfo {
94 index: idx,
95 value: val,
96 score: 0.0, is_outlier: true,
98 })
99 .collect(),
100 threshold: 1.5, }),
102 }
103}
104
105pub fn quality_control_analysis(
107 numbers: &[f64],
108 spec_limits: Option<(f64, f64)>,
109) -> Result<QualityControlResult> {
110 let result = NormalResult::new("quality_control".to_string(), numbers)?;
111
112 let (cp, cpk, process_capability) = if let Some((lsl, usl)) = spec_limits {
113 let cp = (usl - lsl) / (6.0 * result.std_dev);
114 let cpu = (usl - result.mean) / (3.0 * result.std_dev);
115 let cpl = (result.mean - lsl) / (3.0 * result.std_dev);
116 let cpk = cpu.min(cpl);
117
118 let capability = if cpk >= 1.33 {
119 ProcessCapability::Excellent
120 } else if cpk >= 1.0 {
121 ProcessCapability::Adequate
122 } else if cpk >= 0.67 {
123 ProcessCapability::Poor
124 } else {
125 ProcessCapability::Inadequate
126 };
127
128 (Some(cp), Some(cpk), Some(capability))
129 } else {
130 (None, None, None)
131 };
132
133 Ok(QualityControlResult {
134 mean: result.mean,
135 std_dev: result.std_dev,
136 cp,
137 cpk,
138 process_capability,
139 within_spec_percent: spec_limits.map(|(lsl, usl)| {
140 let within_spec_count = numbers.iter().filter(|&&x| x >= lsl && x <= usl).count();
141 (within_spec_count as f64 / numbers.len() as f64) * 100.0
142 }),
143 three_sigma_limits: result.three_sigma_limits,
144 control_chart_violations: detect_control_chart_violations(
145 numbers,
146 result.mean,
147 result.std_dev,
148 ),
149 })
150}
151
152fn detect_control_chart_violations(
154 numbers: &[f64],
155 mean: f64,
156 std_dev: f64,
157) -> Vec<ControlChartViolation> {
158 let mut violations = Vec::new();
159 let ucl = mean + 3.0 * std_dev;
160 let lcl = mean - 3.0 * std_dev;
161 let uwl = mean + 2.0 * std_dev;
162 let lwl = mean - 2.0 * std_dev;
163
164 for (i, &value) in numbers.iter().enumerate() {
165 if value > ucl || value < lcl {
166 violations.push(ControlChartViolation {
167 index: i,
168 value,
169 violation_type: ViolationType::OutOfControlLimits,
170 description: "Point outside 3σ control limits".to_string(),
171 });
172 } else if value > uwl || value < lwl {
173 violations.push(ControlChartViolation {
174 index: i,
175 value,
176 violation_type: ViolationType::OutOfWarningLimits,
177 description: "Point outside 2σ warning limits".to_string(),
178 });
179 }
180 }
181
182 detect_run_violations(numbers, mean, std_dev, &mut violations);
184
185 violations
186}
187
188fn detect_run_violations(
190 numbers: &[f64],
191 mean: f64,
192 _std_dev: f64,
193 violations: &mut Vec<ControlChartViolation>,
194) {
195 let mut consecutive_above = 0;
196 let mut consecutive_below = 0;
197
198 for (i, &value) in numbers.iter().enumerate() {
199 if value > mean {
200 consecutive_above += 1;
201 consecutive_below = 0;
202
203 if consecutive_above >= 7 {
204 violations.push(ControlChartViolation {
205 index: i,
206 value,
207 violation_type: ViolationType::RunAboveMean,
208 description: "7 consecutive points above mean".to_string(),
209 });
210 }
211 } else if value < mean {
212 consecutive_below += 1;
213 consecutive_above = 0;
214
215 if consecutive_below >= 7 {
216 violations.push(ControlChartViolation {
217 index: i,
218 value,
219 violation_type: ViolationType::RunBelowMean,
220 description: "7 consecutive points below mean".to_string(),
221 });
222 }
223 } else {
224 consecutive_above = 0;
225 consecutive_below = 0;
226 }
227 }
228}
229
230#[derive(Debug, Clone)]
232pub enum NormalityTest {
233 ShapiroWilk,
234 AndersonDarling,
235 KolmogorovSmirnov,
236 All,
237}
238
239#[derive(Debug, Clone)]
241pub struct NormalityTestResult {
242 pub test_name: String,
243 pub statistic: f64,
244 pub p_value: f64,
245 pub critical_value: f64,
246 pub is_normal: bool,
247}
248
249#[derive(Debug, Clone)]
251pub enum OutlierDetectionMethod {
252 ZScore,
253 ModifiedZScore,
254 IQR,
255}
256
257#[derive(Debug, Clone)]
259pub struct OutlierDetectionResult {
260 pub method_name: String,
261 pub outliers: Vec<OutlierInfo>,
262 pub threshold: f64,
263}
264
265#[derive(Debug, Clone)]
267pub struct OutlierInfo {
268 pub index: usize,
269 pub value: f64,
270 pub score: f64,
271 pub is_outlier: bool,
272}
273
274#[derive(Debug, Clone)]
276pub struct QualityControlResult {
277 pub mean: f64,
278 pub std_dev: f64,
279 pub cp: Option<f64>,
280 pub cpk: Option<f64>,
281 pub process_capability: Option<ProcessCapability>,
282 pub within_spec_percent: Option<f64>,
283 pub three_sigma_limits: (f64, f64),
284 pub control_chart_violations: Vec<ControlChartViolation>,
285}
286
287#[derive(Debug, Clone)]
289pub enum ProcessCapability {
290 Excellent, Adequate, Poor, Inadequate, }
295
296#[derive(Debug, Clone)]
298pub struct ControlChartViolation {
299 pub index: usize,
300 pub value: f64,
301 pub violation_type: ViolationType,
302 pub description: String,
303}
304
305#[derive(Debug, Clone)]
307pub enum ViolationType {
308 OutOfControlLimits,
309 OutOfWarningLimits,
310 RunAboveMean,
311 RunBelowMean,
312 Trend,
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
320 fn test_normality_tests() {
321 let numbers = vec![1.0, 2.0, 3.0, 4.0, 5.0, 4.0, 3.0, 2.0, 1.0, 2.0];
322
323 let shapiro_result = test_normality(&numbers, NormalityTest::ShapiroWilk).unwrap();
324 assert_eq!(shapiro_result.test_name, "Shapiro-Wilk");
325 assert!(shapiro_result.statistic >= 0.0);
326
327 let all_result = test_normality(&numbers, NormalityTest::All).unwrap();
328 assert_eq!(all_result.test_name, "Combined Test");
329 }
330
331 #[test]
332 fn test_outlier_detection() {
333 let numbers = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 100.0]; let z_result = detect_outliers(&numbers, OutlierDetectionMethod::ZScore).unwrap();
336 assert_eq!(z_result.method_name, "Z-Score");
337 assert!(!z_result.outliers.is_empty());
338
339 let iqr_result = detect_outliers(&numbers, OutlierDetectionMethod::IQR).unwrap();
340 assert_eq!(iqr_result.method_name, "IQR Method");
341 }
342
343 #[test]
344 fn test_quality_control() {
345 let numbers = vec![10.0, 11.0, 9.0, 10.5, 9.5, 10.2, 9.8, 10.3, 9.7, 10.1];
346 let spec_limits = (8.0, 12.0);
347
348 let qc_result = quality_control_analysis(&numbers, Some(spec_limits)).unwrap();
349 assert!(qc_result.cp.is_some());
350 assert!(qc_result.cpk.is_some());
351 assert!(qc_result.within_spec_percent.is_some());
352 }
353
354 #[test]
355 fn test_process_capability_assessment() {
356 let numbers = vec![10.0; 10]; let spec_limits = (8.0, 12.0);
358
359 let qc_result = quality_control_analysis(&numbers, Some(spec_limits)).unwrap();
360 if let Some(cp) = qc_result.cp {
362 assert!(cp > 0.0);
363 }
364 }
365}