1use std::collections::HashMap;
7use std::fmt;
8
9#[derive(Debug, Clone)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct FeatureStatistics {
13 pub name: String,
14 pub count: usize,
15 pub mean: f64,
16 pub median: f64,
17 pub std_dev: f64,
18 pub variance: f64,
19 pub min: f64,
20 pub max: f64,
21 pub range: f64,
22 pub skewness: f64,
23 pub kurtosis: f64,
24 pub q1: f64,
25 pub q3: f64,
26 pub iqr: f64,
27 pub outlier_count: usize,
28 pub outlier_ratio: f64,
29 pub missing_count: usize,
30 pub missing_ratio: f64,
31 pub unique_count: usize,
32 pub mode: Option<f64>,
33 pub percentiles: HashMap<u8, f64>,
34}
35
36#[derive(Debug, Clone)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39pub struct TargetStatistics {
40 pub name: String,
41 pub data_type: String, pub count: usize,
43 pub unique_count: usize,
44 pub missing_count: usize,
45 pub missing_ratio: f64,
46 pub class_distribution: HashMap<String, usize>,
47 pub class_balance_ratio: f64,
48 pub entropy: f64,
49 pub continuous_stats: Option<FeatureStatistics>,
50}
51
52#[derive(Debug, Clone)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55pub struct StatisticalSummary {
56 pub dataset_name: String,
57 pub n_samples: usize,
58 pub n_features: usize,
59 pub feature_statistics: Vec<FeatureStatistics>,
60 pub target_statistics: Option<TargetStatistics>,
61 pub correlation_matrix: Vec<Vec<f64>>,
62 pub feature_correlations: HashMap<String, f64>,
63 pub data_quality_score: f64,
64 pub missing_data_pattern: String,
65 pub outlier_summary: HashMap<String, usize>,
66 pub distribution_types: HashMap<String, String>,
67 pub generation_timestamp: String,
68 pub metadata: HashMap<String, String>,
69}
70
71impl StatisticalSummary {
72 pub fn new(dataset_name: String, n_samples: usize, n_features: usize) -> Self {
74 Self {
75 dataset_name,
76 n_samples,
77 n_features,
78 feature_statistics: Vec::new(),
79 target_statistics: None,
80 correlation_matrix: Vec::new(),
81 feature_correlations: HashMap::new(),
82 data_quality_score: 0.0,
83 missing_data_pattern: "None".to_string(),
84 outlier_summary: HashMap::new(),
85 distribution_types: HashMap::new(),
86 generation_timestamp: chrono::Utc::now().to_rfc3339(),
87 metadata: HashMap::new(),
88 }
89 }
90
91 pub fn add_feature_stats(&mut self, stats: FeatureStatistics) {
93 self.feature_statistics.push(stats);
94 }
95
96 pub fn set_target_stats(&mut self, stats: TargetStatistics) {
98 self.target_statistics = Some(stats);
99 }
100
101 pub fn calculate_quality_score(&mut self) {
103 let mut score = 100.0;
104
105 let total_missing_ratio: f64 = self
107 .feature_statistics
108 .iter()
109 .map(|fs| fs.missing_ratio)
110 .sum::<f64>()
111 / self.feature_statistics.len() as f64;
112 score -= total_missing_ratio * 50.0;
113
114 let total_outlier_ratio: f64 = self
116 .feature_statistics
117 .iter()
118 .map(|fs| fs.outlier_ratio)
119 .sum::<f64>()
120 / self.feature_statistics.len() as f64;
121 if total_outlier_ratio > 0.1 {
122 score -= (total_outlier_ratio - 0.1) * 100.0;
123 }
124
125 if let Some(ref target_stats) = self.target_statistics {
127 if target_stats.class_balance_ratio < 0.1 {
128 score -= (0.1 - target_stats.class_balance_ratio) * 200.0;
129 }
130 }
131
132 self.data_quality_score = score.max(0.0);
133 }
134
135 #[cfg(feature = "serde")]
137 pub fn to_json(&self) -> Result<String, serde_json::Error> {
138 serde_json::to_string_pretty(self)
139 }
140
141 pub fn to_csv(&self) -> String {
143 let mut csv = String::new();
144 csv.push_str("metric,value\n");
145 csv.push_str(&format!("dataset_name,{}\n", self.dataset_name));
146 csv.push_str(&format!("n_samples,{}\n", self.n_samples));
147 csv.push_str(&format!("n_features,{}\n", self.n_features));
148 csv.push_str(&format!(
149 "data_quality_score,{:.2}\n",
150 self.data_quality_score
151 ));
152 csv.push_str(&format!(
153 "missing_data_pattern,{}\n",
154 self.missing_data_pattern
155 ));
156 csv.push_str(&format!(
157 "generation_timestamp,{}\n",
158 self.generation_timestamp
159 ));
160
161 for (i, feature) in self.feature_statistics.iter().enumerate() {
162 csv.push_str(&format!("feature_{}_mean,{:.4}\n", i, feature.mean));
163 csv.push_str(&format!("feature_{}_std,{:.4}\n", i, feature.std_dev));
164 csv.push_str(&format!("feature_{}_min,{:.4}\n", i, feature.min));
165 csv.push_str(&format!("feature_{}_max,{:.4}\n", i, feature.max));
166 csv.push_str(&format!(
167 "feature_{}_outlier_ratio,{:.4}\n",
168 i, feature.outlier_ratio
169 ));
170 }
171
172 csv
173 }
174}
175
176impl fmt::Display for StatisticalSummary {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 writeln!(f, "=== Statistical Summary: {} ===", self.dataset_name)?;
179 writeln!(
180 f,
181 "Dataset Shape: {} samples × {} features",
182 self.n_samples, self.n_features
183 )?;
184 writeln!(f, "Data Quality Score: {:.2}/100", self.data_quality_score)?;
185 writeln!(f, "Missing Data Pattern: {}", self.missing_data_pattern)?;
186 writeln!(f, "Generation Time: {}", self.generation_timestamp)?;
187 writeln!(f)?;
188
189 writeln!(f, "Feature Statistics:")?;
190 for (i, feature) in self.feature_statistics.iter().enumerate() {
191 writeln!(f, " Feature {}: {}", i, feature.name)?;
192 writeln!(
193 f,
194 " Mean: {:.4}, Std: {:.4}",
195 feature.mean, feature.std_dev
196 )?;
197 writeln!(
198 f,
199 " Min: {:.4}, Max: {:.4}, Range: {:.4}",
200 feature.min, feature.max, feature.range
201 )?;
202 writeln!(
203 f,
204 " Skewness: {:.4}, Kurtosis: {:.4}",
205 feature.skewness, feature.kurtosis
206 )?;
207 writeln!(
208 f,
209 " Outliers: {} ({:.2}%)",
210 feature.outlier_count,
211 feature.outlier_ratio * 100.0
212 )?;
213 writeln!(
214 f,
215 " Missing: {} ({:.2}%)",
216 feature.missing_count,
217 feature.missing_ratio * 100.0
218 )?;
219 }
220
221 if let Some(ref target) = self.target_statistics {
222 writeln!(f)?;
223 writeln!(f, "Target Statistics:")?;
224 writeln!(f, " Type: {}", target.data_type)?;
225 writeln!(f, " Unique Values: {}", target.unique_count)?;
226 writeln!(
227 f,
228 " Class Balance Ratio: {:.4}",
229 target.class_balance_ratio
230 )?;
231 writeln!(f, " Entropy: {:.4}", target.entropy)?;
232 if !target.class_distribution.is_empty() {
233 writeln!(f, " Class Distribution:")?;
234 for (class, count) in &target.class_distribution {
235 writeln!(f, " {}: {}", class, count)?;
236 }
237 }
238 }
239
240 Ok(())
241 }
242}
243
244#[derive(Debug, Clone)]
246#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
247pub struct SummaryConfig {
248 pub include_percentiles: bool,
249 pub percentile_values: Vec<u8>,
250 pub include_correlation_matrix: bool,
251 pub outlier_threshold: f64,
252 pub missing_threshold: f64,
253 pub include_distribution_analysis: bool,
254 pub feature_names: Option<Vec<String>>,
255 pub target_name: Option<String>,
256}
257
258impl Default for SummaryConfig {
259 fn default() -> Self {
260 Self {
261 include_percentiles: true,
262 percentile_values: vec![5, 10, 25, 50, 75, 90, 95],
263 include_correlation_matrix: true,
264 outlier_threshold: 1.5,
265 missing_threshold: 0.05,
266 include_distribution_analysis: true,
267 feature_names: None,
268 target_name: None,
269 }
270 }
271}
272
273#[derive(Debug, Clone)]
275pub struct ValidationResult {
276 pub property: String,
277 pub passed: bool,
278 pub expected: f64,
279 pub actual: f64,
280 pub tolerance: f64,
281 pub message: String,
282}
283
284#[derive(Debug, Clone)]
286pub struct ValidationReport {
287 pub total_tests: usize,
288 pub passed_tests: usize,
289 pub failed_tests: usize,
290 pub results: Vec<ValidationResult>,
291 pub overall_pass: bool,
292}
293
294impl Default for ValidationReport {
295 fn default() -> Self {
296 Self::new()
297 }
298}
299
300impl ValidationReport {
301 pub fn new() -> Self {
303 Self {
304 total_tests: 0,
305 passed_tests: 0,
306 failed_tests: 0,
307 results: Vec::new(),
308 overall_pass: true,
309 }
310 }
311
312 pub fn add_result(&mut self, result: ValidationResult) {
314 self.total_tests += 1;
315 if result.passed {
316 self.passed_tests += 1;
317 } else {
318 self.failed_tests += 1;
319 self.overall_pass = false;
320 }
321 self.results.push(result);
322 }
323
324 pub fn success_rate(&self) -> f64 {
326 if self.total_tests == 0 {
327 0.0
328 } else {
329 (self.passed_tests as f64 / self.total_tests as f64) * 100.0
330 }
331 }
332}
333
334impl fmt::Display for ValidationReport {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 writeln!(f, "=== Dataset Validation Report ===")?;
337 writeln!(f, "Total tests: {}", self.total_tests)?;
338 writeln!(f, "Passed: {}", self.passed_tests)?;
339 writeln!(f, "Failed: {}", self.failed_tests)?;
340 writeln!(f, "Success rate: {:.2}%", self.success_rate())?;
341 writeln!(
342 f,
343 "Overall: {}",
344 if self.overall_pass { "PASS" } else { "FAIL" }
345 )?;
346 writeln!(f)?;
347
348 for result in &self.results {
349 let status = if result.passed { "PASS" } else { "FAIL" };
350 writeln!(f, "[{}] {}: {}", status, result.property, result.message)?;
351 }
352
353 Ok(())
354 }
355}
356
357#[derive(Debug, Clone)]
359pub struct ValidationConfig {
360 pub tolerance: f64,
361 pub min_samples: usize,
362 pub check_normality: bool,
363 pub check_correlation: bool,
364 pub check_distribution: bool,
365 pub check_outliers: bool,
366}
367
368impl Default for ValidationConfig {
369 fn default() -> Self {
370 Self {
371 tolerance: 0.1,
372 min_samples: 10,
373 check_normality: true,
374 check_correlation: true,
375 check_distribution: true,
376 check_outliers: true,
377 }
378 }
379}
380
381#[derive(Debug, Clone)]
383#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
384pub struct DatasetQualityMetrics {
385 pub overall_quality_score: f64,
386 pub completeness_score: f64,
387 pub consistency_score: f64,
388 pub validity_score: f64,
389 pub accuracy_score: f64,
390 pub uniqueness_score: f64,
391 pub timeliness_score: f64,
392 pub missing_data_ratio: f64,
393 pub outlier_ratio: f64,
394 pub duplicate_ratio: f64,
395 pub data_type_violations: usize,
396 pub range_violations: usize,
397 pub pattern_violations: usize,
398 pub fingerprint: String,
399 pub quality_issues: Vec<String>,
400 pub recommendations: Vec<String>,
401}
402
403impl DatasetQualityMetrics {
404 pub fn calculate_overall_score(&mut self) {
406 let weights = [0.25, 0.20, 0.15, 0.15, 0.10, 0.15]; let scores = [
408 self.completeness_score,
409 self.consistency_score,
410 self.validity_score,
411 self.accuracy_score,
412 self.uniqueness_score,
413 self.timeliness_score,
414 ];
415
416 self.overall_quality_score = scores
417 .iter()
418 .zip(weights.iter())
419 .map(|(score, weight)| score * weight)
420 .sum::<f64>()
421 .clamp(0.0, 100.0);
422 }
423
424 pub fn add_issue(&mut self, issue: String, recommendation: String) {
426 self.quality_issues.push(issue);
427 self.recommendations.push(recommendation);
428 }
429
430 pub fn generate_report(&self) -> String {
432 let mut report = String::new();
433 report.push_str("=== Dataset Quality Report ===\n");
434 report.push_str(&format!(
435 "Overall Quality Score: {:.2}/100\n",
436 self.overall_quality_score
437 ));
438 report.push_str(&format!(
439 "Completeness: {:.2}/100\n",
440 self.completeness_score
441 ));
442 report.push_str(&format!("Consistency: {:.2}/100\n", self.consistency_score));
443 report.push_str(&format!("Validity: {:.2}/100\n", self.validity_score));
444 report.push_str(&format!("Accuracy: {:.2}/100\n", self.accuracy_score));
445 report.push_str(&format!("Uniqueness: {:.2}/100\n", self.uniqueness_score));
446 report.push_str(&format!("Timeliness: {:.2}/100\n", self.timeliness_score));
447 report.push_str("\nData Issues:\n");
448 report.push_str(&format!(
449 "- Missing Data: {:.2}%\n",
450 self.missing_data_ratio * 100.0
451 ));
452 report.push_str(&format!("- Outliers: {:.2}%\n", self.outlier_ratio * 100.0));
453 report.push_str(&format!(
454 "- Duplicates: {:.2}%\n",
455 self.duplicate_ratio * 100.0
456 ));
457 report.push_str(&format!(
458 "- Type Violations: {}\n",
459 self.data_type_violations
460 ));
461 report.push_str(&format!("- Range Violations: {}\n", self.range_violations));
462 report.push_str(&format!(
463 "- Pattern Violations: {}\n",
464 self.pattern_violations
465 ));
466 report.push_str(&format!("\nFingerprint: {}\n", self.fingerprint));
467
468 if !self.quality_issues.is_empty() {
469 report.push_str("\nQuality Issues:\n");
470 for issue in &self.quality_issues {
471 report.push_str(&format!("- {}\n", issue));
472 }
473 }
474
475 if !self.recommendations.is_empty() {
476 report.push_str("\nRecommendations:\n");
477 for rec in &self.recommendations {
478 report.push_str(&format!("- {}\n", rec));
479 }
480 }
481
482 report
483 }
484}
485
486#[derive(Debug, Clone)]
488#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
489pub struct DataDriftReport {
490 pub drift_detected: bool,
491 pub drift_score: f64,
492 pub drift_threshold: f64,
493 pub affected_features: Vec<String>,
494 pub drift_statistics: HashMap<String, f64>,
495 pub drift_type: String, pub detection_method: String,
497 pub confidence_level: f64,
498 pub timestamp: String,
499}
500
501impl Default for DataDriftReport {
502 fn default() -> Self {
503 Self::new()
504 }
505}
506
507impl DataDriftReport {
508 pub fn new() -> Self {
510 Self {
511 drift_detected: false,
512 drift_score: 0.0,
513 drift_threshold: 0.05,
514 affected_features: Vec::new(),
515 drift_statistics: HashMap::new(),
516 drift_type: "none".to_string(),
517 detection_method: "kolmogorov_smirnov".to_string(),
518 confidence_level: 0.95,
519 timestamp: chrono::Utc::now().to_rfc3339(),
520 }
521 }
522
523 pub fn add_feature_drift(&mut self, feature_name: String, drift_statistic: f64) {
525 self.drift_statistics
526 .insert(feature_name.clone(), drift_statistic);
527 if drift_statistic > self.drift_threshold {
528 self.affected_features.push(feature_name);
529 self.drift_detected = true;
530 }
531 }
532
533 pub fn calculate_overall_drift(&mut self) {
535 if self.drift_statistics.is_empty() {
536 return;
537 }
538
539 self.drift_score =
540 self.drift_statistics.values().sum::<f64>() / self.drift_statistics.len() as f64;
541
542 if self.drift_score > self.drift_threshold {
543 self.drift_detected = true;
544 }
545 }
546}
547
548#[derive(Debug, Clone)]
550#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
551pub struct AnomalyDetectionResult {
552 pub anomalies_detected: bool,
553 pub anomaly_count: usize,
554 pub anomaly_ratio: f64,
555 pub anomaly_threshold: f64,
556 pub anomaly_indices: Vec<usize>,
557 pub anomaly_scores: Vec<f64>,
558 pub detection_method: String,
559 pub feature_anomalies: HashMap<String, Vec<usize>>,
560}
561
562impl Default for AnomalyDetectionResult {
563 fn default() -> Self {
564 Self::new()
565 }
566}
567
568impl AnomalyDetectionResult {
569 pub fn new() -> Self {
571 Self {
572 anomalies_detected: false,
573 anomaly_count: 0,
574 anomaly_ratio: 0.0,
575 anomaly_threshold: 0.05,
576 anomaly_indices: Vec::new(),
577 anomaly_scores: Vec::new(),
578 detection_method: "isolation_forest".to_string(),
579 feature_anomalies: HashMap::new(),
580 }
581 }
582
583 pub fn add_anomaly(&mut self, index: usize, score: f64) {
585 self.anomaly_indices.push(index);
586 self.anomaly_scores.push(score);
587 self.anomalies_detected = true;
588 }
589
590 pub fn calculate_statistics(&mut self, total_samples: usize) {
592 self.anomaly_count = self.anomaly_indices.len();
593 self.anomaly_ratio = self.anomaly_count as f64 / total_samples as f64;
594
595 if self.anomaly_ratio > self.anomaly_threshold {
596 self.anomalies_detected = true;
597 }
598 }
599}