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