scirs2_stats/
api_improvements.rs

1//! API improvements for v1.0.0 release
2//!
3//! This module defines improved API patterns for better consistency and usability.
4
5#![allow(dead_code)]
6
7use crate::error::StatsResult;
8use crate::tests::ttest::Alternative;
9use scirs2_core::ndarray::{ArrayBase, Data, Ix1};
10use scirs2_core::numeric::Float;
11
12/// Standard correlation result that includes both coefficient and p-value
13#[derive(Debug, Clone, Copy)]
14pub struct CorrelationResult<F> {
15    /// The correlation coefficient
16    pub coefficient: F,
17    /// The p-value (if computed)
18    pub p_value: Option<F>,
19}
20
21impl<F: Float + std::fmt::Display> CorrelationResult<F> {
22    /// Create a new correlation result with just the coefficient
23    pub fn new(coefficient: F) -> Self {
24        Self {
25            coefficient,
26            p_value: None,
27        }
28    }
29
30    /// Create a new correlation result with coefficient and p-value
31    pub fn with_p_value(_coefficient: F, pvalue: F) -> Self {
32        Self {
33            coefficient: _coefficient,
34            p_value: Some(pvalue),
35        }
36    }
37}
38
39/// Correlation method selection
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum CorrelationMethod {
42    /// Pearson correlation coefficient
43    Pearson,
44    /// Spearman rank correlation
45    Spearman,
46    /// Kendall tau correlation
47    KendallTau,
48}
49
50impl CorrelationMethod {
51    /// Convert from string representation
52    pub fn from_str(s: &str) -> StatsResult<Self> {
53        match s.to_lowercase().as_str() {
54            "pearson" => Ok(CorrelationMethod::Pearson),
55            "spearman" => Ok(CorrelationMethod::Spearman),
56            "kendall" | "kendall_tau" | "kendalltau" => Ok(CorrelationMethod::KendallTau),
57            _ => Err(crate::error::StatsError::InvalidArgument(format!(
58                "Invalid correlation method: '{}'",
59                s
60            ))),
61        }
62    }
63}
64
65/// Optimization hints for performance-critical operations
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum OptimizationHint {
68    /// Use the best available implementation (default)
69    Auto,
70    /// Force scalar implementation
71    Scalar,
72    /// Force SIMD implementation (if available)
73    Simd,
74    /// Force parallel implementation (if available)
75    Parallel,
76}
77
78/// Configuration for statistical operations
79#[derive(Debug, Clone)]
80pub struct StatsConfig {
81    /// Optimization hint for performance
82    pub optimization: OptimizationHint,
83    /// Whether to compute p-values
84    pub compute_p_value: bool,
85    /// Alternative hypothesis for tests
86    pub alternative: Alternative,
87}
88
89impl Default for StatsConfig {
90    fn default() -> Self {
91        Self {
92            optimization: OptimizationHint::Auto,
93            compute_p_value: false,
94            alternative: Alternative::TwoSided,
95        }
96    }
97}
98
99impl StatsConfig {
100    /// Create a new configuration with p-value computation enabled
101    pub fn with_p_value(mut self) -> Self {
102        self.compute_p_value = true;
103        self
104    }
105
106    /// Set the alternative hypothesis
107    pub fn with_alternative(mut self, alternative: Alternative) -> Self {
108        self.alternative = alternative;
109        self
110    }
111
112    /// Set the optimization hint
113    pub fn with_optimization(mut self, optimization: OptimizationHint) -> Self {
114        self.optimization = optimization;
115        self
116    }
117}
118
119/// Improved correlation API that unifies pearson_r and pearsonr
120pub trait CorrelationExt<F, D>
121where
122    F: Float + std::fmt::Display + std::iter::Sum + Send + Sync,
123    D: Data<Elem = F>,
124{
125    /// Compute correlation with optional p-value based on configuration
126    fn correlation(
127        &self,
128        other: &ArrayBase<D, Ix1>,
129        method: CorrelationMethod,
130        config: Option<StatsConfig>,
131    ) -> StatsResult<CorrelationResult<F>>;
132
133    /// Compute Pearson correlation (convenience method)
134    fn pearson(&self, other: &ArrayBase<D, Ix1>) -> StatsResult<F> {
135        self.correlation(other, CorrelationMethod::Pearson, None)
136            .map(|r| r.coefficient)
137    }
138
139    /// Compute Spearman correlation (convenience method)
140    fn spearman(&self, other: &ArrayBase<D, Ix1>) -> StatsResult<F> {
141        self.correlation(other, CorrelationMethod::Spearman, None)
142            .map(|r| r.coefficient)
143    }
144
145    /// Compute Kendall tau correlation (convenience method)
146    fn kendall(&self, other: &ArrayBase<D, Ix1>) -> StatsResult<F> {
147        self.correlation(other, CorrelationMethod::KendallTau, None)
148            .map(|r| r.coefficient)
149    }
150}
151
152/// Builder pattern for complex statistical operations
153pub struct StatsBuilder<F> {
154    data: Option<Vec<F>>,
155    config: StatsConfig,
156}
157
158impl<F: Float + std::fmt::Display + std::iter::Sum + Send + Sync> Default for StatsBuilder<F> {
159    fn default() -> Self {
160        Self::new()
161    }
162}
163
164impl<F: Float + std::fmt::Display + std::iter::Sum + Send + Sync> StatsBuilder<F> {
165    /// Create a new builder
166    pub fn new() -> Self {
167        Self {
168            data: None,
169            config: StatsConfig::default(),
170        }
171    }
172
173    /// Set the data with validation
174    pub fn data(mut self, data: Vec<F>) -> StatsResult<Self> {
175        // Check if data is empty
176        if data.is_empty() {
177            return Err(crate::error::StatsError::invalid_argument(
178                "Data cannot be empty",
179            ));
180        }
181
182        self.data = Some(data);
183        Ok(self)
184    }
185
186    /// Set data without validation (for performance-critical paths)
187    pub fn data_unchecked(mut self, data: Vec<F>) -> Self {
188        self.data = Some(data);
189        self
190    }
191
192    /// Enable p-value computation
193    pub fn with_p_value(mut self) -> Self {
194        self.config.compute_p_value = true;
195        self
196    }
197
198    /// Set the alternative hypothesis
199    pub fn alternative(mut self, alt: Alternative) -> Self {
200        self.config.alternative = alt;
201        self
202    }
203
204    /// Set optimization hint
205    pub fn optimization(mut self, opt: OptimizationHint) -> Self {
206        self.config.optimization = opt;
207        self
208    }
209
210    /// Validate the current configuration
211    pub fn validate(&self) -> StatsResult<()> {
212        if self.data.is_none() {
213            return Err(crate::error::StatsError::invalid_argument(
214                "No data provided to builder",
215            ));
216        }
217
218        if let Some(ref data) = self.data {
219            if data.is_empty() {
220                return Err(crate::error::StatsError::invalid_argument(
221                    "Data cannot be empty",
222                ));
223            }
224        }
225
226        Ok(())
227    }
228
229    /// Get a reference to the data
230    pub fn getdata(&self) -> Option<&Vec<F>> {
231        self.data.as_ref()
232    }
233
234    /// Get the configuration
235    pub fn get_config(&self) -> &StatsConfig {
236        &self.config
237    }
238}
239
240/// Improved test result that standardizes output across all tests
241#[derive(Debug, Clone)]
242pub struct TestResult<F> {
243    /// The test statistic
244    pub statistic: F,
245    /// The p-value
246    pub p_value: F,
247    /// Degrees of freedom (if applicable)
248    pub df: Option<F>,
249    /// Effect size (if applicable)
250    pub effectsize: Option<F>,
251    /// Confidence interval (if applicable)
252    pub confidence_interval: Option<(F, F)>,
253}
254
255impl<F: Float + std::fmt::Display> TestResult<F> {
256    /// Create a basic test result
257    pub fn new(_statistic: F, pvalue: F) -> Self {
258        Self {
259            statistic: _statistic,
260            p_value: pvalue,
261            df: None,
262            effectsize: None,
263            confidence_interval: None,
264        }
265    }
266
267    /// Add degrees of freedom
268    pub fn with_df(mut self, df: F) -> Self {
269        self.df = Some(df);
270        self
271    }
272
273    /// Add effect size
274    pub fn with_effectsize(mut self, effectsize: F) -> Self {
275        self.effectsize = Some(effectsize);
276        self
277    }
278
279    /// Add confidence interval
280    pub fn with_confidence_interval(mut self, lower: F, upper: F) -> Self {
281        self.confidence_interval = Some((lower, upper));
282        self
283    }
284}
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289
290    #[test]
291    fn test_correlation_method_from_str() {
292        assert_eq!(
293            CorrelationMethod::from_str("pearson").unwrap(),
294            CorrelationMethod::Pearson
295        );
296        assert_eq!(
297            CorrelationMethod::from_str("spearman").unwrap(),
298            CorrelationMethod::Spearman
299        );
300        assert_eq!(
301            CorrelationMethod::from_str("kendall").unwrap(),
302            CorrelationMethod::KendallTau
303        );
304        assert!(CorrelationMethod::from_str("invalid").is_err());
305    }
306
307    #[test]
308    fn test_stats_config_builder() {
309        let config = StatsConfig::default()
310            .with_p_value()
311            .with_alternative(Alternative::Greater);
312
313        assert!(config.compute_p_value);
314        assert_eq!(config.alternative, Alternative::Greater);
315        assert_eq!(config.optimization, OptimizationHint::Auto);
316    }
317}