statistics.rs - source
Description: Source of the Rust file `src/statistics.rs`.

#
scicdh/
statistics.rs

    1use crate::probability::CDHResult;
    2use std::collections::HashMap;
    3
    4
    5/// A high-performance structure encapsulating a linear array of values
    6/// designed for descriptive and bivariate statistical data mining.
    7pub struct DataSet<T>(pub Vec<T>);
    8
    9impl DataSet<f64> {
    10    /// Instantiates a new data frame context around a contiguous memory buffer.
    11    ///
    12    /// # Example
    13    /// ```rust
    14   /// use scicdh::statistics::DataSet;
    15    /// let ds = DataSet::new(vec![1.0, 2.0, 3.0]);
    16    /// ```
    17    pub fn new(data: Vec<f64>) -> Self {
    18        DataSet(data)
    19    }
    20
    21    /// Computes the arithmetic mean ($\mu$) of the sample space.
    22    ///
    23    /// $$\mu = \frac{\sum_{i=1}^n x_i}{n}$$
    24    pub fn mean(&self) -> CDHResult<f64> {
    25        let len = self.0.len() as f64;
    26        if len == 0.0 {
    27            return Err("Cannot compute mean of an empty dataset.".to_string());
    28        }
    29        Ok(self.0.iter().sum::<f64>() / len)
    30    }
    31
    32    /// Computes the sample median using a non-destructive 0-indexed sorting pipeline.
    33    /// Handles both odd and even dataset lengths correctly.
    34    pub fn median(&self) -> CDHResult<f64> {
    35        let len = self.0.len();
    36        if len == 0 {
    37            return Err("Cannot compute median of an empty dataset.".to_string());
    38        }
    39        let mut sorted_arr = self.0.clone();
    40        sorted_arr.sort_by(|a, b| a.total_cmp(b));
    41
    42        match len {
    43            a if a % 2 == 1 => Ok(sorted_arr[(a + 1) / 2 - 1]),
    44            _ => {
    45                let center = len / 2;
    46                let center_elem = sorted_arr[center - 1];
    47                let next_elem = sorted_arr[center];
    48                Ok((center_elem + next_elem) / 2.0)
    49            }
    50        }
    51    }
    52
    53    /// Computes the statistical mode of the dataset in linear time $\mathcal{O}(n)$.
    54    ///
    55    /// Uses low-level float bit-packing (`to_bits()`) to completely bypass
    56    /// hashing restrictions on `f64`. Returns `Ok(None)` if the distribution
    57    /// is completely uniform (no single clear majority frequency exists).
    58    pub fn mode(&self) -> CDHResult<Option<f64>> {
    59        if self.0.is_empty() {
    60            return Err("Cannot compute mode of an empty dataset.".to_string());
    61        }
    62
    63        let mut frequencies: HashMap<u64, usize> = HashMap::with_capacity(self.0.len());
    64        let mut max_count = 0;
    65
    66        // Step A: Aggregate counts via float-to-bit maps in pure O(n)
    67        for &num in &self.0 {
    68            let bits = num.to_bits();
    69            let count = frequencies.entry(bits).or_insert(0);
    70            *count += 1;
    71            if *count > max_count {
    72                max_count = *count;
    73            }
    74        }
    75
    76        // Step B: Extract matching patterns to verify unimodal dominance
    77        let modes: Vec<u64> = frequencies
    78            .iter()
    79            .filter(|&(_, &count)| count == max_count)
    80            .map(|(&bits, _)| bits)
    81            .collect();
    82
    83        // If all items appear exactly the same number of times, there is no mode
    84        if modes.len() == frequencies.len() && max_count == 1 {
    85            return Ok(None);
    86        }
    87
    88        // If multiple distinct elements tie for peak occurrence, it's multimodal
    89        if modes.len() > 1 {
    90            Ok(None)
    91        } else {
    92            Ok(Some(f64::from_bits(modes[0])))
    93        }
    94    }
    95
    96    /// Calculates the algebraic range (Span) between the maximum and minimum parameters.
    97    ///
    98    /// $$\text{Range} = X_{\text{max}} - X_{\text{min}}$$
    99    pub fn range(&self) -> CDHResult<f64> {
    100        if self.0.is_empty() {
    101            return Err("Cannot compute range of an empty dataset.".to_string());
    102        }
    103        Ok(self.max()? - self.min()?)
    104    }
    105
    106    /// Scans a target container or the internal dataset structure to check
    107    /// for the existence of a precise value.
    108    ///
    109    /// Fixed Bug: Bypassed memory copying vulnerabilities from previous design.
    110    pub fn has(&self, value: f64, other: Option<&[f64]>) -> CDHResult<bool> {
    111        let set = match other {
    112            None => &self.0,
    113            Some(slice) => slice,
    114        };
    115        Ok(set.iter().any(|&v| v == value))
    116    }
    117
    118    /// Computes the sum of squared deviations for the primary variable ($SS_{xx}$).
    119    ///
    120    /// $$SS_{xx} = \sum x^2 - \frac{(\sum x)^2}{n}$$
    121    pub fn sample_variation_x2(&self) -> CDHResult<f64> {
    122        let n = self.0.len() as f64;
    123        if n == 0.0 { return Err("Empty dataset context.".to_string()); }
    124        let xsqrsum = self.0.iter().fold(0.0, |acc, b| acc + b.powi(2));
    125        let xsum = self.0.iter().sum::<f64>();
    126        Ok(xsqrsum - xsum.powi(2) / n)
    127    }
    128
    129    /// Computes the sum of squared deviations for an independent reference slice ($SS_{yy}$).
    130    ///
    131    /// $$SS_{yy} = \sum y^2 - \frac{(\sum y)^2}{n}$$
    132    pub fn sample_variation_y2(&self, y: &[f64]) -> CDHResult<f64> {
    133        let n = y.len() as f64;
    134        if n == 0.0 { return Err("Input slice cannot be empty.".to_string()); }
    135        let ysqrsum = y.iter().fold(0.0, |acc, b| acc + b.powi(2));
    136        let ysum = y.iter().sum::<f64>();
    137        Ok(ysqrsum - ysum.powi(2) / n)
    138    }
    139
    140    /// Computes the unbiased sample variance ($s^2$) leveraging Bessel's correction factor.
    141    ///
    142    /// $$s^2 = \frac{SS_{xx}}{n - 1}$$
    143    pub fn sample_variance(&self) -> CDHResult<f64> {
    144        let len = self.0.len();
    145        if len <= 1 {
    146            return Err("Sample variance requires at least 2 data points.".to_string());
    147        }
    148        Ok(self.sample_variation_x2()? / ((len - 1) as f64))
    149    }
    150
    151    /// Computes the unbiased sample standard deviation ($s$).
    152    pub fn sample_std_deviation(&self) -> CDHResult<f64> {
    153        Ok(self.sample_variance()?.sqrt())
    154    }
    155
    156    /// Evaluates the dimensionless sample coefficient of variation ($CV$).
    157    ///
    158    /// $$CV = \frac{s}{\bar{x}}$$
    159    pub fn sample_coeff_of_variation(&self) -> CDHResult<f64> {
    160        let mean = self.mean()?;
    161        if mean == 0.0 {
    162            return Err("Coefficient of variation undefined for a zero mean.".to_string());
    163        }
    164        Ok(self.sample_std_deviation()? / mean)
    165    }
    166
    167    /// Generates the $k$-th mathematical population central moment ($m_k$).
    168    ///
    169    /// $$\mu_k = \frac{\sum (x_i - \bar{x})^k}{n}$$
    170    pub fn kth_central_moment(&self, k: f64) -> CDHResult<f64> {
    171        let n = self.0.len() as f64;
    172        if n == 0.0 { return Err("Empty dataset context.".to_string()); }
    173        let mean = self.mean()?;
    174        Ok(self.0.iter().fold(0.0, |acc, b| acc + (b - mean).powf(k)) / n)
    175    }
    176
    177    /// Measures the skewness ($\beta_3$), tracking distribution symmetry.
    178    pub fn skewness(&self) -> CDHResult<f64> {
    179        let denom = self.population_std_deviation()?.powi(3);
    180        if denom == 0.0 { return Ok(0.0); }
    181        Ok(self.kth_central_moment(3.0)? / denom)
    182    }
    183
    184    /// Computes excess kurtosis ($\beta_4$), tracking peak sharpness relative to standard normal shapes.
    185    pub fn kurtosis(&self) -> CDHResult<f64> {
    186        let denom = self.population_std_deviation()?.powi(4);
    187        if denom == 0.0 { return Ok(0.0); }
    188        Ok((self.kth_central_moment(4.0)? / denom) - 3.0)
    189    }
    190
    191    /// Computes the cross-variation sum of joint distribution observations ($SS_{xy}$).
    192    ///
    193    /// $$SS_{xy} = \sum xy - \frac{(\sum x)(\sum y)}{n}$$
    194    pub fn sample_variation_xy(&self, y: &[f64]) -> CDHResult<f64> {
    195        if y.len() != self.0.len() {
    196            return Err("Length of y should be equal to length of dataset".to_string());
    197        }
    198        let n = y.len() as f64;
    199        let xysum = self.0.iter().zip(y.iter()).fold(0.0, |acc, b| acc + (b.0 * b.1));
    200        let xsum = self.0.iter().sum::<f64>();
    201        let ysum = y.iter().sum::<f64>();
    202        Ok(xysum - (xsum * ysum / n))
    203    }
    204
    205    /// Evaluates Pearson's Product-Moment Correlation Coefficient ($r_{xy}$).
    206    /// Hardened against the textbook typo by using $SS_{yy}$ space arrays.
    207    pub fn pearson_correlation_coeff(&self, y: &[f64]) -> CDHResult<f64> {
    208        let denom = (self.sample_variation_x2()? * self.sample_variation_y2(y)?).sqrt();
    209        if denom == 0.0 {
    210            return Ok(0.0); // Protective baseline against zero variance tracking
    211        }
    212        Ok(self.sample_variation_xy(y)? / denom)
    213    }
    214
    215    /// Deduplicates entries from the dataset to form a unique element mathematical set pool.
    216    pub fn into_set(&self) -> CDHResult<Vec<f64>> {
    217        let mut set: Vec<f64> = Vec::with_capacity(self.0.len());
    218        for &a in self.0.iter() {
    219            if !self.has(a, Some(&set))? {
    220                set.push(a);
    221            }
    222        }
    223        Ok(set)
    224    }
    225
    226    /// Calculates pure population variance ($\sigma^2$) relative to the exact population mean.
    227    pub fn population_variance(&self) -> CDHResult<f64> {
    228        let n = self.0.len() as f64;
    229        if n == 0.0 { return Err("Empty dataset context.".to_string()); }
    230        let mean = self.mean()?;
    231        Ok(self.0.iter().fold(0.0, |acc, &x| acc + (x - mean).powi(2)) / n)
    232    }
    233
    234    /// Computes the population standard deviation ($\sigma$).
    235    pub fn population_std_deviation(&self) -> CDHResult<f64> {
    236        Ok(self.population_variance()?.sqrt())
    237    }
    238
    239    /// Evaluates the peak extreme value bound present inside the dataset.
    240    pub fn max(&self) -> CDHResult<f64> {
    241        if self.0.is_empty() { return Err("Dataset is empty.".to_string()); }
    242        let mut max = self.0[0];
    243        self.0.iter().for_each(|&i| {
    244            if i > max { max = i; }
    245        });
    246        Ok(max)
    247    }
    248
    249    /// Evaluates the lowest value parameter bound present inside the dataset.
    250    pub fn min(&self) -> CDHResult<f64> {
    251        if self.0.is_empty() { return Err("Dataset is empty.".to_string()); }
    252        let mut min = self.0[0];
    253        self.0.iter().for_each(|&i| {
    254            if i < min { min = i; }
    255        });
    256        Ok(min)
    257    }
    258
    259    /// Dumps clean debug statements displaying the full metrics profile of the active dataset.
    260    pub fn info(&self) -> CDHResult<()> {
    261        println!("DataSet === {:?}", self.0);
    262        println!("Mean === {:#?}", self.mean()?);
    263        println!("Median === {:#?}", self.median()?);
    264        println!("Mode === {:#?}", self.mode()?);
    265        println!("Range === {:#?}", self.range()?);
    266        println!("Variance === {:#?}", self.sample_variance()?);
    267        println!("Skewness === {:#?}", self.skewness()?);
    268        println!("Kurtosis === {:#?}", self.kurtosis()?);
    269        println!("self has 3f64 == {:?}", self.has(3.0, None)?);
    270        println!("Set === {:?}", self.into_set()?);
    271        println!("Maximum === {:?}", self.max()?);
    272        println!("Minimum === {:?}", self.min()?);
    273        Ok(())
    274    }
    275}