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
519
base/src/functions/statistical/standard_dev.rs
Normal file
519
base/src/functions/statistical/standard_dev.rs
Normal file
@@ -0,0 +1,519 @@
|
||||
use crate::constants::{LAST_COLUMN, LAST_ROW};
|
||||
use crate::expressions::parser::ArrayNode;
|
||||
use crate::expressions::types::CellReferenceIndex;
|
||||
use crate::{
|
||||
calc_result::CalcResult, expressions::parser::Node, expressions::token::Error, model::Model,
|
||||
};
|
||||
|
||||
impl Model {
|
||||
pub(crate) fn fn_stdev_p(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
if args.is_empty() {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
|
||||
let mut sum = 0.0;
|
||||
let mut sumsq = 0.0;
|
||||
let mut count: u64 = 0;
|
||||
|
||||
#[inline]
|
||||
fn accumulate(sum: &mut f64, sumsq: &mut f64, count: &mut u64, value: f64) {
|
||||
*sum += value;
|
||||
*sumsq += value * value;
|
||||
*count += 1;
|
||||
}
|
||||
|
||||
for arg in args {
|
||||
match self.evaluate_node_in_context(arg, cell) {
|
||||
CalcResult::Number(value) => {
|
||||
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
||||
}
|
||||
CalcResult::Range { left, right } => {
|
||||
if left.sheet != right.sheet {
|
||||
return CalcResult::new_error(
|
||||
Error::VALUE,
|
||||
cell,
|
||||
"Ranges are in different sheets".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let row1 = left.row;
|
||||
let mut row2 = right.row;
|
||||
let column1 = left.column;
|
||||
let mut column2 = right.column;
|
||||
|
||||
if row1 == 1 && row2 == LAST_ROW {
|
||||
row2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_row,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
if column1 == 1 && column2 == LAST_COLUMN {
|
||||
column2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_column,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for row in row1..row2 + 1 {
|
||||
for column in column1..(column2 + 1) {
|
||||
match self.evaluate_cell(CellReferenceIndex {
|
||||
sheet: left.sheet,
|
||||
row,
|
||||
column,
|
||||
}) {
|
||||
CalcResult::Number(value) => {
|
||||
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
||||
}
|
||||
error @ CalcResult::Error { .. } => return error,
|
||||
_ => {
|
||||
// ignore non-numeric
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CalcResult::Array(array) => {
|
||||
for row in array {
|
||||
for value in row {
|
||||
match value {
|
||||
ArrayNode::Number(value) => {
|
||||
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
||||
}
|
||||
ArrayNode::Error(error) => {
|
||||
return CalcResult::Error {
|
||||
error,
|
||||
origin: cell,
|
||||
message: "Error in array".to_string(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// ignore non-numeric
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
error @ CalcResult::Error { .. } => return error,
|
||||
_ => {
|
||||
// ignore non-numeric
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return CalcResult::new_error(
|
||||
Error::DIV,
|
||||
cell,
|
||||
"STDEV.P with no numeric data".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let n = count as f64;
|
||||
let mut var = (sumsq - (sum * sum) / n) / n;
|
||||
|
||||
// clamp tiny negatives from FP noise
|
||||
if var < 0.0 && var > -1e-12 {
|
||||
var = 0.0;
|
||||
}
|
||||
|
||||
CalcResult::Number(var.sqrt())
|
||||
}
|
||||
|
||||
pub(crate) fn fn_stdev_s(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
if args.is_empty() {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
|
||||
let mut sum = 0.0;
|
||||
let mut sumsq = 0.0;
|
||||
let mut count: u64 = 0;
|
||||
|
||||
#[inline]
|
||||
fn accumulate(sum: &mut f64, sumsq: &mut f64, count: &mut u64, value: f64) {
|
||||
*sum += value;
|
||||
*sumsq += value * value;
|
||||
*count += 1;
|
||||
}
|
||||
|
||||
for arg in args {
|
||||
match self.evaluate_node_in_context(arg, cell) {
|
||||
CalcResult::Number(value) => {
|
||||
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
||||
}
|
||||
CalcResult::Range { left, right } => {
|
||||
if left.sheet != right.sheet {
|
||||
return CalcResult::new_error(
|
||||
Error::VALUE,
|
||||
cell,
|
||||
"Ranges are in different sheets".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let row1 = left.row;
|
||||
let mut row2 = right.row;
|
||||
let column1 = left.column;
|
||||
let mut column2 = right.column;
|
||||
|
||||
if row1 == 1 && row2 == LAST_ROW {
|
||||
row2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_row,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
if column1 == 1 && column2 == LAST_COLUMN {
|
||||
column2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_column,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for row in row1..row2 + 1 {
|
||||
for column in column1..(column2 + 1) {
|
||||
match self.evaluate_cell(CellReferenceIndex {
|
||||
sheet: left.sheet,
|
||||
row,
|
||||
column,
|
||||
}) {
|
||||
CalcResult::Number(value) => {
|
||||
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
||||
}
|
||||
error @ CalcResult::Error { .. } => return error,
|
||||
_ => {
|
||||
// ignore non-numeric
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CalcResult::Array(array) => {
|
||||
for row in array {
|
||||
for value in row {
|
||||
match value {
|
||||
ArrayNode::Number(value) => {
|
||||
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
||||
}
|
||||
ArrayNode::Error(error) => {
|
||||
return CalcResult::Error {
|
||||
error,
|
||||
origin: cell,
|
||||
message: "Error in array".to_string(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// ignore non-numeric
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
error @ CalcResult::Error { .. } => return error,
|
||||
_ => {
|
||||
// ignore non-numeric
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if count <= 1 {
|
||||
return CalcResult::new_error(
|
||||
Error::DIV,
|
||||
cell,
|
||||
"STDEV.S requires at least two numeric values".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let n = count as f64;
|
||||
let mut var = (sumsq - (sum * sum) / n) / (n - 1.0);
|
||||
|
||||
if var < 0.0 && var > -1e-12 {
|
||||
var = 0.0;
|
||||
}
|
||||
|
||||
CalcResult::Number(var.sqrt())
|
||||
}
|
||||
|
||||
pub(crate) fn fn_stdeva(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
if args.is_empty() {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
|
||||
let mut sum = 0.0;
|
||||
let mut sumsq = 0.0;
|
||||
let mut count: u64 = 0;
|
||||
|
||||
#[inline]
|
||||
fn accumulate(sum: &mut f64, sumsq: &mut f64, count: &mut u64, value: f64) {
|
||||
*sum += value;
|
||||
*sumsq += value * value;
|
||||
*count += 1;
|
||||
}
|
||||
|
||||
for arg in args {
|
||||
match self.evaluate_node_in_context(arg, cell) {
|
||||
CalcResult::Number(value) => {
|
||||
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
||||
}
|
||||
CalcResult::Range { left, right } => {
|
||||
if left.sheet != right.sheet {
|
||||
return CalcResult::new_error(
|
||||
Error::VALUE,
|
||||
cell,
|
||||
"Ranges are in different sheets".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let row1 = left.row;
|
||||
let mut row2 = right.row;
|
||||
let column1 = left.column;
|
||||
let mut column2 = right.column;
|
||||
|
||||
if row1 == 1 && row2 == LAST_ROW {
|
||||
row2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_row,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
if column1 == 1 && column2 == LAST_COLUMN {
|
||||
column2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_column,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for row in row1..row2 + 1 {
|
||||
for column in column1..(column2 + 1) {
|
||||
match self.evaluate_cell(CellReferenceIndex {
|
||||
sheet: left.sheet,
|
||||
row,
|
||||
column,
|
||||
}) {
|
||||
CalcResult::Number(value) => {
|
||||
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
||||
}
|
||||
CalcResult::String(_) => {
|
||||
accumulate(&mut sum, &mut sumsq, &mut count, 0.0);
|
||||
}
|
||||
CalcResult::Boolean(value) => {
|
||||
let val = if value { 1.0 } else { 0.0 };
|
||||
accumulate(&mut sum, &mut sumsq, &mut count, val);
|
||||
}
|
||||
error @ CalcResult::Error { .. } => return error,
|
||||
_ => {
|
||||
// ignore non-numeric for now
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CalcResult::Array(array) => {
|
||||
for row in array {
|
||||
for value in row {
|
||||
match value {
|
||||
ArrayNode::Number(value) => {
|
||||
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
||||
}
|
||||
ArrayNode::Error(error) => {
|
||||
return CalcResult::Error {
|
||||
error,
|
||||
origin: cell,
|
||||
message: "Error in array".to_string(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// ignore non-numeric for now
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
error @ CalcResult::Error { .. } => return error,
|
||||
_ => {
|
||||
// ignore non-numeric for now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if count <= 1 {
|
||||
return CalcResult::new_error(
|
||||
Error::DIV,
|
||||
cell,
|
||||
"STDEVA requires at least two numeric values".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let n = count as f64;
|
||||
let mut var = (sumsq - (sum * sum) / n) / (n - 1.0);
|
||||
|
||||
if var < 0.0 && var > -1e-12 {
|
||||
var = 0.0;
|
||||
}
|
||||
|
||||
CalcResult::Number(var.sqrt())
|
||||
}
|
||||
|
||||
pub(crate) fn fn_stdevpa(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
if args.is_empty() {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
|
||||
let mut sum = 0.0;
|
||||
let mut sumsq = 0.0;
|
||||
let mut count: u64 = 0;
|
||||
|
||||
#[inline]
|
||||
fn accumulate(sum: &mut f64, sumsq: &mut f64, count: &mut u64, value: f64) {
|
||||
*sum += value;
|
||||
*sumsq += value * value;
|
||||
*count += 1;
|
||||
}
|
||||
|
||||
for arg in args {
|
||||
match self.evaluate_node_in_context(arg, cell) {
|
||||
CalcResult::Number(value) => {
|
||||
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
||||
}
|
||||
CalcResult::Range { left, right } => {
|
||||
if left.sheet != right.sheet {
|
||||
return CalcResult::new_error(
|
||||
Error::VALUE,
|
||||
cell,
|
||||
"Ranges are in different sheets".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let row1 = left.row;
|
||||
let mut row2 = right.row;
|
||||
let column1 = left.column;
|
||||
let mut column2 = right.column;
|
||||
|
||||
if row1 == 1 && row2 == LAST_ROW {
|
||||
row2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_row,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
if column1 == 1 && column2 == LAST_COLUMN {
|
||||
column2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_column,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for row in row1..row2 + 1 {
|
||||
for column in column1..(column2 + 1) {
|
||||
match self.evaluate_cell(CellReferenceIndex {
|
||||
sheet: left.sheet,
|
||||
row,
|
||||
column,
|
||||
}) {
|
||||
CalcResult::Number(value) => {
|
||||
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
||||
}
|
||||
CalcResult::String(_) => {
|
||||
accumulate(&mut sum, &mut sumsq, &mut count, 0.0);
|
||||
}
|
||||
CalcResult::Boolean(value) => {
|
||||
let val = if value { 1.0 } else { 0.0 };
|
||||
accumulate(&mut sum, &mut sumsq, &mut count, val);
|
||||
}
|
||||
error @ CalcResult::Error { .. } => return error,
|
||||
_ => {
|
||||
// ignore non-numeric for now
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CalcResult::Array(array) => {
|
||||
for row in array {
|
||||
for value in row {
|
||||
match value {
|
||||
ArrayNode::Number(value) => {
|
||||
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
||||
}
|
||||
ArrayNode::Error(error) => {
|
||||
return CalcResult::Error {
|
||||
error,
|
||||
origin: cell,
|
||||
message: "Error in array".to_string(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// ignore non-numeric for now
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
error @ CalcResult::Error { .. } => return error,
|
||||
_ => {
|
||||
// ignore non-numeric for now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return CalcResult::new_error(
|
||||
Error::DIV,
|
||||
cell,
|
||||
"STDEVPA with no numeric data".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let n = count as f64;
|
||||
let mut var = (sumsq - (sum * sum) / n) / n;
|
||||
|
||||
if var < 0.0 && var > -1e-12 {
|
||||
var = 0.0;
|
||||
}
|
||||
|
||||
CalcResult::Number(var.sqrt())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user