Skip to content

Commit 882a411

Browse files
jturner314LukeMathWalker
authored andcommitted
Replace min/max, make quantile_mut return None for empty arrays, and fix docs (#13)
* Remove min/max for A: Ord The problem with the old methods was that they panicked when the array was empty, which was very problematic. (See rust-ndarray/ndarray#512 for discussion.) The old `min_partialord` and `max_partialord` have been renamed to `min`/`max`. * Document axis_len == 0 panic for quantile_axis_mut * Make quantile_mut return None for empty arrays * Fix panic docs for histogram strategies
1 parent fcd795e commit 882a411

File tree

3 files changed

+55
-78
lines changed

3 files changed

+55
-78
lines changed

src/histogram/strategies.rs

+16-17
Original file line numberDiff line numberDiff line change
@@ -181,15 +181,15 @@ impl<T> BinsBuildingStrategy for Sqrt<T>
181181
{
182182
type Elem = T;
183183

184-
/// **Panics** if the array is constant or if `a.len()==0` and division by 0 panics for `T`.
184+
/// **Panics** if the array is constant or if `a.len()==0`.
185185
fn from_array<S>(a: &ArrayBase<S, Ix1>) -> Self
186186
where
187187
S: Data<Elem=Self::Elem>
188188
{
189189
let n_elems = a.len();
190190
let n_bins = (n_elems as f64).sqrt().round() as usize;
191-
let min = a.min().clone();
192-
let max = a.max().clone();
191+
let min = a.min().unwrap().clone();
192+
let max = a.max().unwrap().clone();
193193
let bin_width = compute_bin_width(min.clone(), max.clone(), n_bins);
194194
let builder = EquiSpaced::new(bin_width, min, max);
195195
Self { builder }
@@ -220,15 +220,15 @@ impl<T> BinsBuildingStrategy for Rice<T>
220220
{
221221
type Elem = T;
222222

223-
/// **Panics** if the array is constant or if `a.len()==0` and division by 0 panics for `T`.
223+
/// **Panics** if the array is constant or if `a.len()==0`.
224224
fn from_array<S>(a: &ArrayBase<S, Ix1>) -> Self
225225
where
226226
S: Data<Elem=Self::Elem>
227227
{
228228
let n_elems = a.len();
229229
let n_bins = (2. * (n_elems as f64).powf(1./3.)).round() as usize;
230-
let min = a.min().clone();
231-
let max = a.max().clone();
230+
let min = a.min().unwrap().clone();
231+
let max = a.max().unwrap().clone();
232232
let bin_width = compute_bin_width(min.clone(), max.clone(), n_bins);
233233
let builder = EquiSpaced::new(bin_width, min, max);
234234
Self { builder }
@@ -259,15 +259,15 @@ impl<T> BinsBuildingStrategy for Sturges<T>
259259
{
260260
type Elem = T;
261261

262-
/// **Panics** if the array is constant or if `a.len()==0` and division by 0 panics for `T`.
262+
/// **Panics** if the array is constant or if `a.len()==0`.
263263
fn from_array<S>(a: &ArrayBase<S, Ix1>) -> Self
264264
where
265265
S: Data<Elem=Self::Elem>
266266
{
267267
let n_elems = a.len();
268268
let n_bins = (n_elems as f64).log2().round() as usize + 1;
269-
let min = a.min().clone();
270-
let max = a.max().clone();
269+
let min = a.min().unwrap().clone();
270+
let max = a.max().unwrap().clone();
271271
let bin_width = compute_bin_width(min.clone(), max.clone(), n_bins);
272272
let builder = EquiSpaced::new(bin_width, min, max);
273273
Self { builder }
@@ -298,21 +298,21 @@ impl<T> BinsBuildingStrategy for FreedmanDiaconis<T>
298298
{
299299
type Elem = T;
300300

301-
/// **Panics** if `IQR==0` or if `a.len()==0` and division by 0 panics for `T`.
301+
/// **Panics** if `IQR==0` or if `a.len()==0`.
302302
fn from_array<S>(a: &ArrayBase<S, Ix1>) -> Self
303303
where
304304
S: Data<Elem=Self::Elem>
305305
{
306306
let n_points = a.len();
307307

308308
let mut a_copy = a.to_owned();
309-
let first_quartile = a_copy.quantile_mut::<Nearest>(0.25);
310-
let third_quartile = a_copy.quantile_mut::<Nearest>(0.75);
309+
let first_quartile = a_copy.quantile_mut::<Nearest>(0.25).unwrap();
310+
let third_quartile = a_copy.quantile_mut::<Nearest>(0.75).unwrap();
311311
let iqr = third_quartile - first_quartile;
312312

313313
let bin_width = FreedmanDiaconis::compute_bin_width(n_points, iqr);
314-
let min = a_copy.min().clone();
315-
let max = a_copy.max().clone();
314+
let min = a_copy.min().unwrap().clone();
315+
let max = a_copy.max().unwrap().clone();
316316
let builder = EquiSpaced::new(bin_width, min, max);
317317
Self { builder }
318318
}
@@ -349,8 +349,7 @@ impl<T> BinsBuildingStrategy for Auto<T>
349349
{
350350
type Elem = T;
351351

352-
/// **Panics** if `IQR==0`, the array is constant or if
353-
/// `a.len()==0` and division by 0 panics for `T`.
352+
/// **Panics** if `IQR==0`, the array is constant, or `a.len()==0`.
354353
fn from_array<S>(a: &ArrayBase<S, Ix1>) -> Self
355354
where
356355
S: Data<Elem=Self::Elem>
@@ -403,7 +402,7 @@ impl<T> Auto<T>
403402
///
404403
/// `bin_width = (max - min)/n`
405404
///
406-
/// **Panics** if division by 0 panics for `T`.
405+
/// **Panics** if `n_bins == 0` and division by 0 panics for `T`.
407406
fn compute_bin_width<T>(min: T, max: T, n_bins: usize) -> T
408407
where
409408
T: Ord + Clone + FromPrimitive + NumOps + Zero,

src/quantile.rs

+19-49
Original file line numberDiff line numberDiff line change
@@ -175,21 +175,14 @@ where
175175
S: Data<Elem = A>,
176176
D: Dimension,
177177
{
178-
/// Finds the elementwise minimum of the array.
179-
///
180-
/// **Panics** if the array is empty.
181-
fn min(&self) -> &A
182-
where
183-
A: Ord;
184-
185178
/// Finds the elementwise minimum of the array.
186179
///
187180
/// Returns `None` if any of the pairwise orderings tested by the function
188181
/// are undefined. (For example, this occurs if there are any
189182
/// floating-point NaN values in the array.)
190183
///
191184
/// Additionally, returns `None` if the array is empty.
192-
fn min_partialord(&self) -> Option<&A>
185+
fn min(&self) -> Option<&A>
193186
where
194187
A: PartialOrd;
195188

@@ -203,21 +196,14 @@ where
203196
A: MaybeNan,
204197
A::NotNan: Ord;
205198

206-
/// Finds the elementwise maximum of the array.
207-
///
208-
/// **Panics** if the array is empty.
209-
fn max(&self) -> &A
210-
where
211-
A: Ord;
212-
213199
/// Finds the elementwise maximum of the array.
214200
///
215201
/// Returns `None` if any of the pairwise orderings tested by the function
216202
/// are undefined. (For example, this occurs if there are any
217203
/// floating-point NaN values in the array.)
218204
///
219205
/// Additionally, returns `None` if the array is empty.
220-
fn max_partialord(&self) -> Option<&A>
206+
fn max(&self) -> Option<&A>
221207
where
222208
A: PartialOrd;
223209

@@ -259,8 +245,8 @@ where
259245
/// - worst case: O(`m`^2);
260246
/// where `m` is the number of elements in the array.
261247
///
262-
/// **Panics** if `axis` is out of bounds or if `q` is not between
263-
/// `0.` and `1.` (inclusive).
248+
/// **Panics** if `axis` is out of bounds, if the axis has length 0, or if
249+
/// `q` is not between `0.` and `1.` (inclusive).
264250
fn quantile_axis_mut<I>(&mut self, axis: Axis, q: f64) -> Array<A, D::Smaller>
265251
where
266252
D: RemoveAxis,
@@ -285,22 +271,11 @@ where
285271
S: Data<Elem = A>,
286272
D: Dimension,
287273
{
288-
fn min(&self) -> &A
289-
where
290-
A: Ord,
291-
{
292-
let first = self
293-
.iter()
294-
.next()
295-
.expect("Attempted to find min of empty array.");
296-
self.fold(first, |acc, elem| if elem < acc { elem } else { acc })
297-
}
298-
299-
fn min_partialord(&self) -> Option<&A>
274+
fn min(&self) -> Option<&A>
300275
where
301276
A: PartialOrd,
302277
{
303-
let first = self.iter().next()?;
278+
let first = self.first()?;
304279
self.fold(Some(first), |acc, elem| match elem.partial_cmp(acc?)? {
305280
cmp::Ordering::Less => Some(elem),
306281
_ => acc,
@@ -312,7 +287,7 @@ where
312287
A: MaybeNan,
313288
A::NotNan: Ord,
314289
{
315-
let first = self.iter().next().and_then(|v| v.try_as_not_nan());
290+
let first = self.first().and_then(|v| v.try_as_not_nan());
316291
A::from_not_nan_ref_opt(self.fold_skipnan(first, |acc, elem| {
317292
Some(match acc {
318293
Some(acc) => acc.min(elem),
@@ -321,22 +296,11 @@ where
321296
}))
322297
}
323298

324-
fn max(&self) -> &A
325-
where
326-
A: Ord,
327-
{
328-
let first = self
329-
.iter()
330-
.next()
331-
.expect("Attempted to find max of empty array.");
332-
self.fold(first, |acc, elem| if elem > acc { elem } else { acc })
333-
}
334-
335-
fn max_partialord(&self) -> Option<&A>
299+
fn max(&self) -> Option<&A>
336300
where
337301
A: PartialOrd,
338302
{
339-
let first = self.iter().next()?;
303+
let first = self.first()?;
340304
self.fold(Some(first), |acc, elem| match elem.partial_cmp(acc?)? {
341305
cmp::Ordering::Greater => Some(elem),
342306
_ => acc,
@@ -348,7 +312,7 @@ where
348312
A: MaybeNan,
349313
A::NotNan: Ord,
350314
{
351-
let first = self.iter().next().and_then(|v| v.try_as_not_nan());
315+
let first = self.first().and_then(|v| v.try_as_not_nan());
352316
A::from_not_nan_ref_opt(self.fold_skipnan(first, |acc, elem| {
353317
Some(match acc {
354318
Some(acc) => acc.max(elem),
@@ -442,8 +406,10 @@ pub trait Quantile1dExt<A, S>
442406
/// - worst case: O(`m`^2);
443407
/// where `m` is the number of elements in the array.
444408
///
409+
/// Returns `None` if the array is empty.
410+
///
445411
/// **Panics** if `q` is not between `0.` and `1.` (inclusive).
446-
fn quantile_mut<I>(&mut self, q: f64) -> A
412+
fn quantile_mut<I>(&mut self, q: f64) -> Option<A>
447413
where
448414
A: Ord + Clone,
449415
S: DataMut,
@@ -454,13 +420,17 @@ impl<A, S> Quantile1dExt<A, S> for ArrayBase<S, Ix1>
454420
where
455421
S: Data<Elem = A>,
456422
{
457-
fn quantile_mut<I>(&mut self, q: f64) -> A
423+
fn quantile_mut<I>(&mut self, q: f64) -> Option<A>
458424
where
459425
A: Ord + Clone,
460426
S: DataMut,
461427
I: Interpolate<A>,
462428
{
463-
self.quantile_axis_mut::<I>(Axis(0), q).into_scalar()
429+
if self.is_empty() {
430+
None
431+
} else {
432+
Some(self.quantile_axis_mut::<I>(Axis(0), q).into_scalar())
433+
}
464434
}
465435
}
466436

tests/quantile.rs

+20-12
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,13 @@ use ndarray_stats::{
1111
#[test]
1212
fn test_min() {
1313
let a = array![[1, 5, 3], [2, 0, 6]];
14-
assert_eq!(a.min(), &0);
15-
}
14+
assert_eq!(a.min(), Some(&0));
1615

17-
#[test]
18-
fn test_min_partialord() {
1916
let a = array![[1., 5., 3.], [2., 0., 6.]];
20-
assert_eq!(a.min_partialord(), Some(&0.));
17+
assert_eq!(a.min(), Some(&0.));
2118

2219
let a = array![[1., 5., 3.], [2., ::std::f64::NAN, 6.]];
23-
assert_eq!(a.min_partialord(), None);
20+
assert_eq!(a.min(), None);
2421
}
2522

2623
#[test]
@@ -41,16 +38,13 @@ fn test_min_skipnan_all_nan() {
4138
#[test]
4239
fn test_max() {
4340
let a = array![[1, 5, 7], [2, 0, 6]];
44-
assert_eq!(a.max(), &7);
45-
}
41+
assert_eq!(a.max(), Some(&7));
4642

47-
#[test]
48-
fn test_max_partialord() {
4943
let a = array![[1., 5., 7.], [2., 0., 6.]];
50-
assert_eq!(a.max_partialord(), Some(&7.));
44+
assert_eq!(a.max(), Some(&7.));
5145

5246
let a = array![[1., 5., 7.], [2., ::std::f64::NAN, 6.]];
53-
assert_eq!(a.max_partialord(), None);
47+
assert_eq!(a.max(), None);
5448
}
5549

5650
#[test]
@@ -75,6 +69,20 @@ fn test_quantile_axis_mut_with_odd_axis_length() {
7569
assert!(p == a.subview(Axis(0), 1));
7670
}
7771

72+
#[test]
73+
#[should_panic]
74+
fn test_quantile_axis_mut_with_zero_axis_length() {
75+
let mut a = Array2::<i32>::zeros((5, 0));
76+
a.quantile_axis_mut::<Lower>(Axis(1), 0.5);
77+
}
78+
79+
#[test]
80+
fn test_quantile_axis_mut_with_empty_array() {
81+
let mut a = Array2::<i32>::zeros((5, 0));
82+
let p = a.quantile_axis_mut::<Lower>(Axis(0), 0.5);
83+
assert_eq!(p.shape(), &[0]);
84+
}
85+
7886
#[test]
7987
fn test_quantile_axis_mut_with_even_axis_length() {
8088
let mut b = arr2(&[[1, 3, 2, 10], [2, 4, 3, 11], [3, 5, 6, 12], [4, 6, 7, 13]]);

0 commit comments

Comments
 (0)