use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FeatureStatistics {
pub name: String,
pub count: usize,
pub mean: f64,
pub median: f64,
pub std_dev: f64,
pub variance: f64,
pub min: f64,
pub max: f64,
pub range: f64,
pub skewness: f64,
pub kurtosis: f64,
pub q1: f64,
pub q3: f64,
pub iqr: f64,
pub outlier_count: usize,
pub outlier_ratio: f64,
pub missing_count: usize,
pub missing_ratio: f64,
pub unique_count: usize,
pub mode: Option<f64>,
pub percentiles: HashMap<u8, f64>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TargetStatistics {
pub name: String,
pub data_type: String, pub count: usize,
pub unique_count: usize,
pub missing_count: usize,
pub missing_ratio: f64,
pub class_distribution: HashMap<String, usize>,
pub class_balance_ratio: f64,
pub entropy: f64,
pub continuous_stats: Option<FeatureStatistics>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StatisticalSummary {
pub dataset_name: String,
pub n_samples: usize,
pub n_features: usize,
pub feature_statistics: Vec<FeatureStatistics>,
pub target_statistics: Option<TargetStatistics>,
pub correlation_matrix: Vec<Vec<f64>>,
pub feature_correlations: HashMap<String, f64>,
pub data_quality_score: f64,
pub missing_data_pattern: String,
pub outlier_summary: HashMap<String, usize>,
pub distribution_types: HashMap<String, String>,
pub generation_timestamp: String,
pub metadata: HashMap<String, String>,
}
impl StatisticalSummary {
pub fn new(dataset_name: String, n_samples: usize, n_features: usize) -> Self {
Self {
dataset_name,
n_samples,
n_features,
feature_statistics: Vec::new(),
target_statistics: None,
correlation_matrix: Vec::new(),
feature_correlations: HashMap::new(),
data_quality_score: 0.0,
missing_data_pattern: "None".to_string(),
outlier_summary: HashMap::new(),
distribution_types: HashMap::new(),
generation_timestamp: chrono::Utc::now().to_rfc3339(),
metadata: HashMap::new(),
}
}
pub fn add_feature_stats(&mut self, stats: FeatureStatistics) {
self.feature_statistics.push(stats);
}
pub fn set_target_stats(&mut self, stats: TargetStatistics) {
self.target_statistics = Some(stats);
}
pub fn calculate_quality_score(&mut self) {
let mut score = 100.0;
let total_missing_ratio: f64 = self
.feature_statistics
.iter()
.map(|fs| fs.missing_ratio)
.sum::<f64>()
/ self.feature_statistics.len() as f64;
score -= total_missing_ratio * 50.0;
let total_outlier_ratio: f64 = self
.feature_statistics
.iter()
.map(|fs| fs.outlier_ratio)
.sum::<f64>()
/ self.feature_statistics.len() as f64;
if total_outlier_ratio > 0.1 {
score -= (total_outlier_ratio - 0.1) * 100.0;
}
if let Some(ref target_stats) = self.target_statistics {
if target_stats.class_balance_ratio < 0.1 {
score -= (0.1 - target_stats.class_balance_ratio) * 200.0;
}
}
self.data_quality_score = score.max(0.0);
}
#[cfg(feature = "serde")]
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
pub fn to_csv(&self) -> String {
let mut csv = String::new();
csv.push_str("metric,value\n");
csv.push_str(&format!("dataset_name,{}\n", self.dataset_name));
csv.push_str(&format!("n_samples,{}\n", self.n_samples));
csv.push_str(&format!("n_features,{}\n", self.n_features));
csv.push_str(&format!(
"data_quality_score,{:.2}\n",
self.data_quality_score
));
csv.push_str(&format!(
"missing_data_pattern,{}\n",
self.missing_data_pattern
));
csv.push_str(&format!(
"generation_timestamp,{}\n",
self.generation_timestamp
));
for (i, feature) in self.feature_statistics.iter().enumerate() {
csv.push_str(&format!("feature_{}_mean,{:.4}\n", i, feature.mean));
csv.push_str(&format!("feature_{}_std,{:.4}\n", i, feature.std_dev));
csv.push_str(&format!("feature_{}_min,{:.4}\n", i, feature.min));
csv.push_str(&format!("feature_{}_max,{:.4}\n", i, feature.max));
csv.push_str(&format!(
"feature_{}_outlier_ratio,{:.4}\n",
i, feature.outlier_ratio
));
}
csv
}
}
impl fmt::Display for StatisticalSummary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "=== Statistical Summary: {} ===", self.dataset_name)?;
writeln!(
f,
"Dataset Shape: {} samples × {} features",
self.n_samples, self.n_features
)?;
writeln!(f, "Data Quality Score: {:.2}/100", self.data_quality_score)?;
writeln!(f, "Missing Data Pattern: {}", self.missing_data_pattern)?;
writeln!(f, "Generation Time: {}", self.generation_timestamp)?;
writeln!(f)?;
writeln!(f, "Feature Statistics:")?;
for (i, feature) in self.feature_statistics.iter().enumerate() {
writeln!(f, " Feature {}: {}", i, feature.name)?;
writeln!(
f,
" Mean: {:.4}, Std: {:.4}",
feature.mean, feature.std_dev
)?;
writeln!(
f,
" Min: {:.4}, Max: {:.4}, Range: {:.4}",
feature.min, feature.max, feature.range
)?;
writeln!(
f,
" Skewness: {:.4}, Kurtosis: {:.4}",
feature.skewness, feature.kurtosis
)?;
writeln!(
f,
" Outliers: {} ({:.2}%)",
feature.outlier_count,
feature.outlier_ratio * 100.0
)?;
writeln!(
f,
" Missing: {} ({:.2}%)",
feature.missing_count,
feature.missing_ratio * 100.0
)?;
}
if let Some(ref target) = self.target_statistics {
writeln!(f)?;
writeln!(f, "Target Statistics:")?;
writeln!(f, " Type: {}", target.data_type)?;
writeln!(f, " Unique Values: {}", target.unique_count)?;
writeln!(
f,
" Class Balance Ratio: {:.4}",
target.class_balance_ratio
)?;
writeln!(f, " Entropy: {:.4}", target.entropy)?;
if !target.class_distribution.is_empty() {
writeln!(f, " Class Distribution:")?;
for (class, count) in &target.class_distribution {
writeln!(f, " {}: {}", class, count)?;
}
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SummaryConfig {
pub include_percentiles: bool,
pub percentile_values: Vec<u8>,
pub include_correlation_matrix: bool,
pub outlier_threshold: f64,
pub missing_threshold: f64,
pub include_distribution_analysis: bool,
pub feature_names: Option<Vec<String>>,
pub target_name: Option<String>,
}
impl Default for SummaryConfig {
fn default() -> Self {
Self {
include_percentiles: true,
percentile_values: vec![5, 10, 25, 50, 75, 90, 95],
include_correlation_matrix: true,
outlier_threshold: 1.5,
missing_threshold: 0.05,
include_distribution_analysis: true,
feature_names: None,
target_name: None,
}
}
}
#[derive(Debug, Clone)]
pub struct ValidationResult {
pub property: String,
pub passed: bool,
pub expected: f64,
pub actual: f64,
pub tolerance: f64,
pub message: String,
}
#[derive(Debug, Clone)]
pub struct ValidationReport {
pub total_tests: usize,
pub passed_tests: usize,
pub failed_tests: usize,
pub results: Vec<ValidationResult>,
pub overall_pass: bool,
}
impl Default for ValidationReport {
fn default() -> Self {
Self::new()
}
}
impl ValidationReport {
pub fn new() -> Self {
Self {
total_tests: 0,
passed_tests: 0,
failed_tests: 0,
results: Vec::new(),
overall_pass: true,
}
}
pub fn add_result(&mut self, result: ValidationResult) {
self.total_tests += 1;
if result.passed {
self.passed_tests += 1;
} else {
self.failed_tests += 1;
self.overall_pass = false;
}
self.results.push(result);
}
pub fn success_rate(&self) -> f64 {
if self.total_tests == 0 {
0.0
} else {
(self.passed_tests as f64 / self.total_tests as f64) * 100.0
}
}
}
impl fmt::Display for ValidationReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "=== Dataset Validation Report ===")?;
writeln!(f, "Total tests: {}", self.total_tests)?;
writeln!(f, "Passed: {}", self.passed_tests)?;
writeln!(f, "Failed: {}", self.failed_tests)?;
writeln!(f, "Success rate: {:.2}%", self.success_rate())?;
writeln!(
f,
"Overall: {}",
if self.overall_pass { "PASS" } else { "FAIL" }
)?;
writeln!(f)?;
for result in &self.results {
let status = if result.passed { "PASS" } else { "FAIL" };
writeln!(f, "[{}] {}: {}", status, result.property, result.message)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ValidationConfig {
pub tolerance: f64,
pub min_samples: usize,
pub check_normality: bool,
pub check_correlation: bool,
pub check_distribution: bool,
pub check_outliers: bool,
}
impl Default for ValidationConfig {
fn default() -> Self {
Self {
tolerance: 0.1,
min_samples: 10,
check_normality: true,
check_correlation: true,
check_distribution: true,
check_outliers: true,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DatasetQualityMetrics {
pub overall_quality_score: f64,
pub completeness_score: f64,
pub consistency_score: f64,
pub validity_score: f64,
pub accuracy_score: f64,
pub uniqueness_score: f64,
pub timeliness_score: f64,
pub missing_data_ratio: f64,
pub outlier_ratio: f64,
pub duplicate_ratio: f64,
pub data_type_violations: usize,
pub range_violations: usize,
pub pattern_violations: usize,
pub fingerprint: String,
pub quality_issues: Vec<String>,
pub recommendations: Vec<String>,
}
impl DatasetQualityMetrics {
pub fn calculate_overall_score(&mut self) {
let weights = [0.25, 0.20, 0.15, 0.15, 0.10, 0.15]; let scores = [
self.completeness_score,
self.consistency_score,
self.validity_score,
self.accuracy_score,
self.uniqueness_score,
self.timeliness_score,
];
self.overall_quality_score = scores
.iter()
.zip(weights.iter())
.map(|(score, weight)| score * weight)
.sum::<f64>()
.clamp(0.0, 100.0);
}
pub fn add_issue(&mut self, issue: String, recommendation: String) {
self.quality_issues.push(issue);
self.recommendations.push(recommendation);
}
pub fn generate_report(&self) -> String {
let mut report = String::new();
report.push_str("=== Dataset Quality Report ===\n");
report.push_str(&format!(
"Overall Quality Score: {:.2}/100\n",
self.overall_quality_score
));
report.push_str(&format!(
"Completeness: {:.2}/100\n",
self.completeness_score
));
report.push_str(&format!("Consistency: {:.2}/100\n", self.consistency_score));
report.push_str(&format!("Validity: {:.2}/100\n", self.validity_score));
report.push_str(&format!("Accuracy: {:.2}/100\n", self.accuracy_score));
report.push_str(&format!("Uniqueness: {:.2}/100\n", self.uniqueness_score));
report.push_str(&format!("Timeliness: {:.2}/100\n", self.timeliness_score));
report.push_str("\nData Issues:\n");
report.push_str(&format!(
"- Missing Data: {:.2}%\n",
self.missing_data_ratio * 100.0
));
report.push_str(&format!("- Outliers: {:.2}%\n", self.outlier_ratio * 100.0));
report.push_str(&format!(
"- Duplicates: {:.2}%\n",
self.duplicate_ratio * 100.0
));
report.push_str(&format!(
"- Type Violations: {}\n",
self.data_type_violations
));
report.push_str(&format!("- Range Violations: {}\n", self.range_violations));
report.push_str(&format!(
"- Pattern Violations: {}\n",
self.pattern_violations
));
report.push_str(&format!("\nFingerprint: {}\n", self.fingerprint));
if !self.quality_issues.is_empty() {
report.push_str("\nQuality Issues:\n");
for issue in &self.quality_issues {
report.push_str(&format!("- {}\n", issue));
}
}
if !self.recommendations.is_empty() {
report.push_str("\nRecommendations:\n");
for rec in &self.recommendations {
report.push_str(&format!("- {}\n", rec));
}
}
report
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DataDriftReport {
pub drift_detected: bool,
pub drift_score: f64,
pub drift_threshold: f64,
pub affected_features: Vec<String>,
pub drift_statistics: HashMap<String, f64>,
pub drift_type: String, pub detection_method: String,
pub confidence_level: f64,
pub timestamp: String,
}
impl Default for DataDriftReport {
fn default() -> Self {
Self::new()
}
}
impl DataDriftReport {
pub fn new() -> Self {
Self {
drift_detected: false,
drift_score: 0.0,
drift_threshold: 0.05,
affected_features: Vec::new(),
drift_statistics: HashMap::new(),
drift_type: "none".to_string(),
detection_method: "kolmogorov_smirnov".to_string(),
confidence_level: 0.95,
timestamp: chrono::Utc::now().to_rfc3339(),
}
}
pub fn add_feature_drift(&mut self, feature_name: String, drift_statistic: f64) {
self.drift_statistics
.insert(feature_name.clone(), drift_statistic);
if drift_statistic > self.drift_threshold {
self.affected_features.push(feature_name);
self.drift_detected = true;
}
}
pub fn calculate_overall_drift(&mut self) {
if self.drift_statistics.is_empty() {
return;
}
self.drift_score =
self.drift_statistics.values().sum::<f64>() / self.drift_statistics.len() as f64;
if self.drift_score > self.drift_threshold {
self.drift_detected = true;
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AnomalyDetectionResult {
pub anomalies_detected: bool,
pub anomaly_count: usize,
pub anomaly_ratio: f64,
pub anomaly_threshold: f64,
pub anomaly_indices: Vec<usize>,
pub anomaly_scores: Vec<f64>,
pub detection_method: String,
pub feature_anomalies: HashMap<String, Vec<usize>>,
}
impl Default for AnomalyDetectionResult {
fn default() -> Self {
Self::new()
}
}
impl AnomalyDetectionResult {
pub fn new() -> Self {
Self {
anomalies_detected: false,
anomaly_count: 0,
anomaly_ratio: 0.0,
anomaly_threshold: 0.05,
anomaly_indices: Vec::new(),
anomaly_scores: Vec::new(),
detection_method: "isolation_forest".to_string(),
feature_anomalies: HashMap::new(),
}
}
pub fn add_anomaly(&mut self, index: usize, score: f64) {
self.anomaly_indices.push(index);
self.anomaly_scores.push(score);
self.anomalies_detected = true;
}
pub fn calculate_statistics(&mut self, total_samples: usize) {
self.anomaly_count = self.anomaly_indices.len();
self.anomaly_ratio = self.anomaly_count as f64 / total_samples as f64;
if self.anomaly_ratio > self.anomaly_threshold {
self.anomalies_detected = true;
}
}
}