ndarray_histogram/histogram/histograms.rs
1use super::errors::BinNotFound;
2use super::grid::Grid;
3use ndarray::Data;
4use ndarray::prelude::*;
5
6/// Histogram data structure.
7pub struct Histogram<A: Ord + Send> {
8 counts: ArrayD<usize>,
9 grid: Grid<A>,
10}
11
12impl<A: Ord + Send> Histogram<A> {
13 /// Returns a new instance of Histogram given a [`Grid`].
14 ///
15 /// [`Grid`]: struct.Grid.html
16 pub fn new(grid: Grid<A>) -> Self {
17 let counts = ArrayD::zeros(grid.shape());
18 Histogram { counts, grid }
19 }
20
21 /// Adds a single observation to the histogram.
22 ///
23 /// **Panics** if dimensions do not match: `self.ndim() != observation.len()`.
24 ///
25 /// # Example:
26 /// ```
27 /// use ndarray::array;
28 /// use ndarray_histogram::{
29 /// histogram::{Bins, Edges, Grid, Histogram},
30 /// o64,
31 /// };
32 ///
33 /// let edges = Edges::from(vec![o64(-1.), o64(0.), o64(1.)]);
34 /// let bins = Bins::new(edges);
35 /// let square_grid = Grid::from(vec![bins.clone(), bins.clone()]);
36 /// let mut histogram = Histogram::new(square_grid);
37 ///
38 /// let observation = array![o64(0.5), o64(0.6)];
39 ///
40 /// histogram.add_observation(&observation)?;
41 ///
42 /// let histogram_matrix = histogram.counts();
43 /// let expected = array![[0, 0], [0, 1],];
44 /// assert_eq!(histogram_matrix, expected.into_dyn());
45 /// # Ok::<(), Box<dyn std::error::Error>>(())
46 /// ```
47 pub fn add_observation<S>(&mut self, observation: &ArrayBase<S, Ix1>) -> Result<(), BinNotFound>
48 where
49 S: Data<Elem = A>,
50 {
51 match self.grid.index_of(observation) {
52 Some(bin_index) => {
53 self.counts[&*bin_index] += 1;
54 Ok(())
55 }
56 None => Err(BinNotFound),
57 }
58 }
59
60 /// Returns the number of dimensions of the space the histogram is covering.
61 pub fn ndim(&self) -> usize {
62 debug_assert_eq!(self.counts.ndim(), self.grid.ndim());
63 self.counts.ndim()
64 }
65
66 /// Borrows a view on the histogram counts matrix.
67 pub fn counts(&self) -> ArrayViewD<'_, usize> {
68 self.counts.view()
69 }
70
71 /// Borrows an immutable reference to the histogram grid.
72 pub fn grid(&self) -> &Grid<A> {
73 &self.grid
74 }
75}
76
77/// Extension trait for `ArrayBase` providing methods to compute histograms.
78pub trait HistogramExt<A, S>
79where
80 S: Data<Elem = A>,
81{
82 /// Returns the [histogram](https://en.wikipedia.org/wiki/Histogram)
83 /// for a 2-dimensional array of points `M`.
84 ///
85 /// Let `(n, d)` be the shape of `M`:
86 ///
87 /// - `n` is the number of points;
88 /// - `d` is the number of dimensions of the space those points belong to.
89 ///
90 /// It follows that every column in `M` is a `d`-dimensional point.
91 ///
92 /// For example: a (3, 4) matrix `M` is a collection of 3 points in a
93 /// 4-dimensional space.
94 ///
95 /// Important: points outside the grid are ignored!
96 ///
97 /// **Panics** if `d` is different from `grid.ndim()`.
98 ///
99 /// # Example:
100 ///
101 /// ```
102 /// use ndarray::array;
103 /// use ndarray_histogram::{
104 /// HistogramExt, O64,
105 /// histogram::{Bins, Edges, Grid, GridBuilder, Histogram, strategies::Sqrt},
106 /// o64,
107 /// };
108 ///
109 /// let observations = array![
110 /// [o64(1.), o64(0.5)],
111 /// [o64(-0.5), o64(1.)],
112 /// [o64(-1.), o64(-0.5)],
113 /// [o64(0.5), o64(-1.)]
114 /// ];
115 /// let grid = GridBuilder::<Sqrt<O64>>::from_array(&observations)
116 /// .unwrap()
117 /// .build();
118 /// let expected_grid = Grid::from(vec![
119 /// Bins::new(Edges::from(vec![o64(-1.), o64(0.), o64(1.), o64(2.)])),
120 /// Bins::new(Edges::from(vec![o64(-1.), o64(0.), o64(1.), o64(2.)])),
121 /// ]);
122 /// assert_eq!(grid, expected_grid);
123 ///
124 /// let histogram = observations.histogram(grid);
125 ///
126 /// let histogram_matrix = histogram.counts();
127 /// // Bins are left inclusive, right exclusive!
128 /// let expected = array![[1, 0, 1], [1, 0, 0], [0, 1, 0],];
129 /// assert_eq!(histogram_matrix, expected.into_dyn());
130 /// ```
131 fn histogram(&self, grid: Grid<A>) -> Histogram<A>
132 where
133 A: Ord + Send;
134
135 private_decl! {}
136}
137
138impl<A, S> HistogramExt<A, S> for ArrayBase<S, Ix2>
139where
140 S: Data<Elem = A>,
141 A: Ord + Send,
142{
143 fn histogram(&self, grid: Grid<A>) -> Histogram<A> {
144 let mut histogram = Histogram::new(grid);
145 for point in self.axis_iter(Axis(0)) {
146 let _ = histogram.add_observation(&point);
147 }
148 histogram
149 }
150
151 private_impl! {}
152}