UPDATE: Adds 56 functions in the Statistical section
Uses statrs for numerical functions REFACTOR: Put statistical functions on its own module This might seem counter-intuitive but the wasm build after this refactor is 1528 bytes smaller :)
This commit is contained in:
committed by
Nicolás Hatcher Andrés
parent
67ef3bcf87
commit
6822505602
311
base/src/functions/statistical/binom.rs
Normal file
311
base/src/functions/statistical/binom.rs
Normal file
@@ -0,0 +1,311 @@
|
||||
use statrs::distribution::{Binomial, Discrete, DiscreteCDF};
|
||||
|
||||
use crate::expressions::types::CellReferenceIndex;
|
||||
use crate::{
|
||||
calc_result::CalcResult, expressions::parser::Node, expressions::token::Error, model::Model,
|
||||
};
|
||||
|
||||
impl Model {
|
||||
pub(crate) fn fn_binom_dist(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
if args.len() != 4 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
|
||||
// number_s
|
||||
let number_s = match self.get_number_no_bools(&args[0], cell) {
|
||||
Ok(f) => f.trunc(),
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
// trials
|
||||
let trials = match self.get_number_no_bools(&args[1], cell) {
|
||||
Ok(f) => f.trunc(),
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
// probability_s
|
||||
let p = match self.get_number_no_bools(&args[2], cell) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
// cumulative (logical)
|
||||
let cumulative = match self.get_boolean(&args[3], cell) {
|
||||
Ok(b) => b,
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
// Domain checks
|
||||
if trials < 0.0
|
||||
|| number_s < 0.0
|
||||
|| number_s > trials
|
||||
|| p.is_nan()
|
||||
|| !(0.0..=1.0).contains(&p)
|
||||
{
|
||||
return CalcResult::new_error(
|
||||
Error::NUM,
|
||||
cell,
|
||||
"Invalid parameters for BINOM.DIST".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
// Limit to u64
|
||||
if trials > u64::MAX as f64 {
|
||||
return CalcResult::new_error(
|
||||
Error::NUM,
|
||||
cell,
|
||||
"Number of trials too large".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let n = trials as u64;
|
||||
let k = number_s as u64;
|
||||
|
||||
let dist = match Binomial::new(p, n) {
|
||||
Ok(d) => d,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::NUM,
|
||||
cell,
|
||||
"Invalid parameters for binomial distribution".to_string(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let prob = if cumulative { dist.cdf(k) } else { dist.pmf(k) };
|
||||
|
||||
if prob.is_nan() || prob.is_infinite() {
|
||||
return CalcResult::new_error(
|
||||
Error::NUM,
|
||||
cell,
|
||||
"Invalid result for BINOM.DIST".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
CalcResult::Number(prob)
|
||||
}
|
||||
|
||||
pub(crate) fn fn_binom_dist_range(
|
||||
&mut self,
|
||||
args: &[Node],
|
||||
cell: CellReferenceIndex,
|
||||
) -> CalcResult {
|
||||
if args.len() < 3 || args.len() > 4 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
|
||||
// trials
|
||||
let trials = match self.get_number_no_bools(&args[0], cell) {
|
||||
Ok(f) => f.trunc(),
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
// probability_s
|
||||
let p = match self.get_number_no_bools(&args[1], cell) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
// number_s (lower)
|
||||
let number_s = match self.get_number_no_bools(&args[2], cell) {
|
||||
Ok(f) => f.trunc(),
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
// number_s2 (upper, optional)
|
||||
let number_s2 = if args.len() == 4 {
|
||||
match self.get_number_no_bools(&args[3], cell) {
|
||||
Ok(f) => f.trunc(),
|
||||
Err(e) => return e,
|
||||
}
|
||||
} else {
|
||||
number_s
|
||||
};
|
||||
|
||||
if trials < 0.0
|
||||
|| number_s < 0.0
|
||||
|| number_s2 < 0.0
|
||||
|| number_s > number_s2
|
||||
|| number_s2 > trials
|
||||
|| p.is_nan()
|
||||
|| !(0.0..=1.0).contains(&p)
|
||||
{
|
||||
return CalcResult::new_error(
|
||||
Error::NUM,
|
||||
cell,
|
||||
"Invalid parameters for BINOM.DIST.RANGE".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
if trials > u64::MAX as f64 {
|
||||
return CalcResult::new_error(
|
||||
Error::NUM,
|
||||
cell,
|
||||
"Number of trials too large".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let n = trials as u64;
|
||||
let lower = number_s as u64;
|
||||
let upper = number_s2 as u64;
|
||||
|
||||
let dist = match Binomial::new(p, n) {
|
||||
Ok(d) => d,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::NUM,
|
||||
cell,
|
||||
"Invalid parameters for binomial distribution".to_string(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let prob = if lower == 0 {
|
||||
dist.cdf(upper)
|
||||
} else {
|
||||
let cdf_upper = dist.cdf(upper);
|
||||
let cdf_below_lower = dist.cdf(lower - 1);
|
||||
cdf_upper - cdf_below_lower
|
||||
};
|
||||
|
||||
if prob.is_nan() || prob.is_infinite() {
|
||||
return CalcResult::new_error(
|
||||
Error::NUM,
|
||||
cell,
|
||||
"Invalid result for BINOM.DIST.RANGE".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
CalcResult::Number(prob)
|
||||
}
|
||||
|
||||
pub(crate) fn fn_binom_inv(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
if args.len() != 3 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
|
||||
// trials
|
||||
let trials = match self.get_number_no_bools(&args[0], cell) {
|
||||
Ok(f) => f.trunc(),
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
// probability_s
|
||||
let p = match self.get_number_no_bools(&args[1], cell) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
// alpha
|
||||
let alpha = match self.get_number_no_bools(&args[2], cell) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
if trials < 0.0
|
||||
|| trials > u64::MAX as f64
|
||||
|| p.is_nan()
|
||||
|| !(0.0..=1.0).contains(&p)
|
||||
|| alpha.is_nan()
|
||||
|| !(0.0..=1.0).contains(&alpha)
|
||||
{
|
||||
return CalcResult::new_error(
|
||||
Error::NUM,
|
||||
cell,
|
||||
"Invalid parameters for BINOM.INV".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let n = trials as u64;
|
||||
|
||||
let dist = match Binomial::new(p, n) {
|
||||
Ok(d) => d,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::NUM,
|
||||
cell,
|
||||
"Invalid parameters for binomial distribution".to_string(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// DiscreteCDF::inverse_cdf returns u64 for binomial
|
||||
let k = statrs::distribution::DiscreteCDF::inverse_cdf(&dist, alpha);
|
||||
|
||||
CalcResult::Number(k as f64)
|
||||
}
|
||||
|
||||
pub(crate) fn fn_negbinom_dist(
|
||||
&mut self,
|
||||
args: &[Node],
|
||||
cell: CellReferenceIndex,
|
||||
) -> CalcResult {
|
||||
use statrs::distribution::{Discrete, DiscreteCDF, NegativeBinomial};
|
||||
|
||||
if args.len() != 4 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
|
||||
let number_f = match self.get_number_no_bools(&args[0], cell) {
|
||||
Ok(f) => f.trunc(),
|
||||
Err(e) => return e,
|
||||
};
|
||||
let number_s = match self.get_number_no_bools(&args[1], cell) {
|
||||
Ok(f) => f.trunc(),
|
||||
Err(e) => return e,
|
||||
};
|
||||
let probability_s = match self.get_number_no_bools(&args[2], cell) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return e,
|
||||
};
|
||||
let cumulative = match self.get_boolean(&args[3], cell) {
|
||||
Ok(b) => b,
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
if number_f < 0.0 || number_s < 1.0 || !(0.0..=1.0).contains(&probability_s) {
|
||||
return CalcResult::Error {
|
||||
error: Error::NUM,
|
||||
origin: cell,
|
||||
message: "Invalid parameter for NEGBINOM.DIST".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
// Guard against absurdly large failures that won't fit in u64
|
||||
if number_f > (u64::MAX as f64) {
|
||||
return CalcResult::Error {
|
||||
error: Error::NUM,
|
||||
origin: cell,
|
||||
message: "Invalid parameter for NEGBINOM.DIST".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
let dist = match NegativeBinomial::new(number_s, probability_s) {
|
||||
Ok(d) => d,
|
||||
Err(_) => {
|
||||
return CalcResult::Error {
|
||||
error: Error::NUM,
|
||||
origin: cell,
|
||||
message: "Invalid parameter for NEGBINOM.DIST".to_string(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let f_u = number_f as u64;
|
||||
let result = if cumulative {
|
||||
dist.cdf(f_u)
|
||||
} else {
|
||||
dist.pmf(f_u)
|
||||
};
|
||||
|
||||
if !result.is_finite() {
|
||||
return CalcResult::Error {
|
||||
error: Error::NUM,
|
||||
origin: cell,
|
||||
message: "Invalid parameter for NEGBINOM.DIST".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
CalcResult::Number(result)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user