plotters_statistical/stats/
gain.rs1use super::StatsError;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct GainPoint {
8 pub fraction: f64,
10 pub gain: f64,
12 pub lift: f64,
17}
18
19pub fn gain_curve(scores: &[f64], labels: &[bool]) -> Result<Vec<GainPoint>, StatsError> {
28 if scores.len() != labels.len() {
29 return Err(StatsError::LengthMismatch {
30 scores: scores.len(),
31 labels: labels.len(),
32 });
33 }
34 if scores.is_empty() {
35 return Err(StatsError::EmptyInput);
36 }
37 let total_p = labels.iter().filter(|&&l| l).count();
38 if total_p == 0 {
39 return Err(StatsError::NoPositiveLabels);
40 }
41
42 let mut order: Vec<usize> = (0..scores.len()).collect();
43 order.sort_by(|&a, &b| {
44 scores[b]
45 .partial_cmp(&scores[a])
46 .unwrap_or(std::cmp::Ordering::Equal)
47 });
48
49 let n = scores.len() as f64;
50 let p = total_p as f64;
51 let mut points = Vec::with_capacity(order.len() + 1);
52 points.push(GainPoint {
53 fraction: 0.0,
54 gain: 0.0,
55 lift: f64::NAN,
56 });
57 let mut cum_pos = 0usize;
58 for (k, &i) in order.iter().enumerate() {
59 if labels[i] {
60 cum_pos += 1;
61 }
62 let fraction = (k + 1) as f64 / n;
63 let gain = cum_pos as f64 / p;
64 points.push(GainPoint {
65 fraction,
66 gain,
67 lift: gain / fraction,
68 });
69 }
70 Ok(points)
71}