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'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}