linreg-core 0.8.1

Lightweight regression library (OLS, Ridge, Lasso, Elastic Net, WLS, LOESS, Polynomial) with 14 diagnostic tests, cross validation, and prediction intervals. Pure Rust - no external math dependencies. WASM, Python, FFI, and Excel XLL bindings.
Documentation
//! Statistical utility functions for WASM
//!
//! Provides WASM bindings for common statistical functions.

#![cfg(feature = "wasm")]

use wasm_bindgen::prelude::*;

use super::domain::check_domain;
use crate::core;
use crate::distributions::{normal_inverse_cdf, student_t_cdf};
use crate::stats;

/// Computes the Student's t-distribution cumulative distribution function.
///
/// Returns P(T ≤ t) for a t-distribution with the given degrees of freedom.
///
/// # Arguments
///
/// * `t` - t-statistic value
/// * `df` - Degrees of freedom
///
/// # Returns
///
/// The CDF value, or `NaN` if domain check fails.
#[wasm_bindgen]
pub fn get_t_cdf(t: f64, df: f64) -> f64 {
    if check_domain().is_err() {
        return f64::NAN;
    }

    student_t_cdf(t, df)
}

/// Computes the critical t-value for a given significance level.
///
/// Returns the t-value such that the area under the t-distribution curve
/// to the right equals alpha/2 (two-tailed test).
///
/// # Arguments
///
/// * `alpha` - Significance level (typically 0.05 for 95% confidence)
/// * `df` - Degrees of freedom
///
/// # Returns
///
/// The critical t-value, or `NaN` if domain check fails.
#[wasm_bindgen]
pub fn get_t_critical(alpha: f64, df: f64) -> f64 {
    if check_domain().is_err() {
        return f64::NAN;
    }

    core::t_critical_quantile(df, alpha)
}

/// Computes the inverse of the standard normal CDF (probit function).
///
/// Returns the z-score such that P(Z ≤ z) = p for a standard normal distribution.
///
/// # Arguments
///
/// * `p` - Probability (0 < p < 1)
///
/// # Returns
///
/// The z-score, or `NaN` if domain check fails.
#[wasm_bindgen]
pub fn get_normal_inverse(p: f64) -> f64 {
    if check_domain().is_err() {
        return f64::NAN;
    }

    normal_inverse_cdf(p)
}

/// Computes the arithmetic mean of a JSON array of f64 values.
///
/// # Arguments
///
/// * `data_json` - JSON string representing an array of f64 values
///
/// # Returns
///
/// JSON string with the mean, or "null" if input is invalid/empty
#[wasm_bindgen]
pub fn stats_mean(data_json: String) -> String {
    if check_domain().is_err() {
        return "null".to_string();
    }

    let data: Vec<f64> = match serde_json::from_str(&data_json) {
        Ok(d) => d,
        Err(_) => return "null".to_string(),
    };

    serde_json::to_string(&stats::mean(&data)).unwrap_or("null".to_string())
}

/// Computes the sample standard deviation of a JSON array of f64 values.
///
/// Uses the (n-1) denominator for unbiased estimation.
///
/// # Arguments
///
/// * `data_json` - JSON string representing an array of f64 values
///
/// # Returns
///
/// JSON string with the standard deviation, or "null" if input is invalid
#[wasm_bindgen]
pub fn stats_stddev(data_json: String) -> String {
    if check_domain().is_err() {
        return "null".to_string();
    }

    let data: Vec<f64> = match serde_json::from_str(&data_json) {
        Ok(d) => d,
        Err(_) => return "null".to_string(),
    };

    serde_json::to_string(&stats::stddev(&data)).unwrap_or("null".to_string())
}

/// Computes the sample variance of a JSON array of f64 values.
///
/// Uses the (n-1) denominator for unbiased estimation.
///
/// # Arguments
///
/// * `data_json` - JSON string representing an array of f64 values
///
/// # Returns
///
/// JSON string with the variance, or "null" if input is invalid
#[wasm_bindgen]
pub fn stats_variance(data_json: String) -> String {
    if check_domain().is_err() {
        return "null".to_string();
    }

    let data: Vec<f64> = match serde_json::from_str(&data_json) {
        Ok(d) => d,
        Err(_) => return "null".to_string(),
    };

    serde_json::to_string(&stats::variance(&data)).unwrap_or("null".to_string())
}

/// Computes the median of a JSON array of f64 values.
///
/// # Arguments
///
/// * `data_json` - JSON string representing an array of f64 values
///
/// # Returns
///
/// JSON string with the median, or "null" if input is invalid/empty
#[wasm_bindgen]
pub fn stats_median(data_json: String) -> String {
    if check_domain().is_err() {
        return "null".to_string();
    }

    let data: Vec<f64> = match serde_json::from_str(&data_json) {
        Ok(d) => d,
        Err(_) => return "null".to_string(),
    };

    serde_json::to_string(&stats::median(&data)).unwrap_or("null".to_string())
}

/// Computes a quantile of a JSON array of f64 values.
///
/// # Arguments
///
/// * `data_json` - JSON string representing an array of f64 values
/// * `q` - Quantile to calculate (0.0 to 1.0)
///
/// # Returns
///
/// JSON string with the quantile value, or "null" if input is invalid
#[wasm_bindgen]
pub fn stats_quantile(data_json: String, q: f64) -> String {
    if check_domain().is_err() {
        return "null".to_string();
    }

    let data: Vec<f64> = match serde_json::from_str(&data_json) {
        Ok(d) => d,
        Err(_) => return "null".to_string(),
    };

    serde_json::to_string(&stats::quantile(&data, q)).unwrap_or("null".to_string())
}

/// Computes the correlation coefficient between two JSON arrays of f64 values.
///
/// # Arguments
///
/// * `x_json` - JSON string representing the first array of f64 values
/// * `y_json` - JSON string representing the second array of f64 values
///
/// # Returns
///
/// JSON string with the correlation coefficient, or "null" if input is invalid
#[wasm_bindgen]
pub fn stats_correlation(x_json: String, y_json: String) -> String {
    if check_domain().is_err() {
        return "null".to_string();
    }

    let x: Vec<f64> = match serde_json::from_str(&x_json) {
        Ok(d) => d,
        Err(_) => return "null".to_string(),
    };

    let y: Vec<f64> = match serde_json::from_str(&y_json) {
        Ok(d) => d,
        Err(_) => return "null".to_string(),
    };

    serde_json::to_string(&stats::correlation(&x, &y)).unwrap_or("null".to_string())
}

/// Computes the minimum value of a JSON array of f64 values.
///
/// # Arguments
///
/// * `data_json` - JSON string representing an array of f64 values
///
/// # Returns
///
/// JSON string with the minimum value, or "null" if input is invalid/empty
#[wasm_bindgen]
pub fn stats_min(data_json: String) -> String {
    if check_domain().is_err() {
        return "null".to_string();
    }

    let data: Vec<f64> = match serde_json::from_str(&data_json) {
        Ok(d) => d,
        Err(_) => return "null".to_string(),
    };

    serde_json::to_string(&stats::min(&data)).unwrap_or("null".to_string())
}

/// Computes the maximum value of a JSON array of f64 values.
///
/// # Arguments
///
/// * `data_json` - JSON string representing an array of f64 values
///
/// # Returns
///
/// JSON string with the maximum value, or "null" if input is invalid/empty
#[wasm_bindgen]
pub fn stats_max(data_json: String) -> String {
    if check_domain().is_err() {
        return "null".to_string();
    }

    let data: Vec<f64> = match serde_json::from_str(&data_json) {
        Ok(d) => d,
        Err(_) => return "null".to_string(),
    };

    serde_json::to_string(&stats::max(&data)).unwrap_or("null".to_string())
}

/// Computes the range (max - min) of a JSON array of f64 values.
///
/// # Arguments
///
/// * `data_json` - JSON string representing an array of f64 values
///
/// # Returns
///
/// JSON string with the range, or "null" if input is invalid/empty
#[wasm_bindgen]
pub fn stats_range(data_json: String) -> String {
    if check_domain().is_err() {
        return "null".to_string();
    }

    let data: Vec<f64> = match serde_json::from_str(&data_json) {
        Ok(d) => d,
        Err(_) => return "null".to_string(),
    };

    serde_json::to_string(&stats::range(&data)).unwrap_or("null".to_string())
}

/// Computes the mode(s) of a JSON array of f64 values.
///
/// Returns all values that appear most frequently (handles ties).
///
/// # Arguments
///
/// * `data_json` - JSON string representing an array of f64 values
///
/// # Returns
///
/// JSON string with mode result containing modes array, frequency, and unique count,
/// or "null" if input is invalid/empty
///
/// # Example
///
/// ```javascript
/// const result = JSON.parse(stats_mode("[1, 2, 2, 3, 4]"));
/// console.log(result.modes);     // [2]
/// console.log(result.frequency); // 2
/// ```
#[wasm_bindgen]
pub fn stats_mode(data_json: String) -> String {
    if check_domain().is_err() {
        return crate::error::error_json("Domain check failed");
    }

    let data: Vec<f64> = match serde_json::from_str(&data_json) {
        Ok(d) => d,
        Err(e) => return crate::error::error_json(&format!("Invalid JSON: {}", e)),
    };

    match stats::mode(&data) {
        Some(result) => serde_json::to_string(&result).unwrap_or_else(|_| crate::error::error_json("Serialization failed")),
        None => crate::error::error_json("No mode (empty data or all NaN)"),
    }
}

/// Computes the five-number summary of a JSON array of f64 values.
///
/// The five-number summary consists of: minimum, Q1 (25th percentile),
/// median (50th percentile), Q3 (75th percentile), and maximum.
///
/// # Arguments
///
/// * `data_json` - JSON string representing an array of f64 values
///
/// # Returns
///
/// JSON string with the five-number summary (min, q1, median, q3, max),
/// or error JSON if input is invalid/empty
///
/// # Example
///
/// ```javascript
/// const result = JSON.parse(stats_five_number_summary("[1, 2, 3, 4, 5, 6, 7, 8, 9]"));
/// console.log(result.min);    // 1
/// console.log(result.q1);     // ~3
/// console.log(result.median); // 5
/// console.log(result.q3);     // ~7
/// console.log(result.max);    // 9
/// console.log(result.iqr);    // ~4 (calculated as q3 - q1)
/// ```
#[wasm_bindgen]
pub fn stats_five_number_summary(data_json: String) -> String {
    if check_domain().is_err() {
        return crate::error::error_json("Domain check failed");
    }

    let data: Vec<f64> = match serde_json::from_str(&data_json) {
        Ok(d) => d,
        Err(e) => return crate::error::error_json(&format!("Invalid JSON: {}", e)),
    };

    match stats::five_number_summary(&data) {
        Some(summary) => {
            // Convert to a JSON object with iqr included
            serde_json::json!({
                "min": summary.min,
                "q1": summary.q1,
                "median": summary.median,
                "q3": summary.q3,
                "max": summary.max,
                "iqr": summary.iqr()
            }).to_string()
        }
        None => crate::error::error_json("Empty data"),
    }
}