lawkit_core/laws/normal/
analysis.rs

1use super::result::NormalResult;
2use crate::error::Result;
3
4/// 正規分布分析を実行
5pub fn analyze_normal_distribution(numbers: &[f64], dataset_name: &str) -> Result<NormalResult> {
6    NormalResult::new(dataset_name.to_string(), numbers)
7}
8
9/// 正規性検定を実行
10pub 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            // 複数検定の統合結果
37            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
52/// 異常値検出を実行
53pub 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, // IQR法ではスコアなし
97                    is_outlier: true,
98                })
99                .collect(),
100            threshold: 1.5, // IQR倍数
101        }),
102    }
103}
104
105/// 品質管理分析を実行
106pub 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
152/// 管理図違反検出
153fn 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    // 連続点の検出(Western Electric Rules の簡易版)
183    detect_run_violations(numbers, mean, std_dev, &mut violations);
184
185    violations
186}
187
188/// 連続点違反検出
189fn 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/// 正規性検定タイプ
231#[derive(Debug, Clone)]
232pub enum NormalityTest {
233    ShapiroWilk,
234    AndersonDarling,
235    KolmogorovSmirnov,
236    All,
237}
238
239/// 正規性検定結果
240#[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/// 外れ値検出手法
250#[derive(Debug, Clone)]
251pub enum OutlierDetectionMethod {
252    ZScore,
253    ModifiedZScore,
254    IQR,
255}
256
257/// 外れ値検出結果
258#[derive(Debug, Clone)]
259pub struct OutlierDetectionResult {
260    pub method_name: String,
261    pub outliers: Vec<OutlierInfo>,
262    pub threshold: f64,
263}
264
265/// 外れ値情報
266#[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/// 品質管理分析結果
275#[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/// 工程能力評価
288#[derive(Debug, Clone)]
289pub enum ProcessCapability {
290    Excellent,  // Cpk >= 1.33
291    Adequate,   // 1.0 <= Cpk < 1.33
292    Poor,       // 0.67 <= Cpk < 1.0
293    Inadequate, // Cpk < 0.67
294}
295
296/// 管理図違反
297#[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/// 違反タイプ
306#[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]; // 100.0は明らかな外れ値
334
335        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]; // 完全に一定のデータ
357        let spec_limits = (8.0, 12.0);
358
359        let qc_result = quality_control_analysis(&numbers, Some(spec_limits)).unwrap();
360        // 標準偏差が0に近い場合、Cpは非常に高くなる
361        if let Some(cp) = qc_result.cp {
362            assert!(cp > 0.0);
363        }
364    }
365}