Skip to main content

ggplot_rs/stat/
mod.rs

1pub mod bin;
2pub mod bin2d;
3pub mod bindot;
4pub mod binhex;
5pub mod boxplot;
6pub mod calendar;
7#[cfg(feature = "ggpubr")]
8pub mod compare_means;
9pub mod contour;
10pub mod contour_filled;
11#[cfg(feature = "ggpubr")]
12pub mod cor;
13pub mod count;
14pub mod density;
15pub mod density2d;
16pub mod dist;
17pub mod distribution;
18pub mod ecdf;
19pub mod ellipse;
20pub mod function;
21pub mod identity;
22pub mod loess;
23pub mod marching_squares;
24pub mod qq;
25#[cfg(feature = "regression")]
26pub mod quantile;
27pub mod smooth;
28pub mod sum;
29pub mod summary;
30pub mod summary2d;
31pub mod summary_bin;
32pub mod ydensity;
33
34use crate::aes::{Aes, Aesthetic};
35use crate::data::DataFrame;
36use crate::scale::ScaleSet;
37
38/// Trait for statistical transformations.
39pub trait Stat: Send + Sync {
40    /// Transform data for a single group.
41    fn compute_group(&self, data: &DataFrame, scales: &ScaleSet) -> DataFrame;
42
43    /// Required aesthetics this stat needs.
44    fn required_aes(&self) -> Vec<Aesthetic>;
45
46    /// Default aesthetic mappings this stat produces.
47    fn default_aes(&self) -> Aes {
48        Aes::default()
49    }
50
51    /// If true, the stat receives every row of a panel at once (grouped only by
52    /// facet variables) instead of once per aesthetic group. Needed for
53    /// cross-group comparisons such as `stat_compare_means`, which must see all
54    /// groups together. Defaults to per-group behaviour.
55    fn panelwise(&self) -> bool {
56        false
57    }
58
59    /// Name for debug/display.
60    fn name(&self) -> &str;
61}
62
63/// R's `bw.nrd0`: Silverman's rule of thumb, `0.9 · min(sd, IQR/1.34) · n^-0.2`,
64/// with R's fallbacks when the spread is zero — `sd`, then `|x[0]|`, then `1` —
65/// so a constant sample still gets a positive bandwidth (never 0 → NaN).
66pub(crate) fn bw_nrd0(values: &[f64]) -> f64 {
67    let n = values.len();
68    if n < 2 {
69        return 1.0;
70    }
71    let nf = n as f64;
72    let mean = values.iter().sum::<f64>() / nf;
73    let sd = (values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (nf - 1.0)).sqrt();
74    let mut sorted = values.to_vec();
75    sorted.sort_by(|a, b| a.total_cmp(b));
76    let q = |p: f64| {
77        let h = (n - 1) as f64 * p;
78        let lo = h.floor() as usize;
79        let hi = (lo + 1).min(n - 1);
80        sorted[lo] + (h - lo as f64) * (sorted[hi] - sorted[lo])
81    };
82    let iqr = q(0.75) - q(0.25);
83    let mut lo = sd.min(iqr / 1.34);
84    for fallback in [sd, values[0].abs(), 1.0] {
85        if lo > 0.0 && lo.is_finite() {
86            break;
87        }
88        lo = fallback;
89    }
90    0.9 * lo * nf.powf(-0.2)
91}