1use crate::colors;
2use crate::common_options::{get_optimized_reader, setup_automatic_optimization_config};
3use clap::ArgMatches;
4use lawkit_core::{
5 common::{
6 filtering::{apply_number_filter, NumberFilter},
7 input::{parse_input_auto, parse_text_input},
8 memory::{streaming_normal_analysis, MemoryConfig},
9 outliers::{
10 detect_outliers_dbscan, detect_outliers_ensemble, detect_outliers_isolation,
11 detect_outliers_lof, AdvancedOutlierResult,
12 },
13 streaming_io::OptimizedFileReader,
14 timeseries::{analyze_timeseries, create_timeseries_from_values, TimeSeriesAnalysis},
15 },
16 error::{BenfError, Result},
17 laws::normal::{
18 analyze_normal_distribution, detect_outliers, quality_control_analysis, test_normality,
19 NormalResult, NormalityTest, NormalityTestResult, OutlierDetectionMethod,
20 OutlierDetectionResult, ProcessCapability, QualityControlResult,
21 },
22};
23
24pub fn run(matches: &ArgMatches) -> Result<()> {
25 let (_parallel_config, _memory_config) = setup_automatic_optimization_config();
27
28 if matches.get_flag("outliers") {
30 return run_outlier_detection_mode(matches);
31 }
32
33 if matches.get_flag("quality-control") {
34 return run_quality_control_mode(matches);
35 }
36
37 if matches.get_flag("enable-timeseries") {
38 return run_timeseries_analysis_mode(matches);
39 }
40
41 if let Some(test_type) = matches.get_one::<String>("test") {
43 if test_type != "all" {
44 return run_normality_test_mode(matches, test_type);
46 }
47 }
48
49 if matches.get_flag("verbose") {
51 eprintln!(
52 "Debug: input argument = {:?}",
53 matches.get_one::<String>("input")
54 );
55 }
56
57 let numbers = if let Some(input) = matches.get_one::<String>("input") {
59 match parse_input_auto(input) {
61 Ok(numbers) => {
62 if numbers.is_empty() {
63 eprintln!("Error: No valid numbers found in input");
64 std::process::exit(1);
65 }
66 numbers
67 }
68 Err(e) => {
69 eprintln!("Error processing input '{input}': {e}");
70 std::process::exit(1);
71 }
72 }
73 } else {
74 if matches.get_flag("verbose") {
76 eprintln!("Debug: Reading from stdin, using automatic optimization");
77 }
78
79 let mut reader = OptimizedFileReader::from_stdin();
80
81 if matches.get_flag("verbose") {
82 eprintln!(
83 "Debug: Using automatic optimization (streaming + incremental + memory efficiency)"
84 );
85 }
86
87 let numbers = match reader
88 .read_lines_streaming(|line: String| parse_text_input(&line).map(Some).or(Ok(None)))
89 {
90 Ok(nested_numbers) => {
91 let flattened: Vec<f64> = nested_numbers.into_iter().flatten().collect();
92 if matches.get_flag("verbose") {
93 eprintln!("Debug: Collected {} numbers from stream", flattened.len());
94 }
95 flattened
96 }
97 Err(e) => {
98 eprintln!("Analysis error: {e}");
99 std::process::exit(1);
100 }
101 };
102
103 if numbers.is_empty() {
104 eprintln!("Error: No valid numbers found in input");
105 std::process::exit(1);
106 }
107
108 if numbers.len() > 10000 {
110 let memory_config = MemoryConfig::default();
111 let chunk_result = match streaming_normal_analysis(numbers.into_iter(), &memory_config)
112 {
113 Ok(result) => {
114 if matches.get_flag("verbose") {
115 eprintln!(
116 "Debug: Streaming analysis successful - {} items processed",
117 result.total_items
118 );
119 }
120 result
121 }
122 Err(e) => {
123 eprintln!("Streaming analysis error: {e}");
124 std::process::exit(1);
125 }
126 };
127
128 if matches.get_flag("verbose") {
129 eprintln!(
130 "Debug: Processed {} numbers in {} chunks",
131 chunk_result.total_items, chunk_result.chunks_processed
132 );
133 eprintln!("Debug: Memory used: {:.2} MB", chunk_result.memory_used_mb);
134 }
135
136 chunk_result.result.values().to_vec()
137 } else {
138 if matches.get_flag("verbose") {
139 eprintln!("Debug: Memory used: 0.00 MB");
140 }
141 numbers
142 }
143 };
144
145 let dataset_name = matches
146 .get_one::<String>("input")
147 .map(|s| s.to_string())
148 .unwrap_or_else(|| "stdin".to_string());
149
150 let result = match analyze_numbers_with_options(matches, dataset_name, &numbers) {
151 Ok(result) => result,
152 Err(e) => {
153 eprintln!("Analysis error: {e}");
154 std::process::exit(1);
155 }
156 };
157
158 output_results(matches, &result);
159 std::process::exit(result.risk_level.exit_code())
160}
161
162fn run_normality_test_mode(matches: &ArgMatches, test_type: &str) -> Result<()> {
163 let numbers = get_numbers_from_input(matches)?;
164
165 let test = match test_type {
166 "shapiro" => NormalityTest::ShapiroWilk,
167 "anderson" => NormalityTest::AndersonDarling,
168 "ks" => NormalityTest::KolmogorovSmirnov,
169 "all" => NormalityTest::All,
170 _ => {
171 eprintln!(
172 "Error: Unknown test type '{test_type}'. Available: shapiro, anderson, ks, all"
173 );
174 std::process::exit(2);
175 }
176 };
177
178 let test_result = test_normality(&numbers, test)?;
179 output_normality_test_result(matches, &test_result);
180
181 let exit_code = if test_result.is_normal { 0 } else { 1 };
182 std::process::exit(exit_code);
183}
184
185fn run_outlier_detection_mode(matches: &ArgMatches) -> Result<()> {
186 let numbers = get_numbers_from_input(matches)?;
187
188 let method_str = matches
189 .get_one::<String>("outlier-method")
190 .map(|s| s.as_str())
191 .unwrap_or("zscore");
192
193 match method_str {
195 "lof" => {
196 let result = detect_outliers_lof(&numbers, 5)?;
197 output_advanced_outlier_result(matches, &result);
198 let exit_code = if result.outliers.is_empty() { 0 } else { 10 };
199 std::process::exit(exit_code);
200 }
201 "isolation" => {
202 let result = detect_outliers_isolation(&numbers, 8)?;
203 output_advanced_outlier_result(matches, &result);
204 let exit_code = if result.outliers.is_empty() { 0 } else { 10 };
205 std::process::exit(exit_code);
206 }
207 "dbscan" => {
208 let std_dev = calculate_std_dev(&numbers);
209 let eps = std_dev * 0.5;
210 let min_pts = (numbers.len() as f64).sqrt() as usize;
211 let result = detect_outliers_dbscan(&numbers, eps, min_pts)?;
212 output_advanced_outlier_result(matches, &result);
213 let exit_code = if result.outliers.is_empty() { 0 } else { 10 };
214 std::process::exit(exit_code);
215 }
216 "ensemble" => {
217 let result = detect_outliers_ensemble(&numbers)?;
218 output_advanced_outlier_result(matches, &result);
219 let exit_code = if result.outliers.is_empty() { 0 } else { 10 };
220 std::process::exit(exit_code);
221 }
222 _ => {
223 let method = match method_str {
225 "zscore" => OutlierDetectionMethod::ZScore,
226 "modified" | "modified_zscore" => OutlierDetectionMethod::ModifiedZScore,
227 "iqr" => OutlierDetectionMethod::IQR,
228 _ => {
229 eprintln!(
230 "Error: Unknown outlier detection method '{method_str}'. Available: zscore, modified_zscore, iqr, lof, isolation, dbscan, ensemble"
231 );
232 std::process::exit(2);
233 }
234 };
235
236 let outlier_result = detect_outliers(&numbers, method)?;
237 output_outlier_detection_result(matches, &outlier_result);
238
239 let exit_code = if outlier_result.outliers.is_empty() {
240 0
241 } else {
242 1
243 };
244 std::process::exit(exit_code);
245 }
246 }
247}
248
249fn run_timeseries_analysis_mode(matches: &ArgMatches) -> Result<()> {
250 let numbers = get_numbers_from_input(matches)?;
251
252 let timeseries_data = create_timeseries_from_values(&numbers);
254
255 let analysis_result = analyze_timeseries(×eries_data)?;
257
258 output_timeseries_result(matches, &analysis_result);
260
261 std::process::exit(0);
262}
263
264fn run_quality_control_mode(matches: &ArgMatches) -> Result<()> {
265 let numbers = get_numbers_from_input(matches)?;
266
267 let spec_limits = if let Some(limits_str) = matches.get_one::<String>("spec-limits") {
268 parse_spec_limits(limits_str)?
269 } else {
270 None
271 };
272
273 let qc_result = quality_control_analysis(&numbers, spec_limits)?;
274 output_quality_control_result(matches, &qc_result);
275
276 let exit_code = match &qc_result.process_capability {
277 Some(cap) => match cap {
278 ProcessCapability::Excellent => 0,
279 ProcessCapability::Adequate => 1,
280 ProcessCapability::Poor => 2,
281 ProcessCapability::Inadequate => 3,
282 },
283 None => 0,
284 };
285 std::process::exit(exit_code);
286}
287
288fn get_numbers_from_input(matches: &ArgMatches) -> Result<Vec<f64>> {
289 let (_parallel_config, _memory_config) = setup_automatic_optimization_config();
290
291 let buffer = if let Some(input) = matches.get_one::<String>("input") {
292 if input == "-" {
293 get_optimized_reader(None)
294 } else {
295 get_optimized_reader(Some(input))
296 }
297 } else {
298 get_optimized_reader(None)
299 };
300
301 let data = buffer.map_err(|e| BenfError::ParseError(e.to_string()))?;
302 parse_text_input(&data)
303}
304
305fn parse_spec_limits(limits_str: &str) -> Result<Option<(f64, f64)>> {
306 let parts: Vec<&str> = limits_str.split(',').collect();
307 if parts.len() != 2 {
308 return Err(BenfError::ParseError(
309 "Spec limits must be in format 'lower,upper'".to_string(),
310 ));
311 }
312
313 let lower = parts[0]
314 .trim()
315 .parse::<f64>()
316 .map_err(|_| BenfError::ParseError("Invalid lower spec limit".to_string()))?;
317 let upper = parts[1]
318 .trim()
319 .parse::<f64>()
320 .map_err(|_| BenfError::ParseError("Invalid upper spec limit".to_string()))?;
321
322 if lower >= upper {
323 return Err(BenfError::ParseError(
324 "Lower spec limit must be less than upper spec limit".to_string(),
325 ));
326 }
327
328 Ok(Some((lower, upper)))
329}
330
331fn output_results(matches: &clap::ArgMatches, result: &NormalResult) {
332 let format = matches.get_one::<String>("format").unwrap();
333 let quiet = matches.get_flag("quiet");
334 let verbose = matches.get_flag("verbose");
335
336 match format.as_str() {
337 "text" => print_text_output(result, quiet, verbose),
338 "json" => print_json_output(result),
339 "csv" => print_csv_output(result),
340 "yaml" => print_yaml_output(result),
341 "toml" => print_toml_output(result),
342 "xml" => print_xml_output(result),
343 _ => {
344 eprintln!("Error: Unsupported output format: {format}");
345 std::process::exit(2);
346 }
347 }
348}
349
350fn output_normality_test_result(matches: &clap::ArgMatches, result: &NormalityTestResult) {
351 let format_str = matches
352 .get_one::<String>("format")
353 .map(|s| s.as_str())
354 .unwrap_or("text");
355
356 match format_str {
357 "text" => {
358 println!("Test: {}", result.test_name);
359 println!("Statistic: {:.6}", result.statistic);
360 println!("P-value: {:.6}", result.p_value);
361 println!("Is Normal: {}", if result.is_normal { "Yes" } else { "No" });
362 }
363 "json" => {
364 use serde_json::json;
365 let output = json!({
366 "test_name": result.test_name,
367 "statistic": result.statistic,
368 "p_value": result.p_value,
369 "critical_value": result.critical_value,
370 "is_normal": result.is_normal
371 });
372 println!("{}", serde_json::to_string_pretty(&output).unwrap());
373 }
374 _ => print_text_output(
375 &NormalResult::new("test".to_string(), &[0.0; 10]).unwrap(),
376 false,
377 false,
378 ),
379 }
380}
381
382fn output_outlier_detection_result(matches: &clap::ArgMatches, result: &OutlierDetectionResult) {
383 let format_str = matches
384 .get_one::<String>("format")
385 .map(|s| s.as_str())
386 .unwrap_or("text");
387
388 match format_str {
389 "text" => {
390 println!("Method: {}", result.method_name);
391 println!("Outliers found: {}", result.outliers.len());
392
393 if !result.outliers.is_empty() {
394 println!("\nOutlier Details:");
395 for outlier in &result.outliers {
396 println!(" Index: {} (Value: {:.3})", outlier.index, outlier.value);
397 }
398 }
399 }
400 "json" => {
401 use serde_json::json;
402 let output = json!({
403 "method_name": result.method_name,
404 "threshold": result.threshold,
405 "outliers_count": result.outliers.len(),
406 "outliers": result.outliers.iter().map(|o| json!({
407 "index": o.index,
408 "value": o.value,
409 "score": o.score,
410 "is_outlier": o.is_outlier
411 })).collect::<Vec<_>>()
412 });
413 println!("{}", serde_json::to_string_pretty(&output).unwrap());
414 }
415 _ => println!("Unsupported format for outlier detection"),
416 }
417}
418
419fn output_quality_control_result(matches: &clap::ArgMatches, result: &QualityControlResult) {
420 let format_str = matches
421 .get_one::<String>("format")
422 .map(|s| s.as_str())
423 .unwrap_or("text");
424
425 match format_str {
426 "text" => {
427 println!("Quality Control Analysis");
428 println!("Mean: {:.3}", result.mean);
429 println!("Standard Deviation: {:.3}", result.std_dev);
430
431 if let (Some(cp), Some(cpk)) = (result.cp, result.cpk) {
432 println!("Cp: {cp:.3}");
433 println!("Cpk: {cpk:.3}");
434
435 if let Some(ref capability) = result.process_capability {
436 let cap_text = match capability {
437 ProcessCapability::Excellent => "Excellent",
438 ProcessCapability::Adequate => "Adequate",
439 ProcessCapability::Poor => "Poor",
440 ProcessCapability::Inadequate => "Inadequate",
441 };
442 println!("Process Capability: {cap_text}");
443 }
444 }
445
446 if let Some(within_spec) = result.within_spec_percent {
447 println!("Within Specification: {within_spec:.1}%");
448 }
449 }
450 "json" => {
451 use serde_json::json;
452 let output = json!({
453 "mean": result.mean,
454 "std_dev": result.std_dev,
455 "cp": result.cp,
456 "cpk": result.cpk,
457 "within_spec_percent": result.within_spec_percent,
458 "three_sigma_limits": result.three_sigma_limits,
459 "violations_count": result.control_chart_violations.len()
460 });
461 println!("{}", serde_json::to_string_pretty(&output).unwrap());
462 }
463 _ => println!("Unsupported format for quality control"),
464 }
465}
466
467fn print_text_output(result: &NormalResult, quiet: bool, verbose: bool) {
468 if quiet {
469 println!("mean: {:.3}", result.mean);
470 println!("std_dev: {:.3}", result.std_dev);
471 println!("normality_score: {:.3}", result.normality_score);
472 return;
473 }
474
475 println!("Normal Distribution Analysis Results");
476 println!();
477 println!("Dataset: {}", result.dataset_name);
478 println!("Numbers analyzed: {}", result.numbers_analyzed);
479 println!("Quality Level: {:?}", result.risk_level);
480
481 println!();
482 println!("Distribution Histogram:");
483 println!("{}", format_normal_histogram(result));
484
485 println!();
486 println!("Distribution Parameters:");
487 println!(" Mean: {:.3}", result.mean);
488 println!(" Standard Deviation: {:.3}", result.std_dev);
489 println!(" Variance: {:.3}", result.variance);
490 println!(" Skewness: {:.3}", result.skewness);
491 println!(" Kurtosis: {:.3}", result.kurtosis);
492
493 if verbose {
494 println!();
495 println!("Normality Tests:");
496 println!(
497 " Shapiro-Wilk: W={:.3}, p={:.3}",
498 result.shapiro_wilk_statistic, result.shapiro_wilk_p_value
499 );
500 println!(
501 " Anderson-Darling: A²={:.3}, p={:.3}",
502 result.anderson_darling_statistic, result.anderson_darling_p_value
503 );
504 println!(
505 " Kolmogorov-Smirnov: D={:.3}, p={:.3}",
506 result.kolmogorov_smirnov_statistic, result.kolmogorov_smirnov_p_value
507 );
508
509 println!();
510 println!("Quality Metrics:");
511 println!(" Normality Score: {:.3}", result.normality_score);
512 println!(" QQ Correlation: {:.3}", result.qq_correlation);
513 println!(" Distribution Quality: {:.3}", result.distribution_quality);
514
515 if !result.outliers_z_score.is_empty() {
516 println!();
517 println!("Outlier Detection:");
518 println!(" Z-score: {} outliers", result.outliers_z_score.len());
519 println!(
520 " Modified Z-score: {} outliers",
521 result.outliers_modified_z.len()
522 );
523 println!(" IQR method: {} outliers", result.outliers_iqr.len());
524 }
525
526 println!();
527 println!("Sigma Coverage:");
528 println!(" 1σ: {:.1}%", result.within_1_sigma_percent);
529 println!(" 2σ: {:.1}%", result.within_2_sigma_percent);
530 println!(" 3σ: {:.1}%", result.within_3_sigma_percent);
531
532 println!();
533 println!("Interpretation:");
534 print_normal_interpretation(result);
535 }
536}
537
538fn print_normal_interpretation(result: &NormalResult) {
539 use lawkit_core::common::risk::RiskLevel;
540
541 match result.risk_level {
542 RiskLevel::Low => {
543 println!(
544 "{}",
545 colors::pass("[PASS] Data follows normal distribution well")
546 );
547 println!(" Suitable for standard statistical analysis");
548 }
549 RiskLevel::Medium => {
550 println!(
551 "{}",
552 colors::warn("[WARN] Data shows some deviation from normality")
553 );
554 println!(" Consider robust statistical methods");
555 }
556 RiskLevel::High => {
557 println!(
558 "{}",
559 colors::fail("[FAIL] Data significantly deviates from normality")
560 );
561 println!(" Non-parametric methods recommended");
562 }
563 RiskLevel::Critical => {
564 println!(
565 "{}",
566 colors::critical("[CRITICAL] Data shows extreme deviation from normality")
567 );
568 println!(" Requires special handling and investigation");
569 }
570 }
571
572 if result.skewness.abs() > 1.0 {
574 if result.skewness > 0.0 {
575 println!(
576 " {}",
577 colors::info("INFO: Data is right-skewed (positive skewness)")
578 );
579 } else {
580 println!(
581 " {}",
582 colors::info("INFO: Data is left-skewed (negative skewness)")
583 );
584 }
585 }
586
587 if result.kurtosis > 1.0 {
588 println!(
589 " {}",
590 colors::info("INFO: Data has heavy tails (high kurtosis)")
591 );
592 } else if result.kurtosis < -1.0 {
593 println!(
594 " {}",
595 colors::info("INFO: Data has light tails (low kurtosis)")
596 );
597 }
598
599 if !result.outliers_z_score.is_empty() {
601 println!(
602 " {}",
603 colors::alert(&format!(
604 "ALERT: Outliers detected: {}",
605 result.outliers_z_score.len()
606 ))
607 );
608 }
609}
610
611fn print_json_output(result: &NormalResult) {
612 use serde_json::json;
613
614 let output = json!({
615 "dataset": result.dataset_name,
616 "numbers_analyzed": result.numbers_analyzed,
617 "risk_level": format!("{:?}", result.risk_level),
618 "mean": result.mean,
619 "std_dev": result.std_dev,
620 "variance": result.variance,
621 "skewness": result.skewness,
622 "kurtosis": result.kurtosis,
623 "shapiro_wilk": {
624 "statistic": result.shapiro_wilk_statistic,
625 "p_value": result.shapiro_wilk_p_value
626 },
627 "anderson_darling": {
628 "statistic": result.anderson_darling_statistic,
629 "p_value": result.anderson_darling_p_value
630 },
631 "kolmogorov_smirnov": {
632 "statistic": result.kolmogorov_smirnov_statistic,
633 "p_value": result.kolmogorov_smirnov_p_value
634 },
635 "normality_score": result.normality_score,
636 "qq_correlation": result.qq_correlation,
637 "distribution_quality": result.distribution_quality,
638 "outliers": {
639 "z_score_count": result.outliers_z_score.len(),
640 "modified_z_count": result.outliers_modified_z.len(),
641 "iqr_count": result.outliers_iqr.len()
642 },
643 "confidence_intervals": {
644 "mean_95": result.mean_confidence_interval,
645 "prediction_95": result.prediction_interval_95,
646 "three_sigma": result.three_sigma_limits
647 },
648 "sigma_coverage": {
649 "within_1_sigma": result.within_1_sigma_percent,
650 "within_2_sigma": result.within_2_sigma_percent,
651 "within_3_sigma": result.within_3_sigma_percent
652 }
653 });
654
655 println!("{}", serde_json::to_string_pretty(&output).unwrap());
656}
657
658fn print_csv_output(result: &NormalResult) {
659 println!("dataset,numbers_analyzed,risk_level,mean,std_dev,variance,skewness,kurtosis,normality_score");
660 println!(
661 "{},{},{:?},{:.3},{:.3},{:.3},{:.3},{:.3},{:.3}",
662 result.dataset_name,
663 result.numbers_analyzed,
664 result.risk_level,
665 result.mean,
666 result.std_dev,
667 result.variance,
668 result.skewness,
669 result.kurtosis,
670 result.normality_score
671 );
672}
673
674fn print_yaml_output(result: &NormalResult) {
675 println!("dataset: \"{}\"", result.dataset_name);
676 println!("numbers_analyzed: {}", result.numbers_analyzed);
677 println!("risk_level: \"{:?}\"", result.risk_level);
678 println!("mean: {:.3}", result.mean);
679 println!("std_dev: {:.3}", result.std_dev);
680 println!("variance: {:.3}", result.variance);
681 println!("skewness: {:.3}", result.skewness);
682 println!("kurtosis: {:.3}", result.kurtosis);
683 println!("normality_score: {:.3}", result.normality_score);
684}
685
686fn print_toml_output(result: &NormalResult) {
687 println!("dataset = \"{}\"", result.dataset_name);
688 println!("numbers_analyzed = {}", result.numbers_analyzed);
689 println!("risk_level = \"{:?}\"", result.risk_level);
690 println!("mean = {:.3}", result.mean);
691 println!("std_dev = {:.3}", result.std_dev);
692 println!("variance = {:.3}", result.variance);
693 println!("skewness = {:.3}", result.skewness);
694 println!("kurtosis = {:.3}", result.kurtosis);
695 println!("normality_score = {:.3}", result.normality_score);
696}
697
698fn print_xml_output(result: &NormalResult) {
699 println!("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
700 println!("<normal_analysis>");
701 println!(" <dataset>{}</dataset>", result.dataset_name);
702 println!(
703 " <numbers_analyzed>{}</numbers_analyzed>",
704 result.numbers_analyzed
705 );
706 println!(" <risk_level>{:?}</risk_level>", result.risk_level);
707 println!(" <mean>{:.3}</mean>", result.mean);
708 println!(" <std_dev>{:.3}</std_dev>", result.std_dev);
709 println!(" <variance>{:.3}</variance>", result.variance);
710 println!(" <skewness>{:.3}</skewness>", result.skewness);
711 println!(" <kurtosis>{:.3}</kurtosis>", result.kurtosis);
712 println!(
713 " <normality_score>{:.3}</normality_score>",
714 result.normality_score
715 );
716 println!("</normal_analysis>");
717}
718
719fn analyze_numbers_with_options(
721 matches: &clap::ArgMatches,
722 dataset_name: String,
723 numbers: &[f64],
724) -> Result<NormalResult> {
725 let filtered_numbers = if let Some(filter_str) = matches.get_one::<String>("filter") {
727 let filter = NumberFilter::parse(filter_str)
728 .map_err(|e| BenfError::ParseError(format!("無効なフィルタ: {e}")))?;
729
730 let filtered = apply_number_filter(numbers, &filter);
731
732 if filtered.len() != numbers.len() {
734 eprintln!(
735 "フィルタリング結果: {} 個の数値が {} 個に絞り込まれました ({})",
736 numbers.len(),
737 filtered.len(),
738 filter.description()
739 );
740 }
741
742 filtered
743 } else {
744 numbers.to_vec()
745 };
746
747 let min_count = if let Some(min_count_str) = matches.get_one::<String>("min-count") {
749 min_count_str
750 .parse::<usize>()
751 .map_err(|_| BenfError::ParseError("無効な最小数値数".to_string()))?
752 } else {
753 8 };
755
756 if filtered_numbers.len() < min_count {
758 return Err(BenfError::InsufficientData(filtered_numbers.len()));
759 }
760
761 analyze_normal_distribution(&filtered_numbers, &dataset_name)
763}
764
765fn output_advanced_outlier_result(_matches: &ArgMatches, result: &AdvancedOutlierResult) {
767 println!("Advanced Outlier Detection Result: {}", result.method_name);
768 println!("Detection rate: {:.3}", result.detection_rate);
769 println!("Threshold: {:.3}", result.threshold);
770 println!("Outliers found: {}", result.outliers.len());
771
772 if !result.outliers.is_empty() {
773 println!("\nOutlier Details:");
774 for outlier in &result.outliers {
775 println!(
776 " Index {}: Value={:.3}, Score={:.3}, Confidence={:.3}",
777 outlier.index, outlier.value, outlier.outlier_score, outlier.confidence
778 );
779 }
780 }
781
782 if !result.method_params.is_empty() {
783 println!("\nMethod Parameters:");
784 for (param, value) in &result.method_params {
785 println!(" {param}: {value:.3}");
786 }
787 }
788}
789
790fn calculate_std_dev(numbers: &[f64]) -> f64 {
792 if numbers.is_empty() {
793 return 0.0;
794 }
795
796 let mean = numbers.iter().sum::<f64>() / numbers.len() as f64;
797 let variance = numbers.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / numbers.len() as f64;
798 variance.sqrt()
799}
800
801fn output_timeseries_result(_matches: &ArgMatches, result: &TimeSeriesAnalysis) {
803 println!("Time Series Analysis Results");
804 println!("============================");
805
806 println!("\nTrend Analysis:");
808 println!(" Slope: {:.6}", result.trend.slope);
809 println!(" R-squared: {:.3}", result.trend.r_squared);
810 println!(" Direction: {:?}", result.trend.direction);
811 println!(" Trend strength: {:.3}", result.trend.trend_strength);
812
813 if result.seasonality.detected {
815 println!("\nSeasonality Detected:");
816 if let Some(period) = result.seasonality.period {
817 println!(" Period: {period:.1}");
818 }
819 println!(" Strength: {:.3}", result.seasonality.strength);
820 } else {
821 println!("\nNo significant seasonality detected");
822 }
823
824 if !result.changepoints.is_empty() {
826 println!("\nChange Points Detected: {}", result.changepoints.len());
827 for (i, cp) in result.changepoints.iter().enumerate().take(5) {
828 println!(
829 " {}: Index {}, Significance: {:.2}, Type: {:?}",
830 i + 1,
831 cp.index,
832 cp.significance,
833 cp.change_type
834 );
835 }
836 }
837
838 if !result.forecasts.is_empty() {
840 println!("\nForecasts (next {} points):", result.forecasts.len());
841 for (i, forecast) in result.forecasts.iter().enumerate() {
842 println!(
843 " {}: {:.3} (uncertainty: {:.3})",
844 i + 1,
845 forecast.predicted_value,
846 forecast.uncertainty
847 );
848 }
849 }
850
851 if !result.anomalies.is_empty() {
853 println!("\nAnomalies Detected: {}", result.anomalies.len());
854 for anomaly in result.anomalies.iter().take(10) {
855 println!(
856 " Index {}: Value={:.3}, Expected={:.3}, Score={:.3}",
857 anomaly.index, anomaly.value, anomaly.expected_value, anomaly.anomaly_score
858 );
859 }
860 }
861
862 println!("\nData Quality Assessment:");
864 println!(
865 " Completeness: {:.1}%",
866 result.statistics.data_quality.completeness * 100.0
867 );
868 println!(
869 " Consistency: {:.1}%",
870 result.statistics.data_quality.consistency * 100.0
871 );
872 println!(
873 " Outlier ratio: {:.1}%",
874 result.statistics.data_quality.outlier_ratio * 100.0
875 );
876 println!(" Noise level: {:.3}", result.statistics.noise_level);
877}
878
879fn format_normal_histogram(result: &NormalResult) -> String {
880 let mut output = String::new();
881 const CHART_WIDTH: usize = 50;
882 const BINS: usize = 10;
883
884 let mean = result.mean;
887 let std_dev = result.std_dev;
888
889 let range_start = mean - 3.0 * std_dev;
891 let range_end = mean + 3.0 * std_dev;
892 let bin_width = (range_end - range_start) / BINS as f64;
893
894 let mut bin_densities = Vec::new();
896 let mut max_density: f64 = 0.0;
897
898 for i in 0..BINS {
899 let bin_center = range_start + (i as f64 + 0.5) * bin_width;
900 let z_score = (bin_center - mean) / std_dev;
901
902 let density =
904 (-0.5 * z_score * z_score).exp() / (std_dev * (2.0 * std::f64::consts::PI).sqrt());
905 bin_densities.push(density);
906 max_density = max_density.max(density);
907 }
908
909 for (i, &density) in bin_densities.iter().enumerate() {
911 let bin_start = range_start + i as f64 * bin_width;
912 let bin_end = bin_start + bin_width;
913
914 let normalized_density = if max_density > 0.0 {
915 density / max_density
916 } else {
917 0.0
918 };
919 let bar_length = (normalized_density * CHART_WIDTH as f64).round() as usize;
920 let bar_length = bar_length.min(CHART_WIDTH);
921
922 let theoretical_density = density / max_density; let expected_line_pos = (theoretical_density * CHART_WIDTH as f64).round() as usize;
925 let expected_line_pos = expected_line_pos.min(CHART_WIDTH - 1);
926
927 let mut bar_chars = Vec::new();
929 for pos in 0..CHART_WIDTH {
930 if pos == expected_line_pos {
931 bar_chars.push('┃'); } else if pos < bar_length {
933 bar_chars.push('█'); } else {
935 bar_chars.push('░'); }
937 }
938 let full_bar: String = bar_chars.iter().collect();
939
940 output.push_str(&format!(
941 "{:6.2}-{:6.2}: {} {:>5.1}%\n",
942 bin_start,
943 bin_end,
944 full_bar,
945 normalized_density * 100.0
946 ));
947 }
948
949 output.push_str(&format!(
951 "\nDistribution: μ={mean:.2}, σ={std_dev:.2}, Range: [{range_start:.2}, {range_end:.2}]"
952 ));
953
954 output.push_str(&format!(
956 "\n1σ: {:.1}%, 2σ: {:.1}%, 3σ: {:.1}%",
957 result.within_1_sigma_percent, result.within_2_sigma_percent, result.within_3_sigma_percent
958 ));
959
960 output
961}