/rust/registry/src/index.crates.io-1949cf8c6b5b557f/ndarray-stats-0.7.0/src/histogram/grid.rs
Line | Count | Source |
1 | | #![warn(missing_docs, clippy::all, clippy::pedantic)] |
2 | | |
3 | | use super::{bins::Bins, errors::BinsBuildError, strategies::BinsBuildingStrategy}; |
4 | | use itertools::izip; |
5 | | use ndarray::{ArrayRef, Axis, Ix1, Ix2}; |
6 | | use std::ops::Range; |
7 | | |
8 | | /// An orthogonal partition of a rectangular region in an *n*-dimensional space, e.g. |
9 | | /// [*a*<sub>0</sub>, *b*<sub>0</sub>) × ⋯ × [*a*<sub>*n*−1</sub>, *b*<sub>*n*−1</sub>), |
10 | | /// represented as a collection of rectangular *n*-dimensional bins. |
11 | | /// |
12 | | /// The grid is **solely determined by the Cartesian product of its projections** on each coordinate |
13 | | /// axis. Therefore, each element in the product set should correspond to a sub-region in the grid. |
14 | | /// |
15 | | /// For example, this partition can be represented as a `Grid` struct: |
16 | | /// |
17 | | /// ```text |
18 | | /// |
19 | | /// g +---+-------+---+ |
20 | | /// | 3 | 4 | 5 | |
21 | | /// f +---+-------+---+ |
22 | | /// | | | | |
23 | | /// | 0 | 1 | 2 | |
24 | | /// | | | | |
25 | | /// e +---+-------+---+ |
26 | | /// a b c d |
27 | | /// |
28 | | /// R0: [a, b) × [e, f) |
29 | | /// R1: [b, c) × [e, f) |
30 | | /// R2: [c, d) × [e, f) |
31 | | /// R3: [a, b) × [f, g) |
32 | | /// R4: [b, d) × [f, g) |
33 | | /// R5: [c, d) × [f, g) |
34 | | /// Grid: { [a, b), [b, c), [c, d) } × { [e, f), [f, g) } == { R0, R1, R2, R3, R4, R5 } |
35 | | /// ``` |
36 | | /// |
37 | | /// while the next one can't: |
38 | | /// |
39 | | /// ```text |
40 | | /// g +---+-----+---+ |
41 | | /// | | 2 | 3 | |
42 | | /// (f) | +-----+---+ |
43 | | /// | 0 | | |
44 | | /// | | 1 | |
45 | | /// | | | |
46 | | /// e +---+-----+---+ |
47 | | /// a b c d |
48 | | /// |
49 | | /// R0: [a, b) × [e, g) |
50 | | /// R1: [b, d) × [e, f) |
51 | | /// R2: [b, c) × [f, g) |
52 | | /// R3: [c, d) × [f, g) |
53 | | /// // 'f', as long as 'R1', 'R2', or 'R3', doesn't appear on LHS |
54 | | /// // [b, c) × [e, g), [c, d) × [e, g) doesn't appear on RHS |
55 | | /// Grid: { [a, b), [b, c), [c, d) } × { [e, g) } != { R0, R1, R2, R3 } |
56 | | /// ``` |
57 | | /// |
58 | | /// # Examples |
59 | | /// |
60 | | /// Basic usage, building a `Grid` via [`GridBuilder`], with optimal grid layout determined by |
61 | | /// a given [`strategy`], and generating a [`histogram`]: |
62 | | /// |
63 | | /// ``` |
64 | | /// use ndarray::{Array, array}; |
65 | | /// use ndarray_stats::{ |
66 | | /// histogram::{strategies::Auto, Bins, Edges, Grid, GridBuilder}, |
67 | | /// HistogramExt, |
68 | | /// }; |
69 | | /// |
70 | | /// // 1-dimensional observations, as a (n_observations, n_dimension) 2-d matrix |
71 | | /// let observations = Array::from_shape_vec( |
72 | | /// (12, 1), |
73 | | /// vec![1, 4, 5, 2, 100, 20, 50, 65, 27, 40, 45, 23], |
74 | | /// ).unwrap(); |
75 | | /// |
76 | | /// // The optimal grid layout is inferred from the data, given a chosen strategy, Auto in this case |
77 | | /// let grid = GridBuilder::<Auto<usize>>::from_array(&observations).unwrap().build(); |
78 | | /// |
79 | | /// let histogram = observations.histogram(grid); |
80 | | /// |
81 | | /// let histogram_matrix = histogram.counts(); |
82 | | /// // Bins are left-closed, right-open! |
83 | | /// let expected = array![4, 3, 3, 1, 0, 1]; |
84 | | /// assert_eq!(histogram_matrix, expected.into_dyn()); |
85 | | /// ``` |
86 | | /// |
87 | | /// [`histogram`]: trait.HistogramExt.html |
88 | | /// [`GridBuilder`]: struct.GridBuilder.html |
89 | | /// [`strategy`]: strategies/index.html |
90 | | #[derive(Clone, Debug, Eq, PartialEq)] |
91 | | pub struct Grid<A: Ord> { |
92 | | projections: Vec<Bins<A>>, |
93 | | } |
94 | | |
95 | | impl<A: Ord> From<Vec<Bins<A>>> for Grid<A> { |
96 | | /// Converts a `Vec<Bins<A>>` into a `Grid<A>`, consuming the vector of bins. |
97 | | /// |
98 | | /// The `i`-th element in `Vec<Bins<A>>` represents the projection of the bin grid onto the |
99 | | /// `i`-th axis. |
100 | | /// |
101 | | /// Alternatively, a `Grid` can be built directly from data using a [`GridBuilder`]. |
102 | | /// |
103 | | /// [`GridBuilder`]: struct.GridBuilder.html |
104 | 0 | fn from(projections: Vec<Bins<A>>) -> Self { |
105 | 0 | Grid { projections } |
106 | 0 | } |
107 | | } |
108 | | |
109 | | impl<A: Ord> Grid<A> { |
110 | | /// Returns the number of dimensions of the region partitioned by the grid. |
111 | | /// |
112 | | /// # Examples |
113 | | /// |
114 | | /// ``` |
115 | | /// use ndarray_stats::histogram::{Edges, Bins, Grid}; |
116 | | /// |
117 | | /// let edges = Edges::from(vec![0, 1]); |
118 | | /// let bins = Bins::new(edges); |
119 | | /// let square_grid = Grid::from(vec![bins.clone(), bins.clone()]); |
120 | | /// |
121 | | /// assert_eq!(square_grid.ndim(), 2usize) |
122 | | /// ``` |
123 | | #[must_use] |
124 | 0 | pub fn ndim(&self) -> usize { |
125 | 0 | self.projections.len() |
126 | 0 | } |
127 | | |
128 | | /// Returns the numbers of bins along each coordinate axis. |
129 | | /// |
130 | | /// # Examples |
131 | | /// |
132 | | /// ``` |
133 | | /// use ndarray_stats::histogram::{Edges, Bins, Grid}; |
134 | | /// |
135 | | /// let edges_x = Edges::from(vec![0, 1]); |
136 | | /// let edges_y = Edges::from(vec![-1, 0, 1]); |
137 | | /// let bins_x = Bins::new(edges_x); |
138 | | /// let bins_y = Bins::new(edges_y); |
139 | | /// let square_grid = Grid::from(vec![bins_x, bins_y]); |
140 | | /// |
141 | | /// assert_eq!(square_grid.shape(), vec![1usize, 2usize]); |
142 | | /// ``` |
143 | | #[must_use] |
144 | 0 | pub fn shape(&self) -> Vec<usize> { |
145 | 0 | self.projections.iter().map(Bins::len).collect() |
146 | 0 | } |
147 | | |
148 | | /// Returns the grid projections on each coordinate axis as a slice of immutable references. |
149 | | #[must_use] |
150 | 0 | pub fn projections(&self) -> &[Bins<A>] { |
151 | 0 | &self.projections |
152 | 0 | } |
153 | | |
154 | | /// Returns an `n-dimensional` index, of bins along each axis that contains the point, if one |
155 | | /// exists. |
156 | | /// |
157 | | /// Returns `None` if the point is outside the grid. |
158 | | /// |
159 | | /// # Panics |
160 | | /// |
161 | | /// Panics if dimensionality of the point doesn't equal the grid's. |
162 | | /// |
163 | | /// # Examples |
164 | | /// |
165 | | /// Basic usage: |
166 | | /// |
167 | | /// ``` |
168 | | /// use ndarray::array; |
169 | | /// use ndarray_stats::histogram::{Edges, Bins, Grid}; |
170 | | /// use noisy_float::types::n64; |
171 | | /// |
172 | | /// let edges = Edges::from(vec![n64(-1.), n64(0.), n64(1.)]); |
173 | | /// let bins = Bins::new(edges); |
174 | | /// let square_grid = Grid::from(vec![bins.clone(), bins.clone()]); |
175 | | /// |
176 | | /// // (0., -0.7) falls in 1st and 0th bin respectively |
177 | | /// assert_eq!( |
178 | | /// square_grid.index_of(&array![n64(0.), n64(-0.7)]), |
179 | | /// Some(vec![1, 0]), |
180 | | /// ); |
181 | | /// // Returns `None`, as `1.` is outside the grid since bins are right-open |
182 | | /// assert_eq!( |
183 | | /// square_grid.index_of(&array![n64(0.), n64(1.)]), |
184 | | /// None, |
185 | | /// ); |
186 | | /// ``` |
187 | | /// |
188 | | /// A panic upon dimensionality mismatch: |
189 | | /// |
190 | | /// ```should_panic |
191 | | /// # use ndarray::array; |
192 | | /// # use ndarray_stats::histogram::{Edges, Bins, Grid}; |
193 | | /// # use noisy_float::types::n64; |
194 | | /// # let edges = Edges::from(vec![n64(-1.), n64(0.), n64(1.)]); |
195 | | /// # let bins = Bins::new(edges); |
196 | | /// # let square_grid = Grid::from(vec![bins.clone(), bins.clone()]); |
197 | | /// // the point has 3 dimensions, the grid expected 2 dimensions |
198 | | /// assert_eq!( |
199 | | /// square_grid.index_of(&array![n64(0.), n64(-0.7), n64(0.5)]), |
200 | | /// Some(vec![1, 0, 1]), |
201 | | /// ); |
202 | | /// ``` |
203 | 0 | pub fn index_of(&self, point: &ArrayRef<A, Ix1>) -> Option<Vec<usize>> { |
204 | 0 | assert_eq!( |
205 | 0 | point.len(), |
206 | 0 | self.ndim(), |
207 | | "Dimension mismatch: the point has {:?} dimensions, the grid \ |
208 | | expected {:?} dimensions.", |
209 | 0 | point.len(), |
210 | 0 | self.ndim() |
211 | | ); |
212 | 0 | point |
213 | 0 | .iter() |
214 | 0 | .zip(self.projections.iter()) |
215 | 0 | .map(|(v, e)| e.index_of(v)) |
216 | 0 | .collect() |
217 | 0 | } |
218 | | } |
219 | | |
220 | | impl<A: Ord + Clone> Grid<A> { |
221 | | /// Given an `n`-dimensional index, `i = (i_0, ..., i_{n-1})`, returns an `n`-dimensional bin, |
222 | | /// `I_{i_0} x ... x I_{i_{n-1}}`, where `I_{i_j}` is the `i_j`-th interval on the `j`-th |
223 | | /// projection of the grid on the coordinate axes. |
224 | | /// |
225 | | /// # Panics |
226 | | /// |
227 | | /// Panics if at least one in the index, `(i_0, ..., i_{n-1})`, is out of bounds on the |
228 | | /// corresponding coordinate axis, i.e. if there exists `j` s.t. |
229 | | /// `i_j >= self.projections[j].len()`. |
230 | | /// |
231 | | /// # Examples |
232 | | /// |
233 | | /// Basic usage: |
234 | | /// |
235 | | /// ``` |
236 | | /// use ndarray::array; |
237 | | /// use ndarray_stats::histogram::{Edges, Bins, Grid}; |
238 | | /// |
239 | | /// let edges_x = Edges::from(vec![0, 1]); |
240 | | /// let edges_y = Edges::from(vec![2, 3, 4]); |
241 | | /// let bins_x = Bins::new(edges_x); |
242 | | /// let bins_y = Bins::new(edges_y); |
243 | | /// let square_grid = Grid::from(vec![bins_x, bins_y]); |
244 | | /// |
245 | | /// // Query the 0-th bin on x-axis, and 1-st bin on y-axis |
246 | | /// assert_eq!( |
247 | | /// square_grid.index(&[0, 1]), |
248 | | /// vec![0..1, 3..4], |
249 | | /// ); |
250 | | /// ``` |
251 | | /// |
252 | | /// A panic upon out-of-bounds: |
253 | | /// |
254 | | /// ```should_panic |
255 | | /// # use ndarray::array; |
256 | | /// # use ndarray_stats::histogram::{Edges, Bins, Grid}; |
257 | | /// # let edges_x = Edges::from(vec![0, 1]); |
258 | | /// # let edges_y = Edges::from(vec![2, 3, 4]); |
259 | | /// # let bins_x = Bins::new(edges_x); |
260 | | /// # let bins_y = Bins::new(edges_y); |
261 | | /// # let square_grid = Grid::from(vec![bins_x, bins_y]); |
262 | | /// // out-of-bound on y-axis |
263 | | /// assert_eq!( |
264 | | /// square_grid.index(&[0, 2]), |
265 | | /// vec![0..1, 3..4], |
266 | | /// ); |
267 | | /// ``` |
268 | | #[must_use] |
269 | 0 | pub fn index(&self, index: &[usize]) -> Vec<Range<A>> { |
270 | 0 | assert_eq!( |
271 | 0 | index.len(), |
272 | 0 | self.ndim(), |
273 | | "Dimension mismatch: the index has {0:?} dimensions, the grid \ |
274 | | expected {1:?} dimensions.", |
275 | 0 | index.len(), |
276 | 0 | self.ndim() |
277 | | ); |
278 | 0 | izip!(&self.projections, index) |
279 | 0 | .map(|(bins, &i)| bins.index(i)) |
280 | 0 | .collect() |
281 | 0 | } |
282 | | } |
283 | | |
284 | | /// A builder used to create [`Grid`] instances for [`histogram`] computations. |
285 | | /// |
286 | | /// # Examples |
287 | | /// |
288 | | /// Basic usage, creating a `Grid` with some observations and a given [`strategy`]: |
289 | | /// |
290 | | /// ``` |
291 | | /// use ndarray::Array; |
292 | | /// use ndarray_stats::histogram::{strategies::Auto, Bins, Edges, Grid, GridBuilder}; |
293 | | /// |
294 | | /// // 1-dimensional observations, as a (n_observations, n_dimension) 2-d matrix |
295 | | /// let observations = Array::from_shape_vec( |
296 | | /// (12, 1), |
297 | | /// vec![1, 4, 5, 2, 100, 20, 50, 65, 27, 40, 45, 23], |
298 | | /// ).unwrap(); |
299 | | /// |
300 | | /// // The optimal grid layout is inferred from the data, given a chosen strategy, Auto in this case |
301 | | /// let grid = GridBuilder::<Auto<usize>>::from_array(&observations).unwrap().build(); |
302 | | /// // Equivalently, build a Grid directly |
303 | | /// let expected_grid = Grid::from(vec![Bins::new(Edges::from(vec![1, 20, 39, 58, 77, 96, 115]))]); |
304 | | /// |
305 | | /// assert_eq!(grid, expected_grid); |
306 | | /// ``` |
307 | | /// |
308 | | /// [`Grid`]: struct.Grid.html |
309 | | /// [`histogram`]: trait.HistogramExt.html |
310 | | /// [`strategy`]: strategies/index.html |
311 | | #[allow(clippy::module_name_repetitions)] |
312 | | pub struct GridBuilder<B: BinsBuildingStrategy> { |
313 | | bin_builders: Vec<B>, |
314 | | } |
315 | | |
316 | | impl<A, B> GridBuilder<B> |
317 | | where |
318 | | A: Ord, |
319 | | B: BinsBuildingStrategy<Elem = A>, |
320 | | { |
321 | | /// Returns a `GridBuilder` for building a [`Grid`] with a given [`strategy`] and some |
322 | | /// observations in a 2-dimensionalarray with shape `(n_observations, n_dimension)`. |
323 | | /// |
324 | | /// # Errors |
325 | | /// |
326 | | /// It returns [`BinsBuildError`] if it is not possible to build a [`Grid`] given |
327 | | /// the observed data according to the chosen [`strategy`]. |
328 | | /// |
329 | | /// # Examples |
330 | | /// |
331 | | /// See [Trait-level examples] for basic usage. |
332 | | /// |
333 | | /// [`Grid`]: struct.Grid.html |
334 | | /// [`strategy`]: strategies/index.html |
335 | | /// [`BinsBuildError`]: errors/enum.BinsBuildError.html |
336 | | /// [Trait-level examples]: struct.GridBuilder.html#examples |
337 | 0 | pub fn from_array(array: &ArrayRef<A, Ix2>) -> Result<Self, BinsBuildError> { |
338 | 0 | let bin_builders = array |
339 | 0 | .axis_iter(Axis(1)) |
340 | 0 | .map(|data| B::from_array(&data)) |
341 | 0 | .collect::<Result<Vec<B>, BinsBuildError>>()?; |
342 | 0 | Ok(Self { bin_builders }) |
343 | 0 | } |
344 | | |
345 | | /// Returns a [`Grid`] instance, with building parameters infered in [`from_array`], according |
346 | | /// to the specified [`strategy`] and observations provided. |
347 | | /// |
348 | | /// # Examples |
349 | | /// |
350 | | /// See [Trait-level examples] for basic usage. |
351 | | /// |
352 | | /// [`Grid`]: struct.Grid.html |
353 | | /// [`strategy`]: strategies/index.html |
354 | | /// [`from_array`]: #method.from_array.html |
355 | | #[must_use] |
356 | 0 | pub fn build(&self) -> Grid<A> { |
357 | 0 | let projections: Vec<_> = self.bin_builders.iter().map(|b| b.build()).collect(); |
358 | 0 | Grid::from(projections) |
359 | 0 | } |
360 | | } |