Skip to main content

ggplot_rs/stat/
mod.rs

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