Skip to main content

ggplot_rs/stat/
ecdf.rs

1use crate::aes::Aesthetic;
2use crate::data::{DataFrame, Value};
3use crate::scale::ScaleSet;
4
5use super::Stat;
6
7/// Empirical cumulative distribution function.
8/// Sorts x values and assigns y = rank / n.
9pub struct StatEcdf;
10
11impl Default for StatEcdf {
12    fn default() -> Self {
13        StatEcdf
14    }
15}
16
17impl Stat for StatEcdf {
18    fn compute_group(&self, data: &DataFrame, _scales: &ScaleSet) -> DataFrame {
19        let x_col = match data.column("x") {
20            Some(c) => c,
21            None => return DataFrame::new(),
22        };
23
24        let mut values: Vec<f64> = x_col.iter().filter_map(|v| v.as_f64()).collect();
25        if values.is_empty() {
26            return DataFrame::new();
27        }
28
29        values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
30        let n = values.len() as f64;
31
32        let mut x_vals = Vec::with_capacity(values.len() + 2);
33        let mut y_vals = Vec::with_capacity(values.len() + 2);
34
35        // ggplot2 pads the step to ±Inf (y = 0 before the first point, y = 1
36        // after the last) so it spans the panel. Scales ignore non-finite values
37        // when training, and geom_step clamps the ±Inf segments to the panel edge.
38        x_vals.push(Value::Float(f64::NEG_INFINITY));
39        y_vals.push(Value::Float(0.0));
40        for (i, &x) in values.iter().enumerate() {
41            x_vals.push(Value::Float(x));
42            y_vals.push(Value::Float((i + 1) as f64 / n));
43        }
44        x_vals.push(Value::Float(f64::INFINITY));
45        y_vals.push(Value::Float(1.0));
46
47        let mut result = DataFrame::new();
48        result.add_column("x".to_string(), x_vals);
49        result.add_column("y".to_string(), y_vals);
50
51        // Carry over grouping columns
52        let nrows = values.len() + 2;
53        for col_name in &["color", "fill", "group"] {
54            if let Some(col) = data.column(col_name) {
55                if let Some(first) = col.first() {
56                    result.add_column(col_name.to_string(), vec![first.clone(); nrows]);
57                }
58            }
59        }
60
61        result
62    }
63
64    fn required_aes(&self) -> Vec<Aesthetic> {
65        vec![Aesthetic::X]
66    }
67
68    fn name(&self) -> &str {
69        "ecdf"
70    }
71}