Skip to main content

ggplot_rs/plot/
stat_layers.rs

1//! `GGPlot` builder methods for statistical-diagnostic layers: QQ plots
2//! against several distributions with confidence bands, step ribbons and
3//! ECDF bands, censor marks, horizontal error bars and Cook's-distance
4//! contours.
5
6use super::GGPlot;
7use crate::data::{DataFrame, Value};
8use crate::geom::censor::{GeomCensorMarks, StatCensored};
9use crate::geom::cooks::GeomCooksContour;
10use crate::geom::errorbarh::GeomErrorbarh;
11use crate::geom::qq::{GeomQQ, GeomQQBand, GeomQQLine};
12use crate::geom::ribbon::GeomStepribbon;
13use crate::geom::step::GeomStep;
14use crate::stat::ecdf::{StatEcdf, StatEcdfBand};
15use crate::stat::qq::{QQDistribution, StatQQBand, StatQQDist, StatQQLineDist};
16
17impl GGPlot {
18    /// QQ points of `y` against a theoretical `distribution` (ggplot2's
19    /// `stat_qq(distribution = …, dparams = …)`): `x` = quantiles at R's
20    /// `ppoints(n)`, `y` = the sorted sample, per group / panel.
21    pub fn stat_qq(self, distribution: QQDistribution) -> Self {
22        self.add_geom(GeomQQ::default())
23            .stat(StatQQDist::new(distribution))
24    }
25
26    /// QQ reference line through the sample / theoretical 1st and 3rd
27    /// quartiles (ggplot2's `stat_qq_line`), for any distribution.
28    pub fn stat_qq_line(self, distribution: QQDistribution) -> Self {
29        self.add_geom(GeomQQLine::default())
30            .stat(StatQQLineDist::new(distribution))
31    }
32
33    /// QQ confidence envelope (qqplotr's `stat_qq_band`): pointwise
34    /// normal-theory or KS band around the quartile line, drawn as a ribbon.
35    /// Add it *before* `geom_qq` so the points sit on top.
36    pub fn stat_qq_band(self, band: StatQQBand) -> Self {
37        self.add_geom(GeomQQBand::default()).stat(band)
38    }
39
40    /// [`stat_qq_band`](Self::stat_qq_band) with the default 95% pointwise
41    /// band against the standard normal.
42    pub fn geom_qq_band(self) -> Self {
43        self.stat_qq_band(StatQQBand::default())
44    }
45
46    /// A QQ band with custom fill / alpha.
47    pub fn geom_qq_band_with(self, geom: GeomQQBand, band: StatQQBand) -> Self {
48        self.add_geom_with(geom).stat(band)
49    }
50
51    /// Cook's-distance contours for a residuals-vs-leverage plot (`x` =
52    /// leverage, `y` = standardized residual), as in R's `plot.lm(which = 5)`:
53    /// dashed curves `±√(level · p · (1 − h) / h)` for a model with `p`
54    /// parameters, clipped to the panel, labelled with the level, in every
55    /// facet panel. They don't train the scales.
56    ///
57    /// ```
58    /// # use ggplot_rs::prelude::*;
59    /// let obs: Vec<(String, Vec<Value>)> = vec![
60    ///     ("leverage".into(), vec![Value::Float(0.05), Value::Float(0.3)]),
61    ///     ("std_residual".into(), vec![Value::Float(-1.2), Value::Float(2.1)]),
62    /// ];
63    /// let svg = GGPlot::new(obs)
64    ///     .aes(Aes::new().x("leverage").y("std_residual"))
65    ///     .geom_point()
66    ///     .stat_cooks_contour(3, &[0.5, 1.0])
67    ///     .render_svg_native()
68    ///     .unwrap();
69    /// assert!(svg.contains("Cook&#39;s distance = 0.5"));
70    /// ```
71    pub fn stat_cooks_contour(self, p: usize, levels: &[f64]) -> Self {
72        self.geom_cooks_contour_with(GeomCooksContour::new(p, levels))
73    }
74
75    /// Cook's-distance contours with custom styling / leverage range.
76    pub fn geom_cooks_contour_with(self, geom: GeomCooksContour) -> Self {
77        // A one-row placeholder frame (no position columns): the curves are
78        // computed from the trained scales at draw time, in every panel.
79        let mut data = DataFrame::new();
80        data.add_column(".cooks".into(), vec![Value::Float(0.0)]);
81        self.add_geom_with(geom).layer_data(data)
82    }
83
84    /// Step ribbon between `ymin` and `ymax` (pammtools' `geom_stepribbon`,
85    /// `direction = "hv"`): the confidence band of a Kaplan–Meier curve.
86    pub fn geom_stepribbon(self) -> Self {
87        self.add_geom(GeomStepribbon::default())
88    }
89
90    /// Step ribbon with custom fill / alpha / direction (`Hv`, `Vh`, `Mid`).
91    pub fn geom_stepribbon_with(self, geom: GeomStepribbon) -> Self {
92        self.add_geom_with(geom)
93    }
94
95    /// Empirical CDF of `x` as a step line (ggplot2's `stat_ecdf()`), one
96    /// per group, padded to the panel edges.
97    pub fn stat_ecdf(self) -> Self {
98        self.add_geom(GeomStep::default()).stat(StatEcdf)
99    }
100
101    /// Simultaneous DKW confidence band for the ECDF of `x` at `level`
102    /// (`F̂ ± √(ln(2/(1−level))/(2n))`, clamped to [0, 1]) as a step ribbon.
103    /// Add it before [`stat_ecdf`](Self::stat_ecdf) so the line is on top.
104    pub fn stat_ecdf_band(self, level: f64) -> Self {
105        self.add_geom(GeomStepribbon::default())
106            .stat(StatEcdfBand::new(level))
107    }
108
109    /// Censor marks (`+`) at the rows whose `censor_col` is `> 0` / `true`,
110    /// at the layer's `x` (time) and `y` (survival) — e.g. a Kaplan–Meier
111    /// table with an `n_censor` column. Coloured like the curves when the
112    /// plot maps `color`.
113    pub fn geom_censor_marks(self, censor_col: &str) -> Self {
114        self.add_geom(GeomCensorMarks::default())
115            .stat(StatCensored::new(censor_col))
116    }
117
118    /// Censor marks with custom size / colour / shape.
119    pub fn geom_censor_marks_with(self, geom: GeomCensorMarks, censor_col: &str) -> Self {
120        self.add_geom_with(geom).stat(StatCensored::new(censor_col))
121    }
122
123    /// Horizontal error bars from `xmin` to `xmax` at `y`.
124    pub fn geom_errorbarh(self) -> Self {
125        self.add_geom(GeomErrorbarh::default())
126    }
127
128    /// Horizontal error bars with custom styling.
129    pub fn geom_errorbarh_with(self, geom: GeomErrorbarh) -> Self {
130        self.add_geom_with(geom)
131    }
132}