use super::AnalyticsError;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub struct StatisticalAnalyzer {
descriptive_stats: DescriptiveStatistics,
inferential_stats: InferentialStatistics,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DescriptiveStatistics {
pub mean: f64,
pub median: f64,
pub mode: Option<f64>,
pub standard_deviation: f64,
pub variance: f64,
pub min: f64,
pub max: f64,
pub range: f64,
pub quartiles: Quartiles,
pub skewness: f64,
pub kurtosis: f64,
pub coefficient_of_variation: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Quartiles {
pub q1: f64,
pub q2: f64,
pub q3: f64,
pub iqr: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InferentialStatistics {
pub confidence_interval: ConfidenceInterval,
pub hypothesis_test: Option<HypothesisTest>,
pub effect_size: Option<f64>,
pub p_value: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfidenceInterval {
pub lower_bound: f64,
pub upper_bound: f64,
pub confidence_level: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HypothesisTest {
pub test_type: String,
pub null_hypothesis: String,
pub alternative_hypothesis: String,
pub test_statistic: f64,
pub critical_value: f64,
pub degrees_of_freedom: Option<u32>,
}
impl StatisticalAnalyzer {
pub fn new() -> Self {
Self {
descriptive_stats: DescriptiveStatistics {
mean: 0.0,
median: 0.0,
mode: None,
standard_deviation: 0.0,
variance: 0.0,
min: 0.0,
max: 0.0,
range: 0.0,
quartiles: Quartiles {
q1: 0.0,
q2: 0.0,
q3: 0.0,
iqr: 0.0,
},
skewness: 0.0,
kurtosis: 0.0,
coefficient_of_variation: 0.0,
},
inferential_stats: InferentialStatistics {
confidence_interval: ConfidenceInterval {
lower_bound: 0.0,
upper_bound: 0.0,
confidence_level: 0.95,
},
hypothesis_test: None,
effect_size: None,
p_value: None,
},
}
}
pub fn calculate_descriptive_stats(&mut self, data: &[f64]) -> Result<&DescriptiveStatistics, AnalyticsError> {
if data.is_empty() {
return Err(AnalyticsError::StatisticalError {
message: "Cannot calculate statistics for empty dataset".to_string(),
});
}
let mut sorted_data = data.to_vec();
sorted_data.sort_by(|a, b| a.partial_cmp(b).unwrap());
let n = data.len() as f64;
let sum: f64 = data.iter().sum();
let mean = sum / n;
let variance = data.iter()
.map(|x| (x - mean).powi(2))
.sum::<f64>() / (n - 1.0);
let standard_deviation = variance.sqrt();
let median = if sorted_data.len() % 2 == 0 {
let mid = sorted_data.len() / 2;
(sorted_data[mid - 1] + sorted_data[mid]) / 2.0
} else {
sorted_data[sorted_data.len() / 2]
};
let q1 = self.calculate_percentile(&sorted_data, 25.0);
let q2 = median;
let q3 = self.calculate_percentile(&sorted_data, 75.0);
let iqr = q3 - q1;
let min = *sorted_data.first().unwrap();
let max = *sorted_data.last().unwrap();
let range = max - min;
let mode = self.calculate_mode(data);
let skewness = self.calculate_skewness(data, mean, standard_deviation);
let kurtosis = self.calculate_kurtosis(data, mean, standard_deviation);
let coefficient_of_variation = if mean != 0.0 {
standard_deviation / mean
} else {
0.0
};
self.descriptive_stats = DescriptiveStatistics {
mean,
median,
mode,
standard_deviation,
variance,
min,
max,
range,
quartiles: Quartiles { q1, q2, q3, iqr },
skewness,
kurtosis,
coefficient_of_variation,
};
Ok(&self.descriptive_stats)
}
fn calculate_percentile(&self, sorted_data: &[f64], percentile: f64) -> f64 {
let n = sorted_data.len() as f64;
let index = (percentile / 100.0) * (n - 1.0);
let lower = index.floor() as usize;
let upper = index.ceil() as usize;
if lower == upper {
sorted_data[lower]
} else {
let weight = index - lower as f64;
sorted_data[lower] * (1.0 - weight) + sorted_data[upper] * weight
}
}
fn calculate_mode(&self, data: &[f64]) -> Option<f64> {
let mut frequency_map: HashMap<String, usize> = HashMap::new();
for value in data {
let key = format!("{:.6}", value); *frequency_map.entry(key).or_insert(0) += 1;
}
frequency_map
.into_iter()
.max_by_key(|(_, count)| *count)
.map(|(value, _)| value.parse().unwrap())
}
fn calculate_skewness(&self, data: &[f64], mean: f64, std_dev: f64) -> f64 {
if std_dev == 0.0 {
return 0.0;
}
let n = data.len() as f64;
let sum_cubed_deviations: f64 = data.iter()
.map(|x| ((x - mean) / std_dev).powi(3))
.sum();
sum_cubed_deviations / n
}
fn calculate_kurtosis(&self, data: &[f64], mean: f64, std_dev: f64) -> f64 {
if std_dev == 0.0 {
return 0.0;
}
let n = data.len() as f64;
let sum_fourth_deviations: f64 = data.iter()
.map(|x| ((x - mean) / std_dev).powi(4))
.sum();
(sum_fourth_deviations / n) - 3.0 }
pub fn calculate_confidence_interval(
&mut self,
data: &[f64],
confidence_level: f64,
) -> Result<&ConfidenceInterval, AnalyticsError> {
if data.is_empty() {
return Err(AnalyticsError::StatisticalError {
message: "Cannot calculate confidence interval for empty dataset".to_string(),
});
}
let n = data.len() as f64;
let mean = data.iter().sum::<f64>() / n;
let std_dev = self.calculate_sample_standard_deviation(data, mean);
let z_score = match confidence_level {
0.90 => 1.645,
0.95 => 1.96,
0.99 => 2.576,
_ => 1.96, };
let margin_of_error = z_score * (std_dev / n.sqrt());
let lower_bound = mean - margin_of_error;
let upper_bound = mean + margin_of_error;
self.inferential_stats.confidence_interval = ConfidenceInterval {
lower_bound,
upper_bound,
confidence_level,
};
Ok(&self.inferential_stats.confidence_interval)
}
fn calculate_sample_standard_deviation(&self, data: &[f64], mean: f64) -> f64 {
let n = data.len() as f64;
let variance = data.iter()
.map(|x| (x - mean).powi(2))
.sum::<f64>() / (n - 1.0);
variance.sqrt()
}
pub fn get_descriptive_stats(&self) -> &DescriptiveStatistics {
&self.descriptive_stats
}
pub fn get_inferential_stats(&self) -> &InferentialStatistics {
&self.inferential_stats
}
}
impl Default for StatisticalAnalyzer {
fn default() -> Self {
Self::new()
}
}