plotters_statistical/stats/
calibration.rs1use super::StatsError;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
8pub struct CalibrationBin {
9 pub mean_predicted: f64,
11 pub observed_freq: f64,
13 pub count: usize,
15}
16
17pub fn calibration_curve(
25 scores: &[f64],
26 labels: &[bool],
27 n_bins: usize,
28) -> Result<Vec<CalibrationBin>, StatsError> {
29 if scores.len() != labels.len() {
30 return Err(StatsError::LengthMismatch {
31 scores: scores.len(),
32 labels: labels.len(),
33 });
34 }
35 if scores.is_empty() || n_bins == 0 {
36 return Err(StatsError::EmptyInput);
37 }
38 let mut sum_pred = vec![0.0; n_bins];
39 let mut sum_pos = vec![0usize; n_bins];
40 let mut count = vec![0usize; n_bins];
41 for (&s, &l) in scores.iter().zip(labels) {
42 let c = s.clamp(0.0, 1.0);
44 let mut idx = (c * n_bins as f64).floor() as usize;
45 if idx >= n_bins {
46 idx = n_bins - 1;
47 }
48 sum_pred[idx] += c;
49 sum_pos[idx] += l as usize;
50 count[idx] += 1;
51 }
52 Ok((0..n_bins)
53 .filter(|&b| count[b] > 0)
54 .map(|b| CalibrationBin {
55 mean_predicted: sum_pred[b] / count[b] as f64,
56 observed_freq: sum_pos[b] as f64 / count[b] as f64,
57 count: count[b],
58 })
59 .collect())
60}