1use rhai::plugin::*;
2
3fn extremum_index<T: PartialOrd>(
4 values: &[T],
5 direction: std::cmp::Ordering,
6) -> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
7 let mut best = 0;
8 for (index, value) in values.iter().enumerate() {
9 let ordering = value.partial_cmp(&values[best]).ok_or_else(|| {
10 rhai::EvalAltResult::ErrorArithmetic(
11 "Cannot compare NaN values".into(),
12 rhai::Position::NONE,
13 )
14 })?;
15 if ordering == direction {
16 best = index;
17 }
18 }
19 Ok(rhai::Dynamic::from_int(best as rhai::INT))
20}
21
22#[export_module]
23pub mod stats {
24 use super::extremum_index;
25 #[cfg(feature = "nalgebra")]
26 use crate::matrix::RhaiMatrix;
27 use crate::{
28 array_to_vec_float, array_to_vec_int, if_list_convert_to_vec_float_and_do,
29 if_list_do_int_or_do_float,
30 };
31 #[cfg(feature = "nalgebra")]
32 use rhai::Map;
33 use rhai::{Array, Dynamic, EvalAltResult, Position, FLOAT, INT};
34
35 #[cfg(feature = "nalgebra")]
36 use std::collections::BTreeMap;
37 use std::collections::HashMap;
38
39 #[rhai_fn(name = "max", return_raw)]
50 pub fn gen_max(a: Dynamic, b: Dynamic) -> Result<Dynamic, Box<EvalAltResult>> {
51 array_max(&mut vec![a, b])
52 }
53
54 #[rhai_fn(name = "max", return_raw)]
65 pub fn array_max(arr: &mut Array) -> Result<Dynamic, Box<EvalAltResult>> {
66 if_list_do_int_or_do_float(
67 arr,
68 |arr: &mut Array| {
69 let mut y = array_to_vec_int(arr);
70 y.sort();
71 Ok(Dynamic::from(y[y.len() - 1]))
72 },
73 |arr: &mut Array| {
74 let mut y = array_to_vec_float(arr);
75 y.sort_by(|a, b| a.partial_cmp(b).unwrap());
76 Ok(Dynamic::from(y[y.len() - 1]))
77 },
78 )
79 }
80
81 #[rhai_fn(name = "min", return_raw)]
93 pub fn gen_min(a: Dynamic, b: Dynamic) -> Result<Dynamic, Box<EvalAltResult>> {
94 array_min(&mut vec![a, b])
95 }
96
97 #[rhai_fn(name = "min", return_raw, pure)]
109 pub fn array_min(arr: &mut Array) -> Result<Dynamic, Box<EvalAltResult>> {
110 if_list_do_int_or_do_float(
111 arr,
112 |arr: &mut Array| {
113 let mut y = array_to_vec_int(arr);
114 y.sort();
115 Ok(Dynamic::from(y[0]))
116 },
117 |arr: &mut Array| {
118 let mut y = array_to_vec_float(arr);
119 y.sort_by(|a, b| a.partial_cmp(b).unwrap());
120 Ok(Dynamic::from(y[0]))
121 },
122 )
123 }
124
125 #[rhai_fn(name = "bounds", return_raw)]
132 pub fn bounds(arr: &mut Array) -> Result<Array, Box<EvalAltResult>> {
133 match (array_min(arr), array_max(arr)) {
134 (Ok(low), Ok(high)) => Ok(vec![low, high]),
135 (Ok(_), Err(high)) => Err(high),
136 (Err(low), Ok(_)) => Err(low),
137 (Err(low), Err(_)) => Err(low),
138 }
139 }
140
141 #[rhai_fn(name = "maxk", return_raw, pure)]
154 pub fn maxk(arr: &mut Array, k: INT) -> Result<Array, Box<EvalAltResult>> {
155 if_list_do_int_or_do_float(
156 arr,
157 |arr: &mut Array| {
158 let mut y = array_to_vec_int(arr);
159 y.sort();
160 let r = (y.len() - (k as usize))..(y.len());
161 let mut v = Array::new();
162 for idx in r {
163 v.push(Dynamic::from(y[idx]));
164 }
165 Ok(v)
166 },
167 |arr: &mut Array| {
168 let mut y = array_to_vec_float(arr);
169 y.sort_by(|a, b| a.partial_cmp(b).unwrap());
170 let r = (y.len() - (k as usize))..(y.len());
171 let mut v = Array::new();
172 for idx in r {
173 v.push(Dynamic::from(y[idx]));
174 }
175 Ok(v)
176 },
177 )
178 }
179
180 #[rhai_fn(name = "mink", return_raw, pure)]
193 pub fn mink(arr: &mut Array, k: INT) -> Result<Array, Box<EvalAltResult>> {
194 if_list_do_int_or_do_float(
195 arr,
196 |arr| {
197 let mut y = array_to_vec_int(arr);
198 y.sort();
199 let r = (0 as usize)..(k as usize);
200 let mut v = Array::new();
201 for idx in r {
202 v.push(Dynamic::from(y[idx]));
203 }
204 Ok(v)
205 },
206 |arr| {
207 let mut y = array_to_vec_float(arr);
208 y.sort_by(|a, b| a.partial_cmp(b).unwrap());
209 let r = (0 as usize)..(k as usize);
210 let mut v = Array::new();
211 for idx in r {
212 v.push(Dynamic::from(y[idx]));
213 }
214 Ok(v)
215 },
216 )
217 }
218
219 #[rhai_fn(name = "sum", return_raw, pure)]
232 pub fn sum(arr: &mut Array) -> Result<Dynamic, Box<EvalAltResult>> {
233 if arr.is_empty() {
234 return Ok(Dynamic::from_int(0));
235 }
236 if_list_do_int_or_do_float(
237 arr,
238 |arr| {
239 let y = array_to_vec_int(arr);
240 Ok(Dynamic::from_int(y.iter().sum()))
241 },
242 |arr| {
243 let y = array_to_vec_float(arr);
244 Ok(Dynamic::from_float(y.iter().sum()))
245 },
246 )
247 }
248
249 #[rhai_fn(name = "mean", return_raw, pure)]
257 pub fn mean(arr: &mut Array) -> Result<Dynamic, Box<EvalAltResult>> {
258 if_list_do_int_or_do_float(
259 arr,
260 |arr: &mut Array| {
261 sum(arr)
262 .map(|s| Dynamic::from_float(s.as_int().unwrap() as FLOAT / arr.len() as FLOAT))
263 },
264 |arr: &mut Array| {
265 sum(arr).map(|s| Dynamic::from_float(s.as_float().unwrap() / arr.len() as FLOAT))
266 },
267 )
268 }
269
270 #[rhai_fn(name = "argmax", return_raw, pure)]
278 pub fn argmax(arr: &mut Array) -> Result<Dynamic, Box<EvalAltResult>> {
279 if_list_do_int_or_do_float(
280 arr,
281 |values| extremum_index(&array_to_vec_int(values), std::cmp::Ordering::Greater),
282 |values| extremum_index(&array_to_vec_float(values), std::cmp::Ordering::Greater),
283 )
284 }
285
286 #[rhai_fn(name = "argmin", return_raw, pure)]
294 pub fn argmin(arr: &mut Array) -> Result<Dynamic, Box<EvalAltResult>> {
295 if_list_do_int_or_do_float(
296 arr,
297 |values| extremum_index(&array_to_vec_int(values), std::cmp::Ordering::Less),
298 |values| extremum_index(&array_to_vec_float(values), std::cmp::Ordering::Less),
299 )
300 }
301
302 #[rhai_fn(name = "prod", return_raw, pure)]
315 pub fn prod(arr: &mut Array) -> Result<Dynamic, Box<EvalAltResult>> {
316 if arr.is_empty() {
317 return Ok(Dynamic::from_int(1));
318 }
319 if_list_do_int_or_do_float(
320 arr,
321 |arr| {
322 let mut p = 1 as INT;
323 for el in arr {
324 p *= el.as_int().unwrap()
325 }
326 Ok(Dynamic::from_int(p))
327 },
328 |arr| {
329 let mut p = 1.0 as FLOAT;
330 for el in arr {
331 p *= el.as_float().unwrap()
332 }
333 Ok(Dynamic::from_float(p))
334 },
335 )
336 }
337
338 #[rhai_fn(name = "variance", return_raw, pure)]
345 pub fn variance(arr: &mut Array) -> Result<Dynamic, Box<EvalAltResult>> {
346 let m = mean(arr).map(|med| med.as_float().unwrap())?;
347
348 if_list_convert_to_vec_float_and_do(arr, |x| {
349 let mut sum = 0.0 as FLOAT;
350
351 for v in &x {
352 sum += (v - m).powi(2)
353 }
354 let d = sum / (x.len() as FLOAT - 1.0);
355 Ok(Dynamic::from_float(d))
356 })
357 }
358
359 #[rhai_fn(name = "std", return_raw, pure)]
366 pub fn std(arr: &mut Array) -> Result<Dynamic, Box<EvalAltResult>> {
367 variance(arr).map(|v| Dynamic::from_float(v.as_float().unwrap().sqrt()))
368 }
369
370 #[rhai_fn(name = "rms", return_raw, pure)]
377 pub fn rms(arr: &mut Array) -> Result<Dynamic, Box<EvalAltResult>> {
378 if_list_convert_to_vec_float_and_do(arr, |arr| {
379 let mut sum = 0.0 as FLOAT;
380 for v in &arr {
381 sum += v.powi(2)
382 }
383 let d = sum / (arr.len() as FLOAT);
384 Ok(Dynamic::from_float(d.sqrt()))
385 })
386 }
387
388 #[rhai_fn(name = "median", return_raw, pure)]
395 pub fn median(arr: &mut Array) -> Result<Dynamic, Box<EvalAltResult>> {
396 if_list_convert_to_vec_float_and_do(arr, |mut x| {
397 x.sort_by(|a, b| a.partial_cmp(b).unwrap());
398
399 let med = if x.len() % 2 == 1 {
400 x[(x.len() - 1) / 2]
401 } else {
402 (x[x.len() / 2] + x[x.len() / 2 - 1]) / 2.0
403 };
404
405 Ok(Dynamic::from_float(med))
406 })
407 }
408
409 #[rhai_fn(name = "mad", return_raw, pure)]
416 pub fn mad(arr: &mut Array) -> Result<Dynamic, Box<EvalAltResult>> {
417 let m = median(arr).map(|med| med.as_float().unwrap())?;
418
419 if_list_convert_to_vec_float_and_do(arr, |x| {
420 let mut dev = vec![];
421 for v in x {
422 dev.push(Dynamic::from_float((v - m).abs()));
423 }
424 median(&mut dev)
425 })
426 }
427
428 #[rhai_fn(name = "prctile", return_raw, pure)]
449 pub fn prctile(arr: &mut Array, p: Dynamic) -> Result<FLOAT, Box<EvalAltResult>> {
450 if arr.is_empty() {
451 return Err(EvalAltResult::ErrorArithmetic(
452 "Array must not be empty".to_string(),
453 Position::NONE,
454 )
455 .into());
456 }
457 if !p.is_float() && !p.is_int() {
458 return Err(EvalAltResult::ErrorArithmetic(
459 "Percentile value must either be INT or FLOAT".to_string(),
460 Position::NONE,
461 )
462 .into());
463 }
464
465 if_list_convert_to_vec_float_and_do(arr, move |mut float_array| {
466 match float_array.len() {
467 0 => unreachable!(),
468 1 => return Ok(float_array[0]),
469 _ => (),
470 }
471
472 float_array.sort_by(|a, b| a.partial_cmp(b).unwrap());
474
475 let sorted_array = float_array
476 .iter()
477 .map(|el| Dynamic::from_float(*el))
478 .collect::<Array>();
479
480 let mut x = crate::matrix_functions::linspace(
481 Dynamic::from_int(0),
482 Dynamic::from_int(100),
483 float_array.len() as INT,
484 )?;
485 crate::misc_functions::interp1(&mut x, sorted_array, p.clone())
486 })
487 }
488
489 #[rhai_fn(name = "iqr", return_raw, pure)]
496 pub fn iqr(arr: &mut Array) -> Result<FLOAT, Box<EvalAltResult>> {
497 match (
498 prctile(arr, Dynamic::from_int(25)),
499 prctile(arr, Dynamic::from_int(75)),
500 ) {
501 (Ok(low), Ok(high)) => Ok(high - low),
502 (Ok(_), Err(high)) => Err(high),
503 (Err(low), Ok(_)) => Err(low),
504 (Err(low), Err(_)) => Err(low),
505 }
506 }
507
508 #[rhai_fn(name = "mode", return_raw, pure)]
520 pub fn mode(arr: &mut Array) -> Result<Dynamic, Box<EvalAltResult>> {
521 if_list_do_int_or_do_float(
522 arr,
523 |arr| {
524 let v = array_to_vec_int(arr);
525
526 let mut counts: HashMap<INT, usize> = HashMap::new();
527
528 Ok(Dynamic::from_int(
529 v.iter()
530 .copied()
531 .max_by_key(|&n| {
532 let count = counts.entry(n).or_insert(0);
533 *count += 1;
534 *count
535 })
536 .unwrap(),
537 ))
538 },
539 |arr| {
540 let v = array_to_vec_float(arr);
541
542 let mut counts: HashMap<String, usize> = HashMap::new();
543
544 Ok(Dynamic::from_float(
545 v.iter()
546 .copied()
547 .max_by_key(|&n| {
548 let count = counts.entry(format!("{:?}", n)).or_insert(0);
549 *count += 1;
550 *count
551 })
552 .unwrap(),
553 ))
554 },
555 )
556 }
557
558 #[cfg(feature = "nalgebra")]
573 #[rhai_fn(name = "regress", return_raw, pure)]
574 pub fn regress(x: &mut Array, y: Array) -> Result<Map, Box<EvalAltResult>> {
575 use linregress::{FormulaRegressionBuilder, RegressionDataBuilder};
576 crate::matrix_functions::mat_from_array(x.clone())?;
577 let mut response = crate::matrix::numeric_vector_data(&y)?;
578 if response.len() != x.len() {
579 return Err(EvalAltResult::ErrorArithmetic(
580 "regress expects one response per predictor row".into(),
581 Position::NONE,
582 )
583 .into());
584 }
585 let x_transposed = crate::matrix_functions::transpose(RhaiMatrix::from_array(x.clone()))?;
586 let x_arr = x_transposed.to_array();
587 let mut data: Vec<(String, Vec<f64>)> = vec![];
588 let mut vars = vec![];
589 for (iter, column) in x_arr.iter().enumerate() {
590 let var_name = format!("x_{iter}");
591 vars.push(var_name.clone());
592 data.push((
593 var_name,
594 array_to_vec_float(&mut column.clone().into_array().unwrap()),
595 ));
596 }
597 data.push(("y".to_string(), array_to_vec_float(&mut response)));
598
599 let regress_data = RegressionDataBuilder::new()
600 .build_from(data)
601 .map_err(|e| EvalAltResult::ErrorArithmetic(e.to_string(), Position::NONE))?;
602
603 let model = FormulaRegressionBuilder::new()
604 .data(®ress_data)
605 .data_columns("y", vars)
606 .fit()
607 .map_err(|e| EvalAltResult::ErrorArithmetic(e.to_string(), Position::NONE))?;
608
609 let parameters = Dynamic::from_array(
610 model
611 .iter_parameter_pairs()
612 .map(|x| Dynamic::from_float(x.1))
613 .collect::<Array>(),
614 );
615 let pvalues = Dynamic::from_array(
616 model
617 .iter_p_value_pairs()
618 .map(|x| Dynamic::from_float(x.1))
619 .collect::<Array>(),
620 );
621 let standard_errors = Dynamic::from_array(
622 model
623 .iter_se_pairs()
624 .map(|x| Dynamic::from_float(x.1))
625 .collect::<Array>(),
626 );
627
628 let mut result = BTreeMap::new();
629 result.insert(
630 "intercept".into(),
631 Dynamic::from_float(model.parameters()[0]),
632 );
633 let mut params = smartstring::SmartString::new();
634 params.push_str("parameters");
635 result.insert(params, parameters);
636 let mut pv = smartstring::SmartString::new();
637 pv.push_str("pvalues");
638 result.insert(pv, pvalues);
639 let mut se = smartstring::SmartString::new();
640 se.push_str("standard_errors");
641 result.insert(se, standard_errors);
642 Ok(result)
643 }
644}