plotlib/repr/
histogram.rs

1/*!
2
3A module for Histograms
4
5# Examples
6
7```
8# use plotlib::repr::Histogram;
9// Create some dummy data
10let data = vec![0.3, 0.5, 6.4, 5.3, 3.6, 3.6, 3.5, 7.5, 4.0];
11
12// and create a histogram out of it
13let h = Histogram::from_slice(&data, plotlib::repr::HistogramBins::Count(30));
14```
15
16TODO:
17
18- frequency or density option
19    - Variable bins implies frequency
20    - What should be the default?
21*/
22
23use std;
24
25use svg;
26
27use crate::axis;
28use crate::repr::ContinuousRepresentation;
29use crate::style::BoxStyle;
30use crate::svg_render;
31use crate::text_render;
32use crate::utils::PairWise;
33
34#[derive(Debug)]
35enum HistogramType {
36    Count,
37    Density,
38}
39
40#[derive(Debug)]
41pub enum HistogramBins {
42    Count(usize),
43    Bounds(Vec<f64>),
44}
45
46/**
47A one-dimensional histogram with equal binning.
48*/
49#[derive(Debug)]
50pub struct Histogram {
51    pub bin_bounds: Vec<f64>,    // will have N_bins + 1 entries
52    pub bin_counts: Vec<f64>,    // will have N_bins entries
53    pub bin_densities: Vec<f64>, // will have N_bins entries
54    style: BoxStyle,
55    h_type: HistogramType,
56}
57
58impl Histogram {
59    pub fn from_slice(v: &[f64], bins: HistogramBins) -> Histogram {
60        let mut max = v.iter().fold(-1. / 0., |a, &b| f64::max(a, b));
61        let mut min = v.iter().fold(1. / 0., |a, &b| f64::min(a, b));
62
63        if (min - max).abs() < std::f64::EPSILON {
64            min -= 0.5;
65            max += 0.5;
66        }
67
68        let (num_bins, bounds) = match bins {
69            HistogramBins::Count(num_bins) => {
70                let range = max - min;
71                let mut bounds: Vec<f64> = (0..num_bins)
72                    .map(|n| (n as f64 / num_bins as f64) * range + min)
73                    .collect();
74                bounds.push(max);
75                (num_bins, bounds)
76            }
77            HistogramBins::Bounds(bounds) => (bounds.len(), bounds),
78        };
79
80        let mut bins = vec![0; num_bins];
81
82        let bin_width = (max - min) / num_bins as f64; // width of bin in real units
83
84        for &val in v.iter() {
85            let bin = bounds
86                .pairwise()
87                .enumerate()
88                .skip_while(|&(_, (&l, &u))| !(val >= l && val <= u))
89                .map(|(i, (_, _))| i)
90                .next()
91                .unwrap();
92            bins[bin] += 1;
93        }
94        let density_per_bin = bins.iter().map(|&x| f64::from(x) / bin_width).collect();
95
96        Histogram {
97            bin_bounds: bounds,
98            bin_counts: bins.iter().map(|&x| f64::from(x)).collect(),
99            bin_densities: density_per_bin,
100            style: BoxStyle::new(),
101            h_type: HistogramType::Count,
102        }
103    }
104
105    pub fn num_bins(&self) -> usize {
106        self.bin_counts.len()
107    }
108
109    fn x_range(&self) -> (f64, f64) {
110        (
111            *self.bin_bounds.first().unwrap(),
112            *self.bin_bounds.last().unwrap(),
113        )
114    }
115
116    fn y_range(&self) -> (f64, f64) {
117        let max = self
118            .get_values()
119            .iter()
120            .fold(-1. / 0., |a, &b| f64::max(a, b));
121        (0., max)
122    }
123
124    pub fn style(mut self, style: &BoxStyle) -> Self {
125        self.style.overlay(style);
126        self
127    }
128
129    /**
130    Set the histogram to display as normalised densities
131    */
132    pub fn density(mut self) -> Self {
133        self.h_type = HistogramType::Density;
134        self
135    }
136
137    pub fn get_style(&self) -> &BoxStyle {
138        &self.style
139    }
140
141    pub fn get_values(&self) -> &[f64] {
142        match self.h_type {
143            HistogramType::Count => &self.bin_counts,
144            HistogramType::Density => &self.bin_densities,
145        }
146    }
147}
148
149impl ContinuousRepresentation for Histogram {
150    fn range(&self, dim: u32) -> (f64, f64) {
151        match dim {
152            0 => self.x_range(),
153            1 => self.y_range(),
154            _ => panic!("Axis out of range"),
155        }
156    }
157
158    fn to_svg(
159        &self,
160        x_axis: &axis::ContinuousAxis,
161        y_axis: &axis::ContinuousAxis,
162        face_width: f64,
163        face_height: f64,
164    ) -> svg::node::element::Group {
165        svg_render::draw_face_bars(self, x_axis, y_axis, face_width, face_height, &self.style)
166    }
167    fn legend_svg(&self) -> Option<svg::node::element::Group> {
168        // TODO implement
169        None
170    }
171
172    fn to_text(
173        &self,
174        x_axis: &axis::ContinuousAxis,
175        y_axis: &axis::ContinuousAxis,
176        face_width: u32,
177        face_height: u32,
178    ) -> String {
179        text_render::render_face_bars(self, x_axis, y_axis, face_width, face_height)
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn test_histogram_from_slice() {
189        assert_eq!(
190            Histogram::from_slice(&[], HistogramBins::Count(3)).get_values(),
191            [0., 0., 0.]
192        );
193        assert_eq!(
194            Histogram::from_slice(&[0.], HistogramBins::Count(3)).get_values(),
195            [0., 1., 0.]
196        );
197        assert_eq!(
198            Histogram::from_slice(&[0., 3.], HistogramBins::Count(3)).get_values(),
199            [1., 0., 1.]
200        );
201        assert_eq!(
202            Histogram::from_slice(&[0., 1., 2., 3.], HistogramBins::Count(3)).get_values(),
203            [2., 1., 1.]
204        );
205    }
206
207    #[test]
208    fn test_histogram_define_bin_bounds() {
209        assert_eq!(
210            Histogram::from_slice(&[0., 1.], HistogramBins::Count(3)).bin_bounds,
211            [0., 1. / 3., 2. / 3., 1.]
212        );
213        assert_eq!(
214            Histogram::from_slice(&[], HistogramBins::Bounds([0., 1., 1.5, 2., 5.6].to_vec()))
215                .bin_bounds,
216            [0., 1., 1.5, 2., 5.6]
217        );
218    }
219}