use crate::feature_selection::mrmr::mrmr_error::MrmrError;
use deep_causality_num::{Float, FloatOption};
use deep_causality_tensor::CausalTensor;
pub(super) fn pearson_correlation<T, F>(
tensor: &CausalTensor<T>,
col_a_idx: usize,
col_b_idx: usize,
) -> Result<(f64, f64), MrmrError>
where
T: FloatOption<F>,
F: Float,
{
let shape = tensor.shape();
if shape.len() != 2 {
return Err(MrmrError::InvalidInput(
"Input tensor must be 2-dimensional".to_string(),
));
}
let n_rows = shape[0];
let n_cols = shape[1];
if col_a_idx >= n_cols || col_b_idx >= n_cols {
return Err(MrmrError::InvalidInput(
"Column index out of bounds".to_string(),
));
}
let mut sum_a: f64 = 0.0;
let mut sum_b: f64 = 0.0;
let mut sum_sq_a: f64 = 0.0;
let mut sum_sq_b: f64 = 0.0;
let mut sum_prod: f64 = 0.0;
let mut n: f64 = 0.0;
for i in 0..n_rows {
let a_option = tensor
.get(&[i, col_a_idx])
.ok_or_else(|| {
MrmrError::CalculationError("Failed to get value from tensor".to_string())
})?
.to_option();
let b_option = tensor
.get(&[i, col_b_idx])
.ok_or_else(|| {
MrmrError::CalculationError("Failed to get value from tensor".to_string())
})?
.to_option();
if let (Some(a_val), Some(b_val)) = (a_option, b_option) {
let a = a_val.to_f64().ok_or_else(|| {
MrmrError::CalculationError("Failed to cast float to f64".to_string())
})?;
let b = b_val.to_f64().ok_or_else(|| {
MrmrError::CalculationError("Failed to cast float to f64".to_string())
})?;
sum_a += a;
sum_b += b;
sum_sq_a += a * a;
sum_sq_b += b * b;
sum_prod += a * b;
n += 1.0;
}
}
if n < 2.0 {
return Err(MrmrError::SampleTooSmall(2));
}
let numerator = sum_prod - (sum_a * sum_b) / n;
let denominator_a = sum_sq_a - (sum_a * sum_a) / n;
let denominator_b = sum_sq_b - (sum_b * sum_b) / n;
if denominator_a <= 0.0 || denominator_b <= 0.0 {
return Ok((0.0, n));
}
Ok((numerator / (denominator_a.sqrt() * denominator_b.sqrt()), n))
}
pub(super) fn f_statistic<T, F>(
tensor: &CausalTensor<T>,
feature_idx: usize,
target_idx: usize,
) -> Result<f64, MrmrError>
where
T: FloatOption<F>,
F: Float,
{
let (r, n) = pearson_correlation(tensor, feature_idx, target_idx)?;
if n < 3.0 {
return Err(MrmrError::SampleTooSmall(3));
}
let r2 = r.powi(2);
if (1.0 - r2).abs() < 1e-9 {
return Ok(1e12);
}
let f_stat = (n - 2.0) * r2 / (1.0 - r2);
Ok(f_stat)
}