UPDATE: Implement FTEST function
This commit is contained in:
committed by
Nicolás Hatcher Andrés
parent
e61b15655a
commit
080574b112
@@ -1,6 +1,7 @@
|
||||
use statrs::distribution::{Continuous, ContinuousCDF, FisherSnedecor};
|
||||
|
||||
use crate::expressions::types::CellReferenceIndex;
|
||||
use crate::functions::statistical::t_dist::sample_var;
|
||||
use crate::{
|
||||
calc_result::CalcResult, expressions::parser::Node, expressions::token::Error, model::Model,
|
||||
};
|
||||
@@ -296,4 +297,122 @@ impl Model {
|
||||
|
||||
CalcResult::Number(x)
|
||||
}
|
||||
|
||||
// F.TEST(array1, array2)
|
||||
pub(crate) fn fn_f_test(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
if args.len() != 2 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
|
||||
let values1_opts = match self.evaluate_node_in_context(&args[0], cell) {
|
||||
CalcResult::Range { left, right } => match self.values_from_range(left, right) {
|
||||
Ok(v) => v,
|
||||
Err(error) => return error,
|
||||
},
|
||||
CalcResult::Array(a) => match self.values_from_array(a) {
|
||||
Ok(v) => v,
|
||||
Err(error) => {
|
||||
return CalcResult::new_error(
|
||||
Error::VALUE,
|
||||
cell,
|
||||
format!("Error in first array: {:?}", error),
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return CalcResult::new_error(
|
||||
Error::VALUE,
|
||||
cell,
|
||||
"First argument must be a range or array".to_string(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Get second sample as Vec<Option<f64>>
|
||||
let values2_opts = match self.evaluate_node_in_context(&args[1], cell) {
|
||||
CalcResult::Range { left, right } => match self.values_from_range(left, right) {
|
||||
Ok(v) => v,
|
||||
Err(error) => return error,
|
||||
},
|
||||
CalcResult::Array(a) => match self.values_from_array(a) {
|
||||
Ok(v) => v,
|
||||
Err(error) => {
|
||||
return CalcResult::new_error(
|
||||
Error::VALUE,
|
||||
cell,
|
||||
format!("Error in second array: {:?}", error),
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return CalcResult::new_error(
|
||||
Error::VALUE,
|
||||
cell,
|
||||
"Second argument must be a range or array".to_string(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let values1: Vec<f64> = values1_opts.into_iter().flatten().collect();
|
||||
let values2: Vec<f64> = values2_opts.into_iter().flatten().collect();
|
||||
|
||||
let n1 = values1.len();
|
||||
let n2 = values2.len();
|
||||
|
||||
// If fewer than 2 numeric values in either sample -> #DIV/0!
|
||||
if n1 < 2 || n2 < 2 {
|
||||
return CalcResult::new_error(
|
||||
Error::DIV,
|
||||
cell,
|
||||
"F.TEST requires at least two numeric values in each sample".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let v1 = sample_var(&values1);
|
||||
let v2 = sample_var(&values2);
|
||||
|
||||
if v1 <= 0.0 || v2 <= 0.0 {
|
||||
return CalcResult::new_error(
|
||||
Error::DIV,
|
||||
cell,
|
||||
"Variance of one sample is zero in F.TEST".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
// F ratio: larger variance / smaller variance
|
||||
let mut f = v1 / v2;
|
||||
let mut df1 = (n1 - 1) as f64;
|
||||
let mut df2 = (n2 - 1) as f64;
|
||||
|
||||
if f < 1.0 {
|
||||
f = 1.0 / f;
|
||||
std::mem::swap(&mut df1, &mut df2);
|
||||
}
|
||||
|
||||
let dist = match FisherSnedecor::new(df1, df2) {
|
||||
Ok(d) => d,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::NUM,
|
||||
cell,
|
||||
"Invalid parameters for F distribution in F.TEST".to_string(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// One-tailed right-tail probability
|
||||
let tail = 1.0 - dist.cdf(f);
|
||||
// F.TEST is two-tailed: p = 2 * tail (with F >= 1)
|
||||
let mut p = 2.0 * tail;
|
||||
|
||||
// Clamp tiny FP noise
|
||||
if p < 0.0 && p > -1e-15 {
|
||||
p = 0.0;
|
||||
}
|
||||
if p > 1.0 && p < 1.0 + 1e-15 {
|
||||
p = 1.0;
|
||||
}
|
||||
|
||||
CalcResult::Number(p)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user