use chrono::{DateTime, TimeZone, Utc};
#[derive(Clone, Debug)]
pub struct DataContainer {
data: Vec<f64>,
}
impl DataContainer {
pub fn new(data: Vec<f64>) -> Self {
Self { data }
}
pub fn median(&self) -> f64 {
median(&mut self.data.clone()).unwrap_or(0.0)
}
pub fn mean(&self) -> f64 {
mean(&self.data).unwrap_or(0.0)
}
pub fn percentile(&self, p: f64) -> f64 {
percentile(&mut self.data.clone(), p).unwrap_or(0.0)
}
pub fn std_deviation(&self) -> f64 {
std_deviation(&self.data).unwrap_or(0.0)
}
pub fn as_slice(&self) -> &[f64] {
&self.data
}
}
impl Data for DataContainer {
fn median(&self) -> Option<f64> {
Some(median(&mut self.data.clone()).unwrap_or(0.0))
}
fn mean(&self) -> Option<f64> {
mean(&self.data)
}
fn percentile(&self, p: f64) -> Option<f64> {
Some(percentile(&mut self.data.clone(), p).unwrap_or(0.0))
}
fn std_deviation(&self) -> Option<f64> {
std_deviation(&self.data)
}
}
pub use DataContainer as Data_new;
pub fn median(data: &mut [f64]) -> Option<f64> {
if data.is_empty() {
return None;
}
data.sort_unstable_by(f64::total_cmp);
let len = data.len();
if len.is_multiple_of(2) {
Some((data[len / 2 - 1] + data[len / 2]) / 2.0)
} else {
Some(data[len / 2])
}
}
pub fn mean(data: &[f64]) -> Option<f64> {
if data.is_empty() {
return None;
}
Some(data.iter().sum::<f64>() / data.len() as f64)
}
pub fn percentile_95(data: &mut [f64]) -> Option<f64> {
percentile(data, 95.0)
}
pub fn percentile(data: &mut [f64], p: f64) -> Option<f64> {
if data.is_empty() || !(0.0..=100.0).contains(&p) {
return None;
}
data.sort_unstable_by(f64::total_cmp);
let idx = (p / 100.0 * (data.len() - 1) as f64).round() as usize;
Some(data[idx.min(data.len() - 1)])
}
pub fn std_deviation(data: &[f64]) -> Option<f64> {
if data.is_empty() {
return None;
}
let avg = mean(data)?;
let variance = data.iter().map(|&x| (x - avg).powi(2)).sum::<f64>() / data.len() as f64;
Some(variance.sqrt())
}
pub fn min(data: &[f64]) -> Option<f64> {
if data.is_empty() {
return None;
}
data.iter().copied().reduce(|a, b| a.min(b))
}
pub fn max(data: &[f64]) -> Option<f64> {
if data.is_empty() {
return None;
}
data.iter().copied().reduce(|a, b| a.max(b))
}
pub fn median_timestamp(data: &mut [DateTime<Utc>]) -> Option<DateTime<Utc>> {
if data.is_empty() {
return None;
}
let mut ms: Vec<i64> = data
.iter()
.map(|ts: &DateTime<Utc>| ts.timestamp_millis())
.collect();
ms.sort_unstable();
let len = ms.len();
let median_ms = if len.is_multiple_of(2) {
(ms[len / 2 - 1] + ms[len / 2]) / 2
} else {
ms[len / 2]
};
match Utc.timestamp_millis_opt(median_ms) {
chrono::LocalResult::Single(dt) => Some(dt),
chrono::LocalResult::Ambiguous(dt, _) => Some(dt),
chrono::LocalResult::None => None,
}
}
pub trait Data {
fn median(&self) -> Option<f64>;
fn mean(&self) -> Option<f64>;
fn percentile(&self, p: f64) -> Option<f64>;
fn std_deviation(&self) -> Option<f64>;
}
pub trait Median<T> {
fn median(&mut self) -> Option<T>;
}
impl Median<f64> for Vec<f64> {
fn median(&mut self) -> Option<f64> {
median(self)
}
}
impl Median<f64> for &mut [f64] {
fn median(&mut self) -> Option<f64> {
median(self)
}
}
impl Data for Vec<f64> {
fn median(&self) -> Option<f64> {
let mut sorted = self.clone();
median(&mut sorted)
}
fn mean(&self) -> Option<f64> {
mean(self)
}
fn percentile(&self, p: f64) -> Option<f64> {
let mut sorted = self.clone();
percentile(&mut sorted, p)
}
fn std_deviation(&self) -> Option<f64> {
std_deviation(self)
}
}
impl Data for &[f64] {
fn median(&self) -> Option<f64> {
let mut sorted = self.to_vec();
median(&mut sorted)
}
fn mean(&self) -> Option<f64> {
mean(self)
}
fn percentile(&self, p: f64) -> Option<f64> {
let mut sorted = self.to_vec();
percentile(&mut sorted, p)
}
fn std_deviation(&self) -> Option<f64> {
std_deviation(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_median() {
let mut data = vec![5.0, 2.0, 8.0, 1.0, 9.0];
assert_eq!(median(&mut data), Some(5.0));
}
#[test]
fn test_median_even() {
let mut data = vec![5.0, 2.0, 8.0, 1.0];
assert_eq!(median(&mut data), Some(3.5));
}
#[test]
fn test_median_empty() {
let mut data: Vec<f64> = vec![];
assert_eq!(median(&mut data), None);
}
#[test]
fn test_percentile_95() {
let mut data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
assert_eq!(percentile_95(&mut data), Some(10.0));
}
#[test]
fn test_percentile_50() {
let mut data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert_eq!(percentile(&mut data, 50.0), Some(3.0));
}
#[test]
fn test_percentile_invalid() {
let mut data = vec![1.0, 2.0, 3.0];
assert_eq!(percentile(&mut data, -1.0), None);
assert_eq!(percentile(&mut data, 101.0), None);
}
#[test]
fn test_mean() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert_eq!(mean(&data), Some(3.0));
}
#[test]
fn test_mean_empty() {
let data: Vec<f64> = vec![];
assert_eq!(mean(&data), None);
}
#[test]
fn test_std_deviation() {
let data = vec![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
assert_eq!(std_deviation(&data), Some(2.0));
}
#[test]
fn test_min_max() {
let data = vec![5.0, 2.0, 8.0, 1.0, 9.0];
assert_eq!(min(&data), Some(1.0));
assert_eq!(max(&data), Some(9.0));
}
#[test]
fn test_data_trait_median() {
let data = vec![5.0, 2.0, 8.0, 1.0, 9.0];
assert_eq!(data.median(), Some(5.0));
}
#[test]
fn test_data_trait_mean() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert_eq!(data.mean(), Some(3.0));
}
#[test]
fn test_data_trait_percentile() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
assert_eq!(data.percentile(95.0), Some(10.0));
}
#[test]
fn test_data_trait_std_deviation() {
let data = vec![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
assert_eq!(data.std_deviation(), Some(2.0));
}
}