plotters_statistical/series/
regularization_path.rs1use plotters::element::{Drawable, PointCollection};
12use plotters::style::RGBColor;
13use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
14
15use crate::stats::StatsError;
16use crate::style::{fill_style, palette_color, stroke_style};
17
18#[derive(Debug, Clone)]
20pub struct RegLine {
21 points: Vec<(f64, f64)>,
23 n_line: usize,
24 has_zero: bool,
25 color: RGBColor,
26 stroke_width: u32,
27 marker_radius: u32,
28 show_marker: bool,
29 name: Option<String>,
30}
31
32impl RegLine {
33 pub fn name(&self) -> Option<&str> {
35 self.name.as_deref()
36 }
37
38 pub fn color(&self) -> RGBColor {
40 self.color
41 }
42}
43
44impl<'a> PointCollection<'a, (f64, f64)> for &'a RegLine {
45 type Point = &'a (f64, f64);
46 type IntoIter = &'a [(f64, f64)];
47 fn point_iter(self) -> &'a [(f64, f64)] {
48 &self.points
49 }
50}
51
52impl<DB: DrawingBackend> Drawable<DB> for RegLine {
53 fn draw<I: Iterator<Item = BackendCoord>>(
54 &self,
55 points: I,
56 backend: &mut DB,
57 _parent_dim: (u32, u32),
58 ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
59 let pix: Vec<BackendCoord> = points.collect();
60 if pix.len() < self.n_line {
61 return Ok(());
62 }
63 backend.draw_path(
64 pix[..self.n_line].iter().copied(),
65 &stroke_style(&self.color, self.stroke_width),
66 )?;
67 if self.show_marker && self.has_zero && pix.len() > self.n_line {
68 let marker = pix[self.n_line];
69 backend.draw_circle(marker, self.marker_radius, &fill_style(&self.color), true)?;
70 }
71 Ok(())
72 }
73}
74
75#[derive(Debug, Clone)]
81pub struct RegularizationPath {
82 lines: Vec<RegLine>,
83}
84
85impl RegularizationPath {
86 pub fn new(strengths: &[f64], coefficients: &[Vec<f64>]) -> Result<Self, StatsError> {
94 if strengths.is_empty() || coefficients.is_empty() {
95 return Err(StatsError::EmptyInput);
96 }
97 if coefficients.len() != strengths.len() {
98 return Err(StatsError::LengthMismatch {
99 scores: strengths.len(),
100 labels: coefficients.len(),
101 });
102 }
103 let n_features = coefficients[0].len();
104 if coefficients.iter().any(|r| r.len() != n_features) {
105 return Err(StatsError::LengthMismatch {
106 scores: n_features,
107 labels: coefficients.iter().map(|r| r.len()).max().unwrap_or(0),
108 });
109 }
110
111 let mut lines = Vec::with_capacity(n_features);
112 for j in 0..n_features {
113 let series: Vec<(f64, f64)> = strengths
114 .iter()
115 .zip(coefficients.iter())
116 .map(|(&s, row)| (s, row[j]))
117 .collect();
118 let zero = first_zero_crossing(&series);
119 let n_line = series.len();
120 let mut points = series;
121 let has_zero = zero.is_some();
122 if let Some(z) = zero {
123 points.push(z);
124 }
125 lines.push(RegLine {
126 points,
127 n_line,
128 has_zero,
129 color: palette_color(j),
130 stroke_width: 2,
131 marker_radius: 4,
132 show_marker: true,
133 name: None,
134 });
135 }
136 Ok(Self { lines })
137 }
138
139 pub fn feature_names<S: Into<String>, I: IntoIterator<Item = S>>(mut self, names: I) -> Self {
142 for (line, name) in self.lines.iter_mut().zip(names) {
143 line.name = Some(name.into());
144 }
145 self
146 }
147
148 pub fn zero_markers(mut self, show: bool) -> Self {
150 for line in &mut self.lines {
151 line.show_marker = show;
152 }
153 self
154 }
155
156 pub fn stroke_width(mut self, width: u32) -> Self {
158 for line in &mut self.lines {
159 line.stroke_width = width;
160 }
161 self
162 }
163
164 pub fn lines(&self) -> Vec<RegLine> {
167 self.lines.clone()
168 }
169}
170
171impl IntoIterator for RegularizationPath {
172 type Item = RegLine;
173 type IntoIter = std::vec::IntoIter<RegLine>;
174 fn into_iter(self) -> Self::IntoIter {
175 self.lines.into_iter()
176 }
177}
178
179fn first_zero_crossing(series: &[(f64, f64)]) -> Option<(f64, f64)> {
182 for w in series.windows(2) {
183 let (s0, c0) = w[0];
184 let (s1, c1) = w[1];
185 if c0 == 0.0 {
186 return Some((s0, 0.0));
187 }
188 if c1 == 0.0 {
189 return Some((s1, 0.0));
190 }
191 if c0 * c1 < 0.0 {
192 let t = c0 / (c0 - c1); return Some((s0 + t * (s1 - s0), 0.0));
194 }
195 }
196 match series.first() {
198 Some(&(s, 0.0)) => Some((s, 0.0)),
199 _ => None,
200 }
201}