1use crate::interpreter::coerce::usize_to_f64;
6use crate::interpreter::value::*;
7use indexmap::IndexMap;
8use minir_macros::builtin;
9use std::f64::consts::{FRAC_1_SQRT_2, PI};
10
11fn extract_na_rm(named: &[(String, RValue)]) -> bool {
15 named.iter().any(|(n, v)| {
16 n == "na.rm" && v.as_vector().and_then(|v| v.as_logical_scalar()) == Some(true)
17 })
18}
19
20fn extract_param(
22 args: &[RValue],
23 named: &[(String, RValue)],
24 name: &str,
25 positional_index: usize,
26 default: f64,
27) -> f64 {
28 for (k, v) in named {
29 if k == name {
30 if let Some(rv) = v.as_vector() {
31 if let Some(d) = rv.as_double_scalar() {
32 return d;
33 }
34 }
35 }
36 }
37 args.get(positional_index)
38 .and_then(|v| v.as_vector())
39 .and_then(|v| v.as_double_scalar())
40 .unwrap_or(default)
41}
42
43fn extract_bool_param(
45 args: &[RValue],
46 named: &[(String, RValue)],
47 name: &str,
48 positional_index: usize,
49 default: bool,
50) -> bool {
51 for (k, v) in named {
52 if k == name {
53 if let Some(rv) = v.as_vector() {
54 if let Some(b) = rv.as_logical_scalar() {
55 return b;
56 }
57 }
58 }
59 }
60 args.get(positional_index)
61 .and_then(|v| v.as_vector())
62 .and_then(|v| v.as_logical_scalar())
63 .unwrap_or(default)
64}
65
66#[builtin(min_args = 2, namespace = "stats")]
78fn builtin_cov(args: &[RValue], _named: &[(String, RValue)]) -> Result<RValue, RError> {
79 let x_vals = args[0]
80 .as_vector()
81 .ok_or_else(|| {
82 RError::new(
83 RErrorKind::Argument,
84 "cov() requires numeric vectors".to_string(),
85 )
86 })?
87 .to_doubles();
88 let y_vals = args[1]
89 .as_vector()
90 .ok_or_else(|| {
91 RError::new(
92 RErrorKind::Argument,
93 "cov() requires numeric vectors".to_string(),
94 )
95 })?
96 .to_doubles();
97 if x_vals.len() != y_vals.len() {
98 return Err(RError::new(
99 RErrorKind::Argument,
100 format!(
101 "cov() requires vectors of equal length, got {} and {}",
102 x_vals.len(),
103 y_vals.len()
104 ),
105 ));
106 }
107
108 let pairs: Vec<(f64, f64)> = x_vals
110 .iter()
111 .zip(y_vals.iter())
112 .filter_map(|(x, y)| match (x, y) {
113 (Some(a), Some(b)) => Some((*a, *b)),
114 _ => None,
115 })
116 .collect();
117
118 let n = usize_to_f64(pairs.len());
119 if n < 2.0 {
120 return Ok(RValue::vec(Vector::Double(vec![Some(f64::NAN)].into())));
121 }
122 let mean_x = pairs.iter().map(|(x, _)| x).sum::<f64>() / n;
123 let mean_y = pairs.iter().map(|(_, y)| y).sum::<f64>() / n;
124 let cov = pairs
125 .iter()
126 .map(|(x, y)| (x - mean_x) * (y - mean_y))
127 .sum::<f64>()
128 / (n - 1.0);
129 Ok(RValue::vec(Vector::Double(vec![Some(cov)].into())))
130}
131
132#[builtin(min_args = 2, namespace = "stats")]
145fn builtin_cor(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
146 for (k, v) in named {
148 if k == "method" {
149 if let Some(vec) = v.as_vector() {
150 if let Some(s) = vec.as_character_scalar() {
151 if s != "pearson" {
152 return Err(RError::new(
153 RErrorKind::Argument,
154 format!("cor() only supports method = \"pearson\", got \"{}\"", s),
155 ));
156 }
157 }
158 }
159 }
160 }
161
162 let x_vals = args[0]
163 .as_vector()
164 .ok_or_else(|| {
165 RError::new(
166 RErrorKind::Argument,
167 "cor() requires numeric vectors".to_string(),
168 )
169 })?
170 .to_doubles();
171 let y_vals = args[1]
172 .as_vector()
173 .ok_or_else(|| {
174 RError::new(
175 RErrorKind::Argument,
176 "cor() requires numeric vectors".to_string(),
177 )
178 })?
179 .to_doubles();
180 if x_vals.len() != y_vals.len() {
181 return Err(RError::new(
182 RErrorKind::Argument,
183 format!(
184 "cor() requires vectors of equal length, got {} and {}",
185 x_vals.len(),
186 y_vals.len()
187 ),
188 ));
189 }
190
191 let pairs: Vec<(f64, f64)> = x_vals
193 .iter()
194 .zip(y_vals.iter())
195 .filter_map(|(x, y)| match (x, y) {
196 (Some(a), Some(b)) => Some((*a, *b)),
197 _ => None,
198 })
199 .collect();
200
201 let n = usize_to_f64(pairs.len());
202 if n < 2.0 {
203 return Ok(RValue::vec(Vector::Double(vec![Some(f64::NAN)].into())));
204 }
205 let mean_x = pairs.iter().map(|(x, _)| x).sum::<f64>() / n;
206 let mean_y = pairs.iter().map(|(_, y)| y).sum::<f64>() / n;
207 let cov_xy = pairs
208 .iter()
209 .map(|(x, y)| (x - mean_x) * (y - mean_y))
210 .sum::<f64>();
211 let var_x = pairs.iter().map(|(x, _)| (x - mean_x).powi(2)).sum::<f64>();
212 let var_y = pairs.iter().map(|(_, y)| (y - mean_y).powi(2)).sum::<f64>();
213 let denom = (var_x * var_y).sqrt();
214 if denom == 0.0 {
215 return Ok(RValue::vec(Vector::Double(vec![Some(f64::NAN)].into())));
216 }
217 let r = cov_xy / denom;
218 Ok(RValue::vec(Vector::Double(vec![Some(r)].into())))
219}
220
221#[builtin(name = "weighted.mean", min_args = 2, namespace = "stats")]
232fn builtin_weighted_mean(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
233 let na_rm = extract_na_rm(named);
234 let x_vals = args[0]
235 .as_vector()
236 .ok_or_else(|| {
237 RError::new(
238 RErrorKind::Argument,
239 "weighted.mean() requires a numeric vector for 'x'".to_string(),
240 )
241 })?
242 .to_doubles();
243 let w_vals = args[1]
244 .as_vector()
245 .ok_or_else(|| {
246 RError::new(
247 RErrorKind::Argument,
248 "weighted.mean() requires a numeric vector for 'w'".to_string(),
249 )
250 })?
251 .to_doubles();
252 if x_vals.len() != w_vals.len() {
253 return Err(RError::new(
254 RErrorKind::Argument,
255 format!(
256 "weighted.mean() requires 'x' and 'w' of equal length, got {} and {}",
257 x_vals.len(),
258 w_vals.len()
259 ),
260 ));
261 }
262
263 let mut sum_wx = 0.0;
264 let mut sum_w = 0.0;
265 for (x, w) in x_vals.iter().zip(w_vals.iter()) {
266 match (x, w) {
267 (Some(xv), Some(wv)) => {
268 sum_wx += xv * wv;
269 sum_w += wv;
270 }
271 _ if !na_rm => {
272 return Ok(RValue::vec(Vector::Double(vec![None].into())));
273 }
274 _ => {}
275 }
276 }
277 if sum_w == 0.0 {
278 return Ok(RValue::vec(Vector::Double(vec![Some(f64::NAN)].into())));
279 }
280 Ok(RValue::vec(Vector::Double(
281 vec![Some(sum_wx / sum_w)].into(),
282 )))
283}
284
285#[builtin(min_args = 1, namespace = "stats")]
300fn builtin_scale(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
301 let do_center = extract_bool_param(args, named, "center", 1, true);
302 let do_scale = extract_bool_param(args, named, "scale", 2, true);
303
304 let vals = args[0]
305 .as_vector()
306 .ok_or_else(|| {
307 RError::new(
308 RErrorKind::Argument,
309 "scale() requires a numeric vector".to_string(),
310 )
311 })?
312 .to_doubles();
313
314 let non_na: Vec<f64> = vals.iter().copied().flatten().collect();
315 let n = usize_to_f64(non_na.len());
316
317 let center_val = if do_center && n > 0.0 {
318 non_na.iter().sum::<f64>() / n
319 } else {
320 0.0
321 };
322
323 let scale_val = if do_scale && n > 1.0 {
324 let mean = if do_center {
325 center_val
326 } else {
327 non_na.iter().sum::<f64>() / n
328 };
329 let var = non_na.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (n - 1.0);
330 var.sqrt()
331 } else if do_scale {
332 f64::NAN
334 } else {
335 1.0
336 };
337
338 let result: Vec<Option<f64>> = vals
339 .iter()
340 .map(|x| {
341 x.map(|v| {
342 let centered = if do_center { v - center_val } else { v };
343 if do_scale && scale_val != 0.0 && !scale_val.is_nan() {
344 centered / scale_val
345 } else {
346 centered
347 }
348 })
349 })
350 .collect();
351
352 let mut attrs: IndexMap<String, RValue> = IndexMap::new();
353 if do_center {
354 attrs.insert(
355 "scaled:center".to_string(),
356 RValue::vec(Vector::Double(vec![Some(center_val)].into())),
357 );
358 }
359 if do_scale {
360 attrs.insert(
361 "scaled:scale".to_string(),
362 RValue::vec(Vector::Double(vec![Some(scale_val)].into())),
363 );
364 }
365
366 Ok(RValue::Vector(RVector {
367 inner: Vector::Double(result.into()),
368 attrs: if attrs.is_empty() {
369 None
370 } else {
371 Some(Box::new(attrs))
372 },
373 }))
374}
375
376#[builtin(name = "complete.cases", min_args = 1, namespace = "stats")]
389fn builtin_complete_cases(args: &[RValue], _named: &[(String, RValue)]) -> Result<RValue, RError> {
390 let first_vec = args[0].as_vector().ok_or_else(|| {
392 RError::new(
393 RErrorKind::Argument,
394 "complete.cases() requires vector arguments".to_string(),
395 )
396 })?;
397 let n = first_vec.len();
398
399 let mut cols: Vec<Vec<bool>> = Vec::with_capacity(args.len());
401 for arg in args {
402 let vec = arg.as_vector().ok_or_else(|| {
403 RError::new(
404 RErrorKind::Argument,
405 "complete.cases() requires vector arguments".to_string(),
406 )
407 })?;
408 if vec.len() != n {
409 return Err(RError::new(
410 RErrorKind::Argument,
411 "complete.cases() requires all arguments to have the same length".to_string(),
412 ));
413 }
414 let is_na = is_na_vec(vec);
415 cols.push(is_na);
416 }
417
418 let result: Vec<Option<bool>> = (0..n)
420 .map(|i| Some(!cols.iter().any(|col| col[i])))
421 .collect();
422 Ok(RValue::vec(Vector::Logical(result.into())))
423}
424
425fn is_na_vec(v: &Vector) -> Vec<bool> {
427 match v {
428 Vector::Logical(vals) => vals.iter().map(|x| x.is_none()).collect(),
429 Vector::Integer(vals) => vals.iter().map(|x| x.is_none()).collect(),
430 Vector::Double(vals) => vals
431 .iter()
432 .map(|x| x.is_none() || x.map(|f| f.is_nan()).unwrap_or(false))
433 .collect(),
434 Vector::Complex(vals) => vals.iter().map(|x| x.is_none()).collect(),
435 Vector::Character(vals) => vals.iter().map(|x| x.is_none()).collect(),
436 Vector::Raw(vals) => vals.iter().map(|_| false).collect(),
437 }
438}
439
440#[builtin(name = "na.omit", min_args = 1, namespace = "stats")]
452fn builtin_na_omit(args: &[RValue], _named: &[(String, RValue)]) -> Result<RValue, RError> {
453 let vec = args[0].as_vector().ok_or_else(|| {
454 RError::new(
455 RErrorKind::Argument,
456 "na.omit() requires a vector argument".to_string(),
457 )
458 })?;
459
460 let na_flags = is_na_vec(vec);
461 let na_indices: Vec<Option<i64>> = na_flags
462 .iter()
463 .enumerate()
464 .filter(|(_, &is_na)| is_na)
465 .map(|(i, _)| {
466 i64::try_from(i + 1).ok()
468 })
469 .collect();
470
471 let result = filter_non_na(vec, &na_flags);
472
473 let mut attrs: IndexMap<String, RValue> = IndexMap::new();
474 if !na_indices.is_empty() {
475 attrs.insert(
476 "na.action".to_string(),
477 RValue::vec(Vector::Integer(na_indices.into())),
478 );
479 }
480
481 Ok(RValue::Vector(RVector {
482 inner: result,
483 attrs: if attrs.is_empty() {
484 None
485 } else {
486 Some(Box::new(attrs))
487 },
488 }))
489}
490
491fn filter_non_na(v: &Vector, na_flags: &[bool]) -> Vector {
493 match v {
494 Vector::Logical(vals) => Vector::Logical(
495 vals.iter()
496 .zip(na_flags)
497 .filter(|(_, &is_na)| !is_na)
498 .map(|(x, _)| *x)
499 .collect::<Vec<_>>()
500 .into(),
501 ),
502 Vector::Integer(vals) => Vector::Integer(
503 vals.iter_opt()
504 .zip(na_flags)
505 .filter(|(_, &is_na)| !is_na)
506 .map(|(x, _)| x)
507 .collect::<Vec<_>>()
508 .into(),
509 ),
510 Vector::Double(vals) => Vector::Double(
511 vals.iter_opt()
512 .zip(na_flags)
513 .filter(|(_, &is_na)| !is_na)
514 .map(|(x, _)| x)
515 .collect::<Vec<_>>()
516 .into(),
517 ),
518 Vector::Complex(vals) => Vector::Complex(
519 vals.iter()
520 .zip(na_flags)
521 .filter(|(_, &is_na)| !is_na)
522 .map(|(x, _)| *x)
523 .collect::<Vec<_>>()
524 .into(),
525 ),
526 Vector::Character(vals) => Vector::Character(
527 vals.iter()
528 .zip(na_flags)
529 .filter(|(_, &is_na)| !is_na)
530 .map(|(x, _)| x.clone())
531 .collect::<Vec<_>>()
532 .into(),
533 ),
534 Vector::Raw(vals) => Vector::Raw(
535 vals.iter()
536 .zip(na_flags)
537 .filter(|(_, &is_na)| !is_na)
538 .map(|(x, _)| *x)
539 .collect(),
540 ),
541 }
542}
543
544fn extract_log_flag(named: &[(String, RValue)]) -> bool {
550 named
551 .iter()
552 .find(|(n, _)| n == "log")
553 .and_then(|(_, v)| v.as_vector()?.as_logical_scalar())
554 .unwrap_or(false)
555}
556
557fn extract_lower_tail(named: &[(String, RValue)]) -> bool {
559 named
560 .iter()
561 .find(|(n, _)| n == "lower.tail")
562 .and_then(|(_, v)| v.as_vector()?.as_logical_scalar())
563 .unwrap_or(true)
564}
565
566fn extract_log_p(named: &[(String, RValue)]) -> bool {
568 named
569 .iter()
570 .find(|(n, _)| n == "log.p")
571 .and_then(|(_, v)| v.as_vector()?.as_logical_scalar())
572 .unwrap_or(false)
573}
574
575fn apply_d_log(val: f64, log_flag: bool) -> f64 {
577 if log_flag {
578 val.ln()
579 } else {
580 val
581 }
582}
583
584fn apply_p_flags(val: f64, lower_tail: bool, log_p: bool) -> f64 {
586 let p = if lower_tail { val } else { 1.0 - val };
587 if log_p {
588 p.ln()
589 } else {
590 p
591 }
592}
593
594fn apply_q_flags(p: f64, lower_tail: bool, log_p: bool) -> f64 {
596 let p = if log_p { p.exp() } else { p };
597 if lower_tail {
598 p
599 } else {
600 1.0 - p
601 }
602}
603
604fn std_normal_pdf(x: f64) -> f64 {
610 (-0.5 * x * x).exp() / (2.0 * PI).sqrt()
611}
612
613fn std_normal_cdf(x: f64) -> f64 {
616 0.5 * erfc(-x * FRAC_1_SQRT_2)
617}
618
619fn erfc(x: f64) -> f64 {
621 libm::erfc(x)
622}
623
624fn std_normal_quantile(p: f64) -> f64 {
629 if p <= 0.0 {
630 return f64::NEG_INFINITY;
631 }
632 if p >= 1.0 {
633 return f64::INFINITY;
634 }
635 if (p - 0.5).abs() < f64::EPSILON {
636 return 0.0;
637 }
638
639 const A: [f64; 6] = [
641 -3.969_683_028_665_376e1,
642 2.209_460_984_245_205e2,
643 -2.759_285_104_469_687e2,
644 1.383_577_518_672_69e2,
645 -3.066_479_806_614_716e1,
646 2.506_628_277_459_239,
647 ];
648 const B: [f64; 5] = [
649 -5.447_609_879_822_406e1,
650 1.615_858_368_580_409e2,
651 -1.556_989_798_598_866e2,
652 6.680_131_188_771_972e1,
653 -1.328_068_155_288_572e1,
654 ];
655 const C: [f64; 6] = [
656 -7.784_894_002_430_293e-3,
657 -3.223_964_580_411_365e-1,
658 -2.400_758_277_161_838,
659 -2.549_732_539_343_734,
660 4.374_664_141_464_968,
661 2.938_163_982_698_783,
662 ];
663 const D: [f64; 4] = [
664 7.784_695_709_041_462e-3,
665 3.224_671_290_700_398e-1,
666 2.445_134_137_142_996,
667 3.754_408_661_907_416,
668 ];
669
670 const P_LOW: f64 = 0.02425;
671 const P_HIGH: f64 = 1.0 - P_LOW;
672
673 if p < P_LOW {
674 let q = (-2.0 * p.ln()).sqrt();
676 (((((C[0] * q + C[1]) * q + C[2]) * q + C[3]) * q + C[4]) * q + C[5])
677 / ((((D[0] * q + D[1]) * q + D[2]) * q + D[3]) * q + 1.0)
678 } else if p <= P_HIGH {
679 let q = p - 0.5;
681 let r = q * q;
682 (((((A[0] * r + A[1]) * r + A[2]) * r + A[3]) * r + A[4]) * r + A[5]) * q
683 / (((((B[0] * r + B[1]) * r + B[2]) * r + B[3]) * r + B[4]) * r + 1.0)
684 } else {
685 let q = (-2.0 * (1.0 - p).ln()).sqrt();
687 -(((((C[0] * q + C[1]) * q + C[2]) * q + C[3]) * q + C[4]) * q + C[5])
688 / ((((D[0] * q + D[1]) * q + D[2]) * q + D[3]) * q + 1.0)
689 }
690}
691
692#[builtin(min_args = 1, namespace = "stats")]
700fn builtin_dnorm(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
701 let mean = extract_param(args, named, "mean", 1, 0.0);
702 let sd = extract_param(args, named, "sd", 2, 1.0);
703 let log_flag = extract_log_flag(named);
704 if sd < 0.0 {
705 return Err(RError::new(
706 RErrorKind::Argument,
707 "dnorm(): 'sd' must be non-negative".to_string(),
708 ));
709 }
710 let vals = args[0]
711 .as_vector()
712 .ok_or_else(|| {
713 RError::new(
714 RErrorKind::Argument,
715 "dnorm() requires a numeric vector".to_string(),
716 )
717 })?
718 .to_doubles();
719 let result: Vec<Option<f64>> = vals
720 .iter()
721 .map(|x| {
722 x.map(|v| {
723 let d = if sd == 0.0 {
724 if v == mean {
725 f64::INFINITY
726 } else {
727 0.0
728 }
729 } else {
730 let z = (v - mean) / sd;
731 std_normal_pdf(z) / sd
732 };
733 apply_d_log(d, log_flag)
734 })
735 })
736 .collect();
737 Ok(RValue::vec(Vector::Double(result.into())))
738}
739
740#[builtin(min_args = 1, namespace = "stats")]
749fn builtin_pnorm(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
750 let mean = extract_param(args, named, "mean", 1, 0.0);
751 let sd = extract_param(args, named, "sd", 2, 1.0);
752 let lower_tail = extract_lower_tail(named);
753 let log_p = extract_log_p(named);
754 if sd < 0.0 {
755 return Err(RError::new(
756 RErrorKind::Argument,
757 "pnorm(): 'sd' must be non-negative".to_string(),
758 ));
759 }
760 let vals = args[0]
761 .as_vector()
762 .ok_or_else(|| {
763 RError::new(
764 RErrorKind::Argument,
765 "pnorm() requires a numeric vector".to_string(),
766 )
767 })?
768 .to_doubles();
769 let result: Vec<Option<f64>> = vals
770 .iter()
771 .map(|x| {
772 x.map(|v| {
773 let p = if sd == 0.0 {
774 if v < mean {
775 0.0
776 } else {
777 1.0
778 }
779 } else {
780 let z = (v - mean) / sd;
781 std_normal_cdf(z)
782 };
783 apply_p_flags(p, lower_tail, log_p)
784 })
785 })
786 .collect();
787 Ok(RValue::vec(Vector::Double(result.into())))
788}
789
790#[builtin(min_args = 1, namespace = "stats")]
799fn builtin_qnorm(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
800 let mean = extract_param(args, named, "mean", 1, 0.0);
801 let sd = extract_param(args, named, "sd", 2, 1.0);
802 let lower_tail = extract_lower_tail(named);
803 let log_p = extract_log_p(named);
804 if sd < 0.0 {
805 return Err(RError::new(
806 RErrorKind::Argument,
807 "qnorm(): 'sd' must be non-negative".to_string(),
808 ));
809 }
810 let vals = args[0]
811 .as_vector()
812 .ok_or_else(|| {
813 RError::new(
814 RErrorKind::Argument,
815 "qnorm() requires a numeric vector".to_string(),
816 )
817 })?
818 .to_doubles();
819 let result: Vec<Option<f64>> = vals
820 .iter()
821 .map(|x| {
822 x.map(|raw_p| {
823 let p = apply_q_flags(raw_p, lower_tail, log_p);
824 if !(0.0..=1.0).contains(&p) {
825 f64::NAN
826 } else {
827 std_normal_quantile(p) * sd + mean
828 }
829 })
830 })
831 .collect();
832 Ok(RValue::vec(Vector::Double(result.into())))
833}
834
835#[builtin(min_args = 1, namespace = "stats")]
846fn builtin_dunif(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
847 let min = extract_param(args, named, "min", 1, 0.0);
848 let max = extract_param(args, named, "max", 2, 1.0);
849 let log_flag = extract_log_flag(named);
850 if min > max {
851 return Err(RError::new(
852 RErrorKind::Argument,
853 "dunif(): 'min' must not be greater than 'max'".to_string(),
854 ));
855 }
856 let vals = args[0]
857 .as_vector()
858 .ok_or_else(|| {
859 RError::new(
860 RErrorKind::Argument,
861 "dunif() requires a numeric vector".to_string(),
862 )
863 })?
864 .to_doubles();
865 let result: Vec<Option<f64>> = vals
866 .iter()
867 .map(|x| {
868 x.map(|v| {
869 let d = if min == max {
870 if v == min {
871 f64::INFINITY
872 } else {
873 0.0
874 }
875 } else if v >= min && v <= max {
876 1.0 / (max - min)
877 } else {
878 0.0
879 };
880 apply_d_log(d, log_flag)
881 })
882 })
883 .collect();
884 Ok(RValue::vec(Vector::Double(result.into())))
885}
886
887#[builtin(min_args = 1, namespace = "stats")]
894fn builtin_punif(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
895 let min = extract_param(args, named, "min", 1, 0.0);
896 let max = extract_param(args, named, "max", 2, 1.0);
897 let lower_tail = extract_lower_tail(named);
898 let log_p = extract_log_p(named);
899 if min > max {
900 return Err(RError::new(
901 RErrorKind::Argument,
902 "punif(): 'min' must not be greater than 'max'".to_string(),
903 ));
904 }
905 let vals = args[0]
906 .as_vector()
907 .ok_or_else(|| {
908 RError::new(
909 RErrorKind::Argument,
910 "punif() requires a numeric vector".to_string(),
911 )
912 })?
913 .to_doubles();
914 let result: Vec<Option<f64>> = vals
915 .iter()
916 .map(|x| {
917 x.map(|v| {
918 let p = if min == max {
919 if v < min {
920 0.0
921 } else {
922 1.0
923 }
924 } else if v <= min {
925 0.0
926 } else if v >= max {
927 1.0
928 } else {
929 (v - min) / (max - min)
930 };
931 apply_p_flags(p, lower_tail, log_p)
932 })
933 })
934 .collect();
935 Ok(RValue::vec(Vector::Double(result.into())))
936}
937
938#[builtin(min_args = 1, namespace = "stats")]
945fn builtin_qunif(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
946 let min = extract_param(args, named, "min", 1, 0.0);
947 let max = extract_param(args, named, "max", 2, 1.0);
948 let lower_tail = extract_lower_tail(named);
949 let log_p = extract_log_p(named);
950 if min > max {
951 return Err(RError::new(
952 RErrorKind::Argument,
953 "qunif(): 'min' must not be greater than 'max'".to_string(),
954 ));
955 }
956 let vals = args[0]
957 .as_vector()
958 .ok_or_else(|| {
959 RError::new(
960 RErrorKind::Argument,
961 "qunif() requires a numeric vector".to_string(),
962 )
963 })?
964 .to_doubles();
965 let result: Vec<Option<f64>> = vals
966 .iter()
967 .map(|x| {
968 x.map(|raw_p| {
969 let p = apply_q_flags(raw_p, lower_tail, log_p);
970 if !(0.0..=1.0).contains(&p) {
971 f64::NAN
972 } else {
973 min + p * (max - min)
974 }
975 })
976 })
977 .collect();
978 Ok(RValue::vec(Vector::Double(result.into())))
979}
980
981fn ln_gamma(x: f64) -> f64 {
987 libm::lgamma(x)
988}
989
990fn lchoose(n: f64, k: f64) -> f64 {
992 ln_gamma(n + 1.0) - ln_gamma(k + 1.0) - ln_gamma(n - k + 1.0)
993}
994
995fn regularized_gamma_p(a: f64, x: f64) -> f64 {
998 if x <= 0.0 {
999 return 0.0;
1000 }
1001 if a <= 0.0 {
1002 return 1.0;
1003 }
1004 if x < a + 1.0 {
1005 gamma_series(a, x)
1006 } else {
1007 1.0 - gamma_cf_lentz(a, x, ln_gamma(a))
1008 }
1009}
1010
1011fn gamma_series(a: f64, x: f64) -> f64 {
1013 let ln_gamma_a = ln_gamma(a);
1014 let mut sum = 1.0 / a;
1015 let mut term = 1.0 / a;
1016 for n in 1..200 {
1017 let nf = f64::from(n);
1018 term *= x / (a + nf);
1019 sum += term;
1020 if term.abs() < sum.abs() * 1e-15 {
1021 break;
1022 }
1023 }
1024 sum * (-x + a * x.ln() - ln_gamma_a).exp()
1025}
1026
1027fn gamma_cf_lentz(a: f64, x: f64, ln_gamma_a: f64) -> f64 {
1029 let b0 = x + 1.0 - a;
1031 let mut f = if b0.abs() < 1e-30 { 1e-30 } else { b0 };
1032 let mut c = f;
1033 let mut d = 0.0;
1034
1035 for n in 1..200 {
1036 let nf = f64::from(n);
1037 let an = nf * (a - nf);
1038 let bn = x + 2.0 * nf + 1.0 - a;
1039
1040 d = bn + an * d;
1041 if d.abs() < 1e-30 {
1042 d = 1e-30;
1043 }
1044 d = 1.0 / d;
1045
1046 c = bn + an / c;
1047 if c.abs() < 1e-30 {
1048 c = 1e-30;
1049 }
1050
1051 let delta = c * d;
1052 f *= delta;
1053
1054 if (delta - 1.0).abs() < 1e-15 {
1055 break;
1056 }
1057 }
1058
1059 (-x + a * x.ln() - ln_gamma_a).exp() / f
1060}
1061
1062fn regularized_beta(x: f64, a: f64, b: f64) -> f64 {
1064 if x <= 0.0 {
1065 return 0.0;
1066 }
1067 if x >= 1.0 {
1068 return 1.0;
1069 }
1070 if x > (a + 1.0) / (a + b + 2.0) {
1072 return 1.0 - regularized_beta(1.0 - x, b, a);
1073 }
1074 let log_prefix = a * x.ln() + b * (1.0 - x).ln() - ln_gamma(a) - ln_gamma(b) + ln_gamma(a + b);
1075 let prefix = log_prefix.exp();
1076
1077 prefix * betacf(a, b, x) / a
1078}
1079
1080fn betacf(a: f64, b: f64, x: f64) -> f64 {
1084 const TINY: f64 = 1e-30;
1085 const EPS: f64 = 1e-14;
1086
1087 let qab = a + b;
1088 let qap = a + 1.0;
1089 let qam = a - 1.0;
1090
1091 let mut c = 1.0_f64;
1092 let mut d = 1.0 - qab * x / qap;
1093 if d.abs() < TINY {
1094 d = TINY;
1095 }
1096 d = 1.0 / d;
1097 let mut h = d;
1098
1099 for m in 1..300i32 {
1100 let mf = f64::from(m);
1101 let m2f = f64::from(2 * m);
1102
1103 let aa = mf * (b - mf) * x / ((qam + m2f) * (a + m2f));
1105 d = 1.0 + aa * d;
1106 if d.abs() < TINY {
1107 d = TINY;
1108 }
1109 d = 1.0 / d;
1110 c = 1.0 + aa / c;
1111 if c.abs() < TINY {
1112 c = TINY;
1113 }
1114 h *= d * c;
1115
1116 let aa = -(a + mf) * (qab + mf) * x / ((a + m2f) * (qap + m2f));
1118 d = 1.0 + aa * d;
1119 if d.abs() < TINY {
1120 d = TINY;
1121 }
1122 d = 1.0 / d;
1123 c = 1.0 + aa / c;
1124 if c.abs() < TINY {
1125 c = TINY;
1126 }
1127 let del = d * c;
1128 h *= del;
1129
1130 if (del - 1.0).abs() < EPS {
1131 break;
1132 }
1133 }
1134
1135 h
1136}
1137
1138fn quantile_bisect(p: f64, mut lo: f64, mut hi: f64, cdf: impl Fn(f64) -> f64) -> f64 {
1141 if p <= 0.0 {
1142 return lo;
1143 }
1144 if p >= 1.0 {
1145 return hi;
1146 }
1147 while cdf(lo) > p {
1149 lo = if lo <= 0.0 { lo - 1.0 } else { lo * 0.5 };
1150 if lo < -1e15 {
1151 return f64::NEG_INFINITY;
1152 }
1153 }
1154 while cdf(hi) < p {
1155 hi *= 2.0;
1156 hi += 1.0;
1157 if hi > 1e15 {
1158 return f64::INFINITY;
1159 }
1160 }
1161 for _ in 0..100 {
1162 let mid = 0.5 * (lo + hi);
1163 if (hi - lo).abs() < 1e-12 * (1.0 + mid.abs()) {
1164 return mid;
1165 }
1166 if cdf(mid) < p {
1167 lo = mid;
1168 } else {
1169 hi = mid;
1170 }
1171 }
1172 0.5 * (lo + hi)
1173}
1174
1175fn quantile_bisect_discrete(p: f64, lo: i64, hi: i64, cdf: impl Fn(i64) -> f64) -> f64 {
1178 if p <= 0.0 {
1179 return lo as f64;
1180 }
1181 if p >= 1.0 {
1182 return hi as f64;
1183 }
1184 let (mut left, mut right) = (lo, hi);
1185 while left < right {
1186 let mid = left + (right - left) / 2;
1187 if cdf(mid) < p {
1188 left = mid + 1;
1189 } else {
1190 right = mid;
1191 }
1192 }
1193 left as f64
1194}
1195
1196fn dist_vectorize(args: &[RValue], fname: &str, f: impl Fn(f64) -> f64) -> Result<RValue, RError> {
1198 let vals = args[0]
1199 .as_vector()
1200 .ok_or_else(|| {
1201 RError::new(
1202 RErrorKind::Argument,
1203 format!("{fname}() requires a numeric vector"),
1204 )
1205 })?
1206 .to_doubles();
1207 let result: Vec<Option<f64>> = vals.iter().map(|x| x.map(&f)).collect();
1208 Ok(RValue::vec(Vector::Double(result.into())))
1209}
1210
1211fn dist_vectorize_d(
1213 args: &[RValue],
1214 fname: &str,
1215 log_flag: bool,
1216 f: impl Fn(f64) -> f64,
1217) -> Result<RValue, RError> {
1218 dist_vectorize(args, fname, |x| apply_d_log(f(x), log_flag))
1219}
1220
1221fn dist_vectorize_p(
1223 args: &[RValue],
1224 fname: &str,
1225 lower_tail: bool,
1226 log_p: bool,
1227 f: impl Fn(f64) -> f64,
1228) -> Result<RValue, RError> {
1229 dist_vectorize(args, fname, |x| apply_p_flags(f(x), lower_tail, log_p))
1230}
1231
1232fn dist_vectorize_q(
1234 args: &[RValue],
1235 fname: &str,
1236 lower_tail: bool,
1237 log_p: bool,
1238 f: impl Fn(f64) -> f64,
1239) -> Result<RValue, RError> {
1240 dist_vectorize(args, fname, |raw_p| {
1241 f(apply_q_flags(raw_p, lower_tail, log_p))
1242 })
1243}
1244
1245#[builtin(min_args = 1, namespace = "stats")]
1255fn builtin_dexp(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1256 let rate = extract_param(args, named, "rate", 1, 1.0);
1257 let log_flag = extract_log_flag(named);
1258 if rate <= 0.0 {
1259 return Err(RError::new(
1260 RErrorKind::Argument,
1261 "dexp(): 'rate' must be positive".to_string(),
1262 ));
1263 }
1264 dist_vectorize_d(args, "dexp", log_flag, |x| {
1265 if x < 0.0 {
1266 0.0
1267 } else {
1268 rate * (-rate * x).exp()
1269 }
1270 })
1271}
1272
1273#[builtin(min_args = 1, namespace = "stats")]
1279fn builtin_pexp(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1280 let rate = extract_param(args, named, "rate", 1, 1.0);
1281 let lower_tail = extract_lower_tail(named);
1282 let log_p = extract_log_p(named);
1283 if rate <= 0.0 {
1284 return Err(RError::new(
1285 RErrorKind::Argument,
1286 "pexp(): 'rate' must be positive".to_string(),
1287 ));
1288 }
1289 dist_vectorize_p(args, "pexp", lower_tail, log_p, |q| {
1290 if q < 0.0 {
1291 0.0
1292 } else {
1293 1.0 - (-rate * q).exp()
1294 }
1295 })
1296}
1297
1298#[builtin(min_args = 1, namespace = "stats")]
1304fn builtin_qexp(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1305 let rate = extract_param(args, named, "rate", 1, 1.0);
1306 let lower_tail = extract_lower_tail(named);
1307 let log_p = extract_log_p(named);
1308 if rate <= 0.0 {
1309 return Err(RError::new(
1310 RErrorKind::Argument,
1311 "qexp(): 'rate' must be positive".to_string(),
1312 ));
1313 }
1314 dist_vectorize_q(args, "qexp", lower_tail, log_p, |p| {
1315 if !(0.0..=1.0).contains(&p) {
1316 f64::NAN
1317 } else if p == 1.0 {
1318 f64::INFINITY
1319 } else {
1320 -(1.0 - p).ln() / rate
1321 }
1322 })
1323}
1324
1325#[builtin(min_args = 2, namespace = "stats")]
1336fn builtin_dgamma(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1337 let shape = extract_param(args, named, "shape", 1, f64::NAN);
1338 let rate = extract_param(args, named, "rate", 2, 1.0);
1339 let log_flag = extract_log_flag(named);
1340 if shape <= 0.0 || rate <= 0.0 || shape.is_nan() {
1341 return Err(RError::new(
1342 RErrorKind::Argument,
1343 "dgamma(): 'shape' and 'rate' must be positive".to_string(),
1344 ));
1345 }
1346 let scale = 1.0 / rate;
1347 let log_norm = shape * scale.ln() + ln_gamma(shape);
1348 dist_vectorize_d(args, "dgamma", log_flag, |x| {
1349 if x < 0.0 {
1350 0.0
1351 } else if x == 0.0 {
1352 if shape < 1.0 {
1353 f64::INFINITY
1354 } else if shape == 1.0 {
1355 rate
1356 } else {
1357 0.0
1358 }
1359 } else {
1360 ((shape - 1.0) * x.ln() - x / scale - log_norm).exp()
1361 }
1362 })
1363}
1364
1365#[builtin(min_args = 2, namespace = "stats")]
1374fn builtin_pgamma(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1375 let shape = extract_param(args, named, "shape", 1, f64::NAN);
1376 let rate = extract_param(args, named, "rate", 2, 1.0);
1377 let lower_tail = extract_lower_tail(named);
1378 let log_p = extract_log_p(named);
1379 if shape <= 0.0 || rate <= 0.0 || shape.is_nan() {
1380 return Err(RError::new(
1381 RErrorKind::Argument,
1382 "pgamma(): 'shape' and 'rate' must be positive".to_string(),
1383 ));
1384 }
1385 let scale = 1.0 / rate;
1386 dist_vectorize_p(args, "pgamma", lower_tail, log_p, |q| {
1387 if q <= 0.0 {
1388 0.0
1389 } else {
1390 regularized_gamma_p(shape, q / scale)
1391 }
1392 })
1393}
1394
1395#[builtin(min_args = 2, namespace = "stats")]
1404fn builtin_qgamma(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1405 let shape = extract_param(args, named, "shape", 1, f64::NAN);
1406 let rate = extract_param(args, named, "rate", 2, 1.0);
1407 let lower_tail = extract_lower_tail(named);
1408 let log_p = extract_log_p(named);
1409 if shape <= 0.0 || rate <= 0.0 || shape.is_nan() {
1410 return Err(RError::new(
1411 RErrorKind::Argument,
1412 "qgamma(): 'shape' and 'rate' must be positive".to_string(),
1413 ));
1414 }
1415 let scale = 1.0 / rate;
1416 dist_vectorize_q(args, "qgamma", lower_tail, log_p, |p| {
1417 if !(0.0..=1.0).contains(&p) {
1418 f64::NAN
1419 } else if p == 0.0 {
1420 0.0
1421 } else if p == 1.0 {
1422 f64::INFINITY
1423 } else {
1424 let mean = shape * scale;
1425 quantile_bisect(p, 0.0, mean * 5.0 + 10.0, |x| {
1426 regularized_gamma_p(shape, x / scale)
1427 })
1428 }
1429 })
1430}
1431
1432#[builtin(min_args = 3, namespace = "stats")]
1443fn builtin_dbeta(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1444 let shape1 = extract_param(args, named, "shape1", 1, f64::NAN);
1445 let shape2 = extract_param(args, named, "shape2", 2, f64::NAN);
1446 let log_flag = extract_log_flag(named);
1447 if shape1 <= 0.0 || shape2 <= 0.0 || shape1.is_nan() || shape2.is_nan() {
1448 return Err(RError::new(
1449 RErrorKind::Argument,
1450 "dbeta(): 'shape1' and 'shape2' must be positive".to_string(),
1451 ));
1452 }
1453 let log_beta = ln_gamma(shape1) + ln_gamma(shape2) - ln_gamma(shape1 + shape2);
1454 dist_vectorize_d(args, "dbeta", log_flag, |x| {
1455 if !(0.0..=1.0).contains(&x) {
1456 0.0
1457 } else if x == 0.0 {
1458 if shape1 < 1.0 {
1459 f64::INFINITY
1460 } else if shape1 == 1.0 {
1461 shape2
1462 } else {
1463 0.0
1464 }
1465 } else if x == 1.0 {
1466 if shape2 < 1.0 {
1467 f64::INFINITY
1468 } else if shape2 == 1.0 {
1469 shape1
1470 } else {
1471 0.0
1472 }
1473 } else {
1474 ((shape1 - 1.0) * x.ln() + (shape2 - 1.0) * (1.0 - x).ln() - log_beta).exp()
1475 }
1476 })
1477}
1478
1479#[builtin(min_args = 3, namespace = "stats")]
1488fn builtin_pbeta(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1489 let shape1 = extract_param(args, named, "shape1", 1, f64::NAN);
1490 let shape2 = extract_param(args, named, "shape2", 2, f64::NAN);
1491 let lower_tail = extract_lower_tail(named);
1492 let log_p = extract_log_p(named);
1493 if shape1 <= 0.0 || shape2 <= 0.0 || shape1.is_nan() || shape2.is_nan() {
1494 return Err(RError::new(
1495 RErrorKind::Argument,
1496 "pbeta(): 'shape1' and 'shape2' must be positive".to_string(),
1497 ));
1498 }
1499 dist_vectorize_p(args, "pbeta", lower_tail, log_p, |q| {
1500 if q <= 0.0 {
1501 0.0
1502 } else if q >= 1.0 {
1503 1.0
1504 } else {
1505 regularized_beta(q, shape1, shape2)
1506 }
1507 })
1508}
1509
1510#[builtin(min_args = 3, namespace = "stats")]
1519fn builtin_qbeta(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1520 let shape1 = extract_param(args, named, "shape1", 1, f64::NAN);
1521 let shape2 = extract_param(args, named, "shape2", 2, f64::NAN);
1522 let lower_tail = extract_lower_tail(named);
1523 let log_p = extract_log_p(named);
1524 if shape1 <= 0.0 || shape2 <= 0.0 || shape1.is_nan() || shape2.is_nan() {
1525 return Err(RError::new(
1526 RErrorKind::Argument,
1527 "qbeta(): 'shape1' and 'shape2' must be positive".to_string(),
1528 ));
1529 }
1530 dist_vectorize_q(args, "qbeta", lower_tail, log_p, |p| {
1531 if !(0.0..=1.0).contains(&p) {
1532 f64::NAN
1533 } else if p == 0.0 {
1534 0.0
1535 } else if p == 1.0 {
1536 1.0
1537 } else {
1538 quantile_bisect(p, 0.0, 1.0, |x| regularized_beta(x, shape1, shape2))
1539 }
1540 })
1541}
1542
1543#[builtin(min_args = 1, namespace = "stats")]
1554fn builtin_dcauchy(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1555 let location = extract_param(args, named, "location", 1, 0.0);
1556 let scale = extract_param(args, named, "scale", 2, 1.0);
1557 let log_flag = extract_log_flag(named);
1558 if scale <= 0.0 {
1559 return Err(RError::new(
1560 RErrorKind::Argument,
1561 "dcauchy(): 'scale' must be positive".to_string(),
1562 ));
1563 }
1564 dist_vectorize_d(args, "dcauchy", log_flag, |x| {
1565 let z = (x - location) / scale;
1566 1.0 / (PI * scale * (1.0 + z * z))
1567 })
1568}
1569
1570#[builtin(min_args = 1, namespace = "stats")]
1577fn builtin_pcauchy(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1578 let location = extract_param(args, named, "location", 1, 0.0);
1579 let scale = extract_param(args, named, "scale", 2, 1.0);
1580 let lower_tail = extract_lower_tail(named);
1581 let log_p = extract_log_p(named);
1582 if scale <= 0.0 {
1583 return Err(RError::new(
1584 RErrorKind::Argument,
1585 "pcauchy(): 'scale' must be positive".to_string(),
1586 ));
1587 }
1588 dist_vectorize_p(args, "pcauchy", lower_tail, log_p, |q| {
1589 0.5 + ((q - location) / scale).atan() / PI
1590 })
1591}
1592
1593#[builtin(min_args = 1, namespace = "stats")]
1600fn builtin_qcauchy(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1601 let location = extract_param(args, named, "location", 1, 0.0);
1602 let scale = extract_param(args, named, "scale", 2, 1.0);
1603 let lower_tail = extract_lower_tail(named);
1604 let log_p = extract_log_p(named);
1605 if scale <= 0.0 {
1606 return Err(RError::new(
1607 RErrorKind::Argument,
1608 "qcauchy(): 'scale' must be positive".to_string(),
1609 ));
1610 }
1611 dist_vectorize_q(args, "qcauchy", lower_tail, log_p, |p| {
1612 if !(0.0..=1.0).contains(&p) {
1613 f64::NAN
1614 } else if p == 0.0 {
1615 f64::NEG_INFINITY
1616 } else if p == 1.0 {
1617 f64::INFINITY
1618 } else {
1619 location + scale * (PI * (p - 0.5)).tan()
1620 }
1621 })
1622}
1623
1624#[builtin(min_args = 2, namespace = "stats")]
1635fn builtin_dweibull(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1636 let shape = extract_param(args, named, "shape", 1, f64::NAN);
1637 let scale = extract_param(args, named, "scale", 2, 1.0);
1638 let log_flag = extract_log_flag(named);
1639 if shape <= 0.0 || scale <= 0.0 || shape.is_nan() {
1640 return Err(RError::new(
1641 RErrorKind::Argument,
1642 "dweibull(): 'shape' and 'scale' must be positive".to_string(),
1643 ));
1644 }
1645 dist_vectorize_d(args, "dweibull", log_flag, |x| {
1646 if x < 0.0 {
1647 0.0
1648 } else if x == 0.0 {
1649 if shape < 1.0 {
1650 f64::INFINITY
1651 } else if shape == 1.0 {
1652 1.0 / scale
1653 } else {
1654 0.0
1655 }
1656 } else {
1657 let z = x / scale;
1658 (shape / scale) * z.powf(shape - 1.0) * (-z.powf(shape)).exp()
1659 }
1660 })
1661}
1662
1663#[builtin(min_args = 2, namespace = "stats")]
1670fn builtin_pweibull(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1671 let shape = extract_param(args, named, "shape", 1, f64::NAN);
1672 let scale = extract_param(args, named, "scale", 2, 1.0);
1673 let lower_tail = extract_lower_tail(named);
1674 let log_p = extract_log_p(named);
1675 if shape <= 0.0 || scale <= 0.0 || shape.is_nan() {
1676 return Err(RError::new(
1677 RErrorKind::Argument,
1678 "pweibull(): 'shape' and 'scale' must be positive".to_string(),
1679 ));
1680 }
1681 dist_vectorize_p(args, "pweibull", lower_tail, log_p, |q| {
1682 if q <= 0.0 {
1683 0.0
1684 } else {
1685 1.0 - (-(q / scale).powf(shape)).exp()
1686 }
1687 })
1688}
1689
1690#[builtin(min_args = 2, namespace = "stats")]
1697fn builtin_qweibull(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1698 let shape = extract_param(args, named, "shape", 1, f64::NAN);
1699 let scale = extract_param(args, named, "scale", 2, 1.0);
1700 let lower_tail = extract_lower_tail(named);
1701 let log_p = extract_log_p(named);
1702 if shape <= 0.0 || scale <= 0.0 || shape.is_nan() {
1703 return Err(RError::new(
1704 RErrorKind::Argument,
1705 "qweibull(): 'shape' and 'scale' must be positive".to_string(),
1706 ));
1707 }
1708 dist_vectorize_q(args, "qweibull", lower_tail, log_p, |p| {
1709 if !(0.0..=1.0).contains(&p) {
1710 f64::NAN
1711 } else if p == 0.0 {
1712 0.0
1713 } else if p == 1.0 {
1714 f64::INFINITY
1715 } else {
1716 scale * (-(1.0 - p).ln()).powf(1.0 / shape)
1717 }
1718 })
1719}
1720
1721#[builtin(min_args = 1, namespace = "stats")]
1732fn builtin_dlnorm(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1733 let meanlog = extract_param(args, named, "meanlog", 1, 0.0);
1734 let sdlog = extract_param(args, named, "sdlog", 2, 1.0);
1735 let log_flag = extract_log_flag(named);
1736 if sdlog < 0.0 {
1737 return Err(RError::new(
1738 RErrorKind::Argument,
1739 "dlnorm(): 'sdlog' must be non-negative".to_string(),
1740 ));
1741 }
1742 dist_vectorize_d(args, "dlnorm", log_flag, |x| {
1743 if x <= 0.0 {
1744 0.0
1745 } else if sdlog == 0.0 {
1746 if (x.ln() - meanlog).abs() < f64::EPSILON {
1747 f64::INFINITY
1748 } else {
1749 0.0
1750 }
1751 } else {
1752 let z = (x.ln() - meanlog) / sdlog;
1753 std_normal_pdf(z) / (x * sdlog)
1754 }
1755 })
1756}
1757
1758#[builtin(min_args = 1, namespace = "stats")]
1765fn builtin_plnorm(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1766 let meanlog = extract_param(args, named, "meanlog", 1, 0.0);
1767 let sdlog = extract_param(args, named, "sdlog", 2, 1.0);
1768 let lower_tail = extract_lower_tail(named);
1769 let log_p = extract_log_p(named);
1770 if sdlog < 0.0 {
1771 return Err(RError::new(
1772 RErrorKind::Argument,
1773 "plnorm(): 'sdlog' must be non-negative".to_string(),
1774 ));
1775 }
1776 dist_vectorize_p(args, "plnorm", lower_tail, log_p, |q| {
1777 if q <= 0.0 {
1778 0.0
1779 } else if sdlog == 0.0 {
1780 if q.ln() < meanlog {
1781 0.0
1782 } else {
1783 1.0
1784 }
1785 } else {
1786 let z = (q.ln() - meanlog) / sdlog;
1787 std_normal_cdf(z)
1788 }
1789 })
1790}
1791
1792#[builtin(min_args = 1, namespace = "stats")]
1799fn builtin_qlnorm(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1800 let meanlog = extract_param(args, named, "meanlog", 1, 0.0);
1801 let sdlog = extract_param(args, named, "sdlog", 2, 1.0);
1802 let lower_tail = extract_lower_tail(named);
1803 let log_p = extract_log_p(named);
1804 if sdlog < 0.0 {
1805 return Err(RError::new(
1806 RErrorKind::Argument,
1807 "qlnorm(): 'sdlog' must be non-negative".to_string(),
1808 ));
1809 }
1810 dist_vectorize_q(args, "qlnorm", lower_tail, log_p, |p| {
1811 if !(0.0..=1.0).contains(&p) {
1812 f64::NAN
1813 } else if p == 0.0 {
1814 0.0
1815 } else if p == 1.0 {
1816 f64::INFINITY
1817 } else {
1818 (std_normal_quantile(p) * sdlog + meanlog).exp()
1819 }
1820 })
1821}
1822
1823#[builtin(min_args = 2, namespace = "stats")]
1836fn builtin_dchisq(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1837 let df = extract_param(args, named, "df", 1, f64::NAN);
1838 let log_flag = extract_log_flag(named);
1839 if df <= 0.0 || df.is_nan() {
1840 return Err(RError::new(
1841 RErrorKind::Argument,
1842 "dchisq(): 'df' must be positive".to_string(),
1843 ));
1844 }
1845 let shape = df / 2.0;
1846 let scale: f64 = 2.0;
1847 let log_norm = shape * scale.ln() + ln_gamma(shape);
1848 dist_vectorize_d(args, "dchisq", log_flag, |x| {
1849 if x < 0.0 {
1850 0.0
1851 } else if x == 0.0 {
1852 if shape < 1.0 {
1853 f64::INFINITY
1854 } else if shape == 1.0 {
1855 0.5
1856 } else {
1857 0.0
1858 }
1859 } else {
1860 ((shape - 1.0) * x.ln() - x / scale - log_norm).exp()
1861 }
1862 })
1863}
1864
1865#[builtin(min_args = 2, namespace = "stats")]
1871fn builtin_pchisq(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1872 let df = extract_param(args, named, "df", 1, f64::NAN);
1873 let lower_tail = extract_lower_tail(named);
1874 let log_p = extract_log_p(named);
1875 if df <= 0.0 || df.is_nan() {
1876 return Err(RError::new(
1877 RErrorKind::Argument,
1878 "pchisq(): 'df' must be positive".to_string(),
1879 ));
1880 }
1881 let shape = df / 2.0;
1882 dist_vectorize_p(args, "pchisq", lower_tail, log_p, |q| {
1883 if q <= 0.0 {
1884 0.0
1885 } else {
1886 regularized_gamma_p(shape, q / 2.0)
1887 }
1888 })
1889}
1890
1891#[builtin(min_args = 2, namespace = "stats")]
1897fn builtin_qchisq(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1898 let df = extract_param(args, named, "df", 1, f64::NAN);
1899 let lower_tail = extract_lower_tail(named);
1900 let log_p = extract_log_p(named);
1901 if df <= 0.0 || df.is_nan() {
1902 return Err(RError::new(
1903 RErrorKind::Argument,
1904 "qchisq(): 'df' must be positive".to_string(),
1905 ));
1906 }
1907 let shape = df / 2.0;
1908 dist_vectorize_q(args, "qchisq", lower_tail, log_p, |p| {
1909 if !(0.0..=1.0).contains(&p) {
1910 f64::NAN
1911 } else if p == 0.0 {
1912 0.0
1913 } else if p == 1.0 {
1914 f64::INFINITY
1915 } else {
1916 quantile_bisect(p, 0.0, df * 5.0 + 10.0, |x| {
1917 regularized_gamma_p(shape, x / 2.0)
1918 })
1919 }
1920 })
1921}
1922
1923#[builtin(min_args = 2, namespace = "stats")]
1935fn builtin_dt(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1936 let df = extract_param(args, named, "df", 1, f64::NAN);
1937 let log_flag = extract_log_flag(named);
1938 if df <= 0.0 || df.is_nan() {
1939 return Err(RError::new(
1940 RErrorKind::Argument,
1941 "dt(): 'df' must be positive".to_string(),
1942 ));
1943 }
1944 let log_const = ln_gamma((df + 1.0) / 2.0) - ln_gamma(df / 2.0) - 0.5 * (df * PI).ln();
1945 dist_vectorize_d(args, "dt", log_flag, |x| {
1946 (log_const + (-(df + 1.0) / 2.0) * (1.0 + x * x / df).ln()).exp()
1947 })
1948}
1949
1950#[builtin(min_args = 2, namespace = "stats")]
1959fn builtin_pt(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1960 let df = extract_param(args, named, "df", 1, f64::NAN);
1961 let lower_tail = extract_lower_tail(named);
1962 let log_p = extract_log_p(named);
1963 if df <= 0.0 || df.is_nan() {
1964 return Err(RError::new(
1965 RErrorKind::Argument,
1966 "pt(): 'df' must be positive".to_string(),
1967 ));
1968 }
1969 dist_vectorize_p(args, "pt", lower_tail, log_p, |q| {
1970 let x2 = q * q;
1971 let t = df / (df + x2);
1972 let half_ib = 0.5 * regularized_beta(t, df / 2.0, 0.5);
1973 if q >= 0.0 {
1974 1.0 - half_ib
1975 } else {
1976 half_ib
1977 }
1978 })
1979}
1980
1981#[builtin(min_args = 2, namespace = "stats")]
1989fn builtin_qt(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1990 let df = extract_param(args, named, "df", 1, f64::NAN);
1991 let lower_tail = extract_lower_tail(named);
1992 let log_p = extract_log_p(named);
1993 if df <= 0.0 || df.is_nan() {
1994 return Err(RError::new(
1995 RErrorKind::Argument,
1996 "qt(): 'df' must be positive".to_string(),
1997 ));
1998 }
1999 dist_vectorize_q(args, "qt", lower_tail, log_p, |p| {
2000 if !(0.0..=1.0).contains(&p) {
2001 f64::NAN
2002 } else if p == 0.0 {
2003 f64::NEG_INFINITY
2004 } else if p == 1.0 {
2005 f64::INFINITY
2006 } else {
2007 let pt_cdf = |q: f64| -> f64 {
2008 let x2 = q * q;
2009 let t = df / (df + x2);
2010 let half_ib = 0.5 * regularized_beta(t, df / 2.0, 0.5);
2011 if q >= 0.0 {
2012 1.0 - half_ib
2013 } else {
2014 half_ib
2015 }
2016 };
2017 let z = std_normal_quantile(p);
2018 let lo = z * 5.0 - 10.0;
2019 let hi = z * 5.0 + 10.0;
2020 quantile_bisect(p, lo, hi, pt_cdf)
2021 }
2022 })
2023}
2024
2025#[builtin(name = "df", min_args = 3, namespace = "stats")]
2036fn builtin_df_dist(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2037 let df1 = extract_param(args, named, "df1", 1, f64::NAN);
2038 let df2 = extract_param(args, named, "df2", 2, f64::NAN);
2039 let log_flag = extract_log_flag(named);
2040 if df1 <= 0.0 || df2 <= 0.0 || df1.is_nan() || df2.is_nan() {
2041 return Err(RError::new(
2042 RErrorKind::Argument,
2043 "df(): 'df1' and 'df2' must be positive".to_string(),
2044 ));
2045 }
2046 let log_const = 0.5 * (df1 * df1.ln() + df2 * df2.ln()) + ln_gamma((df1 + df2) / 2.0)
2047 - ln_gamma(df1 / 2.0)
2048 - ln_gamma(df2 / 2.0);
2049 dist_vectorize_d(args, "df", log_flag, |x| {
2050 if x < 0.0 {
2051 0.0
2052 } else if x == 0.0 {
2053 if df1 < 2.0 {
2054 f64::INFINITY
2055 } else if df1 == 2.0 {
2056 1.0
2057 } else {
2058 0.0
2059 }
2060 } else {
2061 (log_const + (df1 / 2.0 - 1.0) * x.ln() - ((df1 + df2) / 2.0) * (df1 * x + df2).ln())
2062 .exp()
2063 }
2064 })
2065}
2066
2067#[builtin(min_args = 3, namespace = "stats")]
2077fn builtin_pf(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2078 let df1 = extract_param(args, named, "df1", 1, f64::NAN);
2079 let df2 = extract_param(args, named, "df2", 2, f64::NAN);
2080 let lower_tail = extract_lower_tail(named);
2081 let log_p = extract_log_p(named);
2082 if df1 <= 0.0 || df2 <= 0.0 || df1.is_nan() || df2.is_nan() {
2083 return Err(RError::new(
2084 RErrorKind::Argument,
2085 "pf(): 'df1' and 'df2' must be positive".to_string(),
2086 ));
2087 }
2088 dist_vectorize_p(args, "pf", lower_tail, log_p, |q| {
2089 if q <= 0.0 {
2090 0.0
2091 } else {
2092 let t = df1 * q / (df1 * q + df2);
2093 regularized_beta(t, df1 / 2.0, df2 / 2.0)
2094 }
2095 })
2096}
2097
2098#[builtin(min_args = 3, namespace = "stats")]
2107fn builtin_qf(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2108 let df1 = extract_param(args, named, "df1", 1, f64::NAN);
2109 let df2 = extract_param(args, named, "df2", 2, f64::NAN);
2110 let lower_tail = extract_lower_tail(named);
2111 let log_p = extract_log_p(named);
2112 if df1 <= 0.0 || df2 <= 0.0 || df1.is_nan() || df2.is_nan() {
2113 return Err(RError::new(
2114 RErrorKind::Argument,
2115 "qf(): 'df1' and 'df2' must be positive".to_string(),
2116 ));
2117 }
2118 dist_vectorize_q(args, "qf", lower_tail, log_p, |p| {
2119 if !(0.0..=1.0).contains(&p) {
2120 f64::NAN
2121 } else if p == 0.0 {
2122 0.0
2123 } else if p == 1.0 {
2124 f64::INFINITY
2125 } else {
2126 let pf_cdf = |q: f64| -> f64 {
2127 let t = df1 * q / (df1 * q + df2);
2128 regularized_beta(t, df1 / 2.0, df2 / 2.0)
2129 };
2130 let mean_est = if df2 > 2.0 { df2 / (df2 - 2.0) } else { 1.0 };
2131 quantile_bisect(p, 0.0, mean_est * 10.0 + 10.0, pf_cdf)
2132 }
2133 })
2134}
2135
2136#[builtin(min_args = 3, namespace = "stats")]
2149fn builtin_dbinom(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2150 let size = extract_param(args, named, "size", 1, f64::NAN);
2151 let prob = extract_param(args, named, "prob", 2, f64::NAN);
2152 let log_flag = extract_log_flag(named);
2153 if size < 0.0 || size.is_nan() || size != size.floor() {
2154 return Err(RError::new(
2155 RErrorKind::Argument,
2156 "dbinom(): 'size' must be a non-negative integer".to_string(),
2157 ));
2158 }
2159 if !(0.0..=1.0).contains(&prob) || prob.is_nan() {
2160 return Err(RError::new(
2161 RErrorKind::Argument,
2162 "dbinom(): 'prob' must be in [0, 1]".to_string(),
2163 ));
2164 }
2165 let n = size;
2166 dist_vectorize_d(args, "dbinom", log_flag, |x| {
2167 let k = x.round();
2168 if (x - k).abs() > 1e-7 || k < 0.0 || k > n {
2169 0.0
2170 } else if prob == 0.0 {
2171 if k == 0.0 {
2172 1.0
2173 } else {
2174 0.0
2175 }
2176 } else if prob == 1.0 {
2177 if k == n {
2178 1.0
2179 } else {
2180 0.0
2181 }
2182 } else {
2183 (lchoose(n, k) + k * prob.ln() + (n - k) * (1.0 - prob).ln()).exp()
2184 }
2185 })
2186}
2187
2188#[builtin(min_args = 3, namespace = "stats")]
2198fn builtin_pbinom(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2199 let size = extract_param(args, named, "size", 1, f64::NAN);
2200 let prob = extract_param(args, named, "prob", 2, f64::NAN);
2201 let lower_tail = extract_lower_tail(named);
2202 let log_p = extract_log_p(named);
2203 if size < 0.0 || size.is_nan() || size != size.floor() {
2204 return Err(RError::new(
2205 RErrorKind::Argument,
2206 "pbinom(): 'size' must be a non-negative integer".to_string(),
2207 ));
2208 }
2209 if !(0.0..=1.0).contains(&prob) || prob.is_nan() {
2210 return Err(RError::new(
2211 RErrorKind::Argument,
2212 "pbinom(): 'prob' must be in [0, 1]".to_string(),
2213 ));
2214 }
2215 let n = size as i64;
2216 dist_vectorize_p(args, "pbinom", lower_tail, log_p, |q| {
2217 if q < 0.0 {
2218 0.0
2219 } else if q >= size {
2220 1.0
2221 } else {
2222 let k = q.floor() as i64;
2223 let kf = k as f64;
2224 1.0 - regularized_beta(prob, kf + 1.0, (n - k) as f64)
2225 }
2226 })
2227}
2228
2229#[builtin(min_args = 3, namespace = "stats")]
2238fn builtin_qbinom(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2239 let size = extract_param(args, named, "size", 1, f64::NAN);
2240 let prob = extract_param(args, named, "prob", 2, f64::NAN);
2241 let lower_tail = extract_lower_tail(named);
2242 let log_p = extract_log_p(named);
2243 if size < 0.0 || size.is_nan() || size != size.floor() {
2244 return Err(RError::new(
2245 RErrorKind::Argument,
2246 "qbinom(): 'size' must be a non-negative integer".to_string(),
2247 ));
2248 }
2249 if !(0.0..=1.0).contains(&prob) || prob.is_nan() {
2250 return Err(RError::new(
2251 RErrorKind::Argument,
2252 "qbinom(): 'prob' must be in [0, 1]".to_string(),
2253 ));
2254 }
2255 let n = size as i64;
2256 dist_vectorize_q(args, "qbinom", lower_tail, log_p, |p| {
2257 if !(0.0..=1.0).contains(&p) {
2258 f64::NAN
2259 } else if p == 0.0 {
2260 0.0
2261 } else if p == 1.0 {
2262 size
2263 } else {
2264 quantile_bisect_discrete(p, 0, n, |k| {
2265 let kf = k as f64;
2266 1.0 - regularized_beta(prob, kf + 1.0, (n - k) as f64)
2267 })
2268 }
2269 })
2270}
2271
2272#[builtin(min_args = 2, namespace = "stats")]
2284fn builtin_dpois(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2285 let lambda = extract_param(args, named, "lambda", 1, f64::NAN);
2286 let log_flag = extract_log_flag(named);
2287 if lambda < 0.0 || lambda.is_nan() {
2288 return Err(RError::new(
2289 RErrorKind::Argument,
2290 "dpois(): 'lambda' must be non-negative".to_string(),
2291 ));
2292 }
2293 dist_vectorize_d(args, "dpois", log_flag, |x| {
2294 let k = x.round();
2295 if (x - k).abs() > 1e-7 || k < 0.0 {
2296 0.0
2297 } else if lambda == 0.0 {
2298 if k == 0.0 {
2299 1.0
2300 } else {
2301 0.0
2302 }
2303 } else {
2304 (k * lambda.ln() - lambda - ln_gamma(k + 1.0)).exp()
2305 }
2306 })
2307}
2308
2309#[builtin(min_args = 2, namespace = "stats")]
2318fn builtin_ppois(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2319 let lambda = extract_param(args, named, "lambda", 1, f64::NAN);
2320 let lower_tail = extract_lower_tail(named);
2321 let log_p = extract_log_p(named);
2322 if lambda < 0.0 || lambda.is_nan() {
2323 return Err(RError::new(
2324 RErrorKind::Argument,
2325 "ppois(): 'lambda' must be non-negative".to_string(),
2326 ));
2327 }
2328 dist_vectorize_p(args, "ppois", lower_tail, log_p, |q| {
2329 if q < 0.0 {
2330 0.0
2331 } else if lambda == 0.0 {
2332 1.0
2333 } else {
2334 let k = q.floor();
2335 1.0 - regularized_gamma_p(k + 1.0, lambda)
2336 }
2337 })
2338}
2339
2340#[builtin(min_args = 2, namespace = "stats")]
2348fn builtin_qpois(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2349 let lambda = extract_param(args, named, "lambda", 1, f64::NAN);
2350 let lower_tail = extract_lower_tail(named);
2351 let log_p = extract_log_p(named);
2352 if lambda < 0.0 || lambda.is_nan() {
2353 return Err(RError::new(
2354 RErrorKind::Argument,
2355 "qpois(): 'lambda' must be non-negative".to_string(),
2356 ));
2357 }
2358 dist_vectorize_q(args, "qpois", lower_tail, log_p, |p| {
2359 if !(0.0..=1.0).contains(&p) {
2360 f64::NAN
2361 } else if p == 0.0 {
2362 0.0
2363 } else if p == 1.0 {
2364 f64::INFINITY
2365 } else if lambda == 0.0 {
2366 0.0
2367 } else {
2368 let hi = (lambda + 6.0 * lambda.sqrt() + 10.0).ceil() as i64;
2369 quantile_bisect_discrete(p, 0, hi, |k| {
2370 let kf = k as f64;
2371 1.0 - regularized_gamma_p(kf + 1.0, lambda)
2372 })
2373 }
2374 })
2375}
2376
2377#[builtin(min_args = 2, namespace = "stats")]
2389fn builtin_dgeom(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2390 let prob = extract_param(args, named, "prob", 1, f64::NAN);
2391 let log_flag = extract_log_flag(named);
2392 if !(0.0..=1.0).contains(&prob) || prob.is_nan() {
2393 return Err(RError::new(
2394 RErrorKind::Argument,
2395 "dgeom(): 'prob' must be in (0, 1]".to_string(),
2396 ));
2397 }
2398 dist_vectorize_d(args, "dgeom", log_flag, |x| {
2399 let k = x.round();
2400 if (x - k).abs() > 1e-7 || k < 0.0 {
2401 0.0
2402 } else {
2403 prob * (1.0 - prob).powf(k)
2404 }
2405 })
2406}
2407
2408#[builtin(min_args = 2, namespace = "stats")]
2416fn builtin_pgeom(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2417 let prob = extract_param(args, named, "prob", 1, f64::NAN);
2418 let lower_tail = extract_lower_tail(named);
2419 let log_p = extract_log_p(named);
2420 if !(0.0..=1.0).contains(&prob) || prob.is_nan() {
2421 return Err(RError::new(
2422 RErrorKind::Argument,
2423 "pgeom(): 'prob' must be in (0, 1]".to_string(),
2424 ));
2425 }
2426 dist_vectorize_p(args, "pgeom", lower_tail, log_p, |q| {
2427 if q < 0.0 {
2428 0.0
2429 } else {
2430 1.0 - (1.0 - prob).powf(q.floor() + 1.0)
2431 }
2432 })
2433}
2434
2435#[builtin(min_args = 2, namespace = "stats")]
2443fn builtin_qgeom(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2444 let prob = extract_param(args, named, "prob", 1, f64::NAN);
2445 let lower_tail = extract_lower_tail(named);
2446 let log_p = extract_log_p(named);
2447 if !(0.0..=1.0).contains(&prob) || prob.is_nan() {
2448 return Err(RError::new(
2449 RErrorKind::Argument,
2450 "qgeom(): 'prob' must be in (0, 1]".to_string(),
2451 ));
2452 }
2453 dist_vectorize_q(args, "qgeom", lower_tail, log_p, |p| {
2454 if !(0.0..=1.0).contains(&p) {
2455 f64::NAN
2456 } else if p == 0.0 {
2457 0.0
2458 } else if p == 1.0 {
2459 f64::INFINITY
2460 } else if prob == 1.0 {
2461 0.0
2462 } else {
2463 ((1.0 - p).ln() / (1.0 - prob).ln() - 1.0).ceil().max(0.0)
2464 }
2465 })
2466}
2467
2468#[builtin(min_args = 4, namespace = "stats")]
2482fn builtin_dhyper(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2483 let m = extract_param(args, named, "m", 1, f64::NAN);
2484 let n = extract_param(args, named, "n", 2, f64::NAN);
2485 let k = extract_param(args, named, "k", 3, f64::NAN);
2486 let log_flag = extract_log_flag(named);
2487 if m < 0.0 || n < 0.0 || k < 0.0 || m.is_nan() || n.is_nan() || k.is_nan() {
2488 return Err(RError::new(
2489 RErrorKind::Argument,
2490 "dhyper(): parameters must be non-negative".to_string(),
2491 ));
2492 }
2493 if k > m + n {
2494 return Err(RError::new(
2495 RErrorKind::Argument,
2496 "dhyper(): 'k' must not exceed 'm + n'".to_string(),
2497 ));
2498 }
2499 dist_vectorize_d(args, "dhyper", log_flag, |x| {
2500 let xi = x.round();
2501 if (x - xi).abs() > 1e-7 || xi < 0.0 || xi > m || xi > k || (k - xi) > n {
2502 0.0
2503 } else {
2504 (lchoose(m, xi) + lchoose(n, k - xi) - lchoose(m + n, k)).exp()
2505 }
2506 })
2507}
2508
2509#[builtin(min_args = 4, namespace = "stats")]
2517fn builtin_phyper(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2518 let m = extract_param(args, named, "m", 1, f64::NAN);
2519 let n = extract_param(args, named, "n", 2, f64::NAN);
2520 let k = extract_param(args, named, "k", 3, f64::NAN);
2521 let lower_tail = extract_lower_tail(named);
2522 let log_p = extract_log_p(named);
2523 if m < 0.0 || n < 0.0 || k < 0.0 || m.is_nan() || n.is_nan() || k.is_nan() {
2524 return Err(RError::new(
2525 RErrorKind::Argument,
2526 "phyper(): parameters must be non-negative".to_string(),
2527 ));
2528 }
2529 if k > m + n {
2530 return Err(RError::new(
2531 RErrorKind::Argument,
2532 "phyper(): 'k' must not exceed 'm + n'".to_string(),
2533 ));
2534 }
2535 let lo = (k - n).max(0.0) as i64;
2536 dist_vectorize_p(args, "phyper", lower_tail, log_p, |q| {
2537 if q < lo as f64 {
2538 0.0
2539 } else if q >= m.min(k) {
2540 1.0
2541 } else {
2542 let qi = q.floor() as i64;
2543 let mut sum = 0.0;
2544 for j in lo..=qi {
2545 let jf = j as f64;
2546 sum += (lchoose(m, jf) + lchoose(n, k - jf) - lchoose(m + n, k)).exp();
2547 }
2548 sum.min(1.0)
2549 }
2550 })
2551}
2552
2553#[builtin(min_args = 4, namespace = "stats")]
2561fn builtin_qhyper(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2562 let m = extract_param(args, named, "m", 1, f64::NAN);
2563 let n = extract_param(args, named, "n", 2, f64::NAN);
2564 let k = extract_param(args, named, "k", 3, f64::NAN);
2565 let lower_tail = extract_lower_tail(named);
2566 let log_p = extract_log_p(named);
2567 if m < 0.0 || n < 0.0 || k < 0.0 || m.is_nan() || n.is_nan() || k.is_nan() {
2568 return Err(RError::new(
2569 RErrorKind::Argument,
2570 "qhyper(): parameters must be non-negative".to_string(),
2571 ));
2572 }
2573 if k > m + n {
2574 return Err(RError::new(
2575 RErrorKind::Argument,
2576 "qhyper(): 'k' must not exceed 'm + n'".to_string(),
2577 ));
2578 }
2579 let lo = (k - n).max(0.0) as i64;
2580 let hi = m.min(k) as i64;
2581 dist_vectorize_q(args, "qhyper", lower_tail, log_p, |p| {
2582 if !(0.0..=1.0).contains(&p) {
2583 f64::NAN
2584 } else if p == 0.0 {
2585 lo as f64
2586 } else if p == 1.0 {
2587 hi as f64
2588 } else {
2589 let mut cum = 0.0;
2590 for j in lo..=hi {
2591 let jf = j as f64;
2592 cum += (lchoose(m, jf) + lchoose(n, k - jf) - lchoose(m + n, k)).exp();
2593 if cum >= p {
2594 return jf;
2595 }
2596 }
2597 hi as f64
2598 }
2599 })
2600}
2601
2602